@squord/sdk 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +544 -2
- package/dist/api-DbHoBFJY.d.cts +726 -0
- package/dist/api-DbHoBFJY.d.ts +726 -0
- package/dist/index.cjs +3881 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2340 -0
- package/dist/index.d.ts +2340 -0
- package/dist/index.js +3779 -0
- package/dist/index.js.map +1 -0
- package/dist/testing/index.cjs +789 -0
- package/dist/testing/index.cjs.map +1 -0
- package/dist/testing/index.d.cts +261 -0
- package/dist/testing/index.d.ts +261 -0
- package/dist/testing/index.js +763 -0
- package/dist/testing/index.js.map +1 -0
- package/package.json +70 -4
package/README.md
CHANGED
|
@@ -1,3 +1,545 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @squord/sdk — o SDK oficial do Squord
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/Squord/squord-sdk/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@squord/sdk)
|
|
5
|
+
[](https://nodejs.org)
|
|
6
|
+
[](https://www.typescriptlang.org)
|
|
7
|
+
[](packages/sdk/test)
|
|
8
|
+
[](./LICENSE)
|
|
9
|
+
|
|
10
|
+
O SDK para criar bots do [Squord](https://docs.squord.com.br) em JavaScript e TypeScript:
|
|
11
|
+
REST tipado, gateway em tempo real com reconexão e resume, comandos de texto e de barra,
|
|
12
|
+
botões e menus, uploads, moderação, permissões e testes — sem você implementar nenhum
|
|
13
|
+
WebSocket, retry ou rate limit à mão.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @squord/sdk
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { SquordClient } from "@squord/sdk";
|
|
21
|
+
|
|
22
|
+
const client = new SquordClient({ token: process.env.SQUORD_BOT_TOKEN! });
|
|
23
|
+
|
|
24
|
+
client.on("message.created", async (message) => {
|
|
25
|
+
if (message.author.isBot) return;
|
|
26
|
+
if (message.content === "!ping") await message.reply("Pong! 🏓");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
await client.login();
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
> **Pré-requisitos**: Node.js 20+ (o gateway usa o `WebSocket` global, estável no Node 22+;
|
|
33
|
+
> no Node 20, passe `ws` na opção `ws` do cliente). Crie o bot e o token em
|
|
34
|
+
> **Configurações → Desenvolvedor** do Squord e instale-o em um Space.
|
|
35
|
+
> Documentação da plataforma: <https://docs.squord.com.br>.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Índice
|
|
40
|
+
|
|
41
|
+
1. [Começo rápido](#começo-rápido)
|
|
42
|
+
2. [Instalação e requisitos](#instalação-e-requisitos)
|
|
43
|
+
3. [Eventos](#eventos)
|
|
44
|
+
4. [Comandos](#comandos)
|
|
45
|
+
5. [Interações: botões e menus](#interações-botões-e-menus)
|
|
46
|
+
6. [Mensagens, embeds e anexos](#mensagens-embeds-e-anexos)
|
|
47
|
+
7. [Members, cargos e moderação](#members-cargos-e-moderação)
|
|
48
|
+
8. [Permissões](#permissões)
|
|
49
|
+
9. [Erros, rate limits e retentativas](#erros-rate-limits-e-retentativas)
|
|
50
|
+
10. [Gateway: reconexão e resume](#gateway-reconexão-e-resume)
|
|
51
|
+
11. [Modo REST-only](#modo-rest-only)
|
|
52
|
+
12. [Testando seu bot](#testando-seu-bot)
|
|
53
|
+
13. [Observabilidade](#observabilidade)
|
|
54
|
+
14. [Limites do backend refletidos no SDK](#limites-do-backend-refletidos-no-sdk)
|
|
55
|
+
15. [Estrutura do repositório](#estrutura-do-repositório)
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Começo rápido
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { SquordClient } from "@squord/sdk";
|
|
63
|
+
|
|
64
|
+
const client = new SquordClient({
|
|
65
|
+
token: process.env.SQUORD_BOT_TOKEN!, // sqb_<id>.<segredo>
|
|
66
|
+
gracefulShutdown: true, // encerra no SIGINT/SIGTERM
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Ciclo de vida
|
|
70
|
+
client.on("ready", (event) => {
|
|
71
|
+
console.log(`Conectado como ${event.user.displayName}`);
|
|
72
|
+
client.setPresence({ status: "IDLE", customStatus: "Tocando no Lounge" });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Mensagens (intent GUILD_MESSAGES; conteúdo exige a intent MESSAGE_CONTENT)
|
|
76
|
+
client.on("message.created", (message) => {
|
|
77
|
+
console.log(`${message.authorDisplayName}: ${message.content}`);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Erros de handler nunca derrubam o processo: chegam aqui.
|
|
81
|
+
client.on("error", (error) => console.error(error));
|
|
82
|
+
|
|
83
|
+
await client.login();
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
O `login()` valida o token (`GET /bots/@me`), conecta ao gateway e resolve no `ready`.
|
|
87
|
+
Tudo o que vem depois — heartbeat, reconexão com backoff, resume da sessão,
|
|
88
|
+
deduplicação de eventos — é responsabilidade do SDK.
|
|
89
|
+
|
|
90
|
+
## Instalação e requisitos
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm install @squord/sdk # ESM e CommonJS, tipos incluídos
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- **Node.js 20+**. O gateway usa o `WebSocket` global (estável no 22+); no Node 20,
|
|
97
|
+
instale `ws` e passe `new SquordClient({ ws })`.
|
|
98
|
+
- **Zero dependências em runtime** — só o que o Node já traz (`fetch`, `WebSocket`).
|
|
99
|
+
- ESM e CJS: `dist/index.js` e `dist/index.cjs`, com `.d.ts` completos.
|
|
100
|
+
|
|
101
|
+
```jsonc
|
|
102
|
+
// package.json consumer, se estiver no Node 20:
|
|
103
|
+
{ "dependencies": { "@squord/sdk": "^0.1.0", "ws": "^8" } }
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { SquordClient } from "@squord/sdk";
|
|
108
|
+
import WebSocket from "ws";
|
|
109
|
+
|
|
110
|
+
const client = new SquordClient({ token, ws: (url) => new WebSocket(url) });
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Eventos
|
|
114
|
+
|
|
115
|
+
Os eventos seguem os nomes reais do gateway do Squord. O payload chega hidratado
|
|
116
|
+
em estruturas (`Message`, `Member`, `Channel`, `Role`, `Thread`, `Guild`,
|
|
117
|
+
`Interaction`) quando o evento carrega uma entidade; nos demais casos, o payload
|
|
118
|
+
cru da documentação.
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
client.on("message.created", (message) => {}); // Message
|
|
122
|
+
client.on("message.updated", (message) => {}); // Message (já editada)
|
|
123
|
+
client.on("message.deleted", (data) => {}); // { messageId, channelId, guildId }
|
|
124
|
+
client.on("reaction.added", (data) => {}); // { messageId, userId, reaction }
|
|
125
|
+
client.on("member.joined", ({ guildId, member }) => {}); // Member
|
|
126
|
+
client.on("member.updated", ({ guildId, member }) => {});
|
|
127
|
+
client.on("member.left", (data) => {}); // { guildId, userId }
|
|
128
|
+
client.on("member.banned", (data) => {}); // { guildId, userId, reason }
|
|
129
|
+
client.on("guild.updated", (guild) => {}); // Guild (Space)
|
|
130
|
+
client.on("channel.created", (channel) => {}); // Channel
|
|
131
|
+
client.on("role.created", (role) => {}); // Role
|
|
132
|
+
client.on("thread.created", (thread) => {}); // Thread
|
|
133
|
+
client.on("interaction.created", (interaction) => {}); // Interaction
|
|
134
|
+
client.on("application_command.updated", (data) => {});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Ciclo de vida e diagnóstico: `ready`, `resumed`, `reconnecting`, `disconnect`,
|
|
138
|
+
`error`, `warn`, `commandError`, `raw` (frame cru), `unknownEvent`
|
|
139
|
+
(evento futuro ainda não mapeado — compatibilidade sem quebrar).
|
|
140
|
+
|
|
141
|
+
> **Intents** são configuradas por bot no app do Squord (ou `PATCH /bots/{id}`) e
|
|
142
|
+
> **não** vão no identify — o gateway lê as intents do bot ao conectar. Use
|
|
143
|
+
> `Intents`/`intentsValue` para calcular valores. Sem a intent privilegiada
|
|
144
|
+
> `MESSAGE_CONTENT`, o `content` chega vazio (exceto mensagens do próprio bot e
|
|
145
|
+
> menções a ele) — o SDK entrega o evento do mesmo jeito; a regra é da plataforma.
|
|
146
|
+
|
|
147
|
+
## Comandos
|
|
148
|
+
|
|
149
|
+
O `CommandRegistry` cuida dos três tipos. Ele é opcional: você também pode
|
|
150
|
+
tratar `message.created`/`interaction.created` na mão.
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import { CommandRegistry, SquordClient, EmbedBuilder, userMention } from "@squord/sdk";
|
|
154
|
+
|
|
155
|
+
const client = new SquordClient({ token });
|
|
156
|
+
const commands = new CommandRegistry(client, { prefix: "!" });
|
|
157
|
+
|
|
158
|
+
// Comandos de texto: prefixo, aliases, args com aspas, cooldown e middleware.
|
|
159
|
+
commands.text({
|
|
160
|
+
name: "banir",
|
|
161
|
+
aliases: ["ban"],
|
|
162
|
+
cooldownMs: 5_000,
|
|
163
|
+
beforeExecute: async (context) => true, // middleware (permissões, logs...)
|
|
164
|
+
async execute(context) {
|
|
165
|
+
const [target, ...rest] = context.args; // args com aspas tratadas
|
|
166
|
+
const motivo = rest.join(" ") || "sem motivo";
|
|
167
|
+
await client.moderation.ban(context.message.guildId!, target, { reason: motivo });
|
|
168
|
+
await context.reply(`${userMention(target)} foi banido.`);
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Comandos de barra: definidos aqui e sincronizados no login (PUT em bloco).
|
|
173
|
+
commands.slash({
|
|
174
|
+
name: "eco",
|
|
175
|
+
description: "Repete o que você disse",
|
|
176
|
+
options: [
|
|
177
|
+
{ type: "STRING", name: "texto", description: "O texto", required: true },
|
|
178
|
+
],
|
|
179
|
+
async execute(interaction) {
|
|
180
|
+
await interaction.reply(interaction.getString("texto", true));
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Componentes: handler por customId (aceita prefixo com `*`).
|
|
185
|
+
commands.component({
|
|
186
|
+
customId: "confirmar:*",
|
|
187
|
+
async execute(interaction) {
|
|
188
|
+
await interaction.update({ content: "Feito!", components: [] });
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
await client.login(); // slash commands são publicados no ready
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
- **Registrar é sobrescrever**: o SDK faz o `PUT` da lista completa na partida,
|
|
196
|
+
como recomendado pela plataforma (escopo global; para escopo por Space, use
|
|
197
|
+
`scope: { kind: "guild", guildIds: [...] }` — útil para dev).
|
|
198
|
+
- Respostas de slash: `reply`, `replyEphemeral` (só a pessoa vê), `defer` +
|
|
199
|
+
`followup` (padrão para trabalho demorado) e, em componentes, `update` /
|
|
200
|
+
`acknowledge`.
|
|
201
|
+
- Acesso tipado às opções: `getString`, `getInteger`, `getBoolean`, `getId`.
|
|
202
|
+
|
|
203
|
+
## Interações: botões e menus
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import { ActionRowBuilder, Buttons, SelectMenuBuilder } from "@squord/sdk";
|
|
207
|
+
|
|
208
|
+
const row = new ActionRowBuilder()
|
|
209
|
+
.addButton(Buttons.primary("Confirmar").setCustomId("confirmar"))
|
|
210
|
+
.addButton(Buttons.secondary("Cancelar").setCustomId("cancelar"))
|
|
211
|
+
.addButton(Buttons.link("Documentação").setURL("https://docs.squord.com.br"));
|
|
212
|
+
|
|
213
|
+
const menuRow = new ActionRowBuilder().setSelectMenu(
|
|
214
|
+
new SelectMenuBuilder("tema")
|
|
215
|
+
.addOption({ label: "Claro", value: "claro" })
|
|
216
|
+
.addOption({ label: "Escuro", value: "escuro", description: "O padrão" }),
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
await channel.send({ content: "Escolha:", components: [row, menuRow] });
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Regras da plataforma, validadas localmente pelo builder: 5 linhas por mensagem,
|
|
223
|
+
5 botões por linha ou 1 menu sozinho, `customId` único, botão `link` exige `https`.
|
|
224
|
+
Efeitas (`ephemeral: true`) só aceitam botões de link.
|
|
225
|
+
|
|
226
|
+
## Mensagens, embeds e anexos
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
import { EmbedBuilder } from "@squord/sdk";
|
|
230
|
+
|
|
231
|
+
// Menções seguras por padrão: sem allowedMentions, o SDK envia
|
|
232
|
+
// { users: true, roles: false, everyone: false }.
|
|
233
|
+
await channel.send({
|
|
234
|
+
content: "Atualização de infraestrutura",
|
|
235
|
+
embeds: [
|
|
236
|
+
new EmbedBuilder()
|
|
237
|
+
.setTitle("Status do servidor")
|
|
238
|
+
.setDescription("Todos os serviços operacionais.")
|
|
239
|
+
.setColor(0xf5a623)
|
|
240
|
+
.addFields(
|
|
241
|
+
{ name: "API", value: "Online", inline: true },
|
|
242
|
+
{ name: "Gateway", value: "Online", inline: true },
|
|
243
|
+
)
|
|
244
|
+
.setTimestamp(),
|
|
245
|
+
],
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Idempotência: cada envio ganha um nonce automático; falhas de rede podem ser
|
|
249
|
+
// repetidas com segurança pelo cliente REST sem duplicar a mensagem.
|
|
250
|
+
const sent = await channel.send("Relatório importante");
|
|
251
|
+
await sent.edit("Relatório importante (atualizado)");
|
|
252
|
+
await sent.react("👍");
|
|
253
|
+
await sent.pin();
|
|
254
|
+
|
|
255
|
+
// Histórico com paginação por cursor (sem carregar tudo em memória):
|
|
256
|
+
for await (const message of paginate(
|
|
257
|
+
(params) => client.messages.list(channel.id, params),
|
|
258
|
+
)) {
|
|
259
|
+
// processa cada mensagem
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**Uploads** seguem o fluxo oficial de 3 passos (reserva → `PUT` dos bytes →
|
|
264
|
+
confirmação), abstraído em uma chamada:
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
const upload = await client.uploads.upload({
|
|
268
|
+
guildId: spaceId, // bots sempre informam o Space
|
|
269
|
+
path: "./relatorios/daily.pdf",
|
|
270
|
+
filename: "relatorio.pdf",
|
|
271
|
+
contentType: "application/pdf",
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
await channel.send({ content: "Segue o relatório.", attachmentIds: [upload.id] });
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
O `PUT` dos bytes vai para a URL assinada do storage **sem** o header do bot,
|
|
278
|
+
repetindo os headers do alvo exatamente; a confirmação é idempotente.
|
|
279
|
+
|
|
280
|
+
## Members, cargos e moderação
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
const guild = await client.guilds.fetch(spaceId); // Space, com self.permissions
|
|
284
|
+
const member = await client.members.fetch(spaceId, userId);
|
|
285
|
+
const page = await client.members.list(spaceId, { limit: 50, roleId }); // cursor
|
|
286
|
+
const roles = await client.roles.list(spaceId);
|
|
287
|
+
|
|
288
|
+
await member.addRole(roleId);
|
|
289
|
+
await member.setNickname("Novo apelido");
|
|
290
|
+
await member.timeout(new Date(Date.now() + 60_000)); // 1 min de castigo
|
|
291
|
+
await member.removeTimeout();
|
|
292
|
+
await member.kick("motivo");
|
|
293
|
+
await client.moderation.ban(spaceId, userId, { reason: "spam", deleteMessageSeconds: 3600 });
|
|
294
|
+
await client.moderation.unban(spaceId, userId);
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
A hierarquia é respeitada pelo backend (`HIERARCHY_BLOCKED`, `ROLE_ABOVE_ACTOR`,
|
|
298
|
+
`OWNER_PROTECTED`, `ROLE_MANAGED` chegam como `PermissionError`/`SquordAPIError`
|
|
299
|
+
com o `code` estável para você decidir).
|
|
300
|
+
|
|
301
|
+
## Permissões
|
|
302
|
+
|
|
303
|
+
Permissões são **strings decimais** de 64 bits: calcule com `BigInt`.
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
import { Permissions, permissionsToString } from "@squord/sdk";
|
|
307
|
+
|
|
308
|
+
const bits = Permissions.VIEW_CHANNEL | Permissions.SEND_MESSAGES | Permissions.EMBED_LINKS;
|
|
309
|
+
console.log(permissionsToString(bits)); // "139713"
|
|
310
|
+
|
|
311
|
+
// O que o bot pode neste Space (bloco self do GET /guilds/{id}):
|
|
312
|
+
const guild = await client.guilds.fetch(spaceId);
|
|
313
|
+
guild.botHas(Permissions.BAN_MEMBERS);
|
|
314
|
+
|
|
315
|
+
// Resolução local com overwrites de canal (backend é a autoridade final):
|
|
316
|
+
const mine = await client.myPermissionsIn(channelId);
|
|
317
|
+
const canPin = await client.hasPermissionsIn(channelId, Permissions.PIN_MESSAGES);
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
A utilidade `resolveChannelPermissions` implementa o algoritmo oficial
|
|
321
|
+
(@everyone → cargos → overwrites de cargo → overwrite de membro).
|
|
322
|
+
|
|
323
|
+
## Erros, rate limits e retentativas
|
|
324
|
+
|
|
325
|
+
O cliente REST aplica a política recomendada pela documentação:
|
|
326
|
+
|
|
327
|
+
| Situação | Comportamento |
|
|
328
|
+
|---|---|
|
|
329
|
+
| `429` | espera `details.retryAfter` e repete (até esgotar `maxRetries`) |
|
|
330
|
+
| `5xx`, falha de rede | backoff exponencial **só em operações idempotentes ou com `nonce`** |
|
|
331
|
+
| `409 COMMANDS_CONFLICT`/`THREAD_CHANGED` | nova tentativa (a doc manda repetir) |
|
|
332
|
+
| demais `4xx` | falha imediata, sem loop |
|
|
333
|
+
| timeout | `RequestTimeoutError` tipado |
|
|
334
|
+
|
|
335
|
+
**Regra de ouro respeitada**: o SDK nunca repete um POST cujo efeito é incerto.
|
|
336
|
+
Envio de mensagens usa `nonce` automático (idempotência da plataforma), então pode
|
|
337
|
+
repetir; callbacks de interação não são repetidos — um `409 INTERACTION_ALREADY_ACKNOWLEDGED`
|
|
338
|
+
após repetição manual é tratado como sucesso pela utilidade `reply`.
|
|
339
|
+
|
|
340
|
+
Além do 429 reativo, o SDK observa os headers `X-RateLimit-*` (bloqueia a rota até
|
|
341
|
+
a janela renovar) e aplica um teto global conservador de 45 req/s (o oficial é 50).
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
import { RateLimitError, PermissionError, SquordAPIError } from "@squord/sdk";
|
|
345
|
+
|
|
346
|
+
try {
|
|
347
|
+
await channel.send("oi");
|
|
348
|
+
} catch (error) {
|
|
349
|
+
if (error instanceof PermissionError) {
|
|
350
|
+
console.log("Falta a permissão:", error.missingPermission);
|
|
351
|
+
} else if (error instanceof SquordAPIError) {
|
|
352
|
+
console.log("code:", error.code, "requestId:", error.requestId);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Hierarquia: `SquordError` → `SquordAPIError` (`status`, `code`, `requestId`,
|
|
358
|
+
`details`) → `AuthenticationError`/`InvalidTokenError`, `PermissionError`,
|
|
359
|
+
`NotFoundError`, `ValidationError` (`issues`), `RateLimitError` (`retryAfter`,
|
|
360
|
+
`bucket`, `exhaustedRetries`), `NetworkError`, `RequestTimeoutError`,
|
|
361
|
+
`GatewayError` (`closeCode`, `reason`).
|
|
362
|
+
|
|
363
|
+
## Gateway: reconexão e resume
|
|
364
|
+
|
|
365
|
+
O SDK implementa o protocolo completo: `hello` → `identify` (15 s) → `ready`;
|
|
366
|
+
heartbeat no intervalo anunciado (com jitter no primeiro); `heartbeat.ack` com
|
|
367
|
+
medida de latência; conexão zumbi detectada e fechada; reconexão com backoff
|
|
368
|
+
exponencial e jitter (1 s → 60 s); `resume` com a última sequência (replay dos
|
|
369
|
+
eventos perdidos + `resumed`); `invalid_session` caindo para `identify` na mesma
|
|
370
|
+
conexão; deduplicação por `s`; códigos fatais (4004/4013/4014) sem retry.
|
|
371
|
+
|
|
372
|
+
```ts
|
|
373
|
+
client.on("reconnecting", ({ attempt, delayMs, closeCode }) => {});
|
|
374
|
+
client.on("resumed", ({ replayed, sequence }) => {
|
|
375
|
+
console.log(`${replayed} eventos recuperados na retomada`);
|
|
376
|
+
});
|
|
377
|
+
client.on("disconnect", ({ code, reason, fatal }) => {});
|
|
378
|
+
|
|
379
|
+
client.ping; // latência do último heartbeat
|
|
380
|
+
client.gateway?.sequence; // última sequência processada
|
|
381
|
+
client.gateway?.session; // id da sessão atual
|
|
382
|
+
client.reconnect(); // força reconexão (tenta resume)
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
O que o backend não garante, o SDK não inventa: se o histórico de replay não
|
|
386
|
+
cobrir a sequência, o servidor recusa o resume (`invalid_session`) e uma nova
|
|
387
|
+
sessão começa do zero — eventos do período podem ter sido perdidos; para
|
|
388
|
+
reconciliar, use as consultas REST (histórico de mensagens, membros).
|
|
389
|
+
|
|
390
|
+
## Modo REST-only
|
|
391
|
+
|
|
392
|
+
Para tarefas agendadas, CI e integrações que só publicam:
|
|
393
|
+
|
|
394
|
+
```ts
|
|
395
|
+
const client = new SquordClient({ token, restOnly: true });
|
|
396
|
+
await client.login(); // valida o token; nenhum WebSocket é aberto
|
|
397
|
+
await client.messages.send(channelId, "deploy concluído ✅");
|
|
398
|
+
await client.destroy();
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## Testando seu bot
|
|
402
|
+
|
|
403
|
+
O subpath `@squord/sdk/testing` traz um gateway simulado (que fala o protocolo
|
|
404
|
+
real) e um REST em memória:
|
|
405
|
+
|
|
406
|
+
```ts
|
|
407
|
+
import { SquordClient } from "@squord/sdk";
|
|
408
|
+
import { createMockGateway, createMockRest, fixtures } from "@squord/sdk/testing";
|
|
409
|
+
|
|
410
|
+
const rest = createMockRest();
|
|
411
|
+
rest.get("/bots/@me", () => ({ status: 200, body: fixtures.bot() }));
|
|
412
|
+
rest.post("/channels/:id/messages", () => ({
|
|
413
|
+
status: 201,
|
|
414
|
+
body: fixtures.message({ content: "Pong!" }),
|
|
415
|
+
}));
|
|
416
|
+
|
|
417
|
+
const gateway = createMockGateway({ heartbeatIntervalMs: 100 });
|
|
418
|
+
const client = new SquordClient({
|
|
419
|
+
token: "sqb_1.segredo",
|
|
420
|
+
apiBaseURL: "https://mock.test",
|
|
421
|
+
fetchImpl: rest.fetch,
|
|
422
|
+
ws: gateway.wsFactory,
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
client.on("message.created", (message) => {
|
|
426
|
+
if (message.content === "!ping") void message.reply("Pong!");
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
await client.login();
|
|
430
|
+
gateway.emitEvent("message.created", { message: fixtures.message({ content: "!ping" }) });
|
|
431
|
+
|
|
432
|
+
// Treine queda e retomada sem rede:
|
|
433
|
+
gateway.closeAll(4009, "session timeout"); // o SDK resume a sessão
|
|
434
|
+
gateway.requestReconnect("gateway shutting down"); // frame reconnect + 4007
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
O `MockGateway` aceita `refuseResumeOnce` (treina `invalid_session`) e
|
|
438
|
+
`suppressHeartbeatAck` (treina detecção de zumbi); o `MockRest` registra todas as
|
|
439
|
+
chamadas para asserções (`callLog`, `lastCall`) e pode simular 429/5xx/timeout.
|
|
440
|
+
|
|
441
|
+
## Observabilidade
|
|
442
|
+
|
|
443
|
+
```ts
|
|
444
|
+
const client = new SquordClient({
|
|
445
|
+
token,
|
|
446
|
+
logLevel: "debug", // ou export SQUORD_LOG=debug / SQUORD_DEBUG=1
|
|
447
|
+
logger: customLogger, // Logger customizável { debug, info, warn, error, child }
|
|
448
|
+
});
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Logs são estruturados (JSON), com **redação automática de tokens** (`sqb_...`
|
|
452
|
+
e `Authorization` viram `[REDACTED]`) e sem conteúdo de mensagens por padrão —
|
|
453
|
+
só metadados operacionais (requestId, buckets, heartbeats, tentativas).
|
|
454
|
+
|
|
455
|
+
## Limites do backend refletidos no SDK
|
|
456
|
+
|
|
457
|
+
O SDK valida localmente os limites reais antes de gastar uma chamada:
|
|
458
|
+
embeds (10/mensagem, 25 campos, 6000 caracteres somados, títulos 256...),
|
|
459
|
+
componentes (5 linhas, 5 botões/linha, `customId` 100), `content` 4000,
|
|
460
|
+
`attachmentIds` 10, upload 500 MB, nonce 64, nome de comando 32/regex.
|
|
461
|
+
Erros de validação local chegam como `SquordTypeError`; os da API, como
|
|
462
|
+
`ValidationError` com a lista de `issues` (`path`, `code`, `params`).
|
|
463
|
+
|
|
464
|
+
Recursos que a API **não** expõe a bots não existem no SDK: DMs, presença de
|
|
465
|
+
outros membros, listagem de bans, voz (o fluxo E2EE de bots de voz fica fora
|
|
466
|
+
desta versão). Não há mecanismo para contornar permissões.
|
|
467
|
+
|
|
468
|
+
## Estrutura do repositório
|
|
469
|
+
|
|
470
|
+
```
|
|
471
|
+
squord-sdk/
|
|
472
|
+
├── packages/sdk/ # @squord/sdk (o único pacote publicado)
|
|
473
|
+
│ ├── src/
|
|
474
|
+
│ │ ├── client/ # SquordClient, opções, eventos tipados
|
|
475
|
+
│ │ ├── rest/ # REST, retry, rate limits, paginação
|
|
476
|
+
│ │ ├── gateway/ # WebSocket, heartbeat, resume, backoff
|
|
477
|
+
│ │ ├── managers/ # mensagens, canais, Spaces, membros, cargos,
|
|
478
|
+
│ │ │ # moderação, uploads, interações, comandos, bot
|
|
479
|
+
│ │ ├── structures/ # Message, Channel, Member, Role, User, Guild...
|
|
480
|
+
│ │ ├── builders/ # EmbedBuilder, botões, menus, payload
|
|
481
|
+
│ │ ├── commands/ # CommandRegistry (texto, slash, componentes)
|
|
482
|
+
│ │ ├── cache/ # cache TTL opcional
|
|
483
|
+
│ │ ├── errors/ # hierarquia de erros
|
|
484
|
+
│ │ ├── types/ # tipos dos payloads reais (API + gateway)
|
|
485
|
+
│ │ └── testing/ # @squord/sdk/testing: mocks e fixtures
|
|
486
|
+
│ └── test/ # 90 testes (unit + integração)
|
|
487
|
+
├── examples/ # ping-bot, commands-bot, welcome-bot, reports
|
|
488
|
+
└── docs/ # guias
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
## Desenvolvimento
|
|
492
|
+
|
|
493
|
+
```bash
|
|
494
|
+
pnpm install
|
|
495
|
+
pnpm build # tsup: ESM + CJS + .d.ts
|
|
496
|
+
pnpm test # vitest: 90 testes com gateway simulado
|
|
497
|
+
pnpm typecheck # tsc --noEmit (strict)
|
|
498
|
+
pnpm pack # gera o tarball (squord-sdk-<versão>.tgz) para instalar em outro projeto
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
O `usage-demo/` consome o pacote como um projeto externo (via `file:`) e roda
|
|
502
|
+
um smoke contra os mocks — útil para conferir o pacote distribuído:
|
|
503
|
+
|
|
504
|
+
```bash
|
|
505
|
+
pnpm build
|
|
506
|
+
cd usage-demo && npm install && npm start
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
## Publicar versões no npm
|
|
510
|
+
|
|
511
|
+
O pacote `@squord/sdk` é publicado **automaticamente pelo workflow
|
|
512
|
+
[Publicar no npm](.github/workflows/publish.yml)** quando um Release do GitHub
|
|
513
|
+
é criado — com typecheck, testes, build e *provenance*.
|
|
514
|
+
|
|
515
|
+
Configuração única (secret `NPM_TOKEN`): crie um token de automação no npmjs.com
|
|
516
|
+
(Access Tokens → **Automation**, com permissão de escrever no pacote
|
|
517
|
+
`@squord/sdk`) e:
|
|
518
|
+
|
|
519
|
+
```bash
|
|
520
|
+
gh secret set NPM_TOKEN --repo Squord/squord-sdk
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
Corte uma versão:
|
|
524
|
+
|
|
525
|
+
1. Atualize `packages/sdk/package.json` e o [CHANGELOG.md](./CHANGELOG.md)
|
|
526
|
+
2. `git tag v0.x.y && git push --tags`
|
|
527
|
+
3. Crie o Release do GitHub — o workflow publica no npm
|
|
528
|
+
|
|
529
|
+
Enquanto o pacote não está no npm, consuma pelo tarball (gerado a partir do
|
|
530
|
+
repositório):
|
|
531
|
+
|
|
532
|
+
```bash
|
|
533
|
+
git clone https://github.com/Squord/squord-sdk && cd squord-sdk
|
|
534
|
+
pnpm install && pnpm pack # gera packages/sdk/squord-sdk-0.1.0.tgz
|
|
535
|
+
npm install /caminho/para/squord-sdk-0.1.0.tgz
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
---
|
|
539
|
+
|
|
540
|
+
**Documentação da plataforma**: <https://docs.squord.com.br> ·
|
|
541
|
+
**Referência REST (OpenAPI)**: <https://docs.squord.com.br/api> ·
|
|
542
|
+
**Protocolo do gateway**: <https://docs.squord.com.br/gateway/visao-geral> ·
|
|
543
|
+
**Issues**: <https://github.com/Squord/squord-sdk/issues>
|
|
544
|
+
|
|
545
|
+
MIT © Squord — veja [LICENSE](./LICENSE).
|