#!/usr/bin/env python3 """ ArXiv Daily Bot - Minimal version for cron jobs. Finds interesting papers and generates a daily digest. """ import json import urllib.request import urllib.parse import xml.etree.ElementTree as ET from datetime import datetime, timedelta from pathlib import Path import time import re def fetch_arxiv_papers(category='cs.SD', days_back=1, max_results=200): """Fetch recent papers from arXiv API.""" base_url = 'http://export.arxiv.org/api/query?' query = f'cat:{category}' params = { 'search_query': query, 'start': 0, 'max_results': max_results, 'sortBy': 'submittedDate', 'sortOrder': 'descending' } url = base_url + urllib.parse.urlencode(params) response = urllib.request.urlopen(url) data = response.read().decode('utf-8') root = ET.fromstring(data) ns = {'atom': 'http://www.w3.org/2005/Atom'} papers = [] cutoff_date = datetime.now() - timedelta(days=days_back) for entry in root.findall('atom:entry', ns): arxiv_id = entry.find('atom:id', ns).text.split('/abs/')[-1] title = entry.find('atom:title', ns).text.strip().replace('\n', ' ') abstract = entry.find('atom:summary', ns).text.strip().replace('\n', ' ') published = entry.find('atom:published', ns).text pub_date = datetime.strptime(published[:10], '%Y-%m-%d') if pub_date >= cutoff_date: papers.append({ 'arxiv_id': arxiv_id, 'url': f'https://arxiv.org/abs/{arxiv_id}', 'title': title, 'abstract': abstract, 'published': published[:10] }) return papers def compute_similarity(text1, text2): """Simple word-based similarity.""" words1 = set(re.findall(r'\b\w+\b', text1.lower())) words2 = set(re.findall(r'\b\w+\b', text2.lower())) stopwords = {'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by', 'from', 'is', 'are', 'was', 'were', 'be', 'been', 'we', 'this', 'that', 'these', 'those', 'which', 'using', 'use'} words1 = words1 - stopwords words2 = words2 - stopwords if not words1 or not words2: return 0.0 intersection = len(words1 & words2) union = len(words1 | words2) return intersection / union if union > 0 else 0.0 def score_papers(papers, profile, threshold=0.15): """Score papers based on interest profile.""" scored = [] for paper in papers: paper_text = f"{paper['title']} {paper['abstract']}" # Compare against summaries max_similarity = 0 for summary in profile.get('summaries', []): similarity = compute_similarity(paper_text, summary) max_similarity = max(max_similarity, similarity) # Keyword bonus keyword_score = 0 paper_words = set(re.findall(r'\b\w+\b', paper_text.lower())) for keyword in profile.get('top_keywords', [])[:20]: if keyword in paper_words: keyword_score += 1 combined_score = max_similarity + (keyword_score * 0.02) if combined_score >= threshold: paper['interest_score'] = combined_score scored.append(paper) return sorted(scored, key=lambda x: x['interest_score'], reverse=True) def generate_digest(papers, output_file): """Generate markdown digest.""" lines = [ f"# ArXiv Daily Digest - {datetime.now().strftime('%Y-%m-%d')}", "", f"Found **{len(papers)}** interesting papers:", "" ] for i, paper in enumerate(papers, 1): lines.append(f"## {i}. {paper['title']}") lines.append(f"**Score:** {paper['interest_score']:.3f} | **Published:** {paper['published']}") lines.append(f"**URL:** {paper['url']}") lines.append("") lines.append(f"**Abstract:** {paper['abstract'][:400]}...") lines.append("") lines.append("---") lines.append("") with open(output_file, 'w') as f: f.write('\n'.join(lines)) def main(): import argparse parser = argparse.ArgumentParser() parser.add_argument('--categories', default='cs.SD,cs.LG,cs.CL') parser.add_argument('--days', type=int, default=1) parser.add_argument('--threshold', type=float, default=0.15) parser.add_argument('--output', default='digest.md') parser.add_argument('--profile', default='interest_profile.json') args = parser.parse_args() # Load profile profile_path = Path(args.profile) if not profile_path.exists(): print(f"Error: {profile_path} not found") print("Copy your interest profile here first") return 1 with open(profile_path) as f: profile = json.load(f) print(f"Loaded profile: {profile.get('papers_analyzed', 0)} papers") # Fetch papers all_papers = [] for category in args.categories.split(','): print(f"Fetching {category}...") papers = fetch_arxiv_papers(category.strip(), args.days) all_papers.extend(papers) time.sleep(3) # Remove duplicates seen = set() unique = [] for paper in all_papers: if paper['arxiv_id'] not in seen: seen.add(paper['arxiv_id']) unique.append(paper) print(f"Total papers: {len(unique)}") # Score papers interesting = score_papers(unique, profile, args.threshold) print(f"Interesting papers: {len(interesting)}") if interesting: # Generate digest generate_digest(interesting, args.output) print(f"✓ Digest saved to {args.output}") # Print top 3 print("\nTop 3:") for i, paper in enumerate(interesting[:3], 1): print(f"{i}. {paper['title']}") print(f" Score: {paper['interest_score']:.3f}") print(f" {paper['url']}\n") else: print("No interesting papers found") return 0 if __name__ == '__main__': exit(main())