-
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
Changes from all commits
3352dab
d82070a
686d5bf
897070f
ea34ff9
364040e
9e3cd4e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,48 +1,72 @@ | ||
import { TelegramClient } from "telegram"; | ||
import { Api } from "telegram"; | ||
import { Api, TelegramClient } from "telegram"; | ||
import { trackApiKeyUsage } from './utils/redisUtils'; | ||
|
||
export type TelegramMessage = { id: string; content: string; channelId: string }; | ||
export type TelegramMessages = { id: string; content: string; channelId: string }; | ||
|
||
export async function fetchTelegramMessages( | ||
client: TelegramClient, | ||
channel: string | ||
): Promise<TelegramMessage[]> { | ||
if (!channel) { | ||
throw new Error("TG_CHANNEL environment variable is not set."); | ||
client: TelegramClient, | ||
channel: string | ||
): Promise<TelegramMessages[]> { | ||
if (!channel) { | ||
throw new Error("TG_CHANNEL environment variable is not set."); | ||
} | ||
const apiId = process.env.API_ID; | ||
|
||
// Fetch channel entity to get the actual channel ID | ||
let entity: Api.Channel; | ||
try { | ||
const resolved = await client.getEntity(channel); | ||
if (resolved instanceof Api.Channel) { | ||
entity = resolved; | ||
} else if (resolved instanceof Api.ChannelForbidden) { | ||
throw new Error(`TG_CHANNEL "${channel}" is a private/forbidden channel; cannot fetch history.`); | ||
} else { | ||
throw new Error(`TG_CHANNEL "${channel}" is not a channel-type peer.`); | ||
} | ||
// Fetch channel entity to get the actual channel ID | ||
let entity: Api.Channel; | ||
} catch (e) { | ||
throw new Error(`Failed to resolve TG_CHANNEL "${channel}": ${e instanceof Error ? e.message : e}`); | ||
} | ||
|
||
const channelId = String(entity.id); | ||
|
||
// Fetch the latest 10 messages | ||
const messages = await client.invoke( | ||
new Api.messages.GetHistory({ | ||
peer: entity, | ||
limit: 10, | ||
}) | ||
); | ||
|
||
const out: TelegramMessages[] = []; | ||
|
||
if ("messages" in messages) { | ||
for (const msg of messages.messages as any[]) { | ||
const id = typeof msg?.id === 'number' || typeof msg?.id === 'string' ? String(msg.id) : null; | ||
const content = typeof msg?.message === 'string' ? msg.message : ''; | ||
if (!id || !content) continue; // skip service/media-only messages | ||
const formatted = { id, content, channelId }; | ||
out.push(formatted); | ||
console.log(formatted); | ||
} | ||
} else { | ||
console.log("No messages property found in response:", messages); | ||
} | ||
|
||
// Track API usage after successful fetch | ||
if (apiId) { | ||
let accountId: string; | ||
try { | ||
const resolved = await client.getEntity(channel); | ||
if (resolved instanceof Api.Channel) { | ||
entity = resolved; | ||
} else if (resolved instanceof Api.ChannelForbidden) { | ||
throw new Error(`TG_CHANNEL "${channel}" is a private/forbidden channel; cannot fetch history.`); | ||
} else { | ||
throw new Error(`TG_CHANNEL "${channel}" is not a channel-type peer.`); | ||
} | ||
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(`Failed to resolve TG_CHANNEL \"${channel}\": ${e instanceof Error ? e.message : e}`); | ||
throw new Error('Unable to determine Telegram account ID'); | ||
} | ||
const channelId = String(entity.id); | ||
const messages = await client.invoke( | ||
new Api.messages.GetHistory({ | ||
peer: entity, | ||
limit: 10, | ||
}) | ||
); | ||
const out: TelegramMessage[] = []; | ||
if ("messages" in messages) { | ||
for (const msg of messages.messages as any[]) { | ||
const id = typeof msg?.id === 'number' || typeof msg?.id === 'string' ? String(msg.id) : null; | ||
const content = typeof msg?.message === 'string' ? msg.message : ''; | ||
if (!id || !content) continue; // skip service/media-only | ||
const formatted = { id, content, channelId }; | ||
out.push(formatted); | ||
console.log(formatted); | ||
} | ||
} else { | ||
console.log("No messages property found in response:", messages); | ||
} | ||
return out; | ||
await trackApiKeyUsage({ accountId, platform: 'telegram' }); | ||
} | ||
|
||
return out; | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,8 +1,38 @@ | ||||||||||||||||||||||||
import cron from 'node-cron'; | ||||||||||||||||||||||||
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(); | ||||||||||||||||||||||||
Comment on lines
1
to
6
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Load .env before reading env vars; remove unused constant.
-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
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||
|
||||||||||||||||||||||||
export async function fetchHomeTimeline(seenTweetIds: string[] = []): Promise<Array<{ id: string; content: string; authorId: string }>> { | ||||||||||||||||||||||||
async function fetchViewerAccount(): Promise<{ screenName: string; userId: string } | null> { | ||||||||||||||||||||||||
const url = "https://x.com/i/api/graphql/jMaTSZ5dqXctUg5f97R6xw/Viewer"; | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
const headers = { | ||||||||||||||||||||||||
"authorization": `Bearer ${process.env.BEARER}`, | ||||||||||||||||||||||||
"x-csrf-token": process.env.CSRF_TOKEN as string, | ||||||||||||||||||||||||
"cookie": `auth_token=${process.env.AUTH_TOKEN}; ct0=${process.env.CSRF_TOKEN}`, | ||||||||||||||||||||||||
}; | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
const res = await fetch(url, { method: "GET", headers }); | ||||||||||||||||||||||||
if (!res.ok) { | ||||||||||||||||||||||||
console.error("Viewer API request failed:", res.status, res.statusText); | ||||||||||||||||||||||||
return null; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
const data = await res.json(); | ||||||||||||||||||||||||
const user = data?.data?.viewer?.user_results?.result; | ||||||||||||||||||||||||
if (!user) return null; | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
return { | ||||||||||||||||||||||||
screenName: user.legacy?.screen_name, | ||||||||||||||||||||||||
userId: user.rest_id, | ||||||||||||||||||||||||
}; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
export async function fetchHomeTimeline( | ||||||||||||||||||||||||
seenTweetIds: string[] = [] | ||||||||||||||||||||||||
): Promise<Array<{ id: string; content: string; authorId: string }>> { | ||||||||||||||||||||||||
const queryId = "wEpbv0WrfwV6y2Wlf0fxBQ"; | ||||||||||||||||||||||||
const url = `https://x.com/i/api/graphql/${queryId}/HomeTimeline`; | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
|
@@ -93,13 +123,12 @@ export async function fetchHomeTimeline(seenTweetIds: string[] = []): Promise<Ar | |||||||||||||||||||||||
throw new Error(`Twitter API errors: ${JSON.stringify(data.errors)}`); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
// Format and return tweets as [{ content, id }] | ||||||||||||||||||||||||
// Format and return tweets | ||||||||||||||||||||||||
const timeline = data.data || data; | ||||||||||||||||||||||||
const tweets: Array<{ content: string, id: string, authorId: string }> = []; | ||||||||||||||||||||||||
const tweets: Array<{ content: string; id: string; authorId: string }> = []; | ||||||||||||||||||||||||
const seenTweetIdsSet = new Set(seenTweetIds); | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
try { | ||||||||||||||||||||||||
// Twitter's actual GraphQL HomeTimeline response structure | ||||||||||||||||||||||||
const instructions = timeline?.home?.home_timeline_urt?.instructions || []; | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
for (const instruction of instructions) { | ||||||||||||||||||||||||
|
@@ -124,6 +153,13 @@ export async function fetchHomeTimeline(seenTweetIds: string[] = []): Promise<Ar | |||||||||||||||||||||||
console.error("Error parsing tweets:", e); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
// 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' }); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
return tweets; | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
|
@@ -133,10 +169,15 @@ async function main() { | |||||||||||||||||||||||
try { | ||||||||||||||||||||||||
const data = await fetchHomeTimeline(); | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
// Show JSON response (truncated for readability) | ||||||||||||||||||||||||
const jsonString = JSON.stringify(data, null, 2); | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
console.log(jsonString); | ||||||||||||||||||||||||
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, platform:'twitter'}); | ||||||||||||||||||||||||
console.log('Twitter API usage:', { | ||||||||||||||||||||||||
total_requests: usage.total_requests, | ||||||||||||||||||||||||
last_request: usage.last_request, | ||||||||||||||||||||||||
account_id: usage.account_id | ||||||||||||||||||||||||
}); | ||||||||||||||||||||||||
} catch (err) { | ||||||||||||||||||||||||
console.error('fetchHomeTimeline failed:', err instanceof Error ? err.message : err); | ||||||||||||||||||||||||
process.exit(1); | ||||||||||||||||||||||||
|
@@ -151,10 +192,8 @@ cron.schedule('*/5 * * * *', async () => { | |||||||||||||||||||||||
console.log('Refetching Twitter timeline...'); | ||||||||||||||||||||||||
try { | ||||||||||||||||||||||||
const timeline = await fetchHomeTimeline(); | ||||||||||||||||||||||||
// Process the timeline data here (save to DB, send to another service, etc.) | ||||||||||||||||||||||||
console.log('Fetched timeline:', timeline); | ||||||||||||||||||||||||
} catch (err) { | ||||||||||||||||||||||||
console.error('Scheduled Twitter timeline fetch failed:', err); | ||||||||||||||||||||||||
} | ||||||||||||||||||||||||
}); | ||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,25 +1,90 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { createClient, RedisClientType } from 'redis'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import crypto from 'crypto'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { createClient } from 'redis'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export async function runRedisOperation(operation: (client: RedisClientType) => Promise<void>): Promise<void> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const client: RedisClientType = createClient({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
url: 'redis://localhost:6379' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Singleton Redis client | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+5
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export async function trackApiKeyUsage({ accountId, platform }: { accountId: string, platform: 'telegram' | 'twitter' }): Promise<void> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!accountId?.trim()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
console.warn('trackApiKeyUsage: empty accountId; skipping'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
await client.connect(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
await operation(client); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
await ensureRedisConnected(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let key: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (platform === 'twitter') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
key = `twitter_accounts:${accountId}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else if (platform === 'telegram') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
key = `telegram_accounts:${accountId}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
throw new Error('trackApiKeyUsage: platform must be "twitter" or "telegram"'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const now = new Date().toISOString(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
await redisClient | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.multi() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.hIncrBy(key, 'total_requests', 1) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.hSet(key, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
last_request: now, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
account_id: accountId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
.exec(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
console.error('Redis operation failed:', err); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
throw err; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} finally { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
await client.quit(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} catch (quitErr) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
console.error('Error during Redis client quit:', quitErr); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
console.warn('trackApiKeyUsage: non-fatal Redis error; proceeding without usage update', err); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* Get API usage stats from Redis. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* @param accountId The account ID to query | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* @param platform The platform ('telegram' or 'twitter') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* @returns Object with total_requests and last_request | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
interface dataType { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
accountId: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
platform: 'telegram' | 'twitter' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export async function getApiKeyUsage(data: dataType): Promise<{ total_requests: number; last_request: string | null; account_id?: string }> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const { accountId, platform } = data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let result: { total_requests: number; last_request: string | null; account_id?: string } = { total_requests: 0, last_request: null }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!accountId?.trim()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return result; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (platform !== 'twitter' && platform !== 'telegram') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
throw new Error('getApiKeyUsage: platform must be "twitter" or "telegram"'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
await ensureRedisConnected(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let key: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (platform === 'twitter') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
key = `twitter_accounts:${accountId}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
key = `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; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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.
Also applies to: 55-69
🤖 Prompt for AI Agents