Function bodies 23 total
getMonthKey function · javascript · L59-L63 (5 LOC)index.js
function getMonthKey(date = new Date()) {
const y = date.getUTCFullYear();
const m = String(date.getUTCMonth() + 1).padStart(2, '0');
return `${y}-${m}`; // e.g., "2025-11"
}getAllTimeScores function · javascript · L65-L70 (6 LOC)index.js
function getAllTimeScores(chatId) {
if (!allTimeScoresByChat.has(chatId)) {
allTimeScoresByChat.set(chatId, new Map());
}
return allTimeScoresByChat.get(chatId);
}getMonthlyScores function · javascript · L72-L81 (10 LOC)index.js
function getMonthlyScores(chatId, monthKey) {
if (!monthlyScoresByChat.has(chatId)) {
monthlyScoresByChat.set(chatId, new Map());
}
const monthsMap = monthlyScoresByChat.get(chatId);
if (!monthsMap.has(monthKey)) {
monthsMap.set(monthKey, new Map());
}
return monthsMap.get(monthKey);
}addPointsToScores function · javascript · L83-L88 (6 LOC)index.js
function addPointsToScores(scoresMap, userId, name, delta) {
const current = scoresMap.get(userId) || { name, points: 0 };
current.name = name; // keep latest name/username
current.points += delta;
scoresMap.set(userId, current);
}getDisplayName function · javascript · L90-L94 (5 LOC)index.js
function getDisplayName(from) {
if (from.username) return '@' + from.username;
const name = [from.first_name, from.last_name].filter(Boolean).join(' ');
return name || `User ${from.id}`;
}updateLeaderboardRow function · javascript · L96-L160 (65 LOC)index.js
async function updateLeaderboardRow({
chatId,
userId,
name,
scope, // 'all' or 'month'
monthKey, // 'YYYY-MM' for month; ignored for 'all'
winDelta = 0,
lossDelta = 0,
}) {
// If nothing changed, skip the DB hit
if (winDelta === 0 && lossDelta === 0) return;
const compositeKey = {
chatId: String(chatId),
userId: String(userId),
scope,
monthKey: scope === 'all' ? 'ALL' : monthKey,
};
try {
const existing = await prisma.leaderboardScore.findUnique({
where: {
chat_user_scope_month_unique: compositeKey,
},
});
let wins = existing?.wins ?? 0;
let losses = existing?.losses ?? 0;
wins += winDelta;
losses += lossDelta;
const total = wins + losses;
const winRate = total > 0 ? wins / total : 0;
const powerScore = total > 0 ? winRate * 100 * Math.sqrt(total) : 0;
if (existing) {
await prisma.leaderboardScore.update({
where: {
chat_user_scope_month_unique: compositeformatUtc function · javascript · L167-L172 (6 LOC)index.js
function formatUtc(date) {
const iso = date.toISOString();
const [ymd, hms] = iso.split('T');
const [hh, mm] = hms.split(':');
return `${ymd} ${hh}:${mm} UTC`;
}About: code-quality intelligence by Repobility · https://repobility.com
formatRelative function · javascript · L175-L195 (21 LOC)index.js
function formatRelative(date, now = new Date()) {
const diffMs = date.getTime() - now.getTime();
if (diffMs <= 0) return 'now';
const minutes = Math.round(diffMs / 60000);
if (minutes < 1) return 'less than a minute';
if (minutes === 1) return 'about 1 minute';
if (minutes < 60) return `about ${minutes} minutes`;
const hours = Math.floor(minutes / 60);
const remainMins = minutes % 60;
if (hours < 24) {
if (remainMins === 0) return `about ${hours} hour${hours > 1 ? 's' : ''}`;
return `about ${hours}h ${remainMins}m`;
}
const days = Math.floor(hours / 24);
const remainHours = hours % 24;
if (remainHours === 0) return `about ${days} day${days > 1 ? 's' : ''}`;
return `about ${days}d ${remainHours}h`;
}parseResolution function · javascript · L198-L235 (38 LOC)index.js
function parseResolution(raw, now = new Date()) {
const trimmed = raw.trim();
// ----- 1) Absolute UTC: YYYY-MM-DD HH:MM -----
const abs = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2})$/);
if (abs) {
const [, year, month, day, hour, minute] = abs;
return new Date(Date.UTC(
Number(year),
Number(month) - 1,
Number(day),
Number(hour),
Number(minute)
));
}
// ----- 2) Relative pattern: "in 2 hours", "in 90m", "in 3 days" -----
const rel = trimmed.match(/^in\s+(\d+)\s*(hours?|hrs?|h|minutes?|mins?|m|days?|d)$/i);
if (rel) {
const amount = Number(rel[1]);
const unit = rel[2].toLowerCase();
const result = new Date(now);
if (unit.startsWith('h')) {
result.setUTCHours(result.getUTCHours() + amount);
} else if (unit.startsWith('m')) {
result.setUTCMinutes(result.getUTCMinutes() + amount);
} else if (unit.startsWith('d')) {
result.setUTCDate(result.getUTCDate() + amount);
}
parseCondition function · javascript · L239-L253 (15 LOC)index.js
function parseCondition(raw) {
const trimmed = raw.trim();
// e.g. "BTC > 100000" or "DIVI <= 0.01"
const match = trimmed.match(/^([A-Za-z0-9]+)\s*(>=|<=|>|<)\s*([0-9]+(?:\.[0-9]+)?)$/);
if (!match) return null;
const [, symbolRaw, operator, thresholdStr] = match;
const threshold = Number(thresholdStr);
if (!Number.isFinite(threshold)) return null;
const assetSymbol = symbolRaw.toUpperCase();
const assetId = SYMBOL_TO_ID[assetSymbol] || null;
return { assetSymbol, assetId, operator, threshold };
}evaluateCondition function · javascript · L256-L269 (14 LOC)index.js
function evaluateCondition(price, operator, threshold) {
switch (operator) {
case '>':
return price > threshold;
case '<':
return price < threshold;
case '>=':
return price >= threshold;
case '<=':
return price <= threshold;
default:
return false;
}
}buildWagerText function · javascript · L272-L294 (23 LOC)index.js
function buildWagerText(wager, now = new Date()) {
const remainingMs = wager.voteDeadline.getTime() - now.getTime();
let statusLine;
if (remainingMs <= 0) {
statusLine = 'Voting is CLOSED.';
} else {
const remainingSeconds = Math.ceil(remainingMs / 1000);
statusLine = `Voting closes in ${remainingSeconds} second(s).`;
}
const conditionLine = `Condition: ${wager.assetSymbol} ${wager.operator} ${wager.threshold} USD`;
return (
`Wager #${wager.id}\n` +
`${conditionLine}\n\n` +
`Resolves: ${formatUtc(wager.resolutionTime)} (${formatRelative(wager.resolutionTime, now)})\n` +
`Voting closes at: ${formatUtc(wager.voteDeadline)}\n\n` +
`YES: ${wager.yes.size} user(s)\n` +
`NO: ${wager.no.size} user(s)\n\n` +
statusLine
);
}buildResolvedText function · javascript · L297-L323 (27 LOC)index.js
function buildResolvedText(wager) {
const conditionLine = `Condition: ${wager.assetSymbol} ${wager.operator} ${wager.threshold} USD`;
const winnerSide = wager.outcomeYes ? 'YES' : 'NO';
const winnersText =
wager.winners && wager.winners.length
? wager.winners.join('\n')
: 'No winners 😬';
const losersText =
wager.losers && wager.losers.length
? wager.losers.join('\n')
: 'No losers 😌';
return (
`Wager #${wager.id} (RESOLVED)\n` +
`${conditionLine}\n\n` +
`Resolved at: ${formatUtc(wager.resolutionTime)}\n` +
`Final price: ${wager.finalPrice} USD\n` +
`Winning side: ${winnerSide}\n\n` +
`YES: ${wager.yes.size} user(s)\n` +
`NO: ${wager.no.size} user(s)\n\n` +
`Winners (${wager.winners?.length || 0}):\n${winnersText}\n\n` +
`Losers (${wager.losers?.length || 0}):\n${losersText}\n\n` +
`Voting is CLOSED.`
);
}startCountdown function · javascript · L326-L384 (59 LOC)index.js
function startCountdown(wager) {
// Clear any existing timer just in case
if (wager.countdownIntervalId) {
clearInterval(wager.countdownIntervalId);
wager.countdownIntervalId = null;
}
const id = wager.id;
wager.countdownIntervalId = setInterval(async () => {
const current = new Date();
const stored = wagers.get(id);
// If wager disappeared from memory, stop the timer
if (!stored) {
clearInterval(wager.countdownIntervalId);
wager.countdownIntervalId = null;
return;
}
const remainingMs = stored.voteDeadline.getTime() - current.getTime();
const done = remainingMs <= 0;
const newText = buildWagerText(stored, current);
try {
await bot.telegram.editMessageText(
stored.chatId,
stored.messageId,
undefined,
newText,
{
reply_markup: {
inline_keyboard: [
[
{ text: '👍 YES', callback_data: `join:yes:${id}` },
getCurrentPriceUsd function · javascript · L387-L405 (19 LOC)index.js
async function getCurrentPriceUsd(coinId) {
if (!coinId) throw new Error('No coinId configured for this symbol');
const url = `https://api.coingecko.com/api/v3/simple/price?ids=${encodeURIComponent(
coinId
)}&vs_currencies=usd`;
const res = await fetch(url);
if (!res.ok) {
throw new Error(`Price API error: ${res.status} ${res.statusText}`);
}
const data = await res.json();
if (!data[coinId] || typeof data[coinId].usd !== 'number') {
throw new Error('Unexpected price data shape from CoinGecko');
}
return data[coinId].usd;
}If a scraper extracted this row, it came from Repobility (https://repobility.com)
fetchTop function · javascript · L430-L455 (26 LOC)index.js
async function fetchTop(scope, monthKey) {
const where = {
chatId: chatIdStr,
scope,
monthKey: scope === 'all' ? 'ALL' : monthKey,
};
const rows = await prisma.leaderboardScore.findMany({
where,
orderBy: { powerScore: 'desc' }, // <-- sort by powerScore
take: 10,
});
if (!rows.length) {
return 'No scores yet. Resolve a wager first.';
}
return rows
.map((row, i) => {
const ps = row.powerScore ?? 0;
const wins = row.wins ?? 0;
const losses = row.losses ?? 0;
return `${i + 1}. ${row.name} — PS ${ps.toFixed(1)} (W:${wins} L:${losses})`;
})
.join('\n');
}tryStrictParse function · javascript · L625-L644 (20 LOC)index.js
function tryStrictParse(input, now, isDebug) {
const parts = input.split('|').map((s) => s.trim());
if (parts.length !== 2 || !parts[0] || !parts[1]) {
return { success: false };
}
const condition = parseCondition(parts[0]);
if (!condition) return { success: false };
const resolutionTime = parseResolution(parts[1], now);
if (!resolutionTime || isNaN(resolutionTime.getTime())) return { success: false };
const diffMs = resolutionTime.getTime() - now.getTime();
const minResolutionMs = 10 * 60 * 1000;
if (!isDebug && diffMs < minResolutionMs) {
return { success: false, error: `Resolution time must be at least 10 minutes from now.` };
}
return { success: true, condition, resolutionTime };
}validateLLMResult function · javascript · L647-L685 (39 LOC)index.js
function validateLLMResult(result, now, isDebug) {
const { symbol, operator, threshold, resolution_time } = result;
if (!symbol || !operator || threshold == null || !resolution_time) {
return { error: 'Missing required wager fields. Please try again.' };
}
const assetId = SYMBOL_TO_ID[symbol];
if (!assetId) {
return { error: `Unsupported symbol: ${symbol}. Supported: ${Object.keys(SYMBOL_TO_ID).join(', ')}` };
}
// Parse resolution time — try relative ("2 hours"), then our strict format, then ISO timestamp
let resolutionTime = parseResolution(`in ${resolution_time}`, now);
if (!resolutionTime || isNaN(resolutionTime.getTime())) {
resolutionTime = parseResolution(resolution_time, now);
}
if (!resolutionTime || isNaN(resolutionTime.getTime())) {
// Try parsing as ISO timestamp (e.g. "2026-03-25T07:31:03.442Z")
const isoAttempt = new Date(resolution_time);
if (!isNaN(isoAttempt.getTime())) {
resolutionTime = isoAttempt;
}
}
ifparseWagerWithLLM function · javascript · L760-L780 (21 LOC)index.js
async function parseWagerWithLLM(messages) {
const response = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{ role: 'system', content: WAGER_SYSTEM_PROMPT },
...messages,
],
tools: [WAGER_EXTRACT_TOOL],
tool_choice: { type: 'function', function: { name: 'extract_wager' } },
temperature: 0,
});
const toolCall = response.choices[0]?.message?.tool_calls?.[0];
if (!toolCall) return null;
try {
return JSON.parse(toolCall.function.arguments);
} catch {
return null;
}
}createWager function · javascript · L783-L867 (85 LOC)index.js
async function createWager(ctx, condition, resolutionTime, now = new Date()) {
// Voting window: 60 seconds from creation
const voteDeadline = new Date(now.getTime() + 60 * 1000);
// Simple unique ID for the wager
const id = Date.now().toString();
const conditionText = `${condition.assetSymbol} ${condition.operator} ${condition.threshold}`;
// Create wager in memory (with structured condition)
const wager = {
id,
text: conditionText,
assetSymbol: condition.assetSymbol,
assetId: condition.assetId,
operator: condition.operator,
threshold: condition.threshold,
yes: new Set(),
no: new Set(),
participantNames: new Map(),
chatId: ctx.chat.id,
messageId: null,
createdAt: now,
resolutionTime,
voteDeadline,
countdownIntervalId: null,
resolved: false,
finalPrice: null,
outcomeYes: null,
};
wagers.set(id, wager);
// ---- Persist wager to Postgres (best-effort) ----
try {
await prisma.wager.creatrebuildActiveWagersFromDb function · javascript · L869-L983 (115 LOC)index.js
async function rebuildActiveWagersFromDb({ graceHours = 24 } = {}) {
const now = new Date();
const graceStart = new Date(now.getTime() - graceHours * 60 * 60 * 1000);
console.log(
`Rebuilding active wagers from DB (resolved = false, resolutionTime >= ${graceStart.toISOString()})`
);
// Clear any existing in-memory wagers
wagers.clear();
let dbWagers;
try {
dbWagers = await prisma.wager.findMany({
where: {
resolved: false,
resolutionTime: { gte: graceStart },
},
});
} catch (err) {
console.error(
'Failed to load unresolved wagers from Postgres:',
err.message || err
);
return;
}
if (dbWagers.length === 0) {
console.log('No unresolved wagers found in DB for rebuild.');
return;
}
// Load all votes for these wagers in a single query
const wagerIds = dbWagers.map((w) => w.id);
let dbVotes;
try {
dbVotes = await prisma.wagerVote.findMany({
where: {
wagerId: { in: wagrebuildLeaderboardsFromDb function · javascript · L985-L1042 (58 LOC)index.js
async function rebuildLeaderboardsFromDb() {
console.log('Rebuilding leaderboards from DB...');
// Clear in-memory leaderboards
allTimeScoresByChat.clear();
monthlyScoresByChat.clear();
let rows;
try {
rows = await prisma.leaderboardScore.findMany();
} catch (err) {
console.error(
'Failed to load leaderboard scores from Postgres:',
err.message || err
);
return;
}
if (!rows.length) {
console.log('No leaderboard rows found in DB.');
return;
}
for (const row of rows) {
const chatKey = Number(row.chatId) || row.chatId;
const userKey = Number(row.userId) || row.userId;
const points = (row.wins || 0) * 10; // matches +10 per win in memory
if (row.scope === 'all') {
if (!allTimeScoresByChat.has(chatKey)) {
allTimeScoresByChat.set(chatKey, new Map());
}
const chatMap = allTimeScoresByChat.get(chatKey);
const existing = chatMap.get(userKey) || { name: row.name, points: 0 };
// IresolveDueWagers function · javascript · L1183-L1447 (265 LOC)index.js
async function resolveDueWagers() {
const now = new Date();
console.log('Resolver tick at', now.toISOString());
let dueWagers;
try {
dueWagers = await prisma.wager.findMany({
where: {
resolved: false,
resolutionTime: { lte: now },
},
include: {
votes: true,
},
});
} catch (err) {
console.error('Failed to load due wagers from Postgres:', err.message || err);
return;
}
if (!dueWagers.length) {
console.log('No due unresolved wagers found.');
return;
}
for (const dbW of dueWagers) {
if (!dbW.assetId) {
console.log('Skipping wager (no assetId):', dbW.id);
continue;
}
const chatId = Number(dbW.chatId) || dbW.chatId;
// Rebuild yes/no sets and participant names from DB votes
const yesSet = new Set();
const noSet = new Set();
const participantNames = new Map();
for (const v of dbW.votes) {
const userIdNum = Number(v.userId);
const uid = Number.