-
Notifications
You must be signed in to change notification settings - Fork 0
Add API usage tracking for Telegram and Twitter accounts #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
… track API usage, and refactor message fetching logic
WalkthroughRefactors Telegram and Twitter fetch routines for stricter channel/token validation, message normalization, logging, and conditional telemetry; replaces per-call Redis operations with a lazy singleton client and adds Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant App as App / Caller
participant TG as TelegramClient
participant Redis as Redis
Note over App: fetchTelegramMessages(channel)
App->>TG: getEntity(channel)
alt resolution error or forbidden
TG-->>App: throws (error / ChannelForbidden)
App-->>App: propagate descriptive error
else resolved channel entity
App->>TG: GetHistory(peer=entity, limit=10)
TG-->>App: messages
App-->>App: normalize → [{id, content, channelId}] (filter empties)
end
opt API_ID present
App->>TG: getMe()
TG-->>App: account info
App->>Redis: trackApiKeyUsage({accountId, platform:'telegram'})
Redis-->>App: OK
end
App-->>App: return messages
sequenceDiagram
autonumber
participant App as App / Caller
participant TW as Twitter GraphQL
participant Redis as Redis
Note over App: fetchHomeTimeline()
App->>App: validate BEARER, CSRF_TOKEN, AUTH_TOKEN
App->>TW: POST HomeTimeline (headers, cookies, body)
TW-->>App: response
alt error
App-->>App: throw/log error
else success
App-->>App: parse & dedupe → [{id, content, authorId}]
App->>App: fetchViewerAccount() -> accountId
App->>Redis: trackApiKeyUsage({accountId, platform:'twitter'})
Redis-->>App: OK
App-->>App: return timeline
end
sequenceDiagram
autonumber
participant Caller as Any Caller
participant Redis as Redis Client
Note over Caller: getApiKeyUsage({accountId, platform})
Caller->>Redis: HGETALL <platform_key:accountId>
Redis-->>Caller: {total_requests, last_request, account_id?}
Caller-->>Caller: normalize and return
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal). Please share your feedback with us on this Discord post. Comment |
…d Twitter API usage stats
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/utils/redisUtils.ts (1)
63-85
: Make Redis endpoint configurable and add basic socket timeoutsHardcoded URL impedes deployments; add REDIS_URL and minimal reconnect/timeout settings.
export async function runRedisOperation(operation: (client: RedisClientType) => Promise<void>): Promise<void> { - const client: RedisClientType = createClient({ - url: 'redis://localhost:6379' - }); + const client: RedisClientType = createClient({ + url: process.env.REDIS_URL ?? 'redis://localhost:6379', + socket: { + connectTimeout: 5000, + reconnectStrategy: (retries) => Math.min(retries * 50, 2000), + }, + });Optional next step: memoize a singleton Redis client to avoid per-call connect/quit overhead when telemetry is frequent.
src/twitterApi.ts (1)
104-114
: Add timeout and network error handling to external POST
fetch
can hang; add an AbortController and clearer errors.- const response = await fetch(url, { - method: "POST", - headers, - body: JSON.stringify(body), - }); + const controller = new AbortController(); + const timeoutMs = Number(process.env.TW_TIMEOUT_MS ?? 15000); + const tm = setTimeout(() => controller.abort(), timeoutMs); + let response: Response; + try { + response = await fetch(url, { + method: "POST", + headers, + body: JSON.stringify(body), + signal: controller.signal, + }); + } catch (e: any) { + if (e?.name === 'AbortError') { + throw new Error(`Twitter API request timed out after ${timeoutMs}ms`); + } + throw e; + } finally { + clearTimeout(tm); + }
🧹 Nitpick comments (9)
src/utils/redisUtils.ts (3)
24-36
: Harden parsing and return typingAvoid implicit radix and NaN leakage when parsing totals.
- await runRedisOperation(async (client) => { + await runRedisOperation(async (client) => { const key = `api_usage:${apiKey}`; const data = await client.hGetAll(key); - result.total_requests = data.total_requests ? parseInt(data.total_requests) : 0; + const total = Number(data.total_requests); + result.total_requests = Number.isFinite(total) ? total : 0; result.last_request = data.last_request ? data.last_request : null; if (data.account_handle) { result.account_handle = data.account_handle; } });
41-55
: Align printed field name with stored field (account_handle
)Log label says
account_id
but Redis field isaccount_handle
.console.log('Telegram API usage:', { total_requests: telegramUsage.total_requests, last_request: telegramUsage.last_request || 'No last Telegram request recorded.', - account_id: telegramUsage.account_handle || 'No account handle recorded.' + account_handle: telegramUsage.account_handle || 'No account handle recorded.' }); ... console.log('Twitter API usage:', { total_requests: twitterUsage.total_requests, last_request: twitterUsage.last_request || 'No last Twitter request recorded.', - account_id: twitterUsage.account_handle || 'No account id recorded.' + account_handle: twitterUsage.account_handle || 'No account handle recorded.' });
58-60
: ESM/CJS entrypoint check
require.main
fails under ESM. Make the check conditional.-if (require.main === module) { - main(); -} +// Works in CJS; no-op in ESM where `require` is undefined. +if (typeof require !== 'undefined' && require.main === module) { + main(); +}If the repo is ESM-only, consider removing this example block entirely.
src/twitterApi.ts (2)
5-28
: Unused helperfetchViewerAccount
It’s not referenced. Either wire it to derive
TWITTER_ID
when absent or remove to reduce surface area.
172-180
: Align output label with Redis fieldUse
account_handle
to match stored field name.console.log('Twitter API usage:', { total_requests: usage.total_requests, last_request: usage.last_request, - account_id: usage.account_handle + account_handle: usage.account_handle });src/index.ts (1)
41-52
: Align output label with Redis fieldConsistent naming helps when grepping logs.
const usage = await getApiKeyUsage(process.env.API_ID as string); console.log('Telegram API usage:', { total_requests: usage.total_requests, last_request: usage.last_request, - account_id: usage.account_handle + account_handle: usage.account_handle });src/fetchTelegramMessages.ts (3)
13-26
: PrefergetInputEntity
to ensure the correct peer type
messages.GetHistory
expects an input peer; resolving toApi.Channel
then passing it can be brittle. Convert to an input peer explicitly.
31-36
: Use an input peer for GetHistoryMinor adjustment to avoid peer-type mismatches.
- const messages = await client.invoke( - new Api.messages.GetHistory({ - peer: entity, - limit: 10, - }) - ); + const inputPeer = await client.getInputEntity(entity); + const messages = await client.invoke( + new Api.messages.GetHistory({ + peer: inputPeer, + limit: 10, + }) + );
53-75
: Telemetry key may conflate multiple Telegram accountsUsing only
API_ID
aggregates usage across any accounts sharing the app id;account_handle
will flip-flop. Consider composing the usage key with the account id (e.g.,${API_ID}:${me.id}
) so metrics are per-account.I can provide a small helper to compute a stable key for Telegram and update
index.ts
to query using the same composite key. Want that patch?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/fetchTelegramMessages.ts
(1 hunks)src/index.ts
(3 hunks)src/twitterApi.ts
(4 hunks)src/utils/redisUtils.ts
(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
src/fetchTelegramMessages.ts (2)
src/utils/redisUtils.ts (1)
trackApiKeyUsage
(7-17)src/telegram.ts (2)
console
(11-52)msg
(44-46)
src/utils/redisUtils.ts (1)
src/testRedis.ts (1)
main
(11-17)
src/twitterApi.ts (1)
src/utils/redisUtils.ts (2)
trackApiKeyUsage
(7-17)getApiKeyUsage
(24-36)
src/index.ts (1)
src/utils/redisUtils.ts (1)
getApiKeyUsage
(24-36)
…nd optimize Redis operations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (4)
src/utils/redisUtils.ts (4)
1-1
: Fix crypto import for cross-env compatibility (ESM/CJS-safe).Prefer named import from node:crypto to avoid default-import pitfalls with Node core modules and bundlers.
-import crypto from 'crypto'; +import { createHash } from 'node:crypto'; @@ - const hash = crypto.createHash('sha256').update(apiKey).digest('hex').slice(0, 32); + const hash = createHash('sha256').update(apiKey).digest('hex').slice(0, 32); @@ - const hash = crypto.createHash('sha256').update(apiKey).digest('hex').slice(0, 32); + const hash = createHash('sha256').update(apiKey).digest('hex').slice(0, 32);Also applies to: 15-15, 38-38
8-13
: Short-circuit before opening a Redis connection.Move the empty-key guard outside runRedisOperation to avoid needless connects when apiKey is blank/whitespace.
export async function trackApiKeyUsage(apiKey: string, accountHandle?: string): Promise<void> { - await runRedisOperation(async (client) => { - if (!apiKey?.trim()) { - console.warn('trackApiKeyUsage: empty apiKey; skipping'); - return; - } + if (!apiKey?.trim()) { + console.warn('trackApiKeyUsage: empty apiKey; skipping'); + return; + } + await runRedisOperation(async (client) => {
76-78
: Parameterize Redis URL via env for deploys.Hardcoding localhost limits flexibility in non-dev environments.
- const client: RedisClientType = createClient({ - url: 'redis://localhost:6379' - }); + const client: RedisClientType = createClient({ + url: process.env.REDIS_URL ?? 'redis://localhost:6379' + });
75-97
: Consider reusing a singleton Redis client.Connecting/tearing down per operation is costly under load (usage tracking runs on every API call). A lightweight singleton with health checks will reduce latency and connection churn.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/utils/redisUtils.ts
(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/utils/redisUtils.ts (2)
src/index.ts (1)
main
(57-63)src/testRedis.ts (1)
main
(11-17)
🔇 Additional comments (2)
src/utils/redisUtils.ts (2)
18-25
: Atomic HINCRBY + HSET via MULTI looks good.Good job ensuring the increment and metadata update are atomic.
70-72
: No change needed — keep require.main guard (CommonJS).
tsconfig.json sets "module": "commonjs", so require.main is defined; leave the guard in src/utils/redisUtils.ts (lines 70–72).
…handling and improve Redis key management
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
♻️ Duplicate comments (4)
src/utils/redisUtils.ts (2)
18-46
: Do not key Redis by apiKey/token; key by accountId with a stable prefix (security + correctness).Current logic compares apiKey against account-id envs and builds keys like
api_usage:${apiKey}
. This leaks secrets in key names and doesn’t satisfy the requiredtwitter_accounts:{accountId}
/telegram_accounts:{accountId}
scheme. MakeaccountId
required and derive the key from platform + accountId.-export async function trackApiKeyUsage(apiKey: string, accountId?: string): Promise<void> { - if (!apiKey?.trim()) { +type Platform = 'twitter' | 'telegram'; + +export async function trackApiKeyUsage( + apiKey: string, + accountId: string, + platform: Platform +): Promise<void> { + if (!apiKey?.trim()) { console.warn('trackApiKeyUsage: empty apiKey; skipping'); return; } try { await ensureRedisConnected(); - let key: string; - if (process.env.TWITTER_ACCOUNT_ID && apiKey === process.env.TWITTER_ACCOUNT_ID) { - key = `twitter_accounts:${apiKey}`; - } else if (process.env.TELEGRAM_ACCOUNT_ID && apiKey === process.env.TELEGRAM_ACCOUNT_ID) { - key = `telegram_accounts:${apiKey}`; - } else { - key = `api_usage:${apiKey}`; - } + if (!accountId?.trim()) { + console.warn('trackApiKeyUsage: empty accountId; skipping'); + return; + } + const key = platform === 'twitter' + ? `twitter_accounts:${accountId}` + : `telegram_accounts:${accountId}`; const now = new Date().toISOString(); await redisClient .multi() .hIncrBy(key, 'total_requests', 1) .hSet(key, { last_request: now, - ...(accountId ? { account_id: accountId } : {}), + account_id: accountId, }) .exec();
53-78
: Usage reads must mirror the same accountId-based keying.Reading by
api_usage:${apiKey}
won’t find data after the write fix. Acceptplatform
andaccountId
, and build keys identically to writes.-export async function getApiKeyUsage(apiKey: string): Promise<{ total_requests: number; last_request: string | null; account_id?: string }> { - let result: { total_requests: number; last_request: string | null; account_id?: string } = { total_requests: 0, last_request: null }; - if (!apiKey?.trim()) { +export async function getApiKeyUsage( + platform: 'twitter' | 'telegram', + accountId: string +): Promise<{ total_requests: number; last_request: string | null; account_id?: string }> { + const result: { total_requests: number; last_request: string | null; account_id?: string } = { total_requests: 0, last_request: null }; + if (!accountId?.trim()) { return result; } try { await ensureRedisConnected(); - let key: string; - if (process.env.TWITTER_ACCOUNT_ID && apiKey === process.env.TWITTER_ACCOUNT_ID) { - key = `twitter_accounts:${apiKey}`; - } else if (process.env.TELEGRAM_ACCOUNT_ID && apiKey === process.env.TELEGRAM_ACCOUNT_ID) { - key = `telegram_accounts:${apiKey}`; - } else { - key = `api_usage:${apiKey}`; - } + const key = platform === 'twitter' + ? `twitter_accounts:${accountId}` + : `telegram_accounts:${accountId}`; const data = await redisClient.hGetAll(key); result.total_requests = data.total_requests ? parseInt(data.total_requests) : 0; result.last_request = data.last_request ? data.last_request : null; if (data.account_id) { result.account_id = data.account_id; } } catch (err) { console.error('Redis operation failed:', err); } return result; }src/twitterApi.ts (2)
156-163
: Track usage with numeric accountId and platform, not handle.Use Viewer API to resolve
userId
(orTWITTER_ACCOUNT_ID
fallback) and pass platform.- if (AUTH_TOKEN) { - // Use TWITTER_ID from environment variable for account handle (no '@' prefix) - const accountHandle = process.env.TWITTER_ID ? process.env.TWITTER_ID : "unknown"; - console.log("Authenticated account:", accountHandle); - - await trackApiKeyUsage(AUTH_TOKEN, accountHandle); - } + if (AUTH_TOKEN) { + const viewer = await fetchViewerAccount(); + const accountId = viewer?.userId ?? process.env.TWITTER_ACCOUNT_ID; + if (accountId) { + console.log("Authenticated account id:", accountId); + await trackApiKeyUsage(AUTH_TOKEN, accountId, 'twitter'); + } else { + console.warn("Cannot determine Twitter account id; skipping usage tracking."); + } + }
1-6
: Load env before reading AUTH_TOKEN.
AUTH_TOKEN
is read beforedotenv.config()
, so it may be undefined.-import dotenv from 'dotenv'; +import dotenv from 'dotenv'; +dotenv.config(); import cron from 'node-cron'; import { getApiKeyUsage } from './utils/redisUtils'; import { trackApiKeyUsage } from './utils/redisUtils'; -const AUTH_TOKEN = process.env.AUTH_TOKEN; -dotenv.config(); +const AUTH_TOKEN = process.env.AUTH_TOKEN;
🧹 Nitpick comments (4)
src/utils/redisUtils.ts (2)
1-1
: Remove unused import.
crypto
isn’t used.-import crypto from 'crypto'; +// import crypto from 'crypto'; // unused
8-16
: Reset connection flag on disconnect.If Redis disconnects,
redisConnected
remains true; subsequent calls won’t reconnect.redisClient.on('error', (err) => { console.error('Redis Client Error', err); }); +redisClient.on('end', () => { + redisConnected = false; +});src/fetchTelegramMessages.ts (2)
47-50
: Avoid noisy logs in hot path.Guard per-message logs behind an env flag.
- console.log(formatted); + if (process.env.DEBUG_TG === '1') console.log(formatted);
10-12
: Clarify error message.This function accepts
channel
; refer to the parameter, not the env.- throw new Error("TG_CHANNEL environment variable is not set."); + throw new Error("Channel is required.");
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/fetchTelegramMessages.ts
(1 hunks)src/index.ts
(3 hunks)src/twitterApi.ts
(4 hunks)src/utils/redisUtils.ts
(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
src/index.ts (1)
src/utils/redisUtils.ts (1)
getApiKeyUsage
(53-79)
src/fetchTelegramMessages.ts (2)
src/utils/redisUtils.ts (1)
trackApiKeyUsage
(18-46)src/telegram.ts (2)
console
(11-52)msg
(44-46)
src/twitterApi.ts (1)
src/utils/redisUtils.ts (2)
trackApiKeyUsage
(18-46)getApiKeyUsage
(53-79)
src/utils/redisUtils.ts (1)
src/testRedis.ts (2)
main
(11-17)err
(7-9)
src/utils/redisUtils.ts
Outdated
// Example: Use environment variables for API keys | ||
async function main() { | ||
const telegramAccountId = process.env.TELEGRAM_ACCOUNT_ID; | ||
if (telegramAccountId?.trim()) { | ||
const telegramUsage = await getApiKeyUsage(telegramAccountId); | ||
console.log('Telegram API usage:', { | ||
total_requests: telegramUsage.total_requests, | ||
last_request: telegramUsage.last_request || 'No last Telegram request recorded.', | ||
account_id: telegramUsage.account_id || 'No account id recorded.' | ||
}); | ||
} else { | ||
console.log('Telegram API usage: TELEGRAM_ACCOUNT_ID not set.'); | ||
} | ||
|
||
const twitterAccountId = process.env.TWITTER_ACCOUNT_ID; | ||
if (twitterAccountId?.trim()) { | ||
const twitterUsage = await getApiKeyUsage(twitterAccountId); | ||
console.log('Twitter API usage:', { | ||
total_requests: twitterUsage.total_requests, | ||
last_request: twitterUsage.last_request || 'No last Twitter request recorded.', | ||
account_id: twitterUsage.account_id || 'No account id recorded.' | ||
}); | ||
} else { | ||
console.log('Twitter API usage: TWITTER_ACCOUNT_ID not set.'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Update the example to the new API (platform + accountId).
Ensure the demo reads usage by account id, not token/api id.
- if (telegramAccountId?.trim()) {
- const telegramUsage = await getApiKeyUsage(telegramAccountId);
+ if (telegramAccountId?.trim()) {
+ const telegramUsage = await getApiKeyUsage('telegram', telegramAccountId);
console.log('Telegram API usage:', {
total_requests: telegramUsage.total_requests,
last_request: telegramUsage.last_request || 'No last Telegram request recorded.',
account_id: telegramUsage.account_id || 'No account id recorded.'
});
} else {
console.log('Telegram API usage: TELEGRAM_ACCOUNT_ID not set.');
}
const twitterAccountId = process.env.TWITTER_ACCOUNT_ID;
if (twitterAccountId?.trim()) {
- const twitterUsage = await getApiKeyUsage(twitterAccountId);
+ const twitterUsage = await getApiKeyUsage('twitter', twitterAccountId);
console.log('Twitter API usage:', {
total_requests: twitterUsage.total_requests,
last_request: twitterUsage.last_request || 'No last Twitter request recorded.',
account_id: twitterUsage.account_id || 'No account id recorded.'
});
To catch any remaining old call sites, run:
🏁 Script executed:
#!/bin/bash
# Find and review all usages to update signatures.
rg -nP '\b(trackApiKeyUsage|getApiKeyUsage)\s*\(' -C2
Length of output: 2785
Change getApiKeyUsage/trackApiKeyUsage to (platform, accountId) and update all callers
Multiple call sites still pass tokens/API IDs. Update the functions and callers to accept a platform string plus the accountId; then re-run a search to confirm no remaining old calls.
-
Change signatures in src/utils/redisUtils.ts:
- trackApiKeyUsage(apiKey: string, accountId?: string) -> trackApiKeyUsage(platform: string, accountId?: string)
- getApiKeyUsage(apiKey: string) -> getApiKeyUsage(platform: string, accountId: string)
-
Update the example in src/utils/redisUtils.ts (main):
- if (telegramAccountId?.trim()) {
- const telegramUsage = await getApiKeyUsage(telegramAccountId);
+ if (telegramAccountId?.trim()) {
+ const telegramUsage = await getApiKeyUsage('telegram', telegramAccountId);
console.log('Telegram API usage:', {
total_requests: telegramUsage.total_requests,
last_request: telegramUsage.last_request || 'No last Telegram request recorded.',
account_id: telegramUsage.account_id || 'No account id recorded.'
});
} else {
console.log('Telegram API usage: TELEGRAM_ACCOUNT_ID not set.');
}
const twitterAccountId = process.env.TWITTER_ACCOUNT_ID;
if (twitterAccountId?.trim()) {
- const twitterUsage = await getApiKeyUsage(twitterAccountId);
+ const twitterUsage = await getApiKeyUsage('twitter', twitterAccountId);
console.log('Twitter API usage:', {
total_requests: twitterUsage.total_requests,
last_request: twitterUsage.last_request || 'No last Twitter request recorded.',
account_id: twitterUsage.account_id || 'No account id recorded.'
});
-
Update callers found by your search:
- src/fetchTelegramMessages.ts:68 — await trackApiKeyUsage(apiId, accountId) -> await trackApiKeyUsage('telegram', accountId)
- src/twitterApi.ts:162 — await trackApiKeyUsage(AUTH_TOKEN, accountHandle) -> await trackApiKeyUsage('twitter', accountHandle)
- src/twitterApi.ts:174 — const usage = await getApiKeyUsage(process.env.AUTH_TOKEN as string) -> const usage = await getApiKeyUsage('twitter', accountHandle)
- src/index.ts:46 — const usage = await getApiKeyUsage(process.env.API_ID as string) -> const usage = await getApiKeyUsage('telegram', process.env.TELEGRAM_ACCOUNT_ID as string)
-
After edits run: rg -nP '\b(trackApiKeyUsage|getApiKeyUsage)\s*(' -C2 to verify no remaining old-signature calls.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/utils/redisUtils.ts around lines 83 to 107, update the function
signatures and all callers to use (platform, accountId) instead of the old
apiKey-only parameters: change trackApiKeyUsage(apiKey: string, accountId?:
string) to trackApiKeyUsage(platform: string, accountId?: string) and change
getApiKeyUsage(apiKey: string) to getApiKeyUsage(platform: string, accountId:
string); update the sample main() in this file to pass platform strings
('telegram'/'twitter') and accountId accordingly; update caller sites as listed
in the review (src/fetchTelegramMessages.ts:68 → await
trackApiKeyUsage('telegram', accountId), src/twitterApi.ts:162 → await
trackApiKeyUsage('twitter', accountHandle), src/twitterApi.ts:174 → const usage
= await getApiKeyUsage('twitter', accountHandle), src/index.ts:46 → const usage
= await getApiKeyUsage('telegram', process.env.TELEGRAM_ACCOUNT_ID as string));
then run rg -nP '\b(trackApiKeyUsage|getApiKeyUsage)\s*\(' -C2 to confirm there
are no remaining calls using the old signatures.
…yUsage functions to accept accountId and platform parameters for improved clarity and consistency
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
♻️ Duplicate comments (1)
src/twitterApi.ts (1)
156-161
: Track usage with the same accountId you later read with (viewer.userId → fallback TWITTER_ACCOUNT_ID).Current code tracks using TWITTER_ID but reads using viewer/TWITTER_ACCOUNT_ID, producing mismatched keys and empty stats.
-// Track API usage after successful fetch -if (process.env.TWITTER_ID) { - const accountId = process.env.TWITTER_ID; - console.log("Authenticated account:", accountId); - await trackApiKeyUsage({ accountId, platform: 'twitter' }); -} +// Track API usage after successful fetch +const viewer = await fetchViewerAccount(); +const accountId = viewer?.userId ?? process.env.TWITTER_ACCOUNT_ID; +if (!accountId) { + console.warn('trackApiKeyUsage skipped: missing TWITTER_ACCOUNT_ID and Viewer lookup failed.'); +} else { + await trackApiKeyUsage({ accountId, platform: 'twitter' }); +}
🧹 Nitpick comments (8)
src/utils/redisUtils.ts (3)
54-61
: Shared key builder to avoid drift between read/write paths.Both functions duplicate platform→key mapping. Extract a helper to keep them in lockstep.
Add above the exports (outside selected lines):
function usageKey(platform: 'telegram'|'twitter', accountId: string): string { return `${platform === 'twitter' ? 'twitter_accounts' : 'telegram_accounts'}:${accountId}`; }Then replace the if/else blocks in both functions with:
const key = usageKey(platform, accountId);
73-75
: Parse numbers with explicit radix.Be explicit to avoid edge cases.
-result.total_requests = data.total_requests ? parseInt(data.total_requests) : 0; +result.total_requests = data.total_requests ? parseInt(data.total_requests, 10) : 0;
1-1
: Remove unused import.crypto isn’t used.
-import crypto from 'crypto';
src/twitterApi.ts (3)
5-5
: Remove unused AUTH_TOKEN constant (and avoid reading env before dotenv.config).This variable is never used and is read before dotenv.config().
-const AUTH_TOKEN = process.env.AUTH_TOKEN;
191-196
: Reduce noisy logs in scheduled job (print counts, not full payload).Large payloads in logs are hard to scan and can leak data.
- const timeline = await fetchHomeTimeline(); - console.log('Fetched timeline:', timeline); + const timeline = await fetchHomeTimeline(); + console.log('Fetched timeline:', { count: timeline.length, firstId: timeline[0]?.id });
106-116
: Add request timeouts to external calls.Prevent hangs on slow endpoints with AbortController.
Example for both fetches:
+ const ac = new AbortController(); + const t = setTimeout(() => ac.abort(), 20000); const response = await fetch(url, { method: "POST", headers, body: JSON.stringify(body), + signal: ac.signal, }); + clearTimeout(t);Repeat similarly in fetchViewerAccount().
Also applies to: 17-21
src/fetchTelegramMessages.ts (2)
10-12
: Fix error message to reflect function input, not env.This function takes a channel param; mentioning TG_CHANNEL is misleading.
- if (!channel) { - throw new Error("TG_CHANNEL environment variable is not set."); - } + if (!channel) { + throw new Error("channel is required."); + }
49-50
: Reduce per-message console logs or gate behind DEBUG.Printing every message is noisy and may leak content.
- console.log(formatted); + if (process.env.DEBUG === '1') console.log(formatted);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/fetchTelegramMessages.ts
(1 hunks)src/index.ts
(3 hunks)src/twitterApi.ts
(4 hunks)src/utils/redisUtils.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/index.ts
🧰 Additional context used
🧬 Code graph analysis (3)
src/twitterApi.ts (1)
src/utils/redisUtils.ts (2)
trackApiKeyUsage
(18-46)getApiKeyUsage
(54-82)
src/utils/redisUtils.ts (1)
src/testRedis.ts (1)
main
(11-17)
src/fetchTelegramMessages.ts (2)
src/utils/redisUtils.ts (1)
trackApiKeyUsage
(18-46)src/telegram.ts (2)
console
(11-52)msg
(44-46)
const apiId = process.env.API_ID; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Telemetry must be best-effort: don’t gate on API_ID and don’t throw if getMe() fails.
Tracking should never break message fetching. Also, gating on API_ID is unrelated.
- const apiId = process.env.API_ID;
@@
- // Track API usage after successful fetch
- if (apiId) {
- let accountId: string;
- try {
- const me = await client.getMe();
- if (me && me.id) {
- accountId = String(me.id);
- } else {
- throw new Error('Unable to determine Telegram account ID');
- }
- } catch (e) {
- throw new Error('Unable to determine Telegram account ID');
- }
- await trackApiKeyUsage({ accountId, platform: 'telegram' });
- }
+ // Track API usage after successful fetch (best-effort)
+ try {
+ const me = await client.getMe();
+ const accountId = me?.id ? String(me.id) : '';
+ if (accountId) {
+ await trackApiKeyUsage({ accountId, platform: 'telegram' });
+ } else {
+ console.warn('trackApiKeyUsage skipped: unable to determine Telegram account ID');
+ }
+ } catch (e) {
+ console.warn('trackApiKeyUsage skipped: unable to determine Telegram account ID', e);
+ }
Also applies to: 55-69
🤖 Prompt for AI Agents
In src/fetchTelegramMessages.ts around lines 13-14 (and similarly 55-69), the
code currently gates behavior on process.env.API_ID and allows getMe() failures
to throw, which can break message fetching; change it so telemetry is
best-effort: do not early-return or throw if API_ID is missing, and do not let
getMe() errors propagate. Specifically, stop gating core message fetching on
API_ID (treat API_ID as optional), wrap any telemetry/getMe() calls in a
try/catch that logs the error (or swallows it) without throwing, and proceed
with fetching messages regardless of telemetry success; ensure any telemetry
initialization is conditional and non-blocking so missing API_ID or getMe()
failures do not affect the main flow.
const viewer = await fetchViewerAccount(); | ||
const accountId = viewer?.userId ?? process.env.TWITTER_ACCOUNT_ID; | ||
if (!accountId) throw new Error('Missing TWITTER_ACCOUNT_ID and Viewer lookup failed.'); | ||
const usage = await getApiKeyUsage(accountId, 'twitter'); | ||
console.log('Twitter API usage:', { | ||
total_requests: usage.total_requests, | ||
last_request: usage.last_request, | ||
account_id: usage.account_id | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
LGTM on usage read flow; ensure env var naming is consistent repo-wide.
This aligns with the tracking change above. Make sure TWITTER_ID is not used elsewhere.
Run:
🏁 Script executed:
#!/bin/bash
# Find lingering TWITTER_ID usages and old telemetry calls.
rg -nP '\bTWITTER_ID\b|trackApiKeyUsage\s*\(|getApiKeyUsage\s*\(' -C2
Length of output: 2371
Unify Twitter env var: replace TWITTER_ID with TWITTER_ACCOUNT_ID
LGTM on the usage-read flow; src/twitterApi.ts still uses process.env.TWITTER_ID (lines 157–161) while later code reads process.env.TWITTER_ACCOUNT_ID (lines 173–176). Replace the TWITTER_ID branch and ensure trackApiKeyUsage/getApiKeyUsage use the same accountId source/format.
🤖 Prompt for AI Agents
In src/twitterApi.ts around lines 157–176, the code inconsistently reads
process.env.TWITTER_ID in one place and process.env.TWITTER_ACCOUNT_ID later;
replace the TWITTER_ID branch with TWITTER_ACCOUNT_ID, derive accountId once
(e.g., const accountId = viewer?.userId ?? process.env.TWITTER_ACCOUNT_ID),
ensure accountId is a string/normalized format, and update calls to
trackApiKeyUsage/getApiKeyUsage to accept and use that same accountId
variable/format so both tracking and usage-read use the identical source.
const redisClient = createClient({ url: 'redis://localhost:6379' }); | ||
let redisConnected = false; | ||
|
||
client.on('error', (err) => { | ||
console.error('Redis Client Error', err); | ||
}); | ||
redisClient.on('error', (err) => { | ||
console.error('Redis Client Error', err); | ||
}); | ||
|
||
async function ensureRedisConnected() { | ||
if (!redisConnected) { | ||
await redisClient.connect(); | ||
redisConnected = true; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Make Redis URL configurable and fix connection check (use client.isOpen instead of a manual flag).
Hardcoding localhost breaks deployments and the boolean flag won’t recover after disconnects. Prefer REDIS_URL and client.isOpen.
Apply:
-import { createClient } from 'redis';
+import { createClient } from 'redis';
-// Singleton Redis client
-const redisClient = createClient({ url: 'redis://localhost:6379' });
-let redisConnected = false;
+// Singleton Redis client
+const redisClient = createClient({ url: process.env.REDIS_URL ?? 'redis://localhost:6379' });
async function ensureRedisConnected() {
- if (!redisConnected) {
- await redisClient.connect();
- redisConnected = true;
- }
+ if (!redisClient.isOpen) {
+ await redisClient.connect();
+ }
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const redisClient = createClient({ url: 'redis://localhost:6379' }); | |
let redisConnected = false; | |
client.on('error', (err) => { | |
console.error('Redis Client Error', err); | |
}); | |
redisClient.on('error', (err) => { | |
console.error('Redis Client Error', err); | |
}); | |
async function ensureRedisConnected() { | |
if (!redisConnected) { | |
await redisClient.connect(); | |
redisConnected = true; | |
} | |
} | |
const redisClient = createClient({ url: process.env.REDIS_URL ?? 'redis://localhost:6379' }); | |
redisClient.on('error', (err) => { | |
console.error('Redis Client Error', err); | |
}); | |
async function ensureRedisConnected() { | |
if (!redisClient.isOpen) { | |
await redisClient.connect(); | |
} | |
} |
🤖 Prompt for AI Agents
In src/utils/redisUtils.ts around lines 5-17, the Redis URL is hardcoded and the
connection state uses a manual boolean that won't reflect real disconnects;
change createClient to read the URL from process.env.REDIS_URL (with an optional
fallback if you want), remove the redisConnected flag, and replace checks with
redisClient.isOpen (i.e., only call await redisClient.connect() when
!redisClient.isOpen). Keep the existing error handler and optionally add
listeners for 'end'/'reconnecting' for visibility, but do not rely on a manual
boolean to determine connection state.
…eyUsage functions to accept an object with accountId and platform parameters for improved clarity and error handling
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/twitterApi.ts (1)
1-200
: Unify Twitter env var usage — replace TWITTER_ID with TWITTER_ACCOUNT_ID (or add a backward-compatible fallback)
Found legacy TWITTER_ID in src/twitterApi.ts (lines 157–160) while the main flow uses TWITTER_ACCOUNT_ID (lines 173–174). Replace the legacy usage or use: const accountId = process.env.TWITTER_ACCOUNT_ID ?? process.env.TWITTER_ID. No TELEGRAM_ID occurrences found. Telemetry helpers are defined in src/utils/redisUtils.ts and are used in src/twitterApi.ts, src/fetchTelegramMessages.ts, and src/index.ts.
♻️ Duplicate comments (3)
src/twitterApi.ts (2)
156-160
: Remove stray debug log.Avoid logging user/account IDs in production.
- console.log("Authenticated account:", accountId);
156-161
: Unify on TWITTER_ACCOUNT_ID (avoid TWITTER_ID drift).Tracking uses
TWITTER_ID
while reading usage later usesTWITTER_ACCOUNT_ID
, creating split keys.- if (process.env.TWITTER_ID) { - const accountId = process.env.TWITTER_ID; - console.log("Authenticated account:", accountId); - await trackApiKeyUsage({ accountId, platform: 'twitter' }); - } + if (process.env.TWITTER_ACCOUNT_ID) { + const accountId = process.env.TWITTER_ACCOUNT_ID; + await trackApiKeyUsage({ accountId, platform: 'twitter' }); + }If you want to support legacy
TWITTER_ID
, read it only as a fallback and emit a deprecation warning.src/utils/redisUtils.ts (1)
5-16
: Make Redis connection robust: REDIS_URL + isOpen (drop manual flag).Hardcoded URL and a boolean flag are brittle across reconnects and deployments.
-// Singleton Redis client -const redisClient = createClient({ url: 'redis://localhost:6379' }); -let redisConnected = false; +// Singleton Redis client +const redisClient = createClient({ url: process.env.REDIS_URL ?? 'redis://localhost:6379' }); redisClient.on('error', (err) => { console.error('Redis Client Error', err); }); async function ensureRedisConnected() { - if (!redisConnected) { - await redisClient.connect(); - redisConnected = true; - } + if (!redisClient.isOpen) { + await redisClient.connect(); + } }
🧹 Nitpick comments (7)
src/utils/redisUtils.ts (5)
1-1
: Remove unused import.
crypto
isn’t used anymore.-import crypto from 'crypto';
18-22
: Input contract: decide between “fail-fast” vs “best-effort” for empty accountId.Maintainer comments suggest throwing when inputs are missing; current code logs and returns.
- if (!accountId?.trim()) { - console.warn('trackApiKeyUsage: empty accountId; skipping'); - return; - } + if (!accountId?.trim()) { + throw new Error('trackApiKeyUsage: accountId is required'); + }If you want to keep ingestion non-blocking, throw at call sites during config/boot and keep this function best-effort.
35-42
: Optional: consider key TTL to bound storage.If usage isn’t meant to live forever, set an expiry (e.g., 90d) in the same MULTI.
await redisClient .multi() .hIncrBy(key, 'total_requests', 1) .hSet(key, { last_request: now, account_id: accountId, }) + .expire(key, 60 * 60 * 24 * 90) // 90 days .exec();
55-58
: Name the interface clearly.
dataType
is vague; preferAccountUsageQuery
.-interface dataType { +interface AccountUsageQuery { accountId: string, platform: 'telegram' | 'twitter' }And update the function signature accordingly.
60-88
: Parsing nit: specify radix for parseInt.Avoids accidental octal parsing.
- result.total_requests = data.total_requests ? parseInt(data.total_requests) : 0; + result.total_requests = data.total_requests ? parseInt(data.total_requests, 10) : 0;src/twitterApi.ts (2)
191-199
: Tone down scheduled logging.Dumping full timelines every 5 minutes will spam logs and may leak content.
- const timeline = await fetchHomeTimeline(); - console.log('Fetched timeline:', timeline); + const timeline = await fetchHomeTimeline(); + console.log('Fetched timeline batch size:', timeline.length); + // Optionally log first N ids for tracing: + // console.debug('Tweet IDs:', timeline.slice(0, 5).map(t => t.id));
39-55
: Minor: cookie formatting.Consider adding a space after
;
for consistency (auth_token=...; ct0=...
). No functional change.- const cookie = `auth_token=${process.env.AUTH_TOKEN};ct0=${process.env.CSRF_TOKEN}`; + const cookie = `auth_token=${process.env.AUTH_TOKEN}; ct0=${process.env.CSRF_TOKEN}`;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/index.ts
(3 hunks)src/twitterApi.ts
(4 hunks)src/utils/redisUtils.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/index.ts
🧰 Additional context used
🧬 Code graph analysis (2)
src/utils/redisUtils.ts (1)
src/testRedis.ts (2)
main
(11-17)err
(7-9)
src/twitterApi.ts (1)
src/utils/redisUtils.ts (2)
trackApiKeyUsage
(18-46)getApiKeyUsage
(60-88)
🔇 Additional comments (2)
src/utils/redisUtils.ts (1)
24-33
: Good: strict platform validation.Failing on unknown platforms prevents silent writes to wrong keys.
src/twitterApi.ts (1)
172-180
: LGTM on reading usage by accountId + platform.Matches the Redis keying scheme and avoids tokens.
import dotenv from 'dotenv'; | ||
import cron from 'node-cron'; | ||
import { getApiKeyUsage } from './utils/redisUtils'; | ||
import { trackApiKeyUsage } from './utils/redisUtils'; | ||
const AUTH_TOKEN = process.env.AUTH_TOKEN; | ||
dotenv.config(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Load .env before reading env vars; remove unused constant.
AUTH_TOKEN
is read before dotenv.config()
and never used.
-import dotenv from 'dotenv';
+import dotenv from 'dotenv';
+dotenv.config();
import cron from 'node-cron';
import { getApiKeyUsage } from './utils/redisUtils';
import { trackApiKeyUsage } from './utils/redisUtils';
-const AUTH_TOKEN = process.env.AUTH_TOKEN;
-dotenv.config();
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
import dotenv from 'dotenv'; | |
import cron from 'node-cron'; | |
import { getApiKeyUsage } from './utils/redisUtils'; | |
import { trackApiKeyUsage } from './utils/redisUtils'; | |
const AUTH_TOKEN = process.env.AUTH_TOKEN; | |
dotenv.config(); | |
import dotenv from 'dotenv'; | |
dotenv.config(); | |
import cron from 'node-cron'; | |
import { getApiKeyUsage } from './utils/redisUtils'; | |
import { trackApiKeyUsage } from './utils/redisUtils'; |
🤖 Prompt for AI Agents
In src/twitterApi.ts around lines 1 to 6, dotenv.config() is called after
reading AUTH_TOKEN and AUTH_TOKEN is never used; move dotenv.config() to the top
of the file before any process.env access, remove the unused AUTH_TOKEN
constant, and (optionally) consolidate the two redisUtils imports into a single
import statement to keep imports tidy.
Description:
This pull request implements API usage tracking for both Telegram and Twitter integrations.
TWITTER_ID
environment variable.@
prefix) if available, otherwise falls back to the Telegram user ID.Summary by CodeRabbit
New Features
Bug Fixes
Refactor