Spaces:
Sleeping
Sleeping
Upload 3 files
Browse files- helpers.py +193 -0
- requirements (5).txt +11 -0
- 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)
|