sophhub 0.2.3 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/consensus/skill.json +20 -0
- package/skills/consensus/src/SKILL.md +93 -0
- package/skills/deepwiki/skill.json +20 -0
- package/skills/deepwiki/src/SKILL.md +45 -0
- package/skills/deepwiki/src/_meta.json +6 -0
- package/skills/deepwiki/src/scripts/deepwiki.js +135 -0
- package/skills/feishu-bitable/skill.json +20 -0
- package/skills/feishu-bitable/src/CHECKLIST.md +150 -0
- package/skills/feishu-bitable/src/README.md +178 -0
- package/skills/feishu-bitable/src/SKILL.md +113 -0
- package/skills/feishu-bitable/src/_meta.json +6 -0
- package/skills/feishu-bitable/src/api.js +381 -0
- package/skills/feishu-bitable/src/bin/cli.js +284 -0
- package/skills/feishu-bitable/src/description.md +143 -0
- package/skills/feishu-bitable/src/examples/create-records.json +52 -0
- package/skills/feishu-bitable/src/examples/create-table.json +64 -0
- package/skills/feishu-bitable/src/package-lock.json +324 -0
- package/skills/feishu-bitable/src/package.json +33 -0
- package/skills/feishu-bitable/src/publish-config.json +14 -0
- package/skills/feishu-bitable/src/test-simple.js +61 -0
- package/skills/feishu-bitable/src/utils.js +261 -0
- package/skills/google-maps/skill.json +20 -0
- package/skills/google-maps/src/SKILL.md +237 -0
- package/skills/google-maps/src/_meta.json +6 -0
- package/skills/google-maps/src/lib/map_helper.py +912 -0
- package/skills/large-task-router/skill.json +20 -0
- package/skills/large-task-router/src/SKILL.md +79 -0
- package/skills/large-task-router/src/templates/plan.md +74 -0
- package/skills/skillhub/skill.json +11 -4
- package/skills/skillhub/src/SKILL.md +11 -1
- package/skills/sophnet-dailynews/skill.json +20 -0
- package/skills/sophnet-dailynews/src/SKILL.md +179 -0
- package/skills/sophnet-dailynews/src/cache.json +151 -0
- package/skills/sophnet-dailynews/src/sources.json +230 -0
- package/skills/sophnet-schedule/skill.json +20 -0
- package/skills/sophnet-schedule/src/ARCHITECTURE.md +321 -0
- package/skills/sophnet-schedule/src/IMPROVEMENTS.md +145 -0
- package/skills/sophnet-schedule/src/SKILL.md +1050 -0
- package/skills/sophnet-schedule/src/_meta.json +6 -0
- package/skills/sophnet-schedule/src/api/__init__.py +0 -0
- package/skills/sophnet-schedule/src/api/models.py +245 -0
- package/skills/sophnet-schedule/src/apps/add_event.py +237 -0
- package/skills/sophnet-schedule/src/apps/check_reminders.py +112 -0
- package/skills/sophnet-schedule/src/apps/check_roc.py +246 -0
- package/skills/sophnet-schedule/src/apps/generate_daily_plan.py +342 -0
- package/skills/sophnet-schedule/src/apps/import_events.py +216 -0
- package/skills/sophnet-schedule/src/apps/monitor_calendar_changes.py +140 -0
- package/skills/sophnet-schedule/src/apps/register_tasks.py +169 -0
- package/skills/sophnet-schedule/src/apps/sync_roc_to_gcal.py +174 -0
- package/skills/sophnet-schedule/src/compat.py +66 -0
- package/skills/sophnet-schedule/src/config/__init__.py +0 -0
- package/skills/sophnet-schedule/src/config/reminder_rules.yaml +96 -0
- package/skills/sophnet-schedule/src/config/roc_events.yaml +44 -0
- package/skills/sophnet-schedule/src/config/settings.py +133 -0
- package/skills/sophnet-schedule/src/config/task_registry.yaml +92 -0
- package/skills/sophnet-schedule/src/docs/FRONTEND_INTEGRATION_GUIDE.md +437 -0
- package/skills/sophnet-schedule/src/gcal/__init__.py +0 -0
- package/skills/sophnet-schedule/src/gcal/client.py +374 -0
- package/skills/sophnet-schedule/src/gcal/models.py +91 -0
- package/skills/sophnet-schedule/src/requirements.txt +6 -0
- package/skills/sophnet-schedule/src/scripts/setup_gcal_token.py +85 -0
- package/skills/sophnet-schedule/src/server.py +669 -0
- package/skills/sophnet-schedule/src/services/__init__.py +0 -0
- package/skills/sophnet-schedule/src/services/calendar_backend.py +139 -0
- package/skills/sophnet-schedule/src/services/conflict_detector.py +96 -0
- package/skills/sophnet-schedule/src/services/datetime_utils.py +117 -0
- package/skills/sophnet-schedule/src/services/event_classifier.py +100 -0
- package/skills/sophnet-schedule/src/services/event_diff.py +160 -0
- package/skills/sophnet-schedule/src/services/google_integration.py +500 -0
- package/skills/sophnet-schedule/src/services/job_store.py +100 -0
- package/skills/sophnet-schedule/src/services/local_event_store.py +266 -0
- package/skills/sophnet-schedule/src/services/reminder_planner.py +116 -0
- package/skills/sophnet-schedule/src/services/runtime_utils.py +31 -0
- package/skills/sophnet-schedule/src/services/table_parser.py +286 -0
- package/skills/sophnet-schedule/src/services/task_builder.py +167 -0
- package/skills/sophnet-schedule/src/services/time_window.py +72 -0
- package/skills/sophnet-stock/skill.json +20 -0
- package/skills/sophnet-stock/src/App-Plan.md +442 -0
- package/skills/sophnet-stock/src/README.md +214 -0
- package/skills/sophnet-stock/src/SKILL.md +236 -0
- package/skills/sophnet-stock/src/TODO.md +394 -0
- package/skills/sophnet-stock/src/_meta.json +6 -0
- package/skills/sophnet-stock/src/docs/ARCHITECTURE.md +408 -0
- package/skills/sophnet-stock/src/docs/CONCEPT.md +233 -0
- package/skills/sophnet-stock/src/docs/HOT_SCANNER.md +288 -0
- package/skills/sophnet-stock/src/docs/README.md +95 -0
- package/skills/sophnet-stock/src/docs/USAGE.md +465 -0
- package/skills/sophnet-stock/src/scripts/analyze_stock.py +2565 -0
- package/skills/sophnet-stock/src/scripts/dividends.py +365 -0
- package/skills/sophnet-stock/src/scripts/hot_scanner.py +582 -0
- package/skills/sophnet-stock/src/scripts/portfolio.py +548 -0
- package/skills/sophnet-stock/src/scripts/rumor_scanner.py +342 -0
- package/skills/sophnet-stock/src/scripts/test_stock_analysis.py +409 -0
- package/skills/sophnet-stock/src/scripts/watchlist.py +336 -0
- package/skills/xiaohongshu/skill.json +20 -0
- package/skills/xiaohongshu/src/SKILL.md +91 -0
- package/skills/xiaohongshu/src/_meta.json +6 -0
- package/skills/xiaohongshu/src/assets/card.html +216 -0
- package/skills/xiaohongshu/src/assets/cover.html +82 -0
- package/skills/xiaohongshu/src/assets/example.md +84 -0
- package/skills/xiaohongshu/src/assets/styles.css +318 -0
- package/skills/xiaohongshu/src/scripts/render_xhs_v2.py +737 -0
- package/skills/xiaohongshu/src/scripts/sign_server.py +158 -0
- package/skills/xiaohongshu/src/scripts/stealth.min.js +7 -0
- package/skills/xiaohongshu/src/scripts/xhs_tool.py +186 -0
- package/skills/xiaohongshu/src/workflow.py +185 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
🔮 RUMOR & BUZZ SCANNER
|
|
4
|
+
Scans for early signals, rumors, and whispers before they become mainstream news.
|
|
5
|
+
|
|
6
|
+
Sources:
|
|
7
|
+
- Twitter/X: "hearing", "rumor", "sources say", unusual buzz
|
|
8
|
+
- Google News: M&A, insider, upgrade/downgrade
|
|
9
|
+
- Unusual keywords detection
|
|
10
|
+
|
|
11
|
+
Usage: python3 rumor_scanner.py
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import subprocess
|
|
17
|
+
import sys
|
|
18
|
+
import re
|
|
19
|
+
from datetime import datetime, timezone
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from urllib.request import urlopen, Request
|
|
22
|
+
from urllib.parse import quote_plus
|
|
23
|
+
import gzip
|
|
24
|
+
|
|
25
|
+
CACHE_DIR = Path(__file__).parent.parent / "cache"
|
|
26
|
+
CACHE_DIR.mkdir(exist_ok=True)
|
|
27
|
+
|
|
28
|
+
# Bird CLI path
|
|
29
|
+
BIRD_CLI = "/home/clawdbot/.nvm/versions/node/v24.12.0/bin/bird"
|
|
30
|
+
BIRD_ENV = Path(__file__).parent.parent / ".env"
|
|
31
|
+
|
|
32
|
+
def load_env():
|
|
33
|
+
"""Load environment variables from .env file."""
|
|
34
|
+
if BIRD_ENV.exists():
|
|
35
|
+
for line in BIRD_ENV.read_text().splitlines():
|
|
36
|
+
if '=' in line and not line.startswith('#'):
|
|
37
|
+
key, value = line.split('=', 1)
|
|
38
|
+
os.environ[key.strip()] = value.strip().strip('"').strip("'")
|
|
39
|
+
|
|
40
|
+
def fetch_url(url, timeout=15):
|
|
41
|
+
"""Fetch URL with headers."""
|
|
42
|
+
headers = {
|
|
43
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
|
44
|
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
45
|
+
'Accept-Encoding': 'gzip, deflate',
|
|
46
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
47
|
+
}
|
|
48
|
+
req = Request(url, headers=headers)
|
|
49
|
+
try:
|
|
50
|
+
with urlopen(req, timeout=timeout) as resp:
|
|
51
|
+
data = resp.read()
|
|
52
|
+
if resp.info().get('Content-Encoding') == 'gzip':
|
|
53
|
+
data = gzip.decompress(data)
|
|
54
|
+
return data.decode('utf-8', errors='ignore')
|
|
55
|
+
except Exception as e:
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
def search_twitter_rumors():
|
|
59
|
+
"""Search Twitter for rumors and early signals."""
|
|
60
|
+
results = []
|
|
61
|
+
|
|
62
|
+
# Rumor-focused search queries
|
|
63
|
+
queries = [
|
|
64
|
+
'"hearing that" stock OR $',
|
|
65
|
+
'"sources say" stock OR company',
|
|
66
|
+
'"rumor" merger OR acquisition',
|
|
67
|
+
'insider buying stock',
|
|
68
|
+
'"upgrade" OR "downgrade" stock tomorrow',
|
|
69
|
+
'$AAPL OR $TSLA OR $NVDA rumor',
|
|
70
|
+
'"breaking" stock market',
|
|
71
|
+
'M&A rumor',
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
load_env()
|
|
75
|
+
|
|
76
|
+
for query in queries[:4]: # Limit to avoid rate limits
|
|
77
|
+
try:
|
|
78
|
+
cmd = [BIRD_CLI, 'search', query, '-n', '10', '--json']
|
|
79
|
+
env = os.environ.copy()
|
|
80
|
+
|
|
81
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30, env=env)
|
|
82
|
+
|
|
83
|
+
if result.returncode == 0 and result.stdout:
|
|
84
|
+
try:
|
|
85
|
+
tweets = json.loads(result.stdout)
|
|
86
|
+
for tweet in tweets:
|
|
87
|
+
text = tweet.get('text', '')
|
|
88
|
+
# Filter for actual rumors/signals
|
|
89
|
+
if any(kw in text.lower() for kw in ['hearing', 'rumor', 'source', 'insider', 'upgrade', 'downgrade', 'breaking', 'M&A', 'merger', 'acquisition']):
|
|
90
|
+
results.append({
|
|
91
|
+
'source': 'twitter',
|
|
92
|
+
'type': 'rumor',
|
|
93
|
+
'text': text[:300],
|
|
94
|
+
'author': tweet.get('author', {}).get('username', 'unknown'),
|
|
95
|
+
'likes': tweet.get('likes', 0),
|
|
96
|
+
'retweets': tweet.get('retweets', 0),
|
|
97
|
+
'query': query
|
|
98
|
+
})
|
|
99
|
+
except json.JSONDecodeError:
|
|
100
|
+
pass
|
|
101
|
+
except Exception as e:
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
# Dedupe by text similarity
|
|
105
|
+
seen = set()
|
|
106
|
+
unique = []
|
|
107
|
+
for r in results:
|
|
108
|
+
key = r['text'][:100]
|
|
109
|
+
if key not in seen:
|
|
110
|
+
seen.add(key)
|
|
111
|
+
unique.append(r)
|
|
112
|
+
|
|
113
|
+
return unique
|
|
114
|
+
|
|
115
|
+
def search_twitter_buzz():
|
|
116
|
+
"""Search Twitter for general stock buzz - what are people talking about?"""
|
|
117
|
+
results = []
|
|
118
|
+
|
|
119
|
+
queries = [
|
|
120
|
+
'$SPY OR $QQQ',
|
|
121
|
+
'stock to buy',
|
|
122
|
+
'calls OR puts expiring',
|
|
123
|
+
'earnings play',
|
|
124
|
+
'short squeeze',
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
load_env()
|
|
128
|
+
|
|
129
|
+
for query in queries[:3]:
|
|
130
|
+
try:
|
|
131
|
+
cmd = [BIRD_CLI, 'search', query, '-n', '15', '--json']
|
|
132
|
+
env = os.environ.copy()
|
|
133
|
+
|
|
134
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30, env=env)
|
|
135
|
+
|
|
136
|
+
if result.returncode == 0 and result.stdout:
|
|
137
|
+
try:
|
|
138
|
+
tweets = json.loads(result.stdout)
|
|
139
|
+
for tweet in tweets:
|
|
140
|
+
text = tweet.get('text', '')
|
|
141
|
+
# Extract stock symbols
|
|
142
|
+
symbols = re.findall(r'\$([A-Z]{1,5})\b', text)
|
|
143
|
+
if symbols:
|
|
144
|
+
results.append({
|
|
145
|
+
'source': 'twitter',
|
|
146
|
+
'type': 'buzz',
|
|
147
|
+
'text': text[:300],
|
|
148
|
+
'symbols': symbols,
|
|
149
|
+
'author': tweet.get('author', {}).get('username', 'unknown'),
|
|
150
|
+
'engagement': tweet.get('likes', 0) + tweet.get('retweets', 0) * 2
|
|
151
|
+
})
|
|
152
|
+
except json.JSONDecodeError:
|
|
153
|
+
pass
|
|
154
|
+
except Exception as e:
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
# Sort by engagement
|
|
158
|
+
results.sort(key=lambda x: x.get('engagement', 0), reverse=True)
|
|
159
|
+
return results[:20]
|
|
160
|
+
|
|
161
|
+
def search_news_rumors():
|
|
162
|
+
"""Search Google News for M&A, insider, upgrade news."""
|
|
163
|
+
results = []
|
|
164
|
+
|
|
165
|
+
queries = [
|
|
166
|
+
'merger acquisition rumor',
|
|
167
|
+
'insider buying stock',
|
|
168
|
+
'analyst upgrade stock',
|
|
169
|
+
'takeover bid company',
|
|
170
|
+
'SEC investigation company',
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
for query in queries:
|
|
174
|
+
url = f"https://news.google.com/rss/search?q={quote_plus(query)}&hl=en-US&gl=US&ceid=US:en"
|
|
175
|
+
content = fetch_url(url)
|
|
176
|
+
|
|
177
|
+
if content:
|
|
178
|
+
import xml.etree.ElementTree as ET
|
|
179
|
+
try:
|
|
180
|
+
root = ET.fromstring(content)
|
|
181
|
+
for item in root.findall('.//item')[:5]:
|
|
182
|
+
title = item.find('title')
|
|
183
|
+
link = item.find('link')
|
|
184
|
+
pub_date = item.find('pubDate')
|
|
185
|
+
|
|
186
|
+
if title is not None:
|
|
187
|
+
title_text = title.text or ''
|
|
188
|
+
# Extract company names or symbols
|
|
189
|
+
results.append({
|
|
190
|
+
'source': 'google_news',
|
|
191
|
+
'type': 'news_rumor',
|
|
192
|
+
'title': title_text,
|
|
193
|
+
'link': link.text if link is not None else '',
|
|
194
|
+
'date': pub_date.text if pub_date is not None else '',
|
|
195
|
+
'query': query
|
|
196
|
+
})
|
|
197
|
+
except ET.ParseError:
|
|
198
|
+
pass
|
|
199
|
+
|
|
200
|
+
return results
|
|
201
|
+
|
|
202
|
+
def extract_symbols_from_text(text):
|
|
203
|
+
"""Extract stock symbols from text."""
|
|
204
|
+
# $SYMBOL pattern
|
|
205
|
+
dollar_symbols = re.findall(r'\$([A-Z]{1,5})\b', text)
|
|
206
|
+
|
|
207
|
+
# Common company name to symbol mapping
|
|
208
|
+
company_map = {
|
|
209
|
+
'apple': 'AAPL', 'tesla': 'TSLA', 'nvidia': 'NVDA', 'microsoft': 'MSFT',
|
|
210
|
+
'google': 'GOOGL', 'amazon': 'AMZN', 'meta': 'META', 'netflix': 'NFLX',
|
|
211
|
+
'coinbase': 'COIN', 'robinhood': 'HOOD', 'disney': 'DIS', 'intel': 'INTC',
|
|
212
|
+
'amd': 'AMD', 'palantir': 'PLTR', 'gamestop': 'GME', 'amc': 'AMC',
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
text_lower = text.lower()
|
|
216
|
+
company_symbols = [sym for name, sym in company_map.items() if name in text_lower]
|
|
217
|
+
|
|
218
|
+
return list(set(dollar_symbols + company_symbols))
|
|
219
|
+
|
|
220
|
+
def calculate_rumor_score(item):
|
|
221
|
+
"""Score a rumor by potential impact."""
|
|
222
|
+
score = 0
|
|
223
|
+
text = (item.get('text', '') + item.get('title', '')).lower()
|
|
224
|
+
|
|
225
|
+
# High impact keywords
|
|
226
|
+
if any(kw in text for kw in ['merger', 'acquisition', 'takeover', 'buyout']):
|
|
227
|
+
score += 5
|
|
228
|
+
if any(kw in text for kw in ['insider', 'ceo buying', 'director buying']):
|
|
229
|
+
score += 4
|
|
230
|
+
if any(kw in text for kw in ['upgrade', 'price target raised']):
|
|
231
|
+
score += 3
|
|
232
|
+
if any(kw in text for kw in ['downgrade', 'sec investigation', 'fraud']):
|
|
233
|
+
score += 3
|
|
234
|
+
if any(kw in text for kw in ['hearing', 'sources say', 'rumor']):
|
|
235
|
+
score += 2
|
|
236
|
+
if any(kw in text for kw in ['breaking', 'just in', 'alert']):
|
|
237
|
+
score += 2
|
|
238
|
+
|
|
239
|
+
# Engagement boost
|
|
240
|
+
if item.get('engagement', 0) > 100:
|
|
241
|
+
score += 2
|
|
242
|
+
if item.get('likes', 0) > 50:
|
|
243
|
+
score += 1
|
|
244
|
+
|
|
245
|
+
return score
|
|
246
|
+
|
|
247
|
+
def main():
|
|
248
|
+
print("=" * 60)
|
|
249
|
+
print("🔮 RUMOR & BUZZ SCANNER")
|
|
250
|
+
print(f"📅 {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')} UTC")
|
|
251
|
+
print("=" * 60)
|
|
252
|
+
print()
|
|
253
|
+
print("🔍 Scanning for early signals...")
|
|
254
|
+
print()
|
|
255
|
+
|
|
256
|
+
all_rumors = []
|
|
257
|
+
all_buzz = []
|
|
258
|
+
|
|
259
|
+
# Twitter Rumors
|
|
260
|
+
print(" 🐦 Twitter rumors...")
|
|
261
|
+
rumors = search_twitter_rumors()
|
|
262
|
+
print(f" ✅ {len(rumors)} potential rumors")
|
|
263
|
+
all_rumors.extend(rumors)
|
|
264
|
+
|
|
265
|
+
# Twitter Buzz
|
|
266
|
+
print(" 🐦 Twitter buzz...")
|
|
267
|
+
buzz = search_twitter_buzz()
|
|
268
|
+
print(f" ✅ {len(buzz)} buzz items")
|
|
269
|
+
all_buzz.extend(buzz)
|
|
270
|
+
|
|
271
|
+
# News Rumors
|
|
272
|
+
print(" 📰 News rumors...")
|
|
273
|
+
news = search_news_rumors()
|
|
274
|
+
print(f" ✅ {len(news)} news items")
|
|
275
|
+
all_rumors.extend(news)
|
|
276
|
+
|
|
277
|
+
# Score and sort rumors
|
|
278
|
+
for item in all_rumors:
|
|
279
|
+
item['score'] = calculate_rumor_score(item)
|
|
280
|
+
item['symbols'] = extract_symbols_from_text(item.get('text', '') + item.get('title', ''))
|
|
281
|
+
|
|
282
|
+
all_rumors.sort(key=lambda x: x['score'], reverse=True)
|
|
283
|
+
|
|
284
|
+
# Count symbol mentions in buzz
|
|
285
|
+
symbol_counts = {}
|
|
286
|
+
for item in all_buzz:
|
|
287
|
+
for sym in item.get('symbols', []):
|
|
288
|
+
symbol_counts[sym] = symbol_counts.get(sym, 0) + 1
|
|
289
|
+
|
|
290
|
+
# Output
|
|
291
|
+
print()
|
|
292
|
+
print("=" * 60)
|
|
293
|
+
print("🔮 RESULTS")
|
|
294
|
+
print("=" * 60)
|
|
295
|
+
print()
|
|
296
|
+
|
|
297
|
+
# Top Rumors
|
|
298
|
+
print("🚨 TOP RUMORS (by potential impact):")
|
|
299
|
+
print()
|
|
300
|
+
for item in all_rumors[:10]:
|
|
301
|
+
if item['score'] > 0:
|
|
302
|
+
source = item['source']
|
|
303
|
+
symbols = ', '.join(item.get('symbols', [])) or 'N/A'
|
|
304
|
+
text = item.get('text', item.get('title', ''))[:80]
|
|
305
|
+
print(f" [{item['score']}] [{source}] {symbols}")
|
|
306
|
+
print(f" {text}...")
|
|
307
|
+
print()
|
|
308
|
+
|
|
309
|
+
# Buzz Leaderboard
|
|
310
|
+
print("📊 BUZZ LEADERBOARD (most discussed):")
|
|
311
|
+
print()
|
|
312
|
+
sorted_symbols = sorted(symbol_counts.items(), key=lambda x: x[1], reverse=True)
|
|
313
|
+
for symbol, count in sorted_symbols[:15]:
|
|
314
|
+
bar = "█" * min(count, 20)
|
|
315
|
+
print(f" ${symbol:5} {bar} ({count})")
|
|
316
|
+
|
|
317
|
+
print()
|
|
318
|
+
|
|
319
|
+
# Recent Buzz Snippets
|
|
320
|
+
print("💬 WHAT PEOPLE ARE SAYING:")
|
|
321
|
+
print()
|
|
322
|
+
for item in all_buzz[:8]:
|
|
323
|
+
author = item.get('author', 'anon')
|
|
324
|
+
text = item.get('text', '')[:120]
|
|
325
|
+
engagement = item.get('engagement', 0)
|
|
326
|
+
print(f" @{author} ({engagement}♥): {text}...")
|
|
327
|
+
print()
|
|
328
|
+
|
|
329
|
+
# Save results
|
|
330
|
+
output = {
|
|
331
|
+
'timestamp': datetime.now(timezone.utc).isoformat(),
|
|
332
|
+
'rumors': all_rumors[:20],
|
|
333
|
+
'buzz': all_buzz[:30],
|
|
334
|
+
'symbol_counts': symbol_counts,
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
output_file = CACHE_DIR / 'rumor_scan_latest.json'
|
|
338
|
+
output_file.write_text(json.dumps(output, indent=2, default=str))
|
|
339
|
+
print(f"💾 Saved: {output_file}")
|
|
340
|
+
|
|
341
|
+
if __name__ == "__main__":
|
|
342
|
+
main()
|