← back to Bertha
chore(alshi-dash): update 1 file (.js) [+218/-20]
1d7b7c16eff039f9b7b93ad8096287f9540bba10 · 2026-02-15 16:30:16 +0000 · DW Commit Agent
Files touched
Diff
commit 1d7b7c16eff039f9b7b93ad8096287f9540bba10
Author: DW Commit Agent <commit-agent@dw-agents.com>
Date: Sun Feb 15 16:30:16 2026 +0000
chore(alshi-dash): update 1 file (.js) [+218/-20]
---
kalshi-dash/server.js | 238 +++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 218 insertions(+), 20 deletions(-)
diff --git a/kalshi-dash/server.js b/kalshi-dash/server.js
index 6379d0a..b4c1cd9 100644
--- a/kalshi-dash/server.js
+++ b/kalshi-dash/server.js
@@ -1900,8 +1900,130 @@ async function scrapeMetaculus() {
}
}
+// ── Discord Bot Intelligence (reads messages from joined servers) ──
+const DISCORD_BOT_TOKEN = 'MTQ3MjYyNDg2OTk3NDg2ODA2Mg.GifeyF.C5l9KqQUia-Ex54Ldg23fuxislxwf2QtmkXYII';
+const DISCORD_API = 'https://discord.com/api/v10';
+
+// Channel names to monitor (matched case-insensitive) — covers all Kalshi/Polymarket categories
+const DISCORD_CHANNEL_KEYWORDS = [
+ // Core
+ 'general', 'chat', 'discussion', 'lounge', 'main',
+ // Trading & Markets
+ 'trading', 'markets', 'predictions', 'analysis', 'strategy', 'signals', 'alerts',
+ 'arbitrage', 'polymarket', 'kalshi', 'bets', 'picks', 'plays', 'odds',
+ // News & Facts
+ 'news', 'breaking', 'headlines', 'updates', 'osint', 'intel', 'events',
+ // Weather
+ 'weather', 'hurricane', 'tropical', 'storm', 'forecast', 'severe', 'climate',
+ // Politics & Elections
+ 'politics', 'election', 'polling', 'congress', 'senate', 'trump', 'biden',
+ 'policy', 'legislation', 'vote', 'debate',
+ // Economics & Finance
+ 'economics', 'economy', 'finance', 'stocks', 'fed', 'inflation', 'rates',
+ 'macro', 'earnings', 'options', 'futures', 'bonds',
+ // Sports
+ 'sports', 'nfl', 'nba', 'mlb', 'nhl', 'soccer', 'football', 'basketball',
+ 'baseball', 'ufc', 'mma', 'boxing', 'f1', 'golf', 'tennis',
+ // Crypto
+ 'crypto', 'bitcoin', 'btc', 'ethereum', 'eth', 'defi', 'altcoin', 'solana',
+ // Geopolitics
+ 'geopolitics', 'ukraine', 'russia', 'china', 'military', 'conflict', 'war', 'nato',
+ // Entertainment
+ 'entertainment', 'movies', 'film', 'oscars', 'grammys', 'awards', 'box-office',
+ 'music', 'tv', 'streaming', 'celebrity',
+ // Science & Space
+ 'space', 'spacex', 'nasa', 'launch', 'science', 'rocket',
+ // Tech & AI
+ 'tech', 'ai', 'artificial', 'openai', 'gpt', 'machine-learning', 'nvidia',
+];
+
+async function discordFetch(endpoint) {
+ const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), 10000);
+ const res = await fetch(`${DISCORD_API}${endpoint}`, {
+ headers: { Authorization: `Bot ${DISCORD_BOT_TOKEN}`, 'Content-Type': 'application/json' },
+ signal: controller.signal,
+ });
+ clearTimeout(timeout);
+ if (!res.ok) {
+ if (res.status === 429) {
+ const retry = await res.json().catch(() => ({}));
+ const wait = (retry.retry_after || 5) * 1000;
+ console.log(`[Ken] Discord rate limited, waiting ${wait}ms`);
+ await new Promise(r => setTimeout(r, wait));
+ return null;
+ }
+ return null;
+ }
+ return res.json();
+}
+
+async function scrapeDiscord(limit = 50) {
+ const results = [];
+ try {
+ // 1. Get all guilds the bot is in
+ const guilds = await discordFetch('/users/@me/guilds');
+ if (!guilds || !Array.isArray(guilds)) {
+ console.log('[Ken] Discord: no guilds found (bot may not have joined any servers yet)');
+ return [];
+ }
+ console.log(`[Ken] Discord: bot is in ${guilds.length} server(s): ${guilds.map(g => g.name).join(', ')}`);
+
+ for (const guild of guilds) {
+ // 2. Get channels for this guild
+ const channels = await discordFetch(`/guilds/${guild.id}/channels`);
+ if (!channels || !Array.isArray(channels)) continue;
+
+ // 3. Filter to text channels matching keywords
+ const textChannels = channels.filter(ch => {
+ if (ch.type !== 0) return false; // type 0 = text channel
+ const name = (ch.name || '').toLowerCase();
+ return DISCORD_CHANNEL_KEYWORDS.some(kw => name.includes(kw));
+ });
+
+ console.log(`[Ken] Discord [${guild.name}]: monitoring ${textChannels.length}/${channels.filter(c => c.type === 0).length} text channels`);
+
+ for (const channel of textChannels.slice(0, 15)) { // cap at 15 channels per guild
+ try {
+ const messages = await discordFetch(`/channels/${channel.id}/messages?limit=${Math.min(limit, 50)}`);
+ if (!messages || !Array.isArray(messages)) continue;
+
+ // Only messages from last 2 hours (fresh intel)
+ const cutoff = Date.now() - (2 * 60 * 60 * 1000);
+ for (const msg of messages) {
+ const msgTime = new Date(msg.timestamp).getTime();
+ if (msgTime < cutoff) continue;
+ if (!msg.content || msg.content.length < 20) continue;
+ if (msg.author?.bot) continue; // skip bot messages
+
+ results.push({
+ title: msg.content.substring(0, 500),
+ url: `https://discord.com/channels/${guild.id}/${channel.id}/${msg.id}`,
+ source: `Discord ${guild.name}`,
+ channel: channel.name,
+ author: msg.author?.username || 'unknown',
+ score: (msg.reactions || []).reduce((sum, r) => sum + (r.count || 0), 0),
+ num_comments: msg.referenced_message ? 1 : 0, // thread indicator
+ created: new Date(msg.timestamp),
+ });
+ }
+ // Rate limit: 500ms between channel reads
+ await new Promise(r => setTimeout(r, 500));
+ } catch (e) {
+ console.log(`[Ken] Discord channel ${channel.name} error: ${e.message}`);
+ }
+ }
+ await new Promise(r => setTimeout(r, 1000)); // between guilds
+ }
+ console.log(`[Ken] Discord: ${results.length} messages from ${guilds.length} server(s)`);
+ } catch (e) {
+ console.log(`[Ken] Discord scrape failed: ${e.message}`);
+ }
+ return results;
+}
+
// ══════════════════════════════════════════════
-// INTELLIGENCE LOOP (every 30 min) — Reddit, HN, News, Metaculus
+// INTELLIGENCE LOOP (every 30 min) — Reddit, HN, News, Metaculus, Discord
// ══════════════════════════════════════════════
async function redditIntelligenceLoop() {
console.log('[Ken] Running Reddit intelligence sweep...');
@@ -1934,27 +2056,78 @@ async function redditIntelligenceLoop() {
}
// Also scrape hot posts from key subreddits for general intelligence
- const hotSubs = ['politics', 'worldnews', 'economics', 'wallstreetbets', 'news',
- 'Kalshi', 'Polymarket', 'PredictionMarket', 'sportsbook', 'CryptoMarkets',
+ // Categories: Weather | Politics | Economics | Sports | Crypto | Geopolitics | Entertainment | Science/Space | Tech/AI | General Sentiment
+ const hotSubs = [
+ // ── Core prediction markets ──
+ 'Kalshi', 'Polymarket', 'PredictionMarket', 'sportsbook',
+ // ── Weather (hurricane/temp/storm markets) ──
+ 'TropicalWeather', 'weather', 'tornado', 'hurricane', 'climate',
+ // ── Politics & Elections ──
+ 'politics', 'Conservative', 'Liberal', 'NeutralPolitics', 'PoliticalDiscussion',
+ 'fivethirtyeight', 'elections', 'supremecourt', 'moderatepolitics', 'law',
+ // ── Economics & Finance ──
+ 'economics', 'wallstreetbets', 'economy', 'inflation', 'FederalReserve',
+ 'StockMarket', 'investing', 'RealEstate', 'finance', 'commodities',
+ // ── Sports ──
+ 'nfl', 'nba', 'baseball', 'soccer', 'MMA', 'CFB', 'CollegeBasketball',
+ 'formula1', 'golf', 'hockey', 'tennis', 'boxing',
+ // ── Crypto ──
+ 'CryptoMarkets', 'Bitcoin', 'ethereum', 'CryptoCurrency', 'BitcoinMarkets', 'solana',
+ // ── Geopolitics ──
+ 'worldnews', 'geopolitics', 'UkrainianConflict', 'ukraine', 'China',
+ 'MiddleEastNews', 'foreignpolicy', 'military',
+ // ── Entertainment (awards/box office markets) ──
+ 'movies', 'boxoffice', 'television', 'Oscars', 'Music', 'entertainment',
+ // ── Science & Space (launch/milestone markets) ──
+ 'SpaceX', 'space', 'nasa', 'science', 'Futurology',
+ // ── Tech & AI ──
+ 'technology', 'artificial', 'MachineLearning', 'OpenAI', 'singularity', 'Nvidia',
+ // ── General sentiment ──
+ 'news', 'OutOfTheLoop', 'AskReddit', 'TrueReddit', 'dataisbeautiful',
];
- for (const sub of hotSubs) {
- const hotPosts = await scrapeSubredditHot(sub, 10);
- for (const post of hotPosts) {
- if (post.score > 50) {
- const sentiment = scoreSentiment(post.title);
- await kenQ(`
- INSERT INTO ken_sentiment (source, subreddit, keyword, post_title, post_url, score, num_comments, sentiment_score, raw_text)
- VALUES ('reddit_hot', $1, 'hot', $2, $3, $4, $5, $6, $7)
- ON CONFLICT DO NOTHING
- `, [sub, post.title, post.url, post.score, post.num_comments, sentiment, '']);
- totalPosts++;
+ // Process subreddits in parallel batches of 5 (75+ subs × 2s serial = too slow)
+ const REDDIT_BATCH = 5;
+ for (let i = 0; i < hotSubs.length; i += REDDIT_BATCH) {
+ const batch = hotSubs.slice(i, i + REDDIT_BATCH);
+ const results = await Promise.allSettled(batch.map(sub => scrapeSubredditHot(sub, 8)));
+ for (let j = 0; j < results.length; j++) {
+ if (results[j].status !== 'fulfilled') continue;
+ const sub = batch[j];
+ for (const post of results[j].value) {
+ if (post.score > 30) { // lower threshold to catch more sentiment signals
+ const sentiment = scoreSentiment(post.title);
+ await kenQ(`
+ INSERT INTO ken_sentiment (source, subreddit, keyword, post_title, post_url, score, num_comments, sentiment_score, raw_text)
+ VALUES ('reddit_hot', $1, 'hot', $2, $3, $4, $5, $6, $7)
+ ON CONFLICT DO NOTHING
+ `, [sub, post.title, post.url, post.score, post.num_comments, sentiment, '']);
+ totalPosts++;
+ }
}
}
- await new Promise(r => setTimeout(r, 2000));
+ await new Promise(r => setTimeout(r, 1500)); // 1.5s between batches
}
-
- // Scrape Google News for top market topics
- const newsQueries = ['trump cabinet resign', 'federal reserve chair nomination', 'greenland acquisition', 'trump insurrection act', '2028 presidential election'];
+ console.log(`[Ken] Reddit hot: ${hotSubs.length} subreddits across 10 categories`);
+
+ // Scrape Google News for top market topics (covers all Kalshi categories)
+ const newsQueries = [
+ // Politics & elections
+ 'trump cabinet resign', 'federal reserve chair nomination', '2028 presidential election',
+ 'trump executive order', 'government shutdown', 'supreme court ruling',
+ // Economics
+ 'CPI inflation report', 'federal reserve rate decision', 'jobs report unemployment',
+ 'GDP growth', 'recession forecast',
+ // Weather
+ 'hurricane forecast atlantic', 'severe weather outbreak', 'temperature record',
+ // Geopolitics
+ 'ukraine russia ceasefire', 'china taiwan', 'middle east conflict',
+ // Crypto
+ 'bitcoin price prediction', 'SEC crypto regulation',
+ // Entertainment
+ 'oscar nominations', 'box office weekend',
+ // Space
+ 'SpaceX launch', 'NASA artemis',
+ ];
for (const nq of newsQueries) {
const articles = await scrapeGoogleNews(nq, 5);
for (const article of articles) {
@@ -2210,7 +2383,32 @@ async function redditIntelligenceLoop() {
totalPosts += tvTotal + lemmyTotal + lobstersTotal + tildesTotal + bskyTotal;
totalPosts += manifoldTotal + predMarketRedditTotal;
- console.log(`[Ken] ═══ FULL SWEEP: ${totalPosts} items (Reddit, HN, ${NEWS_FEEDS.length} feeds, TV, YT, Manifold, r/Kalshi, r/Polymarket, Lemmy, Lobsters, Tildes, Bluesky, Mastodon, Metaculus) ═══`);
+
+ // ── Discord (Kalshi, Polymarket, Prediction Markets servers) ──
+ let discordTotal = 0;
+ try {
+ const discordMessages = await scrapeDiscord(30);
+ for (const msg of discordMessages) {
+ const sentiment = scoreSentiment(msg.title);
+ await kenQ(`
+ INSERT INTO ken_sentiment (source, subreddit, keyword, post_title, post_url, score, num_comments, sentiment_score, raw_text)
+ VALUES ('discord', $1, $2, $3, $4, $5, $6, $7, $8)
+ ON CONFLICT DO NOTHING
+ `, [
+ msg.source, msg.channel || 'general', msg.title.substring(0, 500),
+ msg.url, msg.score || 0, msg.num_comments || 0, sentiment,
+ (msg.author || '').substring(0, 200),
+ ]);
+ discordTotal++;
+ }
+ totalPosts += discordTotal;
+ console.log(`[Ken] Discord: ${discordTotal} messages`);
+ } catch (e) {
+ console.log(`[Ken] Discord failed: ${e.message}`);
+ }
+ if (global.gc) global.gc();
+
+ console.log(`[Ken] ═══ FULL SWEEP: ${totalPosts} items (Reddit, HN, ${NEWS_FEEDS.length} feeds, TV, YT, Manifold, r/Kalshi, r/Polymarket, Lemmy, Lobsters, Tildes, Bluesky, Mastodon, Metaculus, Discord) ═══`);
return totalPosts;
} catch (e) {
console.error('[Ken] Intelligence sweep error:', e.message);
@@ -3146,7 +3344,7 @@ server.listen(PORT, '0.0.0.0', () => {
console.log(`[Ken] Market scan: every 15 minutes`);
console.log(`[Ken] Reddit/news: every 30 minutes`);
console.log(`[Ken] Slack alerts: every 4 hours (high-conf bypass)`);
- console.log(`[Ken] Data sources: Kalshi | Reddit (70+) | ${NEWS_FEEDS.length} news feeds | HN | GDELT TV | YouTube | Manifold | r/Kalshi | r/Polymarket | Lemmy | Lobsters | Tildes | Bluesky | Mastodon | Metaculus | Polymarket`);
+ console.log(`[Ken] Data sources: Kalshi | Reddit (70+) | ${NEWS_FEEDS.length} news feeds | HN | GDELT TV | YouTube | Manifold | r/Kalshi | r/Polymarket | Lemmy | Lobsters | Tildes | Bluesky | Mastodon | Metaculus | Polymarket | Discord`);
console.log(`[Ken] ═══════════════════════════════════════`);
// Mutex: prevent concurrent sweeps from overlapping and doubling memory
← ee69ff3 chore(alshi-dash): update 1 file (.js) [+19/-16]
·
back to Bertha
·
chore(alshi-dash): update 1 file (.js) [+256/-1] fd178e4 →