mirror of
https://github.com/hanzoskill/twitter-bookmark-sync.git
synced 2026-10-30 21:04:11 +00:00
215 lines
7.5 KiB
Python
215 lines
7.5 KiB
Python
#!/usr/bin/env python3
|
|
import json
|
|
import sys
|
|
from datetime import datetime, timedelta
|
|
from pathlib import Path
|
|
|
|
def load_config(config_file):
|
|
with open(config_file, 'r') as f:
|
|
return json.load(f)
|
|
|
|
def load_criteria(criteria_file):
|
|
"""Load ranking criteria (the learned algorithm)"""
|
|
with open(criteria_file, 'r') as f:
|
|
return json.load(f)
|
|
|
|
def filter_recent(tweets, hours):
|
|
"""Filter tweets from last N hours"""
|
|
cutoff = datetime.now() - timedelta(hours=hours)
|
|
recent = []
|
|
|
|
for tweet in tweets:
|
|
try:
|
|
# Parse Twitter date: "Thu Jan 22 21:14:20 +0000 2026"
|
|
dt = datetime.strptime(tweet['createdAt'], '%a %b %d %H:%M:%S %z %Y')
|
|
if dt.replace(tzinfo=None) >= cutoff:
|
|
recent.append(tweet)
|
|
except:
|
|
continue
|
|
|
|
return recent
|
|
|
|
def score_tweet(tweet, criteria):
|
|
"""Score tweet based on learned ranking criteria"""
|
|
text = (tweet.get('text', '') + ' ' + tweet.get('user', {}).get('screenName', '')).lower()
|
|
score = 50.0 # base
|
|
|
|
# Match against all value categories
|
|
matched_categories = []
|
|
|
|
for category_name, category_data in criteria['value_categories'].items():
|
|
weight = category_data['weight']
|
|
keywords = category_data.get('keywords', [])
|
|
|
|
# Check if any keywords match
|
|
matches = sum(1 for kw in keywords if kw.lower() in text)
|
|
|
|
if matches > 0:
|
|
# Add weighted score
|
|
category_boost = (weight / 100.0) * matches * 10
|
|
score += category_boost
|
|
matched_categories.append(category_name)
|
|
|
|
# Apply engagement multipliers from criteria
|
|
multipliers = criteria.get('engagement_multipliers', {})
|
|
|
|
if tweet.get('isThread'):
|
|
score *= multipliers.get('thread', 1.0)
|
|
|
|
likes = tweet.get('favoriteCount', 0)
|
|
if likes > 1000:
|
|
score *= multipliers.get('high_likes', 1.0)
|
|
|
|
retweets = tweet.get('retweetCount', 0)
|
|
if retweets > 100:
|
|
score *= multipliers.get('high_retweets', 1.0)
|
|
|
|
if 'http' in text:
|
|
score *= multipliers.get('has_link', 1.0)
|
|
|
|
# Store matched categories for value analysis
|
|
tweet['_matched_categories'] = matched_categories
|
|
|
|
return round(score, 2)
|
|
|
|
def generate_markdown(ranked_tweets, output_dir, criteria):
|
|
"""Generate markdown reading list"""
|
|
today = datetime.now().strftime('%Y-%m-%d')
|
|
output_file = Path(output_dir) / f"twitter-reading-{today}.md"
|
|
|
|
with open(output_file, 'w') as f:
|
|
f.write(f"# Twitter Reading List - {datetime.now().strftime('%B %d, %Y')}\n\n")
|
|
f.write(f"**Generated:** {datetime.now().strftime('%I:%M %p')}\n")
|
|
f.write(f"**Total bookmarks:** {len(ranked_tweets)}\n")
|
|
f.write(f"**Ranking algorithm:** v{criteria.get('version', '1.0.0')}\n\n")
|
|
f.write("---\n\n")
|
|
|
|
# Group by priority
|
|
high = [t for t in ranked_tweets if t['score'] >= 80]
|
|
medium = [t for t in ranked_tweets if 60 <= t['score'] < 80]
|
|
normal = [t for t in ranked_tweets if t['score'] < 60]
|
|
|
|
if high:
|
|
f.write("## 🔥 High Priority (Score 80+)\n\n")
|
|
for t in high:
|
|
write_tweet(f, t, criteria)
|
|
|
|
if medium:
|
|
f.write("## ⭐ Medium Priority (Score 60-79)\n\n")
|
|
for t in medium:
|
|
write_tweet(f, t, criteria)
|
|
|
|
if normal:
|
|
f.write("## 📌 Standard Priority (Score <60)\n\n")
|
|
for t in normal:
|
|
write_tweet(f, t, criteria)
|
|
|
|
return str(output_file), high[:5] # Return file and top 5
|
|
|
|
def analyze_value(tweet, criteria):
|
|
"""Generate value statement from matched categories"""
|
|
matched = tweet.get('_matched_categories', [])
|
|
|
|
if not matched:
|
|
return "📌 High engagement in your interest areas"
|
|
|
|
# Map categories to user-friendly value statements
|
|
value_map = {
|
|
'london_transition': "🎯 Relevant to your London move",
|
|
'career_growth': "📈 Career advancement",
|
|
'crypto_insights': "💰 Crypto work insights",
|
|
'investment_strategy': "📊 Investment strategy",
|
|
'founder_mindset': "🚀 Builder mindset",
|
|
'growth_strategy': "📈 Growth/scaling",
|
|
'relationship_dynamics': "❤️ Relationship insights",
|
|
'psychology': "🧠 Psychology/behavior",
|
|
'productivity_systems': "⚡ Productivity",
|
|
'business_models': "💼 Business model",
|
|
'technical_depth': "🔧 Technical depth"
|
|
}
|
|
|
|
values = []
|
|
for category in matched[:3]: # Top 3 matches
|
|
if category in value_map:
|
|
values.append(value_map[category])
|
|
elif category.startswith('discovered_'):
|
|
# Auto-discovered category
|
|
topic = category.replace('discovered_', '').title()
|
|
values.append(f"🔍 {topic}")
|
|
else:
|
|
# Use description from criteria
|
|
desc = criteria['value_categories'].get(category, {}).get('description', category)
|
|
values.append(f"• {desc}")
|
|
|
|
return " • ".join(values) if values else "📌 Matches your interests"
|
|
|
|
def write_tweet(f, tweet, criteria):
|
|
"""Write single tweet to markdown with value analysis"""
|
|
value_statement = analyze_value(tweet, criteria)
|
|
|
|
f.write(f"### {tweet['rank']}. @{tweet['author']} (Score: {tweet['score']})\n\n")
|
|
f.write(f"**Why this matters:** {value_statement}\n\n")
|
|
f.write(f"{tweet['text']}\n\n")
|
|
f.write(f"👍 {tweet['likes']} | 🔁 {tweet['retweets']}\n\n")
|
|
f.write(f"🔗 {tweet['url']}\n\n")
|
|
f.write("---\n\n")
|
|
|
|
def main():
|
|
if len(sys.argv) < 5:
|
|
print("Usage: rank.py <bookmarks.json> <config.json> <criteria.json> <output_dir>")
|
|
sys.exit(1)
|
|
|
|
bookmarks_file = sys.argv[1]
|
|
config_file = sys.argv[2]
|
|
criteria_file = sys.argv[3]
|
|
output_dir = sys.argv[4]
|
|
|
|
# Load data
|
|
with open(bookmarks_file, 'r') as f:
|
|
data = json.load(f)
|
|
|
|
config = load_config(config_file)
|
|
criteria = load_criteria(criteria_file)
|
|
|
|
tweets = data['tweets']
|
|
lookback_hours = config.get('lookback_hours', 24) # Changed to 24h as per new spec
|
|
|
|
# Filter recent
|
|
recent = filter_recent(tweets, lookback_hours)
|
|
print(f"Found {len(recent)} bookmarks from last {lookback_hours} hours")
|
|
|
|
if not recent:
|
|
print("No recent bookmarks to rank")
|
|
sys.exit(0)
|
|
|
|
# Score and rank using learned criteria
|
|
for tweet in recent:
|
|
tweet['score'] = score_tweet(tweet, criteria)
|
|
|
|
ranked = sorted(recent, key=lambda t: t['score'], reverse=True)[:20]
|
|
|
|
# Prepare output
|
|
for i, tweet in enumerate(ranked, 1):
|
|
tweet['rank'] = i
|
|
tweet['author'] = tweet.get('user', {}).get('screenName', 'unknown')
|
|
tweet['text'] = tweet.get('text', '')[:300]
|
|
tweet['url'] = f"https://x.com/{tweet['author']}/status/{tweet.get('tweetId', '')}"
|
|
tweet['likes'] = tweet.get('favoriteCount', 0)
|
|
tweet['retweets'] = tweet.get('retweetCount', 0)
|
|
|
|
# Generate markdown
|
|
output_file, top5 = generate_markdown(ranked, output_dir, criteria)
|
|
|
|
print(f"✅ Reading list saved: {output_file}")
|
|
print(f"✅ Top 5 bookmarks (scores: {[t['score'] for t in top5]})")
|
|
|
|
# Save top 5 for notification
|
|
top5_file = Path(output_dir) / f"twitter-top5-{datetime.now().strftime('%Y-%m-%d')}.json"
|
|
with open(top5_file, 'w') as f:
|
|
json.dump(top5, f, indent=2)
|
|
|
|
print(f"✅ Top 5 saved for notification: {top5_file}")
|
|
|
|
if __name__ == '__main__':
|
|
main()
|