AiDeveloper1 commited on
Commit
14c24bc
·
verified ·
1 Parent(s): ac10baa

Upload 5 files

Browse files
Files changed (5) hide show
  1. agents.py +170 -0
  2. app.py +472 -0
  3. helpers.py +193 -0
  4. requirements.txt +10 -0
  5. test2.py +306 -0
agents.py ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_google_genai import GoogleGenerativeAI
2
+ from langchain_core.prompts import ChatPromptTemplate
3
+ from langchain.chains import LLMChain
4
+
5
+ class SocialMediaAgents:
6
+ PLATFORM_LIMITS = {
7
+ "twitter": {"chars": 280, "words": None},
8
+ "instagram": {"chars": None, "words": 400},
9
+ "linkedin": {"chars": None, "words": 600},
10
+ "facebook": {"chars": None, "words": 1000}
11
+ }
12
+
13
+ def __init__(self, api_key: str):
14
+ """Initialize the agent with a Google API key."""
15
+ self.llm = GoogleGenerativeAI(model="gemini-1.5-flash", google_api_key=api_key)
16
+
17
+ def _create_chain(self, template: str) -> LLMChain:
18
+ """Create an LLM chain with the given prompt template."""
19
+ prompt = ChatPromptTemplate.from_template(template)
20
+ return LLMChain(llm=self.llm, prompt=prompt)
21
+
22
+ def _enforce_limits(self, text: str, platform: str) -> str:
23
+ """Enforce platform-specific character or word limits."""
24
+ limits = self.PLATFORM_LIMITS[platform.lower()]
25
+ if limits["chars"] and len(text) > limits["chars"]:
26
+ return text[:limits["chars"]-3] + "..."
27
+ if limits["words"]:
28
+ words = text.split()
29
+ if len(words) > limits["words"]:
30
+ return " ".join(words[:limits["words"]]) + "..."
31
+ return text
32
+
33
+ # def twitter_transform(self, title: str, description: str) -> dict:
34
+ # """Transform content for Twitter."""
35
+ # link = "https://www.eye-on.ai/podcast-archive"
36
+ # template = """Transform this into a Twitter post (280 characters max):
37
+ # - Attention-grabbing message
38
+ # - 1-2 relevant hashtags
39
+ # - Essential information only
40
+
41
+ # Format output EXACTLY like this:
42
+ # New Title: [transformed title]
43
+ # ---
44
+ # New Description: [transformed description]
45
+
46
+ # add this line after descripttion and make link clickable listen to full podcast on {link}
47
+
48
+ # Original Content:
49
+ # Title: {title}
50
+ # Description: {description}"""
51
+ # chain = self._create_chain(template)
52
+ # response = chain.invoke({"title": title, "description": description, "link": link})
53
+
54
+ def twitter_transform(self, title: str, description: str,link:str) -> dict:
55
+ """Transform content for Twitter with a clickable link and within 280 characters."""
56
+ template = """
57
+ Transform this into a Twitter post (max 280 characters total):
58
+ - Create an attention-grabbing single-line tweet using key info from the title and description
59
+ - Include 1-2 relevant hashtags
60
+ - End with this line exactly: Listen to full podcast: {link}
61
+ - Ensure the ENTIRE result is no more than 280 characters TOTAL (including the link line)
62
+ - if character more than 280 characters manage limit and exclude description character
63
+ - Don't short {link} i want full link
64
+ Return in this format:
65
+ New Title: [transformed title]
66
+ ---
67
+ New Description: [tweet content]
68
+
69
+ Original Content:
70
+ Title: {title}
71
+ Description: {description}
72
+ """
73
+ chain = self._create_chain(template)
74
+ response = chain.invoke({"title": title, "description": description, "link": link})
75
+
76
+
77
+
78
+ parts = response['text'].split('---')
79
+ result = {
80
+ "new_title": parts[0].replace('New Title:', '').strip(),
81
+ "new_description": parts[1].replace('New Description:', '').strip()
82
+ }
83
+ combined_text = f"{result['new_title']} {result['new_description']}"
84
+ limited_text = self._enforce_limits(combined_text, "twitter")
85
+ if len(limited_text) < len(combined_text):
86
+ result['new_title'] = ""
87
+ result['new_description'] = limited_text
88
+ return result
89
+
90
+ def instagram_transform(self, title: str, description: str) -> dict:
91
+ """Transform content for Instagram."""
92
+ template = """Transform this into an Instagram post (400 words max):
93
+ - Catchy title with relevant emojis
94
+ - Engaging description
95
+ - 3-5 relevant hashtags
96
+
97
+ Format output EXACTLY like this:
98
+ New Title: [transformed title]
99
+ ---
100
+ New Description: [transformed description]
101
+
102
+ Original Content:
103
+ Title: {title}
104
+ Description: {description}"""
105
+ chain = self._create_chain(template)
106
+ response = chain.invoke({"title": title, "description": description})
107
+ parts = response['text'].split('---')
108
+ result = {
109
+ "new_title": parts[0].replace('New Title:', '').strip(),
110
+ "new_description": parts[1].replace('New Description:', '').strip()
111
+ }
112
+ result['new_description'] = self._enforce_limits(result['new_description'], "instagram")
113
+ return result
114
+
115
+ def linkedin_transform(self, title: str, description: str,link) -> dict:
116
+ """Transform content for LinkedIn."""
117
+
118
+ template = """Transform this into a LinkedIn post (600 words max):
119
+ - Professional title
120
+ - Detailed description with business insights
121
+ - 2-3 relevant hashtags
122
+ - Professional tone
123
+ - End with this line exactly: Listen to full podcast: {link}
124
+ - Don't change link format and words.
125
+ - Ensure the ENTIRE result is no more than 600 words TOTAL (including the link line)
126
+ - if character more than 600 words manage limit and exclude description character
127
+
128
+ Format output EXACTLY like this:
129
+ New Title: [transformed title]
130
+ ---
131
+ New Description: [transformed description]
132
+
133
+ Original Content:
134
+ Title: {title}
135
+ Description: {description}"""
136
+ chain = self._create_chain(template)
137
+ response = chain.invoke({"title": title, "description": description, "link": link})
138
+ parts = response['text'].split('---')
139
+ result = {
140
+ "new_title": parts[0].replace('New Title:', '').strip(),
141
+ "new_description": parts[1].replace('New Description:', '').strip()
142
+ }
143
+ result['new_description'] = self._enforce_limits(result['new_description'], "linkedin")
144
+ return result
145
+
146
+ def facebook_transform(self, title: str, description: str) -> dict:
147
+ """Transform content for Facebook."""
148
+ template = """Transform this into a Facebook post (1000 words max):
149
+ - Engaging title
150
+ - Conversational description
151
+ - Call to action for engagement
152
+ - 1-2 relevant hashtags
153
+
154
+ Format output EXACTLY like this:
155
+ New Title: [transformed title]
156
+ ---
157
+ New Description: [transformed description]
158
+
159
+ Original Content:
160
+ Title: {title}
161
+ Description: {description}"""
162
+ chain = self._create_chain(template)
163
+ response = chain.invoke({"title": title, "description": description})
164
+ parts = response['text'].split('---')
165
+ result = {
166
+ "new_title": parts[0].replace('New Title:', '').strip(),
167
+ "new_description": parts[1].replace('New Description:', '').strip()
168
+ }
169
+ result['new_description'] = self._enforce_limits(result['new_description'], "facebook")
170
+ return result
app.py ADDED
@@ -0,0 +1,472 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, redirect, url_for, request, render_template, session
2
+ from apscheduler.schedulers.background import BackgroundScheduler
3
+ import requests
4
+ from datetime import datetime, timedelta
5
+ import tweepy
6
+ from agents import SocialMediaAgents # Assuming this is your agents.py file
7
+ import feedparser
8
+ from helpers import post_to_linkedin, post_to_twitter, extract_image_url
9
+ import random
10
+ import uuid
11
+ from dotenv import load_dotenv
12
+ import os
13
+ import atexit
14
+ import json
15
+
16
+ load_dotenv()
17
+
18
+ ngrok_link = os.getenv("Ngrok_Link")
19
+
20
+ app = Flask(__name__)
21
+ app.secret_key = '12345678765' # Replace with a secure key
22
+
23
+ scheduler = BackgroundScheduler()
24
+ scheduler.start()
25
+
26
+ api_key = os.getenv("Gemini_key")
27
+
28
+ agents = SocialMediaAgents(api_key)
29
+
30
+ LINKEDIN_CLIENT_ID = os.getenv("LINKEDIN_CLIENT_ID")
31
+ LINKEDIN_CLIENT_SECRET = os.getenv("LINKEDIN_CLIENT_SECRET")
32
+ TWITTER_CLIENT_ID = os.getenv("TWITTER_CLIENT_ID")
33
+ TWITTER_CLIENT_SECRET = os.getenv("TWITTER_CLIENT_SECRET")
34
+
35
+ posts = []
36
+ temp_posts = {}
37
+
38
+ @app.route('/')
39
+ def home():
40
+ connected_platforms = {
41
+ 'linkedin': 'linkedin_access_token' in session and 'linkedin_id' in session,
42
+ 'twitter': 'twitter_access_token' in session and 'twitter_access_token_secret' in session,
43
+
44
+ }
45
+ name ={
46
+ 'name':session.get('linkedin_name'),
47
+ 'tw_name':session.get('twitter_name')
48
+ }
49
+ print(name['tw_name'])
50
+ return render_template('home.html', connected_platforms=connected_platforms , name = name)
51
+
52
+ @app.route('/connect_all')
53
+ def connect_all():
54
+ session['connect_all'] = True
55
+ return redirect(url_for('linkedin_auth'))
56
+
57
+ @app.route('/linkedin/auth')
58
+ def linkedin_auth():
59
+ if "linkedin_access_token" not in session:
60
+ redirect_uri = f'{ngrok_link}/linkedin/callback'
61
+ scope = 'openid profile w_member_social'
62
+ auth_url = (
63
+ f'https://www.linkedin.com/oauth/v2/authorization?'
64
+ f'response_type=code&client_id={LINKEDIN_CLIENT_ID}&redirect_uri={redirect_uri}&'
65
+ f'scope={scope}&state=randomstring'
66
+ )
67
+ return redirect(auth_url)
68
+
69
+ @app.route('/linkedin/callback')
70
+ def linkedin_callback():
71
+ code = request.args.get('code')
72
+ if not code:
73
+ return "Error: No authorization code provided"
74
+ token_url = 'https://www.linkedin.com/oauth/v2/accessToken'
75
+
76
+
77
+
78
+ data = {
79
+ 'grant_type': 'authorization_code',
80
+ 'code': code,
81
+ 'redirect_uri': f'{ngrok_link}/linkedin/callback',
82
+ 'client_id': LINKEDIN_CLIENT_ID,
83
+ 'client_secret': LINKEDIN_CLIENT_SECRET
84
+ }
85
+ response = requests.post(token_url, data=data)
86
+ if response.status_code != 200:
87
+ return "Error: Could not get LinkedIn access token"
88
+ token_data = response.json()
89
+ session['linkedin_access_token'] = token_data.get('access_token')
90
+ profile_url = 'https://api.linkedin.com/v2/userinfo'
91
+ headers = {'Authorization': f'Bearer {session["linkedin_access_token"]}'}
92
+ profile_response = requests.get(profile_url, headers=headers)
93
+
94
+
95
+ if profile_response.status_code != 200:
96
+ return "Error: Could not fetch LinkedIn profile"
97
+ user_info = profile_response.json()
98
+ session['linkedin_id'] = user_info.get('sub')
99
+
100
+ print("here333333333",user_info)
101
+ session['linkedin_name'] = user_info['name']
102
+ if session.get('connect_all') and 'twitter_access_token' not in session:
103
+ return redirect(url_for('twitter_auth'))
104
+ return redirect(url_for('home'))
105
+
106
+ @app.route('/twitter/auth')
107
+ def twitter_auth():
108
+ auth = tweepy.OAuth1UserHandler(TWITTER_CLIENT_ID, TWITTER_CLIENT_SECRET, f'{ngrok_link}/twitter/callback')
109
+ try:
110
+ redirect_url = auth.get_authorization_url()
111
+ session['request_token'] = auth.request_token
112
+ return redirect(redirect_url)
113
+ except tweepy.TweepyException as e:
114
+ return f"Error starting Twitter auth: {e}"
115
+
116
+ # @app.route('/twitter/callback')
117
+ # def twitter_callback():
118
+ # request_token = session.pop('request_token', None)
119
+ # if not request_token:
120
+ # return "Error: Request token not found in session. <a href='/twitter/auth'>Please try logging in again</a>."
121
+ # verifier = request.args.get('oauth_verifier')
122
+ # if not verifier:
123
+ # return "Error: No OAuth verifier provided"
124
+ # auth = tweepy.OAuth1UserHandler(TWITTER_CLIENT_ID, TWITTER_CLIENT_SECRET)
125
+ # auth.request_token = request_token
126
+ # try:
127
+ # auth.get_access_token(verifier)
128
+ # session['twitter_access_token'] = auth.access_token
129
+ # session['twitter_access_token_secret'] = auth.access_token_secret
130
+ # session.pop('connect_all', None)
131
+ # return redirect(url_for('home'))
132
+ # except tweepy.TweepyException as e:
133
+ # return f"Twitter authorization failed: {e}"
134
+
135
+
136
+
137
+
138
+ #============== Additional ============#######
139
+
140
+
141
+ @app.route('/twitter/callback')
142
+ def twitter_callback():
143
+ request_token = session.pop('request_token', None)
144
+ if not request_token:
145
+ return "Error: Request token not found in session. <a href='/twitter/auth'>Please try logging in again</a>."
146
+
147
+ verifier = request.args.get('oauth_verifier')
148
+ if not verifier:
149
+ return "Error: No OAuth verifier provided"
150
+
151
+ auth = tweepy.OAuth1UserHandler(TWITTER_CLIENT_ID, TWITTER_CLIENT_SECRET)
152
+ auth.request_token = request_token
153
+
154
+ try:
155
+ auth.get_access_token(verifier)
156
+ session['twitter_access_token'] = auth.access_token
157
+ print("@@@@@@@@@@@@@@",auth.access_token)
158
+
159
+ access_token = auth.access_token
160
+
161
+ # Path to your existing JSON file
162
+ json_file_path = "access.json"
163
+
164
+ # Load the existing data
165
+ try:
166
+ with open(json_file_path, "r") as f:
167
+ data = json.load(f)
168
+ except (FileNotFoundError, json.JSONDecodeError):
169
+ data = {}
170
+ # Store or update the access_token
171
+ data["access_token"] = access_token
172
+
173
+ # Write it back to the file
174
+ with open(json_file_path, "w") as f:
175
+ json.dump(data, f, indent=4)
176
+
177
+
178
+ session['twitter_access_token_secret'] = auth.access_token_secret
179
+
180
+ #Set the access tokens to the auth handler
181
+ auth.set_access_token(auth.access_token, auth.access_token_secret)
182
+ api = tweepy.API(auth)
183
+
184
+ #Get user info
185
+ user = api.verify_credentials()
186
+ if user:
187
+ session['twitter_name'] = user.name # Full display name (e.g., "John Doe")
188
+ session['twitter_username'] = user.screen_name # Handle (e.g., "johndoe")
189
+
190
+ session.pop('connect_all', None)
191
+ return redirect(url_for('home'))
192
+
193
+ except tweepy.TweepyException as e:
194
+ return f"Twitter authorization failed: {e}"
195
+
196
+
197
+
198
+ @app.route('/disconnect/<platform>')
199
+ def disconnect(platform):
200
+ if platform == 'linkedin':
201
+
202
+ print("access",session["linkedin_access_token"])
203
+ session.pop('linkedin_access_token', None)
204
+ # print("sssss",session["linkedin_access_token"])
205
+
206
+ # session.pop('linkedin_access_token', None)
207
+ session.pop('linkedin_id', None)
208
+ elif platform == 'twitter':
209
+ session.pop('twitter_access_token', None)
210
+ session.pop('twitter_access_token_secret', None)
211
+ return redirect(url_for('home'))
212
+
213
+ @app.route('/post', methods=['GET', 'POST'])
214
+ def create_post():
215
+ if not (session.get('linkedin_access_token') or session.get('twitter_access_token')):
216
+ return redirect(url_for('home'))
217
+
218
+ if request.method == 'POST':
219
+ rss_urls = request.form.getlist('rss_urls')
220
+ posts_per_day = int(request.form['posts_per_day'])
221
+ frequency = request.form['frequency']
222
+ schedule_type = request.form['schedule_type']
223
+ first_post_time = datetime.strptime(request.form['first_post_time'], '%Y-%m-%dT%H:%M')
224
+
225
+ if schedule_type == 'daily':
226
+ total_posts = posts_per_day
227
+ elif schedule_type == 'weekly':
228
+ total_posts = posts_per_day * 7
229
+ else: # monthly
230
+ total_posts = posts_per_day * 30
231
+
232
+ all_entries = []
233
+ for rss_url in rss_urls:
234
+ feed = feedparser.parse(rss_url)
235
+ all_entries.extend(feed.entries)
236
+
237
+ selected_entries = random.sample(all_entries, min(total_posts, len(all_entries)))
238
+
239
+ # selected_entries = sorted(
240
+ # all_entries,
241
+ # key=lambda entry: entry.published_parsed,
242
+ # reverse=False
243
+ # )[:total_posts]
244
+
245
+
246
+ # selected_entries = sorted(
247
+ # all_entries,
248
+ # key=lambda entry: entry.published_parsed,
249
+ # reverse=False
250
+ # )[-3:]
251
+
252
+
253
+ generated_posts = {'linkedin': [], 'twitter': []}
254
+ if session.get('linkedin_access_token'):
255
+ for entry in selected_entries:
256
+ title = entry.title
257
+ description = entry.get('description', entry.get('summary', ''))
258
+ print("desc",description)
259
+ print(type(description))
260
+ image_url = extract_image_url(entry)
261
+ print("img_url",image_url)
262
+ if image_url == None:
263
+ print("here44444")
264
+ image_url = "https://static.libsyn.com/p/assets/0/2/1/3/0213c7d9616b570b16c3140a3186d450/LOGO_1400x1400.jpg"
265
+ transformed = agents.linkedin_transform(title, description)
266
+
267
+ text = f"{transformed['new_title']} {transformed['new_description']}"
268
+ generated_posts['linkedin'].append({
269
+ 'text': text,
270
+ 'image_url': image_url,
271
+ 'platform': 'linkedin',
272
+ 'access_token': session['linkedin_access_token'],
273
+ 'linkedin_id': session['linkedin_id'],
274
+ 'status': 'pending'
275
+ })
276
+ if session.get('twitter_access_token'):
277
+ for entry in selected_entries:
278
+ title = entry.title
279
+ description = entry.get('description', entry.get('summary', ''))
280
+ image_url = extract_image_url(entry)
281
+ print("desc",description)
282
+ print(type(description))
283
+ print("img_url",image_url)
284
+ if image_url == None:
285
+ print("here44444")
286
+ image_url = "https://static.libsyn.com/p/assets/0/2/1/3/0213c7d9616b570b16c3140a3186d450/LOGO_1400x1400.jpg"
287
+
288
+
289
+ transformed = agents.twitter_transform(title, description)
290
+ text = f"{transformed['new_title']} {transformed['new_description']}"
291
+
292
+
293
+ generated_posts['twitter'].append({
294
+ 'text': text,
295
+ 'image_url': image_url,
296
+ 'platform': 'twitter',
297
+ 'access_token': session['twitter_access_token'],
298
+ 'access_token_secret': session['twitter_access_token_secret'],
299
+ 'status': 'pending'
300
+ })
301
+
302
+ post_id = str(uuid.uuid4())
303
+ temp_posts[post_id] = {
304
+ 'posts': generated_posts,
305
+ 'first_post_time': first_post_time,
306
+ 'frequency': int(frequency)
307
+ }
308
+ return redirect(url_for('review_posts', post_id=post_id))
309
+
310
+ return render_template('post.html')
311
+
312
+ @app.route('/review/<post_id>', methods=['GET', 'POST'])
313
+ def review_posts(post_id):
314
+ if post_id not in temp_posts:
315
+ return redirect(url_for('create_post'))
316
+
317
+ post_data = temp_posts[post_id]
318
+ all_posts = []
319
+ for platform_posts in post_data['posts'].values():
320
+ all_posts.extend(platform_posts)
321
+
322
+ if request.method == 'POST':
323
+ first_post_time = post_data['first_post_time']
324
+ frequency = post_data['frequency']
325
+
326
+ # Schedule posts separately for each platform
327
+ for platform, platform_posts in post_data['posts'].items():
328
+ for i, post in enumerate(platform_posts):
329
+ scheduled_time = first_post_time + timedelta(minutes=frequency * i)
330
+ post['scheduled_time'] = scheduled_time
331
+ posts.append(post)
332
+ if platform == 'linkedin':
333
+ scheduler.add_job(post_to_linkedin, 'date', run_date=scheduled_time, args=[post])
334
+ elif platform == 'twitter':
335
+ scheduler.add_job(post_to_twitter, 'date', run_date=scheduled_time, args=[post])
336
+
337
+ del temp_posts[post_id]
338
+ return redirect(url_for('scheduled_posts'))
339
+
340
+ return render_template('review.html',
341
+ posts=all_posts,
342
+ first_post_time=post_data['first_post_time'].isoformat(),
343
+ frequency=post_data['frequency'])
344
+
345
+ @app.route('/scheduled')
346
+ def scheduled_posts():
347
+ linkedin_posts = [p for p in posts if p['platform'] == 'linkedin' and p['status'] == 'pending']
348
+ twitter_posts = [p for p in posts if p['platform'] == 'twitter' and p['status'] == 'pending']
349
+ return render_template('scheduled.html', linkedin_posts=linkedin_posts, twitter_posts=twitter_posts)
350
+
351
+
352
+
353
+
354
+
355
+ def scheduled_task():
356
+ print(f"Scheduled task ran at: {datetime.now()}")
357
+ print("eeeeeewwwww",session.get('twitter_access_token'))
358
+ try:
359
+
360
+ json_file_path = 'access.json'
361
+ if os.path.exists(json_file_path) and os.path.getsize(json_file_path) > 0:
362
+ # Load existing JSON data
363
+ with open(json_file_path, "r") as f:
364
+ data = json.load(f)
365
+
366
+ # Save or update the access token
367
+ access_token = data["access_token"]
368
+
369
+ print("Access token saved.")
370
+
371
+
372
+
373
+ rss_url = "https://feeds.libsyn.com/123267/rss"
374
+ feed = feedparser.parse(rss_url)
375
+ print("Podcast Title:", feed.feed.title)
376
+ print("Podcast Link:", feed.feed.link)
377
+ print("Description:", feed.feed.get("description", "No description available."))
378
+
379
+ # Print latest episode info
380
+ print("\nLatest Episode:")
381
+ latest = feed.entries[0]
382
+ print("Title:", latest.title)
383
+ print("Published:", latest.published)
384
+ print("Link:", latest.link)
385
+ print("Description:", latest.description)
386
+
387
+
388
+
389
+ else:
390
+ print("login to linkedin")
391
+
392
+
393
+ except:
394
+ print("not")
395
+
396
+ @app.route('/ping-api')
397
+ def ping_api():
398
+ print("📡 Ping API endpoint hit!")
399
+ json_file_path = 'access.json'
400
+ if os.path.exists(json_file_path) and os.path.getsize(json_file_path) > 0:
401
+ # Load existing JSON data
402
+ with open(json_file_path, "r") as f:
403
+ data = json.load(f)
404
+
405
+ # Save or update the access token
406
+ access_token = data["access_token"]
407
+
408
+ print("Access token saved.")
409
+
410
+
411
+
412
+ rss_url = "https://feeds.libsyn.com/123267/rss"
413
+ feed = feedparser.parse(rss_url)
414
+ print("Podcast Title:", feed.feed.title)
415
+ print("Podcast Link:", feed.feed.link)
416
+ print("Description:", feed.feed.get("description", "No description available."))
417
+
418
+ # Print latest episode info
419
+ print("\nLatest Episode:")
420
+ latest = feed.entries[0]
421
+ print("Title:", latest.title)
422
+ print("Published:", latest.published)
423
+
424
+ if latest.published:
425
+ json_file_path = "latest.json"
426
+
427
+ # Load the existing data
428
+ try:
429
+ with open(json_file_path, "r") as f:
430
+ data = json.load(f)
431
+ except (FileNotFoundError, json.JSONDecodeError):
432
+ data = {}
433
+ # Store or update the access_token
434
+ data["access_token"] = access_token
435
+
436
+ # Write it back to the file
437
+ with open(json_file_path, "w") as f:
438
+ json.dump(data, f, indent=4)
439
+ print("Link:", latest.link)
440
+ print("Description:", latest.description)
441
+
442
+
443
+
444
+ else:
445
+ print("login to linkedin")
446
+ s = {
447
+ 'na':'yes'
448
+ }
449
+ return s
450
+
451
+ def scheduled_task():
452
+ try:
453
+ response = requests.get("http://127.0.0.1:5000/ping-api")
454
+ print(f"[{datetime.now()}] Status: {response.status_code}, Response: {response.text}")
455
+ except requests.exceptions.RequestException as e:
456
+ print(f"[{datetime.now()}] Failed to call API: {e}")
457
+
458
+
459
+ # ✅ Setup scheduler
460
+ scheduler = BackgroundScheduler()
461
+ scheduler.add_job(func=scheduled_task, trigger="interval", seconds=10)
462
+ scheduler.start()
463
+
464
+ # ✅ Clean up scheduler on shutdown
465
+ atexit.register(lambda: scheduler.shutdown())
466
+
467
+
468
+
469
+
470
+ if __name__ == '__main__':
471
+ port = int(os.environ.get("PORT", 5000)) # Get the port from Render
472
+ app.run(debug=True, host='0.0.0.0', port=port)
helpers.py ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ from io import BytesIO
3
+ import requests
4
+ import tweepy
5
+
6
+ import os
7
+ from dotenv import load_dotenv
8
+ load_dotenv()
9
+
10
+ LINKEDIN_CLIENT_ID = os.getenv("LINKEDIN_CLIENT_ID")
11
+ LINKEDIN_CLIENT_SECRET = os.getenv("LINKEDIN_CLIENT_SECRET")
12
+ TWITTER_CLIENT_ID = os.getenv("TWITTER_CLIENT_ID")
13
+ TWITTER_CLIENT_SECRET = os.getenv("TWITTER_CLIENT_SECRET")
14
+
15
+ def extract_image_url(entry):
16
+ """Extract an image URL from an RSS feed entry."""
17
+ for enclosure in entry.get('enclosures', []):
18
+ if enclosure.get('type', '').startswith('image/'):
19
+ return enclosure.get('url')
20
+ for media in entry.get('media_content', []):
21
+ if media.get('type', '').startswith('image/'):
22
+ return media.get('url')
23
+ for thumbnail in entry.get('media_thumbnail', []):
24
+ if thumbnail.get('url'):
25
+ return thumbnail.get('url')
26
+ if 'image' in entry:
27
+ image = entry['image']
28
+ if isinstance(image, dict) and 'url' in image:
29
+ return image['url']
30
+ elif isinstance(image, list):
31
+ for img in image:
32
+ if 'url' in img:
33
+ return img['url']
34
+ if 'itunes_image' in entry:
35
+ return entry['itunes_image'].get('href')
36
+ for field in ['description', 'summary', 'content']:
37
+ if field in entry:
38
+ content = entry[field]
39
+ if isinstance(content, list):
40
+ content = content[0].get('value', '')
41
+ elif isinstance(content, dict):
42
+ content = content.get('value', '')
43
+ else:
44
+ content = str(content)
45
+ match = re.search(r'<img[^>]+src=["\'](.*?)["\']', content, re.I)
46
+ if match:
47
+ return match.group(1)
48
+ return None
49
+
50
+ def post_to_linkedin(post):
51
+ """Post content to LinkedIn with optional image."""
52
+ if post['status'] not in ['pending', 'posting']:
53
+ return
54
+ access_token = post['access_token']
55
+
56
+
57
+ print("linkedin_access_token",access_token)
58
+ linkedin_id = post['linkedin_id']
59
+ image_url = post.get('image_url')
60
+ headers = {
61
+ 'Authorization': f'Bearer {access_token}',
62
+ 'Content-Type': 'application/json',
63
+ }
64
+ if image_url:
65
+ response = requests.get(image_url, timeout=10)
66
+ if response.status_code == 200:
67
+ image_content = response.content
68
+ register_url = 'https://api.linkedin.com/v2/assets?action=registerUpload'
69
+ register_body = {
70
+ 'registerUploadRequest': {
71
+ 'recipes': ['urn:li:digitalmediaRecipe:feedshare-image'],
72
+ 'owner': f'urn:li:person:{linkedin_id}',
73
+ 'serviceRelationships': [
74
+ {'relationshipType': 'OWNER', 'identifier': 'urn:li:userGeneratedContent'}
75
+ ]
76
+ }
77
+ }
78
+ register_response = requests.post(register_url, headers=headers, json=register_body)
79
+ if register_response.status_code == 200:
80
+ upload_data = register_response.json()['value']
81
+ upload_url = upload_data['uploadMechanism']['com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest']['uploadUrl']
82
+ asset = upload_data['asset']
83
+ upload_headers = {'Authorization': f'Bearer {access_token}'}
84
+ upload_response = requests.put(upload_url, headers=upload_headers, data=image_content)
85
+ if upload_response.status_code == 201:
86
+ api_url = 'https://api.linkedin.com/v2/ugcPosts'
87
+ post_body = {
88
+ 'author': f'urn:li:person:{linkedin_id}',
89
+ 'lifecycleState': 'PUBLISHED',
90
+ 'specificContent': {
91
+ 'com.linkedin.ugc.ShareContent': {
92
+ 'shareCommentary': {'text': post['text']},
93
+ 'shareMediaCategory': 'IMAGE',
94
+ 'media': [{'status': 'READY', 'media': asset}]
95
+ }
96
+ },
97
+ 'visibility': {'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC'}
98
+ }
99
+ response = requests.post(api_url, headers=headers, json=post_body)
100
+ post['status'] = 'posted' if response.status_code == 201 else 'failed'
101
+ print(f"LinkedIn post attempt: {response.status_code} - {response.text}")
102
+ else:
103
+ print(f"Image upload failed: {upload_response.status_code}")
104
+ else:
105
+ print(f"Upload registration failed: {register_response.status_code}")
106
+ else:
107
+ print(f"Image download failed: {response.status_code}")
108
+ if post['status'] != 'posted':
109
+ api_url = 'https://api.linkedin.com/v2/ugcPosts'
110
+ post_body = {
111
+ 'author': f'urn:li:person:{linkedin_id}',
112
+ 'lifecycleState': 'PUBLISHED',
113
+ 'specificContent': {
114
+ 'com.linkedin.ugc.ShareContent': {
115
+ 'shareCommentary': {'text': post['text']},
116
+ 'shareMediaCategory': 'NONE'
117
+ }
118
+ },
119
+ 'visibility': {'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC'}
120
+ }
121
+ response = requests.post(api_url, headers=headers, json=post_body)
122
+ post['status'] = 'posted' if response.status_code == 201 else 'failed'
123
+ print(f"LinkedIn text-only post: {response.status_code} - {response.text}")
124
+ else:
125
+ api_url = 'https://api.linkedin.com/v2/ugcPosts'
126
+ post_body = {
127
+ 'author': f'urn:li:person:{linkedin_id}',
128
+ 'lifecycleState': 'PUBLISHED',
129
+ 'specificContent': {
130
+ 'com.linkedin.ugc.ShareContent': {
131
+ 'shareCommentary': {'text': post['text']},
132
+ 'shareMediaCategory': 'NONE'
133
+ }
134
+ },
135
+ 'visibility': {'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC'}
136
+ }
137
+ response = requests.post(api_url, headers=headers, json=post_body)
138
+ post['status'] = 'posted' if response.status_code == 201 else 'failed'
139
+ print(f"LinkedIn post attempt: {response.status_code} - {response.text}")
140
+
141
+ def post_to_twitter(post):
142
+ """Post content to Twitter with optional image."""
143
+ if post['status'] not in ['pending', 'posting']:
144
+ return
145
+ client = tweepy.Client(
146
+ consumer_key=TWITTER_CLIENT_ID,
147
+ consumer_secret=TWITTER_CLIENT_SECRET,
148
+ access_token=post['access_token'],
149
+ access_token_secret=post['access_token_secret']
150
+ )
151
+ print("access_token_secret",client.access_token_secret)
152
+ image_url = post.get('image_url')
153
+ if image_url:
154
+ response = requests.get(image_url, timeout=10)
155
+ if response.status_code == 200:
156
+ image_content = BytesIO(response.content)
157
+ try:
158
+ api = tweepy.API(tweepy.OAuth1UserHandler(
159
+ TWITTER_CLIENT_ID, TWITTER_CLIENT_SECRET,
160
+ post['access_token'], post['access_token_secret']
161
+ ))
162
+ media = api.media_upload(filename='image', file=image_content)
163
+ client.create_tweet(text=post['text'], media_ids=[media.media_id])
164
+ post['status'] = 'posted'
165
+ print("Twitter post with image successful")
166
+ except tweepy.TweepyException as e:
167
+ print(f"Twitter image post error: {e}")
168
+ try:
169
+ client.create_tweet(text=post['text'])
170
+ post['status'] = 'posted'
171
+ print("Twitter text-only post successful")
172
+ except tweepy.TweepyException as e:
173
+ post['status'] = 'failed'
174
+ print(f"Twitter text-only error: {e}")
175
+ except Exception as e:
176
+ print(f"Media upload error: {e}")
177
+ else:
178
+ print(f"Image download failed: {response.status_code}")
179
+ try:
180
+ client.create_tweet(text=post['text'])
181
+ post['status'] = 'posted'
182
+ print("Twitter text-only post successful")
183
+ except tweepy.TweepyException as e:
184
+ post['status'] = 'failed'
185
+ print(f"Twitter text-only error: {e}")
186
+ else:
187
+ try:
188
+ client.create_tweet(text=post['text'])
189
+ post['status'] = 'posted'
190
+ print("Twitter post successful")
191
+ except tweepy.TweepyException as e:
192
+ post['status'] = 'failed'
193
+ print(f"Twitter error: {e}")
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ flask
2
+ apscheduler
3
+ requests
4
+ tweepy
5
+ feedparser
6
+ langchain_google_genai
7
+ langchain_core
8
+ flask-session
9
+ langchain
10
+ python-dotenv
test2.py ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, redirect, url_for, request, render_template, session
2
+ from apscheduler.schedulers.background import BackgroundScheduler
3
+ import requests
4
+ from datetime import datetime, timedelta
5
+ import tweepy
6
+ from agents import SocialMediaAgents # Assuming this is your agents.py file
7
+ import feedparser
8
+ from helpers import post_to_linkedin, post_to_twitter, extract_image_url
9
+ import random
10
+ import uuid
11
+ from dotenv import load_dotenv
12
+ import os
13
+
14
+ load_dotenv()
15
+
16
+ ngrok_link = os.getenv("Ngrok_Link")
17
+
18
+ app = Flask(__name__)
19
+ app.secret_key = '12345678765' # Replace with a secure key
20
+
21
+ scheduler = BackgroundScheduler()
22
+ scheduler.start()
23
+
24
+ api_key = os.getenv("Gemini_key")
25
+
26
+ agents = SocialMediaAgents(api_key)
27
+
28
+ LINKEDIN_CLIENT_ID = os.getenv("LINKEDIN_CLIENT_ID")
29
+ LINKEDIN_CLIENT_SECRET = os.getenv("LINKEDIN_CLIENT_SECRET")
30
+ TWITTER_CLIENT_ID = os.getenv("TWITTER_CLIENT_ID")
31
+ TWITTER_CLIENT_SECRET = os.getenv("TWITTER_CLIENT_SECRET")
32
+
33
+ posts = []
34
+ temp_posts = {}
35
+
36
+ @app.route('/')
37
+ def home():
38
+ connected_platforms = {
39
+ 'linkedin': 'linkedin_access_token' in session and 'linkedin_id' in session,
40
+ 'twitter': 'twitter_access_token' in session and 'twitter_access_token_secret' in session
41
+ }
42
+
43
+ name ={
44
+ 'name':session.get('linkedin_name'),
45
+ 'tw_name':session.get('twitter_name')
46
+ }
47
+
48
+
49
+ return render_template('home.html', connected_platforms=connected_platforms,name=name)
50
+
51
+ @app.route('/connect_all')
52
+ def connect_all():
53
+ session['connect_all'] = True
54
+ return redirect(url_for('linkedin_auth'))
55
+
56
+ @app.route('/linkedin/auth')
57
+ def linkedin_auth():
58
+ redirect_uri = f'{ngrok_link}/linkedin/callback'
59
+ scope = 'openid profile w_member_social'
60
+ auth_url = (
61
+ f'https://www.linkedin.com/oauth/v2/authorization?'
62
+ f'response_type=code&client_id={LINKEDIN_CLIENT_ID}&redirect_uri={redirect_uri}&'
63
+ f'scope={scope}&state=randomstring'
64
+ )
65
+
66
+ print("auth------------",auth_url)
67
+ return redirect(auth_url)
68
+
69
+ @app.route('/linkedin/callback')
70
+ def linkedin_callback():
71
+ code = request.args.get('code')
72
+ if not code:
73
+ return "Error: No authorization code provided"
74
+
75
+ print("code11111111",code)
76
+ token_url = 'https://www.linkedin.com/oauth/v2/accessToken'
77
+ data = {
78
+ 'grant_type': 'authorization_code',
79
+ 'code': code,
80
+ 'redirect_uri': f'{ngrok_link}/linkedin/callback',
81
+ 'client_id': LINKEDIN_CLIENT_ID,
82
+ 'client_secret': LINKEDIN_CLIENT_SECRET
83
+ }
84
+ response = requests.post(token_url, data=data)
85
+ if response.status_code != 200:
86
+ return "Error: Could not get LinkedIn access token"
87
+ token_data = response.json()
88
+ session['linkedin_access_token'] = token_data.get('access_token')
89
+ profile_url = 'https://api.linkedin.com/v2/userinfo'
90
+ headers = {'Authorization': f'Bearer {session["linkedin_access_token"]}'}
91
+ profile_response = requests.get(profile_url, headers=headers)
92
+ if profile_response.status_code != 200:
93
+ return "Error: Could not fetch LinkedIn profile"
94
+ user_info = profile_response.json()
95
+
96
+ session['linkedin_name'] = user_info['name']
97
+ session['linkedin_id'] = user_info.get('sub')
98
+
99
+ if session.get('connect_all') and 'twitter_access_token' not in session:
100
+ return redirect(url_for('twitter_auth'))
101
+ return redirect(url_for('home'))
102
+
103
+ @app.route('/twitter/auth')
104
+ def twitter_auth():
105
+ auth = tweepy.OAuth1UserHandler(TWITTER_CLIENT_ID, TWITTER_CLIENT_SECRET, f'{ngrok_link}/twitter/callback')
106
+ try:
107
+ redirect_url = auth.get_authorization_url()
108
+ session['request_token'] = auth.request_token
109
+ return redirect(redirect_url)
110
+ except tweepy.TweepyException as e:
111
+ return f"Error starting Twitter auth: {e}"
112
+
113
+ @app.route('/twitter/callback')
114
+ def twitter_callback():
115
+ request_token = session.pop('request_token', None)
116
+ if not request_token:
117
+ return "Error: Request token not found in session. <a href='/twitter/auth'>Please try logging in again</a>."
118
+ verifier = request.args.get('oauth_verifier')
119
+ if not verifier:
120
+ return "Error: No OAuth verifier provided"
121
+ auth = tweepy.OAuth1UserHandler(TWITTER_CLIENT_ID, TWITTER_CLIENT_SECRET)
122
+ auth.request_token = request_token
123
+ try:
124
+ auth.get_access_token(verifier)
125
+ session['twitter_access_token'] = auth.access_token
126
+ session['twitter_access_token_secret'] = auth.access_token_secret
127
+
128
+ print("twitter_session = 1",session.get('twitter_access_token'))
129
+ auth.set_access_token(auth.access_token, auth.access_token_secret)
130
+ api = tweepy.API(auth)
131
+
132
+
133
+ user = api.verify_credentials()
134
+ if user:
135
+ session['twitter_name'] = user.name
136
+ # session['twitter_username'] = user.screen_name
137
+
138
+
139
+ session.pop('connect_all', None)
140
+ return redirect(url_for('home'))
141
+ except tweepy.TweepyException as e:
142
+ return f"Twitter authorization failed: {e}"
143
+
144
+ @app.route('/disconnect/<platform>')
145
+ def disconnect(platform):
146
+ if platform == 'linkedin':
147
+ session.pop('linkedin_access_token', None)
148
+ session.pop('linkedin_id', None)
149
+ elif platform == 'twitter':
150
+ session.pop('twitter_access_token', None)
151
+ session.pop('twitter_access_token_secret', None)
152
+ return redirect(url_for('home'))
153
+
154
+ @app.route('/post', methods=['GET', 'POST'])
155
+ def create_post():
156
+ if not (session.get('linkedin_access_token') or session.get('twitter_access_token')):
157
+ return redirect(url_for('home'))
158
+
159
+ if request.method == 'POST':
160
+ rss_urls = request.form.getlist('rss_urls')
161
+ posts_per_day = int(request.form['posts_per_day'])
162
+ frequency = request.form['frequency']
163
+ schedule_type = request.form['schedule_type']
164
+ first_post_time = datetime.strptime(request.form['first_post_time'], '%Y-%m-%dT%H:%M')
165
+
166
+ if schedule_type == 'daily':
167
+ total_posts = posts_per_day
168
+ elif schedule_type == 'weekly':
169
+ total_posts = posts_per_day * 7
170
+ else: # monthly
171
+ total_posts = posts_per_day * 30
172
+
173
+ all_entries = []
174
+ for rss_url in rss_urls:
175
+ feed = feedparser.parse(rss_url)
176
+ all_entries.extend(feed.entries)
177
+
178
+ selected_entries = random.sample(all_entries, min(total_posts, len(all_entries)))
179
+
180
+ # selected_entries = sorted(
181
+ # all_entries,
182
+ # key=lambda entry: entry.published_parsed,
183
+ # reverse=False
184
+ # )[:total_posts]
185
+
186
+
187
+ # selected_entries = sorted(
188
+ # all_entries,
189
+ # key=lambda entry: entry.published_parsed,
190
+ # reverse=False
191
+ # )[-3:]
192
+
193
+
194
+ generated_posts = {'linkedin': [], 'twitter': []}
195
+ if session.get('linkedin_access_token'):
196
+ for entry in selected_entries:
197
+ title = entry.title
198
+ description = entry.get('description', entry.get('summary', ''))
199
+ print("desc",description)
200
+ print(type(description))
201
+ image_url = extract_image_url(entry)
202
+ print("img_url",image_url)
203
+ link = entry.get('link', '')
204
+ if image_url == None:
205
+ print("here44444")
206
+ image_url = "https://static.libsyn.com/p/assets/0/2/1/3/0213c7d9616b570b16c3140a3186d450/LOGO_1400x1400.jpg"
207
+ transformed = agents.linkedin_transform(title, description,link)
208
+
209
+ text = f"{transformed['new_title']} {transformed['new_description']}"
210
+ generated_posts['linkedin'].append({
211
+ 'text': text,
212
+ 'image_url': image_url,
213
+ 'platform': 'linkedin',
214
+ 'access_token': session['linkedin_access_token'],
215
+ 'linkedin_id': session['linkedin_id'],
216
+ 'status': 'pending'
217
+ })
218
+ if session.get('twitter_access_token'):
219
+ print("twitter_session = 2",session.get('twitter_access_token'))
220
+ for entry in selected_entries:
221
+ title = entry.title
222
+ description = entry.get('description', entry.get('summary', ''))
223
+ image_url = extract_image_url(entry)
224
+ print("desc",description)
225
+ print(type(description))
226
+ print("img_url",image_url)
227
+ link = entry.get('link', '')
228
+ if image_url == None:
229
+ print("here44444")
230
+ image_url = "https://static.libsyn.com/p/assets/0/2/1/3/0213c7d9616b570b16c3140a3186d450/LOGO_1400x1400.jpg"
231
+
232
+
233
+ transformed = agents.twitter_transform(title, description,link)
234
+ text = f"{transformed['new_title']} {transformed['new_description']}"
235
+
236
+
237
+ generated_posts['twitter'].append({
238
+ 'text': text,
239
+ 'image_url': image_url,
240
+ 'platform': 'twitter',
241
+ 'access_token': session['twitter_access_token'],
242
+ 'access_token_secret': session['twitter_access_token_secret'],
243
+ 'status': 'pending'
244
+ })
245
+
246
+ post_id = str(uuid.uuid4())
247
+ temp_posts[post_id] = {
248
+ 'posts': generated_posts,
249
+ 'first_post_time': first_post_time,
250
+ 'frequency': int(frequency)
251
+ }
252
+ return redirect(url_for('review_posts', post_id=post_id))
253
+
254
+ return render_template('post.html')
255
+
256
+ @app.route('/review/<post_id>', methods=['GET', 'POST'])
257
+ def review_posts(post_id):
258
+ if post_id not in temp_posts:
259
+ return redirect(url_for('create_post'))
260
+
261
+ now = datetime.now()
262
+
263
+ current_time = now.strftime("%H:%M:%S")
264
+ print("Current Time =", current_time)
265
+
266
+ post_data = temp_posts[post_id]
267
+ all_posts = []
268
+ for platform_posts in post_data['posts'].values():
269
+ all_posts.extend(platform_posts)
270
+
271
+ if request.method == 'POST':
272
+ first_post_time = post_data['first_post_time']
273
+ frequency = post_data['frequency']
274
+
275
+ # Schedule posts separately for each platform
276
+ for platform, platform_posts in post_data['posts'].items():
277
+ for i, post in enumerate(platform_posts):
278
+ scheduled_time = first_post_time + timedelta(minutes=frequency * i)
279
+ post['scheduled_time'] = scheduled_time
280
+ posts.append(post)
281
+ if platform == 'linkedin':
282
+ scheduler.add_job(post_to_linkedin, 'date', run_date=scheduled_time, args=[post])
283
+ elif platform == 'twitter':
284
+
285
+ print("pooooooooossssssssssssttttttt",post)
286
+ scheduler.add_job(post_to_twitter, 'date', run_date=scheduled_time, args=[post])
287
+ now = datetime.now()
288
+ current_time = now.strftime("%H:%M:%S")
289
+ print("end Time =", current_time)
290
+ del temp_posts[post_id]
291
+ return redirect(url_for('scheduled_posts'))
292
+
293
+ return render_template('review.html',
294
+ posts=all_posts,
295
+ first_post_time=post_data['first_post_time'].isoformat(),
296
+ frequency=post_data['frequency'])
297
+
298
+ @app.route('/scheduled')
299
+ def scheduled_posts():
300
+ linkedin_posts = [p for p in posts if p['platform'] == 'linkedin' and p['status'] == 'pending']
301
+ twitter_posts = [p for p in posts if p['platform'] == 'twitter' and p['status'] == 'pending']
302
+ return render_template('scheduled.html', linkedin_posts=linkedin_posts, twitter_posts=twitter_posts)
303
+
304
+ if __name__ == '__main__':
305
+ port = int(os.environ.get("PORT", 5000)) # Get the port from Render
306
+ app.run(debug=True, host='0.0.0.0', port=port)