1
0
Fork
You've already forked crom-discord-bot
0
No description
  • TypeScript 98.5%
  • Shell 1%
  • Dockerfile 0.5%
Anderson da Campo c490f1ad8f
All checks were successful
deploy / deploy (push) Successful in 19s
feat(context): deterministic SQL routes, retention ledger, race fixes
AgentService: detect first/last/count intents in pt-BR and answer via SQL;
inject structured channel context (stats, coverage, latest summary) into
every prompt; pass excludeSourceId to RAG so the current user message
cannot appear as a source.
SummaryStoreService: channel_messages.content now nullable with
content_expires_at metadata (metadata is permanent, text expires per
MESSAGE_RETENTION_DAYS=7); add channel_sync_state separating latest
live cursor from oldest backfilled cursor; expose getChannelStats,
getFirstStoredMessage, getLastStoredMessage, getChannelSyncState,
upsertChannelSyncState, getSummaryCoveringTimestamp, expireOldMessageContent;
on boot, null out expired content automatically.
RagStoreService: unique index on (source_type, source_id) prevents
duplicates; saveDocument silently skips 23505 collisions; searchHybrid
accepts excludeSourceId; cleanupExpiredDocuments removes discord_message
chunks older than retention while keeping discord_summary forever;
getStats now reports discordMessages vs summaries separately.
DiscordService: every persistence and checkpoint call is now awaited;
mentions persist -> respond -> index RAG (no more self-citation race);
bot replies are no longer saved to the ledger; channel sync writes
both ingest_checkpoint and channel_sync_state with the actual newest
message; backfill loops over the backfill cursor and ingests recent
messages into RAG; content for messages older than retention is stored
as null with content_expires_at; MESSAGE_RETENTION_DAYS default aligned
to 7 days.
2026年06月02日 09:04:26 -03:00
.forgejo/workflows fix: RAG SQL param index, catch error, unify OPENAI vars, remove embeddings 2026年06月01日 13:43:07 -03:00
scripts refactor app 2026年05月28日 01:06:46 -03:00
src feat(context): deterministic SQL routes, retention ledger, race fixes 2026年06月02日 09:04:26 -03:00
.containerignore added podman scripts and Containerfile 2026年05月11日 01:24:28 -03:00
.env.example fix: RAG SQL param index, catch error, unify OPENAI vars, remove embeddings 2026年06月01日 13:43:07 -03:00
.gitignore first commit 2026年05月11日 00:19:30 -03:00
compose.yaml refactor(storage): migrate SummaryStoreService from node:sqlite to pg Pool 2026年06月01日 11:24:05 -03:00
Dockerfile fix(build): align solid-js peer to match @opentui requirement 2026年06月01日 11:01:53 -03:00
IDEAS.md doc: added idea 2026年05月11日 23:04:16 -03:00
nest.cli.json first commit 2026年05月11日 00:19:30 -03:00
package-lock.json chore(deps): remove unused pgvector package 2026年06月01日 11:41:17 -03:00
package.json chore(deps): remove unused pgvector package 2026年06月01日 11:41:17 -03:00
README.md fix: RAG SQL param index, catch error, unify OPENAI vars, remove embeddings 2026年06月01日 13:43:07 -03:00
tsconfig.json refactor app 2026年05月28日 01:06:46 -03:00

Discord Bot de Resumo com NestJS

Bot para Discord que resume mensagens de um canal usando a API da OpenCode, guarda memoria recente em Postgres e responde tanto por slash command quanto por @mencao.

Funcionalidades

  • Comando /resumir com filtro por horas e limite de mensagens.
  • Atualizacao de status na mesma resposta do Discord enquanto o resumo e processado.
  • Resumo incremental por canal: depois do primeiro resumo, o bot considera apenas mensagens novas.
  • Persistencia em Postgres do historico de resumos, do ultimo message_id resumido por canal e das mensagens dos ultimos 3 dias.
  • Sincronizacao inicial no startup para puxar pelo menos as ultimas 12 horas de mensagens dos canais acessiveis.
  • Resposta por @mencao ao bot com uso do contexto recente do canal e do ultimo resumo salvo.
  • Comando /historico para listar mensagens recentes sem chamar a IA.
  • Fallback local simples quando OPENCODE_API_KEY nao estiver configurada ou a chamada externa falhar.

