AiDeveloper1 commited on
Commit
6f79f6c
·
verified ·
1 Parent(s): 243af3d

Upload 3 files

Browse files
Files changed (3) hide show
  1. helpers.py +193 -0
  2. requirements (5).txt +11 -0
  3. test2.py +307 -0
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 (5).txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
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
11
+ gunicorn
test2.py ADDED
@@ -0,0 +1,307 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 = None
202
+ print("img_url",image_url)
203
+ link = "https://youtube.com/playlist?list=PLacDrP-7Ys6IsnPRN0ToTfjH8gQ4s6mL9&si=shb65ODGWXhcG1wq"
204
+ # if image_url == None:
205
+ # print("here44444")
206
+ # image_url = None
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 = None
224
+ print("desc",description)
225
+ print(type(description))
226
+ print("img_url",image_url)
227
+ link = "https://youtube.com/playlist?list=PLacDrP-7Ys6IsnPRN0ToTfjH8gQ4s6mL9&si=shb65ODGWXhcG1wq"
228
+ print("link----------------------------", link)
229
+ # if image_url == None:
230
+ # print("here44444")
231
+ # image_url = "https://youtube.com/playlist?list=PLacDrP-7Ys6IsnPRN0ToTfjH8gQ4s6mL9&si=shb65ODGWXhcG1wq"
232
+
233
+
234
+ transformed = agents.twitter_transform(title, description,link)
235
+ text = f"{transformed['new_title']} {transformed['new_description']}"
236
+
237
+
238
+ generated_posts['twitter'].append({
239
+ 'text': text,
240
+ 'image_url': image_url,
241
+ 'platform': 'twitter',
242
+ 'access_token': session['twitter_access_token'],
243
+ 'access_token_secret': session['twitter_access_token_secret'],
244
+ 'status': 'pending'
245
+ })
246
+
247
+ post_id = str(uuid.uuid4())
248
+ temp_posts[post_id] = {
249
+ 'posts': generated_posts,
250
+ 'first_post_time': first_post_time,
251
+ 'frequency': int(frequency)
252
+ }
253
+ return redirect(url_for('review_posts', post_id=post_id))
254
+
255
+ return render_template('post.html')
256
+
257
+ @app.route('/review/<post_id>', methods=['GET', 'POST'])
258
+ def review_posts(post_id):
259
+ if post_id not in temp_posts:
260
+ return redirect(url_for('create_post'))
261
+
262
+ now = datetime.now()
263
+
264
+ current_time = now.strftime("%H:%M:%S")
265
+ print("Current Time =", current_time)
266
+
267
+ post_data = temp_posts[post_id]
268
+ all_posts = []
269
+ for platform_posts in post_data['posts'].values():
270
+ all_posts.extend(platform_posts)
271
+
272
+ if request.method == 'POST':
273
+ first_post_time = post_data['first_post_time']
274
+ frequency = post_data['frequency']
275
+
276
+ # Schedule posts separately for each platform
277
+ for platform, platform_posts in post_data['posts'].items():
278
+ for i, post in enumerate(platform_posts):
279
+ scheduled_time = first_post_time + timedelta(minutes=frequency * i)
280
+ post['scheduled_time'] = scheduled_time
281
+ posts.append(post)
282
+ if platform == 'linkedin':
283
+ scheduler.add_job(post_to_linkedin, 'date', run_date=scheduled_time, args=[post])
284
+ elif platform == 'twitter':
285
+
286
+ print("pooooooooossssssssssssttttttt",post)
287
+ scheduler.add_job(post_to_twitter, 'date', run_date=scheduled_time, args=[post])
288
+ now = datetime.now()
289
+ current_time = now.strftime("%H:%M:%S")
290
+ print("end Time =", current_time)
291
+ del temp_posts[post_id]
292
+ return redirect(url_for('scheduled_posts'))
293
+
294
+ return render_template('review.html',
295
+ posts=all_posts,
296
+ first_post_time=post_data['first_post_time'].isoformat(),
297
+ frequency=post_data['frequency'])
298
+
299
+ @app.route('/scheduled')
300
+ def scheduled_posts():
301
+ linkedin_posts = [p for p in posts if p['platform'] == 'linkedin' and p['status'] == 'pending']
302
+ twitter_posts = [p for p in posts if p['platform'] == 'twitter' and p['status'] == 'pending']
303
+ return render_template('scheduled.html', linkedin_posts=linkedin_posts, twitter_posts=twitter_posts)
304
+
305
+ if __name__ == '__main__':
306
+ port = int(os.environ.get("PORT", 5000)) # Get the port from Render
307
+ app.run(debug=True, host='0.0.0.0', port=port)