koala73/worldmonitor · warning · TelegramLookupError
Invalid Telegram username
Error message
Invalid Telegram username
What it means
fetchTelegramChannelPreview normalizes the given username and uses the result as both cache key and wire value; if normalization yields an empty string (no valid handle extractable), it throws TelegramLookupError('Invalid Telegram username', 400) before any network call. This intentionally replaces older behavior where un-normalized inputs caused duplicate cache entries and rejected requests.
Source
Thrown at src/services/telegram-intel.ts:273
} catch (error) {
// A truncated or non-JSON 200 is the same class of upstream blip as a 5xx;
// handling it differently would strand the panel on exactly the shape the
// relay's own normalization fallthrough exists to tolerate.
const stale = staleFallback();
if (stale) return stale;
throw error;
}
cachedResponse = json;
cachedAt = Date.now();
return json;
}
export async function fetchTelegramChannelPreview(username: string): Promise<TelegramChannelPreview> {
// Normalize here rather than trusting callers: an un-normalized value became
// both the cache key and the wire value, so `@foo` and `t.me/foo` would hold
// separate entries and issue requests the edge then rejects.
const cacheKey = normalizeTelegramUsername(username);
if (!cacheKey) throw new TelegramLookupError('Invalid Telegram username', 400);
const cached = previewCache.get(cacheKey);
if (cached && cached.expiresAt > Date.now()) return cached.data;
const inflight = previewInflight.get(cacheKey);
if (inflight) return inflight;
const request = (async () => {
const preview = parseTelegramChannelPreview(await readJson(await fetch(telegramResolveUrl(cacheKey), {
signal: createTimeoutSignal(PREVIEW_REQUEST_TIMEOUT_MS),
})));
setLookupCache(previewCache, cacheKey, preview, RESOLVE_CACHE_TTL);
return preview;
})();
previewInflight.set(cacheKey, request);
try {
return await request;
} finally {View on GitHub (pinned to 9361220cc0)
Solutions
- Run the input through normalizeTelegramUsername first and refuse empty results before calling the API.
- Trim and strip leading '@' / 'https://t.me/' prefixes from user input before invoking.
- Show inline form validation ('enter a public channel handle') instead of surfacing the thrown error.
- Exclude private invite links (+hash) since they have no public username.
Example fix
// before
await fetchTelegramChannelPreview(userInput); // throws 400 for 'https://t.me/'
// after
const handle = normalizeTelegramUsername(userInput);
if (!handle) { showFormError('Enter a valid public channel handle'); return; }
await fetchTelegramChannelPreview(handle); Defensive patterns
Strategy: validation
Validate before calling
import { normalizeTelegramUsername } from '../services/telegram-intel';
const handle = normalizeTelegramUsername(rawInput);
if (!handle) {
showFormError('Enter a valid public channel handle');
return;
}
Type guard
function isValidTelegramHandle(input: string): boolean {
return normalizeTelegramUsername(input).length > 0;
}
Try / catch
try {
const preview = await fetchTelegramChannelPreview(handle);
} catch (e) {
if (e instanceof TelegramLookupError && e.status === 400) {
showFormError('Invalid Telegram username');
} else throw e;
}
Prevention
- Validate handles in the form layer before invoking the fetch service
- Normalize once at input boundaries; never pass raw URLs or '@'-prefixed strings
- Exclude private invite links (t.me/+hash) from preview lookups
- Trim whitespace and strip invisible characters from pasted input
When it happens
Trigger: Calling fetchTelegramChannelPreview('') with an empty string, whitespace, a bare '@', a full URL like 'https://t.me/' with no path, or any string normalizeTelegramUsername cannot reduce to a valid handle.
Common situations: Users typing full t.me URLs or handles with @/spaces into a watchlist form; empty form submission; pasted invite links (t.me/+hash) that don't map to a public username; copy-paste with invisible characters.
Related errors
- Invalid Telegram channel preview
- Invalid Telegram feed response
- get_intel_timeline requires at least one of domain ("conflic
- INCOMPATIBLE_DELIVERY
- COUNTRIES_LIMIT_EXCEEDED
AI-assisted analysis of koala73/worldmonitor@9361220cc0 (2026-09-01).
Data as JSON: /api/errors/6de0247eb57a9a0e.
Report an issue: GitHub.