Stack

  • Node.js 24
  • NestJS 10
  • discord.js 14
  • Postgres com pgvector nativo via pg e pgvector/pg

Estrutura

src/
 app.module.ts
 discord/
 discord.module.ts
 discord.service.ts
 storage/
 storage.module.ts
 summary-store.service.ts
 summarizer/
 summarizer.module.ts
 summarizer.service.ts
data/

Variaveis de ambiente

Copie .env.example para .env e preencha os valores.

Variavel Obrigatoria Descricao Default
DISCORD_BOT_TOKEN sim Token do bot no Discord Developer Portal -
OPENAI_API_KEY sim Chave da API OpenAI-compatible -
OPENAI_BASE_URL nao Base da API OpenAI-compatible https://omni.andersonc.dev.br/v1
OPENAI_MODEL nao Modelo no campo model da API antigravity/gemini-3.5-flash-preview
POSTGRES_HOST nao Host do Postgres localhost
POSTGRES_PORT nao Porta do Postgres 5432
POSTGRES_USER nao Usuario do Postgres postgres
POSTGRES_DB nao Nome do banco Postgres crom_rag
MESSAGE_RETENTION_DAYS nao Dias de mensagens retidas no banco 3
STARTUP_SYNC_HOURS nao Horas de hist. hydrate no startup 168
STARTUP_SYNC_MAX_MESSAGES_PER_CHANNEL nao Limite de msgs por canal no startup 500
CROM_HOST nao Hostname do bot (usado no resumo) crombot.andersonc.dev.br

Instalacao

npm install

Executar em desenvolvimento

npm run start:dev

Build e producao

npm run build
npm run start:prod

Como configurar o bot no Discord

  1. Crie uma application no Discord Developer Portal.
  2. Crie o bot e copie o token para DISCORD_BOT_TOKEN.
  3. Em Bot > Privileged Gateway Intents, habilite Message Content Intent.
  4. Convide o bot para o servidor com escopos bot e applications.commands.
  5. Inicie o projeto.
  6. No log, confirme que apareceram:
Bot conectado como ...
Slash commands registrados na guild <id>.

Observacao:

  • O bot registra slash commands em todas as guilds carregadas no startup.
  • Quando entra em uma guild nova, registra automaticamente os comandos nela.

Comandos do bot

/resumir

Resume apenas mensagens novas do canal desde o ultimo resumo salvo no SQLite.

Opcoes:

  • horas: janela de busca para tras. Minimo 1, maximo 168. Default 1.
  • mensagens: limite maximo de mensagens novas consideradas. Minimo 5, maximo 100. Default 50.

Exemplos:

/resumir horas:1 mensagens:50
/resumir horas:24 mensagens:100
/resumir mensagens:20

Fluxo:

  1. O bot responde imediatamente com status.
  2. Busca mensagens do canal.
  3. Para no ultimo message_id ja resumido para aquele canal.
  4. Monta o contexto.
  5. Chama a OpenCode.
  6. Salva resumo e checkpoint no Postgres.
  7. Edita a mesma mensagem com o resultado final.

Se nao houver mensagens novas desde o ultimo resumo, o bot informa isso e nao chama a IA.

/historico

Lista mensagens recentes do canal sem resumir.

Opcao:

  • mensagens: minimo 5, maximo 50, default 20.

@bot ...

Quando um usuario menciona o bot em um canal, ele responde como assistente usando:

  • mensagens salvas no Postgres dos ultimos 3 dias
  • filtro heuristico para puxar mensagens mais relevantes para o pedido
  • ultimo resumo salvo do canal, quando existir

Exemplos:

@bot me resume o que estavam falando hoje
@bot o que o Joao falou sobre deploy?
@bot me atualiza do que mudou desde ontem

