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

  1. Run the input through normalizeTelegramUsername first and refuse empty results before calling the API.
  2. Trim and strip leading '@' / 'https://t.me/' prefixes from user input before invoking.
  3. Show inline form validation ('enter a public channel handle') instead of surfacing the thrown error.
  4. 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

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


AI-assisted analysis of koala73/worldmonitor@9361220cc0 (2026-09-01). Data as JSON: /api/errors/6de0247eb57a9a0e. Report an issue: GitHub.