File size: 6,680 Bytes
83c8c73
 
a1394be
 
 
d24e71f
6b7bfdd
a1394be
83c8c73
d24e71f
a1394be
83c8c73
d24e71f
83c8c73
 
 
 
 
 
 
a1394be
 
 
 
 
 
f9e7cf4
a1394be
 
 
 
 
d24e71f
 
 
a1394be
d24e71f
 
 
 
a1394be
d24e71f
 
 
 
 
a1394be
 
d24e71f
 
 
a1394be
d24e71f
 
 
 
 
 
6b7bfdd
 
d24e71f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83c8c73
d24e71f
 
 
 
2f38435
83c8c73
d24e71f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83c8c73
a1394be
 
 
 
 
 
 
 
 
 
 
d24e71f
 
 
 
 
 
 
 
 
 
 
83c8c73
d24e71f
 
 
 
 
83c8c73
d24e71f
83c8c73
 
d24e71f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a1394be
83c8c73
 
 
d343201
83c8c73
a1394be
 
 
d24e71f
a1394be
 
83c8c73
 
 
 
 
d343201
a1394be
d24e71f
 
 
83c8c73
d24e71f
83c8c73
a1394be
d24e71f
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
from shiny import reactive, render, ui
import os
import requests
from bs4 import BeautifulSoup
from dotenv import load_dotenv, find_dotenv
import tweepy
import time
from llm_connect import get_response

# Load environment variables
load_dotenv(find_dotenv(), override=True)

# Load credentials
API_KEY = os.getenv("TWITTER_ACC_API_KEY")
API_SECRET = os.getenv("TWITTER_ACC_API_SECRET")
ACCESS_TOKEN = os.getenv("TWITTER_ACC_ACCESS_TOKEN")
ACCESS_TOKEN_SECRET = os.getenv("TWITTER_ACC_ACCESS_TOKEN_SECRET")

generated_tweet = reactive.Value("")

def scrape_shopify_blog(url: str) -> str:
    try:
        resp = requests.get(url)
        soup = BeautifulSoup(resp.content, 'html.parser')
        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_tweet_from_text(text: str, url: str='https://ultimasupply.com/blogs/news', max_len: int = 250) -> str:
    # Step 1: Generate tweet from blog content
    gen_prompt = (
        "You are a social media manager for a hobby e-commerce company called 'Ultima Supply'.\n"
        f"Write a single engaging Twitter post (max {max_len} characters) summarizing the following Shopify blog content:\n\n"
        f"{text}\n\n"
        f"The tweet MUST include this URL: {url}\n"
        "Use casual, fun language. Add emojis and 3–5 SEO-relevant hashtags. Keep it under the character limit and make it attention-grabbing."
    )


    tweet = get_response(
        input=gen_prompt,
        template=lambda x: x.strip(),
        llm='gemini',
        md=False,
        temperature=0.6,
        max_tokens=100
    ).strip()

    print(f'Base Tweet: {tweet}')

    # Step 2: If too long, ask Gemini to shorten it
    if len(tweet) <= max_len:
        return tweet
    else:
        time.sleep(5)

        shorten_prompt = (
            f"You are shortening this tweet to {max_len} characters or fewer.\n"
            f"YOU MUST KEEP ALL of the original SEO hashtags and the Shopify blog link intact in the final output.\n"
            f"DO NOT remove or modify the link or any hashtags.\n\n"
            f"Original tweet:\n{tweet}\n"
        )

        shortened = get_response(
            input=shorten_prompt,
            template=lambda x: x.strip(),
            llm='gemini',
            md=False,
            temperature=0.5,
            max_tokens=75
        ).strip()

        return shortened  # Final hard cap just in case


