Sentia falta da timeline do Orkut, então fiz um agregador cronológico de canais de Telegram
Sentia falta da timeline do Orkut. Não da rede em si — disso ninguém sente falta. Da timeline. Aquela parada cronológica onde tudo aparecia em ordem, do mais novo pro mais velho, sem algoritmo decidindo o que era importante. Recado de amigo, depoimento, scrap aleatório, alguém adicionando comunidade nova. Caótico, honesto, e principalmente: tudo num lugar só.
Aí o Facebook chegou, depois o Instagram, depois o TikTok, e cada um foi um passo a mais nessa direção de "o algoritmo sabe o que você quer melhor que você." E a gente foi perdendo essa noção de feed cronológico simples.
O Telegram, dentro disso, é uma exceção. É honesto: ordem cronológica, sem ranqueamento, você lê o que assinou. Mas tem uma coisa que ele nunca resolveu — não existe feed unificado entre canais. Você segue 20 canais e isso são 20 lugares separados que você abre na mão, todo dia.
Pasta? Pasta agrupa, mas não consolida. Você ainda abre canal por canal. Bot? Bot só lê canal onde foi adicionado como admin. Ninguém adiciona bot de terceiro como admin no canal público dele. Ou seja, agregadores via bot são estruturalmente quebrados.
A certa altura cansei e fiz o meu.
Chama Televizor. Loga com Telegram, escolhe os canais, as mensagens são encaminhadas pra um chat de destino dentro do teu próprio Telegram. Lê como timeline — cronológico, sem algoritmo, dentro do app que você já usa.
- Repo: github.com/s0larpunk/televizor (GPL-3)
- Hospedado: televizor.click
Aviso necessário: não sou dev hardcore. Vibe-codei isso usando Claude Code pra resolver uma coceira pessoal. Por isso o código tá aberto desde o primeiro commit — quem entender melhor que eu pode apontar onde tá ruim.
Telethon vs Bot API: a decisão que definiu tudo
A Bot API é uma camada HTTP em cima do MTProto, bem documentada, fácil de usar. E completamente inútil pra agregação. O problema não é a qualidade — é o modelo de autenticação.
Bot existe como identidade separada do usuário. Só lê mensagem em canal onde foi promovido a admin. Pra agregar 20 canais públicos via bot, eu precisaria convencer 20 donos de canal a me dar permissão de admin. Não vai rolar.
Telethon é cliente MTProto — mesmo protocolo do app oficial, do Unigram, do Plus Messenger, de qualquer cliente terceiro. Autentica como usuário, então tem acesso a tudo que a tua conta tem acesso. Mesmo nível do app oficial, porque funcionalmente é a mesma coisa.
Trade-off: área cinza do ToS. Telegram não bane cliente MTProto de terceiro (banar mataria todo o ecossistema de clientes alternativos), mas flagra automação que parece spam ou scraping em massa. Mitigação:
- Delay de 15s no forward (configurável, intencionalmente não-instantâneo)
- Rate limit por fonte e por feed via Redis
- Event-driven, sem polling
- Tratamento de
SessionRevokedErroreAuthKeyError
Rodando minha instância há meses, zero ban.
Event-driven vs polling: por que isso não é opcional
Primeiro instinto quando você lê sobre Telethon é escrever um loop que pergunta "tem mensagem nova?" pra cada canal a cada N segundos. Isso é polling, e tem dois problemas estruturais aqui.
Primeiro: latência. Polling em intervalo N tem latência média N/2. A 30 segundos de polling, mensagem chega 15s atrasada na média. Pra notícia, ok. Pra sinal de trade ou breaking news, inútil.
Segundo: escala. Polling de N canais pra M usuários é N×M chamadas por ciclo. Telegram tem rate limit por conta. Você bate no muro rapidinho.
Telethon suporta event-driven via events.NewMessage — conexão persistente que recebe update no momento que acontece. Uma conexão por usuário, independente de quantos canais. Latência fica determinada só pelo delay deliberado de 15s.
Albums: o bug que demorei pra perceber
Telegram entrega álbum (foto/vídeo agrupados num post) como N mensagens separadas com grouped_id em comum. Forward individual quebra o álbum no destino — chega como mídia solta sem caption.
Solução: debounce de 2 segundos, key (source_id, grouped_id). Cada parte nova reseta o timer. Depois de 2s de silêncio, manda forward_messages(messages=[ids]) e o destino remonta como álbum único.
Em produção, partes chegam em 0.5–1.5s. 2s é conservador, mas nunca vi álbum rachado.
Modelo de ameaça — sem rodeios
Session string é funcionalmente equivalente a um auth token do Telegram. É a parte mais sensível da arquitetura, e descrevo direto em vez de esconder em letrinha miúda.
Armazenado: telefone, Telegram ID, Telethon session string, configs de feed, tier e validade.
Não armazenado: conteúdo de mensagem, histórico de chat, contatos, mídia, analytics.
Se o banco vazar, atacante pega session strings e consegue conectar ao Telegram como o usuário. Contramedida: o usuário pode matar todas as sessões pelas configurações do Telegram a qualquer momento, isso desativa a string imediatamente. Televizor não pede senha 2FA, então com a session string sozinha ninguém consegue trocar senha ou deletar a conta.
Session strings no banco são criptografadas com Fernet (AES-128-CBC + HMAC-SHA256). Criptografia é gated em SESSION_ENCRYPTION_KEY no env — se a chave tá setada, vai cifrado pro banco; se não, plaintext (compatibilidade com dados legados). No self-host: gera uma chave, bota no .env, sessão nova já entra criptografada.
Se o modelo de ameaça ainda incomoda, self-host. A sessão nem sai da tua máquina.
Como isso foi feito: agentes Claude Code
Honesto sobre essa parte porque é relevante pra como você lê o código.
Não comecei com documento de arquitetura nem decompus em sprint. Abri o Claude Code, descrevi o problema, olhei o que voltou, ajustei, iterei. A maior parte do código foi feita assim.
Diferentes partes pediram diferentes tipos de interação. Backend foi conversa em volta de problema concreto — "tem Telethon, tem lista de canais, preciso de forward event-driven com filtro." O agente propunha estrutura, eu perguntava sobre concorrência ou rate limit, iterávamos. Decisões como SELECT FOR UPDATE no fluxo de referral ou o debounce do álbum saíram desse tipo de conversa — não porque eu sabia a resposta, mas porque o agente trazia o caso onde a solução ingênua quebra.
Frontend Next.js com i18n e RTL pra Farsi foi outro tipo de trabalho. Lá o agente funcionou mais como pair programmer que conhece bem o ecossistema. Localização em quatro línguas com RTL eu não conseguiria fazer manualmente em tempo razoável.
Onde agente é ruim: manter contexto através de várias sessões, perceber conflito entre módulos feitos em sessões separadas, gerar teste pra código que ele mesmo não modela completamente. Refatoração pra unificar estilo entre saídas de sessões diferentes é trabalho manual. Teste continua sendo dívida — escrever teste pra código que você não internalizou completamente é mais difícil do que parece.
Abri o código desde o primeiro commit justamente porque foi vibe-codado. Quando você constrói com agente, revisão externa fica mais importante, não menos.
Self-host
git clone https://github.com/s0larpunk/televizor
cd televizor
cp .env.example .env # TELEGRAM_API_ID / TELEGRAM_API_HASH do my.telegram.org
docker compose up -d
Quatro containers: frontend, backend, postgres, redis. Em idle ~180MB RAM, sob carga ~350MB. Setar SESSION_ENCRYPTION_KEY no .env ativa criptografia at-rest. Pra produção, reverse proxy com TLS. Backup do postgres_data — perder é re-login pra todo usuário.
Planos
Grátis (permanente): 1 feed, canais ilimitados.
Premium: múltiplos feeds + filtros por palavra-chave.
Indicação: manda código, os dois ganham 1 semana de Premium.
Self-host tem todas as features.
Próximo passo
- Sharding de worker em múltiplos processos — gargalo hoje não é CPU, é
FloodWaitpor conta. Um processo aguenta centenas de usuários. Vira relevante pra 1000+. - Dedup de mensagem em feeds que compartilham fonte — exige tabela de fingerprint, escrita extra por entrega. Adia pra quando alguém reclamar.
- Cobertura de teste — magra, dívida honesta de vibe-coding.
PRs welcome, especialmente em teste, sharding e o dedup.
github.com/s0larpunk/televizor
P.S. Pra quem ainda sente falta de comunidade obscura do VK que nunca migrou pro Telegram — fiz um conversor VK-to-RSS pra mim, leio com feeeed. Privado ainda. Se rolar interesse nos comments, limpo e solto.