File size: 5,840 Bytes
f9e7cf4
 
 
 
 
6b7bfdd
f9e7cf4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6b7bfdd
 
f9e7cf4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
from shiny import reactive, render, ui
import os
import requests
from dotenv import load_dotenv
from bs4 import BeautifulSoup
import time
from llm_connect import get_response

# Load env
load_dotenv()

# ===== Threads config =====
THREADS_TOKEN = os.getenv("THREADS_ACCESS_TOKEN")  # <-- set this in your env

# Store latest generated post text
generated_threads_post = reactive.Value("")  # keeping the same name so your UI doesn't change


# ---- Helpers ----
def scrape_shopify_blog(url: str) -> str:
    try:
        resp = requests.get(url, timeout=10)
        resp.raise_for_status()
        soup = BeautifulSoup(resp.content, 'html.parser')
        # Adjust this selector to match your Shopify theme if needed
        section = soup.find('div', class_='w940 align-center size-content')
        return section.get_text(strip=True, separator=' ') if section else ""
    except Exception as e:
        return f"❌ Error scraping blog: {e}"


def generate_threads_post(  # keeping the function name to avoid UI refactor
    topic: str = "",
    url: str = "",
    min_len: int = 100,
    max_len: int = 450
) -> str:
    """
    Generates post text using your existing LLM helper.
    (Text is suitable for Threads now; you can tweak tone if you want.)
    """
    if url:
        scraped = scrape_shopify_blog(url)
        if scraped.startswith("❌") or not scraped.strip():
            return f"⚠️ Failed to extract blog content from URL: {url}"
        prompt = (
            "You are a social media manager for a hobby e-commerce company called 'Ultima Supply'.\n"
            f"Write a detailed, engaging Threads post (min {min_len} chars, max {max_len} chars) summarizing the Shopify blog:\n\n"
            f"{scraped}\n\n"
            f"The post MUST include this exact URL: {url}\n"
            "Use a casual, friendly tone with emojis.\n"
            "VERY IMPORTANT: Include exactly 5 to 10 SEO-relevant hashtags grouped at the end (not inline).\n"
            f"Keep everything under {max_len} characters total."
        )
    elif topic:
        prompt = (
            "You are a social media manager for a hobby e-commerce company called 'Ultima Supply'.\n"
            f"Write a short, engaging Threads post (min {min_len} chars, max {max_len} chars) about: '{topic}'.\n"
            "Use a casual, friendly tone with fun emojis.\n"
            "VERY IMPORTANT: Include exactly 5 to 10 SEO-relevant hashtags grouped at the end (not inline).\n"
            f"The entire post must be under {max_len} characters total."
        )
    else:
        return "⚠️ Provide either a topic or a Shopify blog URL."

    # Step 1: Generate
    post = get_response(
        input=prompt,
        template=lambda x: x.strip(),
        llm="gemini",
        md=False,
        temperature=0.9,
        max_tokens=250
    ).strip()

    if len(post) <= max_len:
        return post
    
    time.sleep(5)

    # Step 2: Shorten if needed
    shorten_prompt = (
        f"Shorten this post to {max_len} characters or fewer. Keep the Shopify link ({url}) and all original hashtags:\n\n{post}"
        if url else
        f"Shorten this post to {max_len} characters or fewer, keeping the tone, emojis, and all original hashtags:\n\n{post}"
    )

    shortened = get_response(
        input=shorten_prompt,
        template=lambda x: x.strip(),
        llm="gemini",
        md=False,
        temperature=0.7,
        max_tokens=200
    ).strip()

    return shortened[:max_len]


# ---- Threads poster (replaces the proxy) ----
def post_to_threads(message: str) -> str:
    """
    One-shot TEXT post to Threads using your long-lived token.
    Uses: POST https://graph.threads.net/me/threads with text params
    """
    if not THREADS_TOKEN:
        return "❌ Missing THREADS_USER_ACCESS_TOKEN environment variable."

    try:
        r = requests.post(
            "https://graph.threads.net/me/threads",
            headers={"Authorization": f"Bearer {THREADS_TOKEN}"},
            params={
                "text": message,
                "media_type": "TEXT",
                "auto_publish_text": "true",  # one-call publish for text
            },
            timeout=30,
        )
        # Prefer JSON, fall back to raw text for debugging
        data = {}
        try:
            data = r.json()
        except Exception:
            data = {"raw": r.text}

        if r.ok and isinstance(data, dict) and data.get("id"):
            return f"✅ Post successful! Threads Post ID: {data['id']}"
        return f"❌ Threads error ({r.status_code}): {data}"

    except requests.RequestException as e:
        return f"❌ Network error posting to Threads: {e}"
    except Exception as e:
        return f"❌ Unexpected error: {e}"


# ---- Shiny server wiring (IDs unchanged) ----
def server(input, output, session):
    post_status = reactive.Value("")

    @output
    @render.ui
    @reactive.event(input.gen_btn_threads)
    def threads_post_draft():
        topic = input.threads_topic().strip()
        url = input.threads_url().strip()

        if not topic and not url:
            return ui.HTML("<p><strong>⚠️ Enter a topic or a blog URL.</strong></p>")

        post = generate_threads_post(topic=topic, url=url)
        if post.startswith(("⚠️", "❌")):
            return ui.HTML(f"<p><strong>{post}</strong></p>")

        generated_threads_post.set(post)
        return ui.HTML(f"<p><strong>Generated Threads Post:</strong><br>{post}</p>")

    @output
    @render.text
    def threads_post_status():
        return post_status()

    @reactive.effect
    @reactive.event(input.post_btn_threads)
    def _():
        post = generated_threads_post()
        if not post:
            post_status.set("⚠️ No post generated yet.")
        else:
            post_status.set(post_to_threads(post))