def generate_tweet_from_topic(topic: str, max_len: int = 250) -> str:
    # Step 1: Generate tweet from topic
    gen_prompt = (
        f"You are a social media manager for a hobby e-commerce brand called 'Ultima Supply'.\n"
        f"Write a SHORT SINGLE SENTENCE Twitter post (max {max_len} characters) about: '{topic}'.\n"
        f"Use casual, fun language. Include emojis and 3-5 SEO-relevant hashtags."
    )

    tweet = get_response(
        input=gen_prompt,
        template=lambda x: x.strip(),
        llm='gemini',
        md=False,
        temperature=0.6,
        max_tokens=100
    ).strip()

    print(f'Base Tweet: {tweet}')

    # Step 2: If too long, regenerate with shortening prompt
    if len(tweet) <= max_len:
        return tweet
    else:
        shorten_prompt = (
            f"You are shortening this tweet to {max_len} characters or fewer.\n"
            f"YOU MUST KEEP ALL of the original SEO hashtags and the Shopify blog link intact in the final output.\n"
            f"DO NOT remove or modify the link or any hashtags.\n\n"
            f"Original tweet:\n{tweet}\n"
        )


        shortened = get_response(
            input=shorten_prompt,
            template=lambda x: x.strip(),
            llm='gemini',
            md=False,
            temperature=0.5,
            max_tokens=75
        ).strip()


        return shortened  # Ensure hard cutoff if still too long


def tweet_input_handler(url: str, topic: str) -> str:
    if url.strip():
        raw_text = scrape_shopify_blog(url)
        if not raw_text or raw_text.startswith("❌"):
            return "⚠️ Failed to extract content from blog."
        return generate_tweet_from_text(raw_text)
    elif topic.strip():
        return generate_tweet_from_topic(topic)
    else:
        return "⚠️ Provide either a blog URL or a topic."

# Create Tweepy v2 Client
def get_tweepy_client():
    return tweepy.Client(
        consumer_key=API_KEY,
        consumer_secret=API_SECRET,
        access_token=ACCESS_TOKEN,
        access_token_secret=ACCESS_TOKEN_SECRET
    )

# Post tweet using Tweepy v2 Client
def post_tweet_tweepy(tweet_text: str) -> str:
    try:
        client = get_tweepy_client()
        response = client.create_tweet(text=tweet_text)
        tweet_id = response.data.get("id")
        print("βœ… Tweet posted:", tweet_id)
        return f"βœ… Tweet posted (ID: {tweet_id})"
    except Exception as e:
        print("❌ Failed to post tweet:", e)
        return f"❌ Failed to post tweet: {e}"

# Check auth by attempting to fetch own user (optional)
def check_twitter_connection() -> str:
    try:
        client = get_tweepy_client()
        user_data = client.get_me()
        if user_data and user_data.data:
            username = user_data.data.username
            print(f"βœ… Connected to Twitter as @{username}")
            return f"βœ… Connected to Twitter as @{username}"
        else:
            return "❌ Unable to fetch Twitter account info."
    except Exception as e:
        print("❌ Connection check failed:", e)
        return f"❌ Twitter auth error: {e}"

# ---- EXPORT THIS FOR app.py TO CALL ----
def server(input, output, session):
    @output
    @render.ui
    @reactive.event(input.gen_btn_twt)
    def tweet_draft():
        url = input.shopify_url().strip()
        topic = input.tweet_topic().strip()
        tweet = tweet_input_handler(url, topic)
        print("πŸ“ Generated Tweet:\n", tweet)
        if tweet.startswith("⚠️"):
            return ui.HTML(f"<p><strong>{tweet}</strong></p>")
        generated_tweet.set(tweet)
        return ui.HTML(f"<p><strong>Generated Tweet:</strong><br>{tweet}</p>")

    @output
    @render.text
    @reactive.event(input.post_btn_twt)
    def tweet_post_status():
        status = check_twitter_connection()
        if status.startswith("❌"):
            return status
        tweet = generated_tweet()
        print("πŸ“€ Attempting to post tweet:", tweet)
        if not tweet:
            return "⚠️ No tweet generated yet."
        return post_tweet_tweepy(tweet)