@tryty/easydc-js 1.1.0 → 1.2.2

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.
@@ -0,0 +1,215 @@
1
+ 'use strict';
2
+ const { BotError } = require('./errors');
3
+ const { parse } = require('./parser');
4
+ const { Context } = require('./context');
5
+
6
+ const PERM_RE = /missing (permissions|access)|50013|50001/i;
7
+ const GROUP_TITLES = {
8
+ basic: 'Dasar (tanpa kirim apa pun)',
9
+ db: 'Database',
10
+ send: 'Kirim pesan ke Discord (embed, tombol, select, file, poll)',
11
+ full: 'Penuh (membuat lalu menghapus channel/role/thread sementara)',
12
+ };
13
+
14
+ /**
15
+ * Pasang command tes otomatis untuk dijalankan di Discord asli (khusus owner).
16
+ * !selftest -> tes aman: fungsi dasar, database, dan pengiriman pesan uji
17
+ * !selftest full -> ditambah tes yang membuat lalu menghapus channel & role sementara
18
+ * Dengan { ui: true } juga didaftarkan /tesauto (autocomplete) dan 2 menu klik-kanan untuk dicoba manual.
19
+ */
20
+ function installSelfTest(bot, { name = 'selftest', aliases = [], ui = false } = {}) {
21
+ if (!bot.options.owners.length) throw new BotError('selfTestCommand butuh owners: ["ID_KAMU"] pada new Bot({...})');
22
+ bot.variables({ __selftest: 0 });
23
+
24
+ // Komponen yang dikirim saat tes: diklik manual untuk membuktikan handler tombol/select/modal
25
+ bot.button({ customId: 'selftest_btn', code: '$ephemeral ✅ Tombol berfungsi! Ditekan oleh $username' });
26
+ bot.button({ customId: 'selftest_modal', code: '$addModal[selftest_form;Tes Modal]$addTextInput[teks;Ketik apa saja;short;Halo]' });
27
+ bot.modal({ customId: 'selftest_form', code: '$ephemeral ✅ Modal berfungsi! Kamu mengetik: $modalValue[teks]' });
28
+ bot.select({ customId: 'selftest_sel', code: '$ephemeral ✅ Select menu berfungsi! Pilihanmu: $interactionValues' });
29
+ bot.select({ customId: 'selftest_usr', code: '$ephemeral ✅ Select user berfungsi! Terpilih: $interactionValues' });
30
+ if (ui) {
31
+ bot.slashCommand({
32
+ name: 'tesauto', description: 'Uji autocomplete',
33
+ options: [{ name: 'buah', description: 'Ketik untuk mencari', autocomplete: true }],
34
+ code: '$ephemeral ✅ Slash berfungsi! Kamu memilih: $option[buah]',
35
+ });
36
+ bot.autocomplete({ command: 'tesauto', option: 'buah', code: '$addChoice[Apel;apel]$addChoice[Jeruk;jeruk]$addChoice[Mangga;mangga]$addChoice[Anda mengetik: $focusedValue;ketikan]' });
37
+ bot.contextMenu({ name: 'Tes Profil', menu: 'user', code: '$ephemeral ✅ Menu user berfungsi! Sasaran: $targetName ($targetID)' });
38
+ bot.contextMenu({ name: 'Tes Pesan', menu: 'message', code: '$ephemeral ✅ Menu pesan berfungsi! Isi: $targetContent' });
39
+ }
40
+
41
+ bot.addFunction({
42
+ name: '$runSelfTest',
43
+ params: 1,
44
+ description: 'Jalankan tes otomatis (dipakai oleh bot.selfTestCommand)',
45
+ run: (ctx, [mode]) => runSelfTest(bot, ctx, mode.trim().toLowerCase() === 'full'),
46
+ });
47
+ return bot.command({
48
+ name,
49
+ aliases,
50
+ code: '$onlyOwner[❌ Perintah ini hanya untuk owner bot.]$cooldown[10s;⏳ Tunggu %time% sebelum mengulang tes.]$runSelfTest[$message[1]]',
51
+ });
52
+ }
53
+
54
+ async function runSelfTest(bot, ctx, full) {
55
+ const results = [];
56
+ const record = (group, label, status, note = '') => results.push({ group, label, status, note });
57
+ const step = async (group, label, fn) => {
58
+ try {
59
+ await fn();
60
+ record(group, label, 'ok');
61
+ } catch (e) {
62
+ const msg = String(e?.message ?? e);
63
+ record(group, label, PERM_RE.test(msg) ? 'warn' : 'fail', PERM_RE.test(msg) ? `bot kurang izin (${msg})` : msg);
64
+ }
65
+ };
66
+ const fresh = () => new Context(bot, {
67
+ kind: 'message', message: ctx.message, guild: ctx.guild, channel: ctx.channel,
68
+ member: ctx.member, user: ctx.user, command: ctx.command, args: [], rawArgs: '',
69
+ });
70
+ const port = async (code) => {
71
+ const c = fresh();
72
+ const out = await bot.interpreter.run(parse(code, bot.registry), c);
73
+ return { out, c };
74
+ };
75
+ const expectOut = (group, label, code, expected) => step(group, label, async () => {
76
+ const { out } = await port(code);
77
+ const ok = expected instanceof RegExp ? expected.test(out) : out === expected;
78
+ if (!ok) throw new Error(`hasil "${out}", diharapkan ${expected}`);
79
+ });
80
+
81
+ await ctx.sendPlain(`⏳ Menjalankan tes${full ? ' penuh' : ''}... (beberapa pesan uji akan muncul lalu dihapus)`);
82
+
83
+ // ---- Dasar ------------------------------------------------------------------
84
+ const g = 'basic';
85
+ await expectOut(g, '$math', '$math[6*7]', '42');
86
+ await expectOut(g, '$if / $else', '$if[1==1]ya$else tidak$endif', 'ya');
87
+ await expectOut(g, '$toUpperCase', '$toUpperCase[halo]', 'HALO');
88
+ await expectOut(g, '$replaceText', '$replaceText[a-b;-;+]', 'a+b');
89
+ await expectOut(g, '$date', '$date[YYYY]', /^\d{4}$/);
90
+ await expectOut(g, '$random', '$random[1;3]', /^[123]$/);
91
+ await expectOut(g, '$username', '$username', ctx.user.username);
92
+ await expectOut(g, '$botName', '$botName', bot.client.user.username);
93
+ await expectOut(g, '$ping', '$ping', /^-?\d+$/);
94
+ await expectOut(g, '$uptime', '$uptime', /\d+s/);
95
+ await expectOut(g, '$serverCount', '$serverCount', /^\d+$/);
96
+ if (ctx.guild) {
97
+ await expectOut(g, '$serverName', '$serverName', ctx.guild.name);
98
+ await expectOut(g, '$memberCount', '$memberCount', /^\d+$/);
99
+ await expectOut(g, '$channelName', '$channelName', ctx.channel.name ?? '');
100
+ await expectOut(g, '$isAdmin', '$isAdmin', /^(true|false)$/);
101
+ await expectOut(g, '$roleCount', '$roleCount', /^\d+$/);
102
+ } else {
103
+ record(g, 'fungsi server', 'warn', 'dilewati: jalankan di dalam server, bukan DM');
104
+ }
105
+
106
+ // ---- Database ---------------------------------------------------------------
107
+ const d = 'db';
108
+ const dbName = bot.db?.constructor?.name ?? 'adapter';
109
+ await step(d, `simpan & baca (${dbName})`, async () => {
110
+ await bot.setVar('global', '__selftest', '123', null);
111
+ const v = await bot.getVar('global', '__selftest', null);
112
+ if (v !== '123') throw new Error(`dibaca "${v}", diharapkan "123"`);
113
+ });
114
+ await step(d, 'tambah angka ($inc...Var)', async () => {
115
+ const v = await bot.incVar('global', '__selftest', 7, null);
116
+ if (v !== '130') throw new Error(`hasil "${v}", diharapkan "130"`);
117
+ });
118
+ await step(d, 'reset ke nilai awal', async () => {
119
+ await bot.resetVar('global', '__selftest', null);
120
+ const v = await bot.getVar('global', '__selftest', null);
121
+ if (v !== '0') throw new Error(`hasil "${v}", diharapkan "0"`);
122
+ });
123
+
124
+ // ---- Kirim pesan ------------------------------------------------------------
125
+ const s = 'send';
126
+ const sendCase = (label, code, text, keep) => step(s, label, async () => {
127
+ const { c } = await port(code);
128
+ await c.respond(text);
129
+ if (!c.sent.length) throw new Error('tidak ada pesan yang terkirim');
130
+ if (!keep) for (const m of c.sent) await m?.delete?.().catch(() => {});
131
+ });
132
+ await sendCase(
133
+ 'embed + tombol + select menu + select user',
134
+ '$title[Selftest]$description[Klik komponen di bawah untuk menguji handler (tidak dihapus otomatis).]$color[blurple]'
135
+ + '$addField[A;1;true]$addField[B;2;true]$footer[easydc selftest]$addTimestamp'
136
+ + '$addButton[Tes tombol;primary;selftest_btn]$addButton[Tes modal;secondary;selftest_modal]'
137
+ + '$newRow$addSelectMenu[selftest_sel;Pilih salah satu]$addSelectOption[Satu;1;Opsi pertama]$addSelectOption[Dua;2]'
138
+ + '$newRow$addUserSelect[selftest_usr;Pilih user]',
139
+ '🧪 Pesan uji komponen: klik tiap komponen untuk memastikan jawabannya muncul.',
140
+ true,
141
+ );
142
+ await sendCase('lampiran file teks', '$addTextFile[selftest.txt;halo dari easydc]', 'Uji lampiran', false);
143
+ await sendCase('polling', '$poll[Selftest berhasil?;1;false;Ya;Tidak]', '', false);
144
+
145
+ // ---- Penuh (membuat & menghapus objek sementara) ----------------------------------
146
+ if (full) {
147
+ const f = 'full';
148
+ if (!ctx.guild) {
149
+ record(f, 'tes penuh', 'fail', 'harus dijalankan di dalam server');
150
+ } else {
151
+ let chId = null;
152
+ try {
153
+ await step(f, '$createChannel', async () => { chId = (await port('$createChannel[selftest-sementara;text]')).out; if (!/^\d+$/.test(chId)) throw new Error(`ID aneh "${chId}"`); });
154
+ if (chId) {
155
+ await expectOut(f, '$setChannelName', `$setChannelName[${chId};selftest-ubah]`, '');
156
+ await expectOut(f, '$setChannelTopic', `$setChannelTopic[${chId};topik uji]`, '');
157
+ await expectOut(f, '$lockChannel + $unlockChannel', `$lockChannel[${chId}]$unlockChannel[${chId}]`, '');
158
+ await expectOut(f, '$createInvite', `$createInvite[${chId};60;1]`, /^https:\/\/discord\.(gg|com)\//);
159
+ await expectOut(f, '$channelType', `$channelType[${chId}]`, 'text');
160
+ await expectOut(f, '$sendMessage', `$sendMessage[${chId};pesan uji]`, '');
161
+ let mid = null;
162
+ await step(f, 'kirim pesan uji', async () => {
163
+ const ch = await bot.client.channels.fetch(chId);
164
+ mid = (await ch.send('pesan awal')).id;
165
+ });
166
+ if (mid) {
167
+ await expectOut(f, '$editMessage', `$editMessage[${mid};sudah diedit;${chId}]`, '');
168
+ await expectOut(f, '$messageContentOf', `$messageContentOf[${mid};${chId}]`, 'sudah diedit');
169
+ await expectOut(f, '$pinMessage + $unpinMessage', `$pinMessage[${mid};${chId}]$unpinMessage[${mid};${chId}]`, '');
170
+ await expectOut(f, '$addReaction', `$addReaction[${mid};👍;${chId}]`, '');
171
+ await expectOut(f, '$createThread', `$createThread[utas-uji;${mid};;${chId}]`, /^\d+$/);
172
+ await expectOut(f, '$deleteMessage', `$deleteMessage[${mid};${chId}]`, '');
173
+ }
174
+ }
175
+ } finally {
176
+ if (chId) {
177
+ await step(f, '$deleteChannel (bersih-bersih)', async () => {
178
+ await port(`$deleteChannel[${chId}]`);
179
+ });
180
+ }
181
+ }
182
+
183
+ let roleId = null;
184
+ try {
185
+ await step(f, '$createRole', async () => { roleId = (await port('$createRole[selftest-role;#5865F2]')).out; if (!/^\d+$/.test(roleId)) throw new Error(`ID aneh "${roleId}"`); });
186
+ if (roleId) {
187
+ await expectOut(f, '$setRoleName', `$setRoleName[${roleId};selftest-ubah]`, '');
188
+ await expectOut(f, '$setRoleColor', `$setRoleColor[${roleId};red]`, '');
189
+ }
190
+ } finally {
191
+ if (roleId) await step(f, '$deleteRole (bersih-bersih)', async () => { await port(`$deleteRole[${roleId}]`); });
192
+ }
193
+ await expectOut(f, '$isBanned (bot sendiri)', `$isBanned[${bot.client.user.id}]`, 'false');
194
+ await expectOut(f, '$banCount', '$banCount', /^\d+$/);
195
+ }
196
+ }
197
+
198
+ // ---- Laporan ------------------------------------------------------------------
199
+ const icon = { ok: '✅', fail: '❌', warn: '⚠️' };
200
+ const lines = [];
201
+ for (const group of Object.keys(GROUP_TITLES)) {
202
+ const rows = results.filter((r) => r.group === group);
203
+ if (!rows.length) continue;
204
+ lines.push(`**${GROUP_TITLES[group]}**`);
205
+ for (const r of rows) lines.push(`${icon[r.status]} ${r.label}${r.note ? ` — ${r.note}` : ''}`);
206
+ lines.push('');
207
+ }
208
+ const count = (st) => results.filter((r) => r.status === st).length;
209
+ lines.push(`**Ringkasan:** ${count('ok')} lulus, ${count('fail')} gagal, ${count('warn')} peringatan.`);
210
+ lines.push(full ? '' : 'Ketik `selftest full` untuk tes yang membuat & menghapus channel/role sementara.');
211
+ lines.push('Komponen di pesan uji (tombol, modal, select) perlu diklik manual.');
212
+ return lines.join('\n').replace(/\n{3,}/g, '\n\n');
213
+ }
214
+
215
+ module.exports = { installSelfTest, runSelfTest };
package/voice.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ export interface LavalinkNode {
2
+ name: string;
3
+ url: string;
4
+ auth: string;
5
+ secure?: boolean;
6
+ }
7
+
8
+ export interface VoicePluginOptions {
9
+ /** Daftar node Lavalink. Kosongkan bila hanya butuh koneksi voice channel dasar ($joinVC / $leaveVC). */
10
+ nodes?: LavalinkNode[];
11
+ /** Mesin pencari default bila query bukan link. Bawaan: 'ytsearch' */
12
+ defaultSearchEngine?: 'ytsearch' | 'ytmsearch' | 'scsearch' | 'spsearch' | string;
13
+ shoukakuOptions?: Record<string, unknown>;
14
+ driver?: unknown;
15
+ }
16
+
17
+ export class VoiceManager {
18
+ constructor(bot: unknown, options?: VoicePluginOptions);
19
+ initShoukaku(client: unknown): void;
20
+ destroy(): Promise<void>;
21
+ }
22
+
23
+ /**
24
+ * Plugin:
25
+ * bot.use(voice({
26
+ * nodes: [{ name: 'main', url: 'localhost:2333', auth: 'sandi', secure: false }]
27
+ * }))
28
+ */
29
+ export function voice(options?: VoicePluginOptions): { name: string; install(bot: unknown): void };