Comportamento:

  1. O bot salva a mensagem do usuario no SQLite.
  2. Remove a mencao do texto e trata o resto como prompt.
  3. Busca contexto recente do mesmo canal.
  4. Tenta filtrar mensagens mais relevantes pelos termos do pedido.
  5. Envia contexto + ultimo resumo do canal para a OpenCode.
  6. Responde no proprio canal.
  7. Salva a resposta final dele tambem no SQLite.

Como funciona o resumo incremental

O projeto salva dois tipos de dado:

  • summary_checkpoints: ultimo resumo por canal
  • summaries: historico completo dos resumos gerados
  • channel_messages: memoria recente de mensagens por canal

Campos principais persistidos:

  • channel_id
  • guild_id
  • from_message_id
  • to_message_id
  • from_timestamp
  • to_timestamp
  • message_count
  • summary

Fluxo incremental:

  1. Primeira execucao no canal: o bot busca mensagens dentro da janela e do limite pedido.
  2. Execucoes seguintes: o bot continua buscando do mais recente para tras, mas interrompe ao encontrar o ultimo message_id salvo no checkpoint.

Isso evita resumir repetidamente o mesmo conteudo para pessoas diferentes no mesmo canal.

Memoria de mensagens

O bot tambem guarda mensagens recentes em Postgres para sustentar o modo conversacional por @mencao.

Campos principais persistidos em channel_messages:

  • message_id
  • guild_id
  • channel_id
  • author_id
  • author_username
  • author_display_name
  • content
  • created_at

Politica atual:

  • mensagens de bots sao ignoradas
  • mensagens vazias sao ignoradas
  • a retencao padrao e de 3 dias
  • o armazenamento usa upsert por message_id
  • no startup, o bot tenta hidratar pelo menos as ultimas 12 horas dos canais acessiveis

OpenCode

O resumo usa chamada HTTP direta para:

POST https://opencode.ai/zen/v1/chat/completions

Corpo relevante:

{
 "model": "minimax-m2.5-free",
 "max_tokens": 8192,
 "messages": [
 { "role": "system", "content": "..." },
 { "role": "user", "content": "..." }
 ]
}

Se OPENCODE_API_KEY nao estiver definida, o app retorna um resumo simples local apenas com preview das mensagens.

Banco Postgres

Conexao atraves das variaveis POSTGRES_*. O banco crom_rag e criado automaticamente se nao existir.

Tabelas:

-- Resumos e checkpoints
SELECT*FROMsummary_checkpoints;SELECTid,channel_id,message_count,created_atFROMsummariesORDERBYidDESC;-- Mensagens recentes
SELECTchannel_id,author_display_name,created_atFROMchannel_messagesORDERBYcreated_atDESCLIMIT20;-- RAG
SELECT*FROMrag_documents;SELECT*FROMrag_chunksLIMIT10;

Observacoes operacionais

  • O bot ignora mensagens de outros bots.
  • O bot ignora mensagens vazias no resumo incremental.
  • O nome usado no contexto do resumo prioriza apelido no servidor (displayName), depois globalName, depois username.
  • O status do /resumir e atualizado sempre na mesma resposta da interacao.
  • O modo @mencao depende da memoria local do SQLite e de um filtro heuristico simples por termos.
  • Se o canal tiver pouco contexto salvo, a resposta por mencao pode ficar mais generica.
  • O bot precisa de View Channel e Read Message History para ler e hidratar canais.

Arquivos importantes

Riscos e proximos ajustes

  • O filtro de relevancia do modo @mencao ainda e heuristico; nomes ambiguos e pedidos muito amplos ainda podem puxar contexto ruim.
  • O startup sync pode ficar caro em servidores com muitos canais acessiveis; por isso existe limite por canal.
  • O filtro por janela de tempo ainda depende da leitura paginada recente do canal; para canais muito movimentados, o limite de mensagens continua mandando no volume maximo analisado.
  • O projeto nao tem testes automatizados ainda.