@ibbybuilds/discli 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 +250 -0
- package/dist/cli.js +867 -0
- package/dist/cli.js.map +1 -0
- package/package.json +46 -0
- package/skills/SKILL.md +76 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,867 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/utils/config.ts
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
8
|
+
import { homedir } from "os";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
var CONFIG_DIR = join(homedir(), ".discli");
|
|
11
|
+
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
12
|
+
var ENV_FILE = join(CONFIG_DIR, ".env");
|
|
13
|
+
function ensureConfigDir() {
|
|
14
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
15
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function loadConfig() {
|
|
19
|
+
if (!existsSync(CONFIG_FILE)) return {};
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
22
|
+
} catch {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function saveConfig(data) {
|
|
27
|
+
ensureConfigDir();
|
|
28
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2) + "\n");
|
|
29
|
+
}
|
|
30
|
+
function loadToken() {
|
|
31
|
+
if (!existsSync(ENV_FILE)) return null;
|
|
32
|
+
const content = readFileSync(ENV_FILE, "utf-8");
|
|
33
|
+
const match = content.match(/^BOT_TOKEN=(.+)$/m);
|
|
34
|
+
return match ? match[1].trim() : null;
|
|
35
|
+
}
|
|
36
|
+
function saveToken(token) {
|
|
37
|
+
ensureConfigDir();
|
|
38
|
+
writeFileSync(ENV_FILE, `BOT_TOKEN=${token}
|
|
39
|
+
`);
|
|
40
|
+
}
|
|
41
|
+
function getDefaultServer() {
|
|
42
|
+
return loadConfig().default_server_id ?? null;
|
|
43
|
+
}
|
|
44
|
+
function setDefaultServer(id, name) {
|
|
45
|
+
const cfg = loadConfig();
|
|
46
|
+
cfg.default_server_id = id;
|
|
47
|
+
cfg.default_server_name = name;
|
|
48
|
+
saveConfig(cfg);
|
|
49
|
+
}
|
|
50
|
+
function requireToken() {
|
|
51
|
+
const token = loadToken();
|
|
52
|
+
if (!token) {
|
|
53
|
+
console.error('Error: Not configured. Run "discli init" first.');
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
return token;
|
|
57
|
+
}
|
|
58
|
+
function requireServer(override) {
|
|
59
|
+
const server = override || getDefaultServer();
|
|
60
|
+
if (!server) {
|
|
61
|
+
console.error('Error: No server selected. Run "discli server select" or use --server <id>.');
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
return server;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/utils/api.ts
|
|
68
|
+
var BASE = "https://discord.com/api/v10";
|
|
69
|
+
var PERMISSION = {
|
|
70
|
+
view_channel: 1n << 10n,
|
|
71
|
+
send_messages: 1n << 11n,
|
|
72
|
+
send_messages_in_threads: 1n << 38n,
|
|
73
|
+
create_public_threads: 1n << 35n,
|
|
74
|
+
create_private_threads: 1n << 36n,
|
|
75
|
+
embed_links: 1n << 14n,
|
|
76
|
+
attach_files: 1n << 15n,
|
|
77
|
+
add_reactions: 1n << 6n,
|
|
78
|
+
use_external_emojis: 1n << 18n,
|
|
79
|
+
read_message_history: 1n << 16n,
|
|
80
|
+
mention_everyone: 1n << 17n,
|
|
81
|
+
manage_messages: 1n << 13n,
|
|
82
|
+
manage_channels: 1n << 4n,
|
|
83
|
+
manage_roles: 1n << 28n,
|
|
84
|
+
connect: 1n << 20n,
|
|
85
|
+
speak: 1n << 21n,
|
|
86
|
+
mute_members: 1n << 22n,
|
|
87
|
+
deafen_members: 1n << 23n,
|
|
88
|
+
move_members: 1n << 24n,
|
|
89
|
+
use_voice_activity: 1n << 25n
|
|
90
|
+
};
|
|
91
|
+
var CHANNEL_TYPE = {
|
|
92
|
+
text: 0,
|
|
93
|
+
voice: 2,
|
|
94
|
+
category: 4,
|
|
95
|
+
announcement: 5,
|
|
96
|
+
stage: 13,
|
|
97
|
+
forum: 15
|
|
98
|
+
};
|
|
99
|
+
var CHANNEL_TYPE_NAME = Object.fromEntries(
|
|
100
|
+
Object.entries(CHANNEL_TYPE).map(([k, v]) => [v, k])
|
|
101
|
+
);
|
|
102
|
+
var DiscordAPI = class {
|
|
103
|
+
token;
|
|
104
|
+
constructor(token) {
|
|
105
|
+
this.token = token;
|
|
106
|
+
}
|
|
107
|
+
async request(method, path, body) {
|
|
108
|
+
const headers = {
|
|
109
|
+
Authorization: `Bot ${this.token}`
|
|
110
|
+
};
|
|
111
|
+
if (body) {
|
|
112
|
+
headers["Content-Type"] = "application/json";
|
|
113
|
+
}
|
|
114
|
+
const res = await fetch(`${BASE}${path}`, {
|
|
115
|
+
method,
|
|
116
|
+
headers,
|
|
117
|
+
body: body ? JSON.stringify(body) : void 0
|
|
118
|
+
});
|
|
119
|
+
if (res.status === 429) {
|
|
120
|
+
const data = await res.json();
|
|
121
|
+
console.error(`Rate limited. Retry after ${data.retry_after ?? "?"}s.`);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
if (res.status === 403) {
|
|
125
|
+
console.error("403 Forbidden \u2014 missing permissions.");
|
|
126
|
+
process.exit(4);
|
|
127
|
+
}
|
|
128
|
+
if (res.status === 404) {
|
|
129
|
+
console.error("404 Not found.");
|
|
130
|
+
process.exit(3);
|
|
131
|
+
}
|
|
132
|
+
if (res.status >= 400) {
|
|
133
|
+
const text = await res.text();
|
|
134
|
+
console.error(`Discord API error ${res.status}: ${text.slice(0, 200)}`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
if (res.status === 204) return null;
|
|
138
|
+
return res.json();
|
|
139
|
+
}
|
|
140
|
+
// ── Guilds ──
|
|
141
|
+
async listGuilds() {
|
|
142
|
+
return await this.request("GET", "/users/@me/guilds");
|
|
143
|
+
}
|
|
144
|
+
async getGuild(guildId) {
|
|
145
|
+
return await this.request("GET", `/guilds/${guildId}?with_counts=true`);
|
|
146
|
+
}
|
|
147
|
+
// ── Channels ──
|
|
148
|
+
async listChannels(guildId) {
|
|
149
|
+
return await this.request("GET", `/guilds/${guildId}/channels`);
|
|
150
|
+
}
|
|
151
|
+
async createChannel(guildId, opts) {
|
|
152
|
+
return await this.request("POST", `/guilds/${guildId}/channels`, opts);
|
|
153
|
+
}
|
|
154
|
+
async deleteChannel(channelId) {
|
|
155
|
+
await this.request("DELETE", `/channels/${channelId}`);
|
|
156
|
+
}
|
|
157
|
+
async modifyChannel(channelId, data) {
|
|
158
|
+
return await this.request("PATCH", `/channels/${channelId}`, data);
|
|
159
|
+
}
|
|
160
|
+
async getChannel(channelId) {
|
|
161
|
+
return await this.request("GET", `/channels/${channelId}`);
|
|
162
|
+
}
|
|
163
|
+
async editChannelPermission(channelId, overwriteId, data) {
|
|
164
|
+
await this.request("PUT", `/channels/${channelId}/permissions/${overwriteId}`, data);
|
|
165
|
+
}
|
|
166
|
+
async deleteChannelPermission(channelId, overwriteId) {
|
|
167
|
+
await this.request("DELETE", `/channels/${channelId}/permissions/${overwriteId}`);
|
|
168
|
+
}
|
|
169
|
+
// ── Roles ──
|
|
170
|
+
async listRoles(guildId) {
|
|
171
|
+
return await this.request("GET", `/guilds/${guildId}/roles`);
|
|
172
|
+
}
|
|
173
|
+
async createRole(guildId, data) {
|
|
174
|
+
return await this.request("POST", `/guilds/${guildId}/roles`, data);
|
|
175
|
+
}
|
|
176
|
+
async deleteRole(guildId, roleId) {
|
|
177
|
+
await this.request("DELETE", `/guilds/${guildId}/roles/${roleId}`);
|
|
178
|
+
}
|
|
179
|
+
async addRoleToMember(guildId, userId, roleId) {
|
|
180
|
+
await this.request("PUT", `/guilds/${guildId}/members/${userId}/roles/${roleId}`);
|
|
181
|
+
}
|
|
182
|
+
async removeRoleFromMember(guildId, userId, roleId) {
|
|
183
|
+
await this.request("DELETE", `/guilds/${guildId}/members/${userId}/roles/${roleId}`);
|
|
184
|
+
}
|
|
185
|
+
// ── Members ──
|
|
186
|
+
async listMembers(guildId, limit = 100) {
|
|
187
|
+
return await this.request("GET", `/guilds/${guildId}/members?limit=${Math.min(limit, 1e3)}`);
|
|
188
|
+
}
|
|
189
|
+
async getMember(guildId, userId) {
|
|
190
|
+
return await this.request("GET", `/guilds/${guildId}/members/${userId}`);
|
|
191
|
+
}
|
|
192
|
+
async kickMember(guildId, userId) {
|
|
193
|
+
await this.request("DELETE", `/guilds/${guildId}/members/${userId}`);
|
|
194
|
+
}
|
|
195
|
+
async banMember(guildId, userId) {
|
|
196
|
+
await this.request("PUT", `/guilds/${guildId}/bans/${userId}`);
|
|
197
|
+
}
|
|
198
|
+
async modifyMember(guildId, userId, data) {
|
|
199
|
+
return await this.request("PATCH", `/guilds/${guildId}/members/${userId}`, data);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// src/commands/init.ts
|
|
204
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
205
|
+
function registerInit(program2) {
|
|
206
|
+
program2.command("init").description("Set up discli with your bot token and default server").option("--token <token>", "Bot token (or reads from stdin)").action(async (opts) => {
|
|
207
|
+
let token = opts.token;
|
|
208
|
+
if (!token) {
|
|
209
|
+
if (!process.stdin.isTTY) {
|
|
210
|
+
token = readFileSync2(0, "utf-8").trim();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (!token) {
|
|
214
|
+
const existing = loadToken();
|
|
215
|
+
if (existing) {
|
|
216
|
+
console.log("Already configured. Use --token to update.");
|
|
217
|
+
token = existing;
|
|
218
|
+
} else {
|
|
219
|
+
console.error("Usage: discli init --token <your-bot-token>");
|
|
220
|
+
console.error(" Get your token from https://discord.com/developers/applications");
|
|
221
|
+
process.exit(2);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const api = new DiscordAPI(token);
|
|
225
|
+
let guilds;
|
|
226
|
+
try {
|
|
227
|
+
guilds = await api.listGuilds();
|
|
228
|
+
} catch {
|
|
229
|
+
console.error("Invalid token or cannot connect to Discord.");
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
saveToken(token);
|
|
233
|
+
console.log(`Token saved to ~/.discli/.env`);
|
|
234
|
+
if (guilds.length === 0) {
|
|
235
|
+
console.log("Bot is not in any servers yet. Add it to a server first.");
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
console.log("\nServers:");
|
|
239
|
+
guilds.forEach((g, i) => {
|
|
240
|
+
console.log(` ${i + 1}. ${g.name} (${g.id})`);
|
|
241
|
+
});
|
|
242
|
+
if (guilds.length === 1) {
|
|
243
|
+
setDefaultServer(guilds[0].id, guilds[0].name);
|
|
244
|
+
console.log(`
|
|
245
|
+
Default server: ${guilds[0].name}`);
|
|
246
|
+
} else {
|
|
247
|
+
console.log(`
|
|
248
|
+
Set default with: discli server select <id>`);
|
|
249
|
+
}
|
|
250
|
+
console.log("\nReady! Try: discli server info");
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/utils/output.ts
|
|
255
|
+
import { stringify as yamlStringify } from "yaml";
|
|
256
|
+
function resolveFormat(explicit) {
|
|
257
|
+
if (explicit !== "auto") return explicit;
|
|
258
|
+
return process.stdout.isTTY ? "table" : "yaml";
|
|
259
|
+
}
|
|
260
|
+
function printResult(data, format) {
|
|
261
|
+
const fmt = resolveFormat(format);
|
|
262
|
+
if (fmt === "json") {
|
|
263
|
+
console.log(JSON.stringify(data, null, 2));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (fmt === "yaml") {
|
|
267
|
+
console.log(yamlStringify(data, { indent: 2 }).trimEnd());
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (Array.isArray(data)) {
|
|
271
|
+
if (data.length === 0) {
|
|
272
|
+
console.log(" (none)");
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (typeof data[0] === "object") {
|
|
276
|
+
printTable(data);
|
|
277
|
+
} else {
|
|
278
|
+
data.forEach((item) => console.log(` ${item}`));
|
|
279
|
+
}
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (typeof data === "object" && data !== null) {
|
|
283
|
+
const obj = data;
|
|
284
|
+
const maxKey = Math.max(...Object.keys(obj).map((k) => k.length));
|
|
285
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
286
|
+
console.log(` ${k.padEnd(maxKey)} ${v}`);
|
|
287
|
+
}
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
console.log(String(data));
|
|
291
|
+
}
|
|
292
|
+
function printTable(rows, columns) {
|
|
293
|
+
if (rows.length === 0) {
|
|
294
|
+
console.log(" (none)");
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const cols = columns ?? Object.keys(rows[0]);
|
|
298
|
+
const widths = {};
|
|
299
|
+
for (const col of cols) {
|
|
300
|
+
widths[col] = col.length;
|
|
301
|
+
}
|
|
302
|
+
for (const row of rows) {
|
|
303
|
+
for (const col of cols) {
|
|
304
|
+
widths[col] = Math.max(widths[col], String(row[col] ?? "").length);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
const header = cols.map((c) => c.toUpperCase().padEnd(widths[c])).join(" ");
|
|
308
|
+
const sep = cols.map((c) => "\u2500".repeat(widths[c])).join(" ");
|
|
309
|
+
console.log(` ${header}`);
|
|
310
|
+
console.log(` ${sep}`);
|
|
311
|
+
for (const row of rows) {
|
|
312
|
+
const line = cols.map((c) => String(row[c] ?? "").padEnd(widths[c])).join(" ");
|
|
313
|
+
console.log(` ${line}`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/commands/server.ts
|
|
318
|
+
function registerServer(program2) {
|
|
319
|
+
const server = program2.command("server").description("Manage and inspect servers");
|
|
320
|
+
server.command("list").description("List all servers the bot is in").action(async () => {
|
|
321
|
+
const fmt = resolveFormat(program2.opts().format);
|
|
322
|
+
const api = new DiscordAPI(requireToken());
|
|
323
|
+
const guilds = await api.listGuilds();
|
|
324
|
+
if (fmt !== "table") {
|
|
325
|
+
printResult(guilds, fmt);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const rows = guilds.map((g) => ({
|
|
329
|
+
id: g.id,
|
|
330
|
+
name: g.name,
|
|
331
|
+
owner: g.owner ? "yes" : "no"
|
|
332
|
+
}));
|
|
333
|
+
console.log("\nServers");
|
|
334
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
335
|
+
printResult(rows, fmt);
|
|
336
|
+
});
|
|
337
|
+
server.command("select").description("Set the default server").argument("<id>", "Server ID").action(async (id) => {
|
|
338
|
+
const api = new DiscordAPI(requireToken());
|
|
339
|
+
const guild = await api.getGuild(id);
|
|
340
|
+
setDefaultServer(guild.id, guild.name);
|
|
341
|
+
console.log(`Default server set to: ${guild.name} (${guild.id})`);
|
|
342
|
+
});
|
|
343
|
+
server.command("info").description("Show server details").action(async () => {
|
|
344
|
+
const fmt = resolveFormat(program2.opts().format);
|
|
345
|
+
const api = new DiscordAPI(requireToken());
|
|
346
|
+
const guildId = requireServer(program2.opts().server);
|
|
347
|
+
const guild = await api.getGuild(guildId);
|
|
348
|
+
if (fmt !== "table") {
|
|
349
|
+
printResult(guild, fmt);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const info = {
|
|
353
|
+
name: guild.name,
|
|
354
|
+
id: guild.id,
|
|
355
|
+
description: guild.description || "(none)",
|
|
356
|
+
members: guild.approximate_member_count ?? "?",
|
|
357
|
+
online: guild.approximate_presence_count ?? "?",
|
|
358
|
+
boosts: guild.premium_subscription_count,
|
|
359
|
+
boost_tier: guild.premium_tier
|
|
360
|
+
};
|
|
361
|
+
console.log(`
|
|
362
|
+
${guild.name}`);
|
|
363
|
+
console.log("\u2500".repeat(guild.name.length));
|
|
364
|
+
printResult(info, fmt);
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/utils/resolve.ts
|
|
369
|
+
async function resolveChannel(api, guildId, name) {
|
|
370
|
+
const channels = await api.listChannels(guildId);
|
|
371
|
+
const byId = channels.find((c) => c.id === name);
|
|
372
|
+
if (byId) return byId;
|
|
373
|
+
const clean = name.replace(/^#/, "");
|
|
374
|
+
const matches = channels.filter((c) => c.name.toLowerCase() === clean.toLowerCase());
|
|
375
|
+
if (matches.length === 1) return matches[0];
|
|
376
|
+
if (matches.length > 1) {
|
|
377
|
+
const names = matches.map((m) => `#${m.name} (${CHANNEL_TYPE_NAME[m.type] ?? "?"})`).join(", ");
|
|
378
|
+
console.error(`Ambiguous channel "${clean}". Matches: ${names}`);
|
|
379
|
+
process.exit(1);
|
|
380
|
+
}
|
|
381
|
+
console.error(`Channel "${name}" not found.`);
|
|
382
|
+
process.exit(3);
|
|
383
|
+
}
|
|
384
|
+
async function resolveCategory(api, guildId, name) {
|
|
385
|
+
const channels = await api.listChannels(guildId);
|
|
386
|
+
const byId = channels.find((c) => c.id === name && c.type === 4);
|
|
387
|
+
if (byId) return byId;
|
|
388
|
+
const matches = channels.filter(
|
|
389
|
+
(c) => c.type === 4 && c.name.toLowerCase() === name.toLowerCase()
|
|
390
|
+
);
|
|
391
|
+
if (matches.length === 1) return matches[0];
|
|
392
|
+
if (matches.length > 1) {
|
|
393
|
+
console.error(`Ambiguous category "${name}".`);
|
|
394
|
+
process.exit(1);
|
|
395
|
+
}
|
|
396
|
+
console.error(`Category "${name}" not found.`);
|
|
397
|
+
process.exit(3);
|
|
398
|
+
}
|
|
399
|
+
async function resolveRole(api, guildId, name) {
|
|
400
|
+
const roles = await api.listRoles(guildId);
|
|
401
|
+
const byId = roles.find((r) => r.id === name);
|
|
402
|
+
if (byId) return byId;
|
|
403
|
+
const clean = name.replace(/^@/, "");
|
|
404
|
+
const matches = roles.filter((r) => r.name.toLowerCase() === clean.toLowerCase());
|
|
405
|
+
if (matches.length === 1) return matches[0];
|
|
406
|
+
if (matches.length > 1) {
|
|
407
|
+
const names = matches.map((m) => `@${m.name}`).join(", ");
|
|
408
|
+
console.error(`Ambiguous role "${clean}". Matches: ${names}`);
|
|
409
|
+
process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
console.error(`Role "${name}" not found.`);
|
|
412
|
+
process.exit(3);
|
|
413
|
+
}
|
|
414
|
+
async function resolveMember(api, guildId, name) {
|
|
415
|
+
if (/^\d+$/.test(name)) {
|
|
416
|
+
try {
|
|
417
|
+
return await api.getMember(guildId, name);
|
|
418
|
+
} catch {
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
const members = await api.listMembers(guildId, 1e3);
|
|
422
|
+
const clean = name.replace(/^@/, "").toLowerCase();
|
|
423
|
+
const matches = members.filter((m) => {
|
|
424
|
+
const username = m.user?.username?.toLowerCase() ?? "";
|
|
425
|
+
const globalName = m.user?.global_name?.toLowerCase() ?? "";
|
|
426
|
+
const nick = m.nick?.toLowerCase() ?? "";
|
|
427
|
+
return username === clean || globalName === clean || nick === clean;
|
|
428
|
+
});
|
|
429
|
+
if (matches.length === 1) return matches[0];
|
|
430
|
+
if (matches.length > 1) {
|
|
431
|
+
const names = matches.map((m) => m.user?.username).join(", ");
|
|
432
|
+
console.error(`Ambiguous user "${name}". Matches: ${names}`);
|
|
433
|
+
process.exit(1);
|
|
434
|
+
}
|
|
435
|
+
console.error(`Member "${name}" not found.`);
|
|
436
|
+
process.exit(3);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// src/commands/channel.ts
|
|
440
|
+
function registerChannel(program2) {
|
|
441
|
+
const channel = program2.command("channel").description("Manage server channels");
|
|
442
|
+
channel.command("list").description("List all channels grouped by category").option("-n <count>", "Limit number of channels shown", parseInt).action(async (opts) => {
|
|
443
|
+
const fmt = resolveFormat(program2.opts().format);
|
|
444
|
+
const api = new DiscordAPI(requireToken());
|
|
445
|
+
const guildId = requireServer(program2.opts().server);
|
|
446
|
+
let channels = await api.listChannels(guildId);
|
|
447
|
+
if (opts.n) channels = channels.slice(0, opts.n);
|
|
448
|
+
if (fmt !== "table") {
|
|
449
|
+
printResult(channels, fmt);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
const categories = channels.filter((c) => c.type === 4).sort((a, b) => a.position - b.position);
|
|
453
|
+
const uncategorized = channels.filter(
|
|
454
|
+
(c) => c.type !== 4 && !c.parent_id
|
|
455
|
+
);
|
|
456
|
+
if (uncategorized.length > 0) {
|
|
457
|
+
console.log("\n (no category)");
|
|
458
|
+
for (const ch of uncategorized.sort((a, b) => a.position - b.position)) {
|
|
459
|
+
const type = CHANNEL_TYPE_NAME[ch.type] ?? "?";
|
|
460
|
+
console.log(` ${type === "text" ? "#" : "\u{1F50A}"} ${ch.name}`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
for (const cat of categories) {
|
|
464
|
+
console.log(`
|
|
465
|
+
${cat.name.toUpperCase()}`);
|
|
466
|
+
const children = channels.filter((c) => c.parent_id === cat.id && c.type !== 4).sort((a, b) => a.position - b.position);
|
|
467
|
+
for (const ch of children) {
|
|
468
|
+
const type = CHANNEL_TYPE_NAME[ch.type] ?? "?";
|
|
469
|
+
const prefix = type === "voice" || type === "stage" ? "\u{1F50A}" : "#";
|
|
470
|
+
const topic = ch.topic ? ` \u2014 ${ch.topic}` : "";
|
|
471
|
+
console.log(` ${prefix} ${ch.name}${topic}`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
console.log();
|
|
475
|
+
});
|
|
476
|
+
channel.command("create").description("Create a new channel").argument("<name>", "Channel name").option("--type <type>", "Channel type: text, voice, category, announcement, stage, forum", "text").option("--category <name>", "Parent category name or ID").option("--topic <topic>", "Channel topic").option("--dry-run", "Show what would be created without creating it").action(async (name, opts) => {
|
|
477
|
+
const fmt = resolveFormat(program2.opts().format);
|
|
478
|
+
const api = new DiscordAPI(requireToken());
|
|
479
|
+
const guildId = requireServer(program2.opts().server);
|
|
480
|
+
const channelType = CHANNEL_TYPE[opts.type];
|
|
481
|
+
if (channelType === void 0) {
|
|
482
|
+
console.error(`Unknown channel type: ${opts.type}. Use: ${Object.keys(CHANNEL_TYPE).join(", ")}`);
|
|
483
|
+
process.exit(2);
|
|
484
|
+
}
|
|
485
|
+
let parentId;
|
|
486
|
+
if (opts.category) {
|
|
487
|
+
const cat = await resolveCategory(api, guildId, opts.category);
|
|
488
|
+
parentId = cat.id;
|
|
489
|
+
}
|
|
490
|
+
if (opts.dryRun) {
|
|
491
|
+
const result = { action: "create_channel", name, type: opts.type, category: opts.category ?? null, topic: opts.topic ?? null };
|
|
492
|
+
printResult(result, fmt);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
const ch = await api.createChannel(guildId, {
|
|
496
|
+
name,
|
|
497
|
+
type: channelType,
|
|
498
|
+
parent_id: parentId,
|
|
499
|
+
topic: opts.topic
|
|
500
|
+
});
|
|
501
|
+
if (fmt !== "table") {
|
|
502
|
+
printResult(ch, fmt);
|
|
503
|
+
} else {
|
|
504
|
+
console.log(`Created #${ch.name} (${CHANNEL_TYPE_NAME[ch.type] ?? "?"}) \u2014 ${ch.id}`);
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
channel.command("delete").description("Delete a channel").argument("<name>", "Channel name or ID").option("--confirm", "Required to actually delete").action(async (name, opts) => {
|
|
508
|
+
const api = new DiscordAPI(requireToken());
|
|
509
|
+
const guildId = requireServer(program2.opts().server);
|
|
510
|
+
const ch = await resolveChannel(api, guildId, name);
|
|
511
|
+
if (!opts.confirm) {
|
|
512
|
+
console.error(`This will delete #${ch.name} (${ch.id}). Add --confirm to proceed.`);
|
|
513
|
+
process.exit(2);
|
|
514
|
+
}
|
|
515
|
+
await api.deleteChannel(ch.id);
|
|
516
|
+
console.log(`Deleted #${ch.name}`);
|
|
517
|
+
});
|
|
518
|
+
channel.command("rename").description("Rename a channel").argument("<channel>", "Channel name or ID").argument("<new-name>", "New channel name").option("--dry-run", "Show what would change").action(async (channelName, newName, opts) => {
|
|
519
|
+
const fmt = resolveFormat(program2.opts().format);
|
|
520
|
+
const api = new DiscordAPI(requireToken());
|
|
521
|
+
const guildId = requireServer(program2.opts().server);
|
|
522
|
+
const ch = await resolveChannel(api, guildId, channelName);
|
|
523
|
+
if (opts.dryRun) {
|
|
524
|
+
printResult({ action: "rename_channel", from: ch.name, to: newName, id: ch.id }, fmt);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
await api.modifyChannel(ch.id, { name: newName });
|
|
528
|
+
console.log(`Renamed #${ch.name} \u2192 #${newName}`);
|
|
529
|
+
});
|
|
530
|
+
channel.command("topic").description("Set a channel topic").argument("<channel>", "Channel name or ID").argument("<topic>", "New topic text").action(async (channelName, topic) => {
|
|
531
|
+
const fmt = resolveFormat(program2.opts().format);
|
|
532
|
+
const api = new DiscordAPI(requireToken());
|
|
533
|
+
const guildId = requireServer(program2.opts().server);
|
|
534
|
+
const ch = await resolveChannel(api, guildId, channelName);
|
|
535
|
+
const updated = await api.modifyChannel(ch.id, { topic });
|
|
536
|
+
if (fmt !== "table") {
|
|
537
|
+
printResult(updated, fmt);
|
|
538
|
+
} else {
|
|
539
|
+
console.log(`Set topic for #${ch.name}: ${topic}`);
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
channel.command("move").description("Move a channel to a category").argument("<channel>", "Channel name or ID").option("--category <name>", "Target category name or ID").option("--position <n>", "Position within category", parseInt).action(async (channelName, opts) => {
|
|
543
|
+
const fmt = resolveFormat(program2.opts().format);
|
|
544
|
+
const api = new DiscordAPI(requireToken());
|
|
545
|
+
const guildId = requireServer(program2.opts().server);
|
|
546
|
+
const ch = await resolveChannel(api, guildId, channelName);
|
|
547
|
+
const update = {};
|
|
548
|
+
if (opts.category) {
|
|
549
|
+
const cat = await resolveCategory(api, guildId, opts.category);
|
|
550
|
+
update.parent_id = cat.id;
|
|
551
|
+
}
|
|
552
|
+
if (opts.position !== void 0) {
|
|
553
|
+
update.position = opts.position;
|
|
554
|
+
}
|
|
555
|
+
if (Object.keys(update).length === 0) {
|
|
556
|
+
console.error("Specify --category and/or --position.");
|
|
557
|
+
process.exit(2);
|
|
558
|
+
}
|
|
559
|
+
const updated = await api.modifyChannel(ch.id, update);
|
|
560
|
+
if (fmt !== "table") {
|
|
561
|
+
printResult(updated, fmt);
|
|
562
|
+
} else {
|
|
563
|
+
console.log(`Moved #${ch.name}${opts.category ? ` \u2192 ${opts.category}` : ""}${opts.position !== void 0 ? ` (position ${opts.position})` : ""}`);
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// src/commands/role.ts
|
|
569
|
+
function registerRole(program2) {
|
|
570
|
+
const role = program2.command("role").description("Manage server roles");
|
|
571
|
+
role.command("list").description("List all roles").option("-n <count>", "Limit number of roles shown", parseInt).action(async (opts) => {
|
|
572
|
+
const fmt = resolveFormat(program2.opts().format);
|
|
573
|
+
const api = new DiscordAPI(requireToken());
|
|
574
|
+
const guildId = requireServer(program2.opts().server);
|
|
575
|
+
const roles = await api.listRoles(guildId);
|
|
576
|
+
const sorted = roles.sort((a, b) => b.position - a.position).slice(0, opts.n ?? roles.length);
|
|
577
|
+
if (fmt !== "table") {
|
|
578
|
+
printResult(sorted, fmt);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
const rows = sorted.map((r) => ({
|
|
582
|
+
name: r.name,
|
|
583
|
+
id: r.id,
|
|
584
|
+
color: r.color ? `#${r.color.toString(16).padStart(6, "0")}` : "(none)",
|
|
585
|
+
managed: r.managed ? "bot" : "",
|
|
586
|
+
position: r.position
|
|
587
|
+
}));
|
|
588
|
+
console.log("\nRoles");
|
|
589
|
+
console.log("\u2500\u2500\u2500\u2500\u2500");
|
|
590
|
+
printResult(rows, fmt);
|
|
591
|
+
});
|
|
592
|
+
role.command("create").description("Create a new role").argument("<name>", "Role name").option("--color <hex>", "Color hex (e.g. #ff5733)").option("--mentionable", "Allow anyone to mention this role").option("--dry-run", "Show what would be created").action(async (name, opts) => {
|
|
593
|
+
const fmt = resolveFormat(program2.opts().format);
|
|
594
|
+
const api = new DiscordAPI(requireToken());
|
|
595
|
+
const guildId = requireServer(program2.opts().server);
|
|
596
|
+
const data = { name };
|
|
597
|
+
if (opts.color) {
|
|
598
|
+
data.color = parseInt(opts.color.replace("#", ""), 16);
|
|
599
|
+
}
|
|
600
|
+
if (opts.mentionable) {
|
|
601
|
+
data.mentionable = true;
|
|
602
|
+
}
|
|
603
|
+
if (opts.dryRun) {
|
|
604
|
+
printResult({ action: "create_role", ...data }, fmt);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
const r = await api.createRole(guildId, data);
|
|
608
|
+
if (fmt !== "table") {
|
|
609
|
+
printResult(r, fmt);
|
|
610
|
+
} else {
|
|
611
|
+
console.log(`Created role @${r.name} \u2014 ${r.id}`);
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
role.command("delete").description("Delete a role").argument("<name>", "Role name or ID").option("--confirm", "Required to actually delete").action(async (name, opts) => {
|
|
615
|
+
const api = new DiscordAPI(requireToken());
|
|
616
|
+
const guildId = requireServer(program2.opts().server);
|
|
617
|
+
const r = await resolveRole(api, guildId, name);
|
|
618
|
+
if (!opts.confirm) {
|
|
619
|
+
console.error(`This will delete @${r.name} (${r.id}). Add --confirm to proceed.`);
|
|
620
|
+
process.exit(2);
|
|
621
|
+
}
|
|
622
|
+
await api.deleteRole(guildId, r.id);
|
|
623
|
+
console.log(`Deleted role @${r.name}`);
|
|
624
|
+
});
|
|
625
|
+
role.command("assign").description("Assign a role to a member").argument("<role>", "Role name or ID").argument("<user>", "Username or ID").action(async (roleName, userName) => {
|
|
626
|
+
const api = new DiscordAPI(requireToken());
|
|
627
|
+
const guildId = requireServer(program2.opts().server);
|
|
628
|
+
const r = await resolveRole(api, guildId, roleName);
|
|
629
|
+
const m = await resolveMember(api, guildId, userName);
|
|
630
|
+
await api.addRoleToMember(guildId, m.user.id, r.id);
|
|
631
|
+
console.log(`Assigned @${r.name} to ${m.user.username}`);
|
|
632
|
+
});
|
|
633
|
+
role.command("remove").description("Remove a role from a member").argument("<role>", "Role name or ID").argument("<user>", "Username or ID").action(async (roleName, userName) => {
|
|
634
|
+
const api = new DiscordAPI(requireToken());
|
|
635
|
+
const guildId = requireServer(program2.opts().server);
|
|
636
|
+
const r = await resolveRole(api, guildId, roleName);
|
|
637
|
+
const m = await resolveMember(api, guildId, userName);
|
|
638
|
+
await api.removeRoleFromMember(guildId, m.user.id, r.id);
|
|
639
|
+
console.log(`Removed @${r.name} from ${m.user.username}`);
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// src/commands/member.ts
|
|
644
|
+
function registerMember(program2) {
|
|
645
|
+
const member = program2.command("member").description("Manage server members");
|
|
646
|
+
member.command("list").description("List server members").option("-n, --limit <n>", "Max members to show", "100").action(async (opts) => {
|
|
647
|
+
const fmt = resolveFormat(program2.opts().format);
|
|
648
|
+
const api = new DiscordAPI(requireToken());
|
|
649
|
+
const guildId = requireServer(program2.opts().server);
|
|
650
|
+
const members = await api.listMembers(guildId, parseInt(opts.limit));
|
|
651
|
+
if (fmt !== "table") {
|
|
652
|
+
printResult(members, fmt);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
const rows = members.map((m) => ({
|
|
656
|
+
username: m.user?.username ?? "?",
|
|
657
|
+
display: m.user?.global_name ?? m.nick ?? "",
|
|
658
|
+
nick: m.nick ?? "",
|
|
659
|
+
roles: m.roles.length,
|
|
660
|
+
joined: m.joined_at?.slice(0, 10) ?? "?"
|
|
661
|
+
}));
|
|
662
|
+
console.log(`
|
|
663
|
+
Members (${members.length})`);
|
|
664
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
665
|
+
printResult(rows, fmt);
|
|
666
|
+
});
|
|
667
|
+
member.command("info").description("Show member details").argument("<user>", "Username or ID").action(async (userName) => {
|
|
668
|
+
const fmt = resolveFormat(program2.opts().format);
|
|
669
|
+
const api = new DiscordAPI(requireToken());
|
|
670
|
+
const guildId = requireServer(program2.opts().server);
|
|
671
|
+
const m = await resolveMember(api, guildId, userName);
|
|
672
|
+
if (fmt !== "table") {
|
|
673
|
+
printResult(m, fmt);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
const roles = await api.listRoles(guildId);
|
|
677
|
+
const memberRoles = m.roles.map((rid) => roles.find((r) => r.id === rid)?.name ?? rid).join(", ");
|
|
678
|
+
const info = {
|
|
679
|
+
username: m.user?.username ?? "?",
|
|
680
|
+
display_name: m.user?.global_name ?? "(none)",
|
|
681
|
+
nickname: m.nick ?? "(none)",
|
|
682
|
+
id: m.user?.id ?? "?",
|
|
683
|
+
roles: memberRoles || "(none)",
|
|
684
|
+
joined: m.joined_at?.slice(0, 10) ?? "?"
|
|
685
|
+
};
|
|
686
|
+
console.log(`
|
|
687
|
+
${m.user?.username ?? "Member"}`);
|
|
688
|
+
console.log("\u2500".repeat((m.user?.username ?? "Member").length));
|
|
689
|
+
printResult(info, fmt);
|
|
690
|
+
});
|
|
691
|
+
member.command("kick").description("Kick a member from the server").argument("<user>", "Username or ID").option("--reason <text>", "Reason for kick").option("--confirm", "Required to actually kick").action(async (userName, opts) => {
|
|
692
|
+
const api = new DiscordAPI(requireToken());
|
|
693
|
+
const guildId = requireServer(program2.opts().server);
|
|
694
|
+
const m = await resolveMember(api, guildId, userName);
|
|
695
|
+
if (!opts.confirm) {
|
|
696
|
+
console.error(`This will kick ${m.user.username} (${m.user.id}). Add --confirm to proceed.`);
|
|
697
|
+
process.exit(2);
|
|
698
|
+
}
|
|
699
|
+
await api.kickMember(guildId, m.user.id);
|
|
700
|
+
console.log(`Kicked ${m.user.username}${opts.reason ? ` \u2014 ${opts.reason}` : ""}`);
|
|
701
|
+
});
|
|
702
|
+
member.command("ban").description("Ban a member from the server").argument("<user>", "Username or ID").option("--reason <text>", "Reason for ban").option("--confirm", "Required to actually ban").action(async (userName, opts) => {
|
|
703
|
+
const api = new DiscordAPI(requireToken());
|
|
704
|
+
const guildId = requireServer(program2.opts().server);
|
|
705
|
+
const m = await resolveMember(api, guildId, userName);
|
|
706
|
+
if (!opts.confirm) {
|
|
707
|
+
console.error(`This will BAN ${m.user.username} (${m.user.id}). Add --confirm to proceed.`);
|
|
708
|
+
process.exit(2);
|
|
709
|
+
}
|
|
710
|
+
await api.banMember(guildId, m.user.id);
|
|
711
|
+
console.log(`Banned ${m.user.username}${opts.reason ? ` \u2014 ${opts.reason}` : ""}`);
|
|
712
|
+
});
|
|
713
|
+
member.command("nick").description("Change a member's nickname").argument("<user>", "Username or ID").argument("<nickname>", "New nickname").action(async (userName, nickname) => {
|
|
714
|
+
const fmt = resolveFormat(program2.opts().format);
|
|
715
|
+
const api = new DiscordAPI(requireToken());
|
|
716
|
+
const guildId = requireServer(program2.opts().server);
|
|
717
|
+
const m = await resolveMember(api, guildId, userName);
|
|
718
|
+
await api.modifyMember(guildId, m.user.id, { nick: nickname });
|
|
719
|
+
if (fmt !== "table") {
|
|
720
|
+
printResult({ action: "nick", user: m.user.username, nick: nickname }, fmt);
|
|
721
|
+
} else {
|
|
722
|
+
console.log(`Set nickname for ${m.user.username} \u2192 ${nickname}`);
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// src/commands/permission.ts
|
|
728
|
+
function parsePermissions(perms) {
|
|
729
|
+
let bits = 0n;
|
|
730
|
+
for (const p of perms.split(",")) {
|
|
731
|
+
const name = p.trim().toLowerCase();
|
|
732
|
+
const val = PERMISSION[name];
|
|
733
|
+
if (val === void 0) {
|
|
734
|
+
console.error(`Unknown permission: ${name}`);
|
|
735
|
+
console.error(`Available: ${Object.keys(PERMISSION).join(", ")}`);
|
|
736
|
+
process.exit(2);
|
|
737
|
+
}
|
|
738
|
+
bits |= val;
|
|
739
|
+
}
|
|
740
|
+
return bits;
|
|
741
|
+
}
|
|
742
|
+
function describePermissions(bitfield) {
|
|
743
|
+
const bits = BigInt(bitfield);
|
|
744
|
+
if (bits === 0n) return [];
|
|
745
|
+
return Object.entries(PERMISSION).filter(([, val]) => (bits & val) === val).map(([name]) => name);
|
|
746
|
+
}
|
|
747
|
+
function registerPermission(program2) {
|
|
748
|
+
const perm = program2.command("permission").alias("perm").description("Manage channel permissions");
|
|
749
|
+
perm.command("view").description("View permission overwrites for a channel").argument("<channel>", "Channel name or ID").action(async (channelName) => {
|
|
750
|
+
const fmt = resolveFormat(program2.opts().format);
|
|
751
|
+
const api = new DiscordAPI(requireToken());
|
|
752
|
+
const guildId = requireServer(program2.opts().server);
|
|
753
|
+
const ch = await resolveChannel(api, guildId, channelName);
|
|
754
|
+
const full = await api.getChannel(ch.id);
|
|
755
|
+
const overwrites = full.permission_overwrites ?? [];
|
|
756
|
+
const roles = await api.listRoles(guildId);
|
|
757
|
+
if (fmt !== "table") {
|
|
758
|
+
printResult(overwrites, fmt);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
if (overwrites.length === 0) {
|
|
762
|
+
console.log(`
|
|
763
|
+
#${ch.name}: no permission overwrites (inherits from category/server)`);
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
console.log(`
|
|
767
|
+
#${ch.name} \u2014 Permission Overwrites`);
|
|
768
|
+
console.log("\u2500".repeat(40));
|
|
769
|
+
for (const ow of overwrites) {
|
|
770
|
+
const target = ow.type === 0 ? roles.find((r) => r.id === ow.id)?.name ?? ow.id : `member:${ow.id}`;
|
|
771
|
+
const allowed = describePermissions(ow.allow);
|
|
772
|
+
const denied = describePermissions(ow.deny);
|
|
773
|
+
console.log(`
|
|
774
|
+
@${target} (${ow.type === 0 ? "role" : "member"})`);
|
|
775
|
+
if (allowed.length > 0) console.log(` \u2705 allow: ${allowed.join(", ")}`);
|
|
776
|
+
if (denied.length > 0) console.log(` \u274C deny: ${denied.join(", ")}`);
|
|
777
|
+
if (allowed.length === 0 && denied.length === 0) console.log(` (neutral)`);
|
|
778
|
+
}
|
|
779
|
+
console.log();
|
|
780
|
+
});
|
|
781
|
+
perm.command("set").description("Set permission overwrite for a role on a channel").argument("<channel>", "Channel name or ID").argument("<role>", "Role name or ID").option("--allow <perms>", "Comma-separated permissions to allow").option("--deny <perms>", "Comma-separated permissions to deny").option("--dry-run", "Show what would change").action(async (channelName, roleName, opts) => {
|
|
782
|
+
const fmt = resolveFormat(program2.opts().format);
|
|
783
|
+
const api = new DiscordAPI(requireToken());
|
|
784
|
+
const guildId = requireServer(program2.opts().server);
|
|
785
|
+
const ch = await resolveChannel(api, guildId, channelName);
|
|
786
|
+
const role = await resolveRole(api, guildId, roleName);
|
|
787
|
+
if (!opts.allow && !opts.deny) {
|
|
788
|
+
console.error("Specify --allow and/or --deny with comma-separated permissions.");
|
|
789
|
+
console.error(`Available: ${Object.keys(PERMISSION).join(", ")}`);
|
|
790
|
+
process.exit(2);
|
|
791
|
+
}
|
|
792
|
+
const allow = opts.allow ? parsePermissions(opts.allow) : 0n;
|
|
793
|
+
const deny = opts.deny ? parsePermissions(opts.deny) : 0n;
|
|
794
|
+
if (opts.dryRun) {
|
|
795
|
+
printResult({
|
|
796
|
+
action: "set_permission",
|
|
797
|
+
channel: ch.name,
|
|
798
|
+
role: role.name,
|
|
799
|
+
allow: opts.allow ?? "(none)",
|
|
800
|
+
deny: opts.deny ?? "(none)"
|
|
801
|
+
}, fmt);
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
await api.editChannelPermission(ch.id, role.id, {
|
|
805
|
+
allow: allow.toString(),
|
|
806
|
+
deny: deny.toString(),
|
|
807
|
+
type: 0
|
|
808
|
+
// role
|
|
809
|
+
});
|
|
810
|
+
if (fmt !== "table") {
|
|
811
|
+
printResult({ channel: ch.name, role: role.name, allow: allow.toString(), deny: deny.toString() }, fmt);
|
|
812
|
+
} else {
|
|
813
|
+
console.log(`Set permissions on #${ch.name} for @${role.name}`);
|
|
814
|
+
if (opts.allow) console.log(` \u2705 allow: ${opts.allow}`);
|
|
815
|
+
if (opts.deny) console.log(` \u274C deny: ${opts.deny}`);
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
perm.command("lock").description("Make a channel read-only for @everyone").argument("<channel>", "Channel name or ID").option("--dry-run", "Show what would change").action(async (channelName, opts) => {
|
|
819
|
+
const fmt = resolveFormat(program2.opts().format);
|
|
820
|
+
const api = new DiscordAPI(requireToken());
|
|
821
|
+
const guildId = requireServer(program2.opts().server);
|
|
822
|
+
const ch = await resolveChannel(api, guildId, channelName);
|
|
823
|
+
const deny = PERMISSION.send_messages | PERMISSION.send_messages_in_threads | PERMISSION.create_public_threads;
|
|
824
|
+
if (opts.dryRun) {
|
|
825
|
+
printResult({ action: "lock", channel: ch.name, deny_for: "@everyone" }, fmt);
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
await api.editChannelPermission(ch.id, guildId, {
|
|
829
|
+
allow: "0",
|
|
830
|
+
deny: deny.toString(),
|
|
831
|
+
type: 0
|
|
832
|
+
});
|
|
833
|
+
console.log(`Locked #${ch.name} \u2014 read-only for @everyone`);
|
|
834
|
+
});
|
|
835
|
+
perm.command("unlock").description("Remove read-only restriction for @everyone").argument("<channel>", "Channel name or ID").action(async (channelName) => {
|
|
836
|
+
const api = new DiscordAPI(requireToken());
|
|
837
|
+
const guildId = requireServer(program2.opts().server);
|
|
838
|
+
const ch = await resolveChannel(api, guildId, channelName);
|
|
839
|
+
await api.deleteChannelPermission(ch.id, guildId);
|
|
840
|
+
console.log(`Unlocked #${ch.name} \u2014 @everyone can send messages`);
|
|
841
|
+
});
|
|
842
|
+
perm.command("list").description("List all available permission names").action(() => {
|
|
843
|
+
const fmt = resolveFormat(program2.opts().format);
|
|
844
|
+
if (fmt !== "table") {
|
|
845
|
+
const perms = Object.entries(PERMISSION).map(([name, val]) => ({ name, bit: val.toString() }));
|
|
846
|
+
printResult(perms, fmt);
|
|
847
|
+
} else {
|
|
848
|
+
console.log("\nAvailable Permissions");
|
|
849
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
850
|
+
for (const name of Object.keys(PERMISSION)) {
|
|
851
|
+
console.log(` ${name}`);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// src/cli.ts
|
|
858
|
+
var program = new Command();
|
|
859
|
+
program.name("discli").description("discli \u2014 Discord server management CLI").version("0.1.0").option("--format <fmt>", "Output format: json, yaml, table, auto (auto = yaml when piped, table in terminal)", "auto").option("--server <id>", "Server ID override");
|
|
860
|
+
registerInit(program);
|
|
861
|
+
registerServer(program);
|
|
862
|
+
registerChannel(program);
|
|
863
|
+
registerRole(program);
|
|
864
|
+
registerMember(program);
|
|
865
|
+
registerPermission(program);
|
|
866
|
+
program.parse();
|
|
867
|
+
//# sourceMappingURL=cli.js.map
|