Instalação
Requisitos
| O quê | Versão | Por quê |
|---|---|---|
| Node.js | 24 ou mais | O SDK usa fetch e WebSocket globais, e o pacote de voz depende de APIs de stream recentes |
| TypeScript | 5.6 ou mais | Decorators com metadados e satisfies |
| pnpm | 10 (ou npm 10) | O monorepo do nescord usa workspaces do pnpm; para consumir o SDK, qualquer um serve |
| ffmpeg | 6 ou mais | Só para voz. Transcodifica qualquer entrada para Opus |
| yt-dlp | recente | Só se o bot for tocar de sites de vídeo |
Confira o que já tem:
node --version # v24.x
pnpm --version # 10.x
ffmpeg -version # só se for usar voz
yt-dlp --version # só se for baixar de sites de vídeo
São binários do sistema, não dependências npm. O concord.js/voice os chama por processo filho e
falha com mensagem clara se não achar.
Instalando
Os nomes concord.js e nescord já estão ocupados no registro público por pacotes de terceiros,
sem relação nenhuma com este projeto: pnpm add concord.js nescord baixa o pacote errado, e o
import { Client } from 'concord.js' vira erro de export inexistente.
Enquanto não publicamos (provavelmente sob escopo, @concord/concord.js e @concord/nescord), o
caminho é trabalhar dentro do monorepo: pnpm install na raiz e pnpm --filter @nescord/music dev
para ver o bot de exemplo de pé. Os comandos abaixo valem para quando a publicação sair.
pnpm add concord.js nescord @nestjs/common @nestjs/core reflect-metadata
npm install concord.js nescord @nestjs/common @nestjs/core reflect-metadata
Se o bot é só de texto e você não quer o NestJS, o concord.js roda sozinho:
pnpm add concord.js
Só o pacote de voz
A parte de voz fica num subcaminho próprio, concord.js/voice, justamente para não pesar num bot
de texto: ela carrega a stack WebRTC e fala com o ffmpeg.
import { createAudioResource } from 'concord.js/voice';
Nada mais a instalar — o subcaminho faz parte do mesmo pacote. O que precisa existir é o ffmpeg
no PATH — ou, se ele estiver em outro lugar, a variável CONCORD_FFMPEG apontando para o binário:
CONCORD_FFMPEG=/opt/ffmpeg/bin/ffmpeg
É essa a variável que o concord.js lê (voice/AudioResource.ts); não existe outra.
TypeScript
O concord.js publica só ESM: o exports dele não tem condição require, então um app CommonJS
estoura ERR_PACKAGE_PATH_NOT_EXPORTED ao importá-lo — inclusive no Node 24, porque a resolução
ainda passa pela condição require. Como npm init cria um pacote CommonJS por padrão, declare:
{
"type": "module"
}
E, com module: nodenext + ESM, import relativo leva extensão: ./app.module.js, não
./app.module.
Os decorators do nescord (e do NestJS) precisam de duas opções ligadas. Sem elas, @On e
@SlashCommand compilam mas não registram nada:
{
"compilerOptions": {
"target": "ES2023",
"module": "nodenext",
"moduleResolution": "nodenext",
"lib": ["ES2023"],
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "dist"
}
}
E reflect-metadata carregado uma vez, antes de tudo:
import 'reflect-metadata';
experimentalDecorators, e não os decorators padrãoO NestJS ainda usa os decorators legados com emitDecoratorMetadata — é assim que ele descobre os
tipos dos parâmetros para injetar dependência. O nescord segue o mesmo modelo para não exigir dois
regimes de decorator no mesmo arquivo.
Variáveis de ambiente
O SDK lê três variáveis, e nenhuma delas tem valor padrão secreto:
| Variável | Para quê | Padrão |
|---|---|---|
CONCORD_TOKEN | Token de sessão da conta do bot | nenhum — sem ele, login() falha |
CONCORD_URL | Base HTTP do servidor | https://app.concord.win |
CONCORD_INSTALL_TOKEN | Credencial de instalação, para rest.bots | nenhum — opcional |
CONCORD_TOKEN=cole-aqui-o-token-de-sessao
CONCORD_URL=http://localhost:3000
O token de sessão dá acesso total à conta do bot por 30 dias. Ele vive em variável de ambiente, no
cofre do seu provedor de deploy, e no .env que está no .gitignore. Se vazar, faça logout da
conta — isso invalida a sessão na hora.
Conferindo que funcionou
Um arquivo, sem NestJS, só para ver o handshake acontecer:
import { Client } from 'concord.js';
const client = new Client({ token: process.env.CONCORD_TOKEN });
// Sem um listener de `error` o processo cai na primeira falha de socket.
client.on('error', (err) => console.error('erro:', err.message));
client.on('ready', (c) => {
console.log(`conectado como @${c.user!.username}`);
console.log(`servidores: ${c.servers.size}, canais: ${c.channels.size}`);
console.log(`SFU ligado no servidor: ${c.sfuEnabled}`);
void c.destroy();
});
await client.login();
node --experimental-strip-types smoke.ts
Saiu o nome do bot? O token está certo e o gateway está aberto. Agora vá para Primeiro bot.
Deu Sessão inválida, entre de novo? O token não é um token de sessão válido — veja como obter um
em Primeiro bot.