@roidev/kachina-md 1.0.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/LICENSE +21 -0
- package/README.md +461 -0
- package/lib/client/Client.js +257 -0
- package/lib/handlers/PluginHandler.js +190 -0
- package/lib/helpers/database.js +117 -0
- package/lib/helpers/index.js +74 -0
- package/lib/helpers/logger.js +66 -0
- package/lib/helpers/serialize.js +120 -0
- package/lib/helpers/sticker.js +72 -0
- package/lib/index.js +30 -0
- package/package.json +67 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import makeWASocket, {
|
|
2
|
+
DisconnectReason,
|
|
3
|
+
useMultiFileAuthState,
|
|
4
|
+
makeInMemoryStore,
|
|
5
|
+
makeCacheableSignalKeyStore,
|
|
6
|
+
fetchLatestBaileysVersion
|
|
7
|
+
} from 'baileys';
|
|
8
|
+
import { Boom } from '@hapi/boom';
|
|
9
|
+
import pino from 'pino';
|
|
10
|
+
import qrcode from 'qrcode-terminal';
|
|
11
|
+
import EventEmitter from 'events';
|
|
12
|
+
import { serialize } from '../helpers/serialize.js';
|
|
13
|
+
import { PluginHandler } from '../handlers/PluginHandler.js';
|
|
14
|
+
|
|
15
|
+
export class Client extends EventEmitter {
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
super();
|
|
18
|
+
|
|
19
|
+
this.config = {
|
|
20
|
+
sessionId: options.sessionId || 'kachina-session',
|
|
21
|
+
phoneNumber: options.phoneNumber || '',
|
|
22
|
+
printQRInTerminal: options.printQRInTerminal !== false,
|
|
23
|
+
loginMethod: options.loginMethod || 'qr', // 'qr' or 'pairing'
|
|
24
|
+
browser: options.browser || ['Kachina-MD', 'Chrome', '1.0.0'],
|
|
25
|
+
logger: options.logger || pino({ level: 'silent' }),
|
|
26
|
+
...options
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
this.sock = null;
|
|
30
|
+
this.store = null;
|
|
31
|
+
this.isReady = false;
|
|
32
|
+
this.pluginHandler = new PluginHandler(this);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async start() {
|
|
36
|
+
const { state, saveCreds } = await useMultiFileAuthState(this.config.sessionId);
|
|
37
|
+
const { version } = await fetchLatestBaileysVersion();
|
|
38
|
+
|
|
39
|
+
if (this.config.useStore) {
|
|
40
|
+
this.store = makeInMemoryStore({
|
|
41
|
+
logger: pino().child({ level: 'silent', stream: 'store' })
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.sock = makeWASocket({
|
|
46
|
+
version,
|
|
47
|
+
auth: {
|
|
48
|
+
creds: state.creds,
|
|
49
|
+
keys: makeCacheableSignalKeyStore(state.keys, this.config.logger)
|
|
50
|
+
},
|
|
51
|
+
logger: this.config.logger,
|
|
52
|
+
printQRInTerminal: this.config.printQRInTerminal && this.config.loginMethod === 'qr',
|
|
53
|
+
browser: this.config.browser,
|
|
54
|
+
getMessage: async (key) => {
|
|
55
|
+
if (this.store) {
|
|
56
|
+
const msg = await this.store.loadMessage(key.remoteJid, key.id);
|
|
57
|
+
return msg?.message || undefined;
|
|
58
|
+
}
|
|
59
|
+
return { conversation: '' };
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (this.store) {
|
|
64
|
+
this.store.bind(this.sock.ev);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this.sock.ev.on('creds.update', saveCreds);
|
|
68
|
+
|
|
69
|
+
this.sock.ev.on('connection.update', async (update) => {
|
|
70
|
+
await this.handleConnectionUpdate(update);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
this.sock.ev.on('messages.upsert', async ({ messages, type }) => {
|
|
74
|
+
if (type !== 'notify') return;
|
|
75
|
+
|
|
76
|
+
for (const message of messages) {
|
|
77
|
+
const m = await serialize(message, this.sock);
|
|
78
|
+
this.emit('message', m);
|
|
79
|
+
|
|
80
|
+
// Auto plugin handler
|
|
81
|
+
if (this.pluginHandler.isLoaded && m.body?.startsWith(this.config.prefix || '!')) {
|
|
82
|
+
await this.pluginHandler.execute(m);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
this.sock.ev.on('group-participants.update', (update) => {
|
|
88
|
+
this.emit('group.update', update);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
this.sock.ev.on('groups.update', (updates) => {
|
|
92
|
+
this.emit('groups.update', updates);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
this.sock.ev.on('call', (calls) => {
|
|
96
|
+
this.emit('call', calls);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return this.sock;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async handleConnectionUpdate(update) {
|
|
103
|
+
const { connection, lastDisconnect, qr } = update;
|
|
104
|
+
|
|
105
|
+
if (qr && this.config.loginMethod === 'qr') {
|
|
106
|
+
qrcode.generate(qr, { small: true });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (connection === 'close') {
|
|
110
|
+
const shouldReconnect = (lastDisconnect?.error instanceof Boom)
|
|
111
|
+
? lastDisconnect.error.output.statusCode !== DisconnectReason.loggedOut
|
|
112
|
+
: true;
|
|
113
|
+
|
|
114
|
+
if (shouldReconnect) {
|
|
115
|
+
this.emit('reconnecting');
|
|
116
|
+
await this.start();
|
|
117
|
+
} else {
|
|
118
|
+
this.emit('logout');
|
|
119
|
+
}
|
|
120
|
+
} else if (connection === 'open') {
|
|
121
|
+
this.isReady = true;
|
|
122
|
+
this.user = this.sock.user;
|
|
123
|
+
this.emit('ready', this.user);
|
|
124
|
+
|
|
125
|
+
// Request pairing code if needed
|
|
126
|
+
if (!this.sock.authState.creds.registered && this.config.loginMethod === 'pairing') {
|
|
127
|
+
if (this.config.phoneNumber) {
|
|
128
|
+
setTimeout(async () => {
|
|
129
|
+
const code = await this.sock.requestPairingCode(this.config.phoneNumber);
|
|
130
|
+
this.emit('pairing.code', code);
|
|
131
|
+
}, 3000);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} else if (connection === 'connecting') {
|
|
135
|
+
this.emit('connecting');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Helper methods
|
|
140
|
+
async sendMessage(jid, content, options = {}) {
|
|
141
|
+
return await this.sock.sendMessage(jid, content, options);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async sendText(jid, text, options = {}) {
|
|
145
|
+
return await this.sendMessage(jid, { text }, options);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async sendImage(jid, buffer, caption = '', options = {}) {
|
|
149
|
+
const content = {
|
|
150
|
+
image: buffer,
|
|
151
|
+
...options
|
|
152
|
+
};
|
|
153
|
+
if (caption) content.caption = caption;
|
|
154
|
+
return await this.sendMessage(jid, content, options);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async sendVideo(jid, buffer, caption = '', options = {}) {
|
|
158
|
+
const content = {
|
|
159
|
+
video: buffer,
|
|
160
|
+
...options
|
|
161
|
+
};
|
|
162
|
+
if (caption) content.caption = caption;
|
|
163
|
+
return await this.sendMessage(jid, content, options);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async sendAudio(jid, buffer, options = {}) {
|
|
167
|
+
const content = {
|
|
168
|
+
audio: buffer,
|
|
169
|
+
mimetype: options.mimetype || 'audio/mp4',
|
|
170
|
+
ptt: options.ptt || false, // Push to talk (voice note)
|
|
171
|
+
...options
|
|
172
|
+
};
|
|
173
|
+
return await this.sendMessage(jid, content, options);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async sendDocument(jid, buffer, filename, mimetype, options = {}) {
|
|
177
|
+
return await this.sendMessage(jid, {
|
|
178
|
+
document: buffer,
|
|
179
|
+
fileName: filename,
|
|
180
|
+
mimetype,
|
|
181
|
+
...options
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async sendSticker(jid, buffer, options = {}) {
|
|
186
|
+
return await this.sendMessage(jid, {
|
|
187
|
+
sticker: buffer,
|
|
188
|
+
...options
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async sendContact(jid, contacts, options = {}) {
|
|
193
|
+
// contacts = [{ displayName, vcard }]
|
|
194
|
+
return await this.sendMessage(jid, {
|
|
195
|
+
contacts: { contacts },
|
|
196
|
+
...options
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async sendLocation(jid, latitude, longitude, options = {}) {
|
|
201
|
+
return await this.sendMessage(jid, {
|
|
202
|
+
location: { degreesLatitude: latitude, degreesLongitude: longitude },
|
|
203
|
+
...options
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async sendPoll(jid, name, values, options = {}) {
|
|
208
|
+
return await this.sendMessage(jid, {
|
|
209
|
+
poll: {
|
|
210
|
+
name,
|
|
211
|
+
values,
|
|
212
|
+
selectableCount: options.selectableCount || 1
|
|
213
|
+
},
|
|
214
|
+
...options
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async sendReact(jid, messageKey, emoji) {
|
|
219
|
+
return await this.sendMessage(jid, {
|
|
220
|
+
react: { text: emoji, key: messageKey }
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async groupMetadata(jid) {
|
|
225
|
+
return await this.sock.groupMetadata(jid);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async groupParticipantsUpdate(jid, participants, action) {
|
|
229
|
+
return await this.sock.groupParticipantsUpdate(jid, participants, action);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async groupUpdateSubject(jid, subject) {
|
|
233
|
+
return await this.sock.groupUpdateSubject(jid, subject);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async groupUpdateDescription(jid, description) {
|
|
237
|
+
return await this.sock.groupUpdateDescription(jid, description);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async loadPlugin(path) {
|
|
241
|
+
await this.pluginHandler.load(path);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async loadPlugins(directory) {
|
|
245
|
+
await this.pluginHandler.loadAll(directory);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
get prefix() {
|
|
249
|
+
return this.config.prefix || '!';
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
set prefix(prefix) {
|
|
253
|
+
this.config.prefix = prefix;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export default Client;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
export class PluginHandler {
|
|
6
|
+
constructor(client) {
|
|
7
|
+
this.client = client;
|
|
8
|
+
this.plugins = new Map();
|
|
9
|
+
this.commands = new Map();
|
|
10
|
+
this.isLoaded = false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async load(pluginPath) {
|
|
14
|
+
try {
|
|
15
|
+
const module = await import(`file://${pluginPath}?update=${Date.now()}`);
|
|
16
|
+
|
|
17
|
+
if (!module.default && !module.handler) {
|
|
18
|
+
throw new Error('Plugin must export default or handler');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const plugin = module.default || module.handler;
|
|
22
|
+
|
|
23
|
+
// Validate plugin
|
|
24
|
+
if (!plugin.name) {
|
|
25
|
+
plugin.name = path.basename(pluginPath, '.js');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!plugin.exec && !plugin.execute) {
|
|
29
|
+
throw new Error('Plugin must have exec or execute function');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Normalize commands
|
|
33
|
+
const commands = Array.isArray(plugin.command)
|
|
34
|
+
? plugin.command
|
|
35
|
+
: plugin.commands
|
|
36
|
+
? Array.isArray(plugin.commands)
|
|
37
|
+
? plugin.commands
|
|
38
|
+
: [plugin.commands]
|
|
39
|
+
: [plugin.name];
|
|
40
|
+
|
|
41
|
+
plugin.commands = commands.map(cmd => cmd.toLowerCase());
|
|
42
|
+
|
|
43
|
+
// Store plugin
|
|
44
|
+
this.plugins.set(plugin.name, plugin);
|
|
45
|
+
|
|
46
|
+
// Map commands to plugin
|
|
47
|
+
for (const cmd of plugin.commands) {
|
|
48
|
+
this.commands.set(cmd, plugin);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return plugin;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error(`Failed to load plugin ${pluginPath}:`, error.message);
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async loadAll(directory) {
|
|
59
|
+
if (!fs.existsSync(directory)) {
|
|
60
|
+
console.warn(`Plugin directory not found: ${directory}`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const files = this.findFiles(directory, '.js');
|
|
65
|
+
|
|
66
|
+
let loaded = 0;
|
|
67
|
+
for (const file of files) {
|
|
68
|
+
const plugin = await this.load(file);
|
|
69
|
+
if (plugin) loaded++;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.isLoaded = true;
|
|
73
|
+
console.log(`Loaded ${loaded}/${files.length} plugins`);
|
|
74
|
+
|
|
75
|
+
return loaded;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
findFiles(dir, ext) {
|
|
79
|
+
let results = [];
|
|
80
|
+
const list = fs.readdirSync(dir);
|
|
81
|
+
|
|
82
|
+
for (const file of list) {
|
|
83
|
+
const filePath = path.join(dir, file);
|
|
84
|
+
const stat = fs.statSync(filePath);
|
|
85
|
+
|
|
86
|
+
if (stat && stat.isDirectory()) {
|
|
87
|
+
results = results.concat(this.findFiles(filePath, ext));
|
|
88
|
+
} else if (file.endsWith(ext)) {
|
|
89
|
+
results.push(filePath);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return results;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async execute(m) {
|
|
97
|
+
if (!m.body) return;
|
|
98
|
+
|
|
99
|
+
// Parse command
|
|
100
|
+
const prefix = this.client.config.prefix || '!';
|
|
101
|
+
if (!m.body.startsWith(prefix)) return;
|
|
102
|
+
|
|
103
|
+
const [cmdName, ...args] = m.body.slice(prefix.length).trim().split(/\s+/);
|
|
104
|
+
const command = cmdName.toLowerCase();
|
|
105
|
+
|
|
106
|
+
// Find plugin
|
|
107
|
+
const plugin = this.commands.get(command);
|
|
108
|
+
if (!plugin) return;
|
|
109
|
+
|
|
110
|
+
// Check conditions
|
|
111
|
+
if (plugin.owner && !this.isOwner(m.sender)) {
|
|
112
|
+
return await m.reply('⚠️ This command is for owner only!');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (plugin.group && !m.isGroup) {
|
|
116
|
+
return await m.reply('⚠️ This command can only be used in groups!');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (plugin.private && m.isGroup) {
|
|
120
|
+
return await m.reply('⚠️ This command can only be used in private chat!');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (plugin.admin && m.isGroup) {
|
|
124
|
+
const groupMetadata = await this.client.groupMetadata(m.chat);
|
|
125
|
+
const participants = groupMetadata.participants;
|
|
126
|
+
const admins = participants.filter(p => p.admin).map(p => p.id);
|
|
127
|
+
|
|
128
|
+
if (!admins.includes(m.sender)) {
|
|
129
|
+
return await m.reply('⚠️ This command is for group admins only!');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (plugin.botAdmin && m.isGroup) {
|
|
134
|
+
const groupMetadata = await this.client.groupMetadata(m.chat);
|
|
135
|
+
const participants = groupMetadata.participants;
|
|
136
|
+
const admins = participants.filter(p => p.admin).map(p => p.id);
|
|
137
|
+
const botNumber = this.client.user.id.split(':')[0] + '@s.whatsapp.net';
|
|
138
|
+
|
|
139
|
+
if (!admins.includes(botNumber)) {
|
|
140
|
+
return await m.reply('⚠️ Bot must be admin to use this command!');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Execute plugin
|
|
145
|
+
try {
|
|
146
|
+
const exec = plugin.exec || plugin.execute;
|
|
147
|
+
await exec({
|
|
148
|
+
client: this.client,
|
|
149
|
+
m,
|
|
150
|
+
args,
|
|
151
|
+
command,
|
|
152
|
+
prefix,
|
|
153
|
+
sock: this.client.sock
|
|
154
|
+
});
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error(`Error executing ${command}:`, error);
|
|
157
|
+
await m.reply(`❌ Error: ${error.message}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
isOwner(jid) {
|
|
162
|
+
const owners = this.client.config.owners || this.client.config.owner || [];
|
|
163
|
+
const ownerList = Array.isArray(owners) ? owners : [owners];
|
|
164
|
+
const number = jid.split('@')[0];
|
|
165
|
+
return ownerList.includes(number) || ownerList.includes(jid);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
reload(pluginName) {
|
|
169
|
+
const plugin = this.plugins.get(pluginName);
|
|
170
|
+
if (!plugin) return false;
|
|
171
|
+
|
|
172
|
+
// Remove from maps
|
|
173
|
+
for (const cmd of plugin.commands) {
|
|
174
|
+
this.commands.delete(cmd);
|
|
175
|
+
}
|
|
176
|
+
this.plugins.delete(pluginName);
|
|
177
|
+
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
list() {
|
|
182
|
+
return Array.from(this.plugins.values());
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
get(name) {
|
|
186
|
+
return this.plugins.get(name);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export default PluginHandler;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Low } from 'lowdb';
|
|
2
|
+
import { JSONFile } from 'lowdb/node';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
export class Database {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.path = options.path || './database';
|
|
9
|
+
this.collections = new Map();
|
|
10
|
+
|
|
11
|
+
// Ensure database directory exists
|
|
12
|
+
if (!fs.existsSync(this.path)) {
|
|
13
|
+
fs.mkdirSync(this.path, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async collection(name, defaultData = {}) {
|
|
18
|
+
if (this.collections.has(name)) {
|
|
19
|
+
return this.collections.get(name);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const filePath = path.join(this.path, `${name}.json`);
|
|
23
|
+
const adapter = new JSONFile(filePath);
|
|
24
|
+
const db = new Low(adapter, defaultData);
|
|
25
|
+
|
|
26
|
+
await db.read();
|
|
27
|
+
db.data ||= defaultData;
|
|
28
|
+
await db.write();
|
|
29
|
+
|
|
30
|
+
this.collections.set(name, db);
|
|
31
|
+
return db;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async get(collection, key, defaultValue = null) {
|
|
35
|
+
const db = await this.collection(collection, {});
|
|
36
|
+
await db.read();
|
|
37
|
+
return db.data[key] ?? defaultValue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async set(collection, key, value) {
|
|
41
|
+
const db = await this.collection(collection, {});
|
|
42
|
+
await db.read();
|
|
43
|
+
db.data[key] = value;
|
|
44
|
+
await db.write();
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async has(collection, key) {
|
|
49
|
+
const db = await this.collection(collection, {});
|
|
50
|
+
await db.read();
|
|
51
|
+
return key in db.data;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async delete(collection, key) {
|
|
55
|
+
const db = await this.collection(collection, {});
|
|
56
|
+
await db.read();
|
|
57
|
+
delete db.data[key];
|
|
58
|
+
await db.write();
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async all(collection) {
|
|
63
|
+
const db = await this.collection(collection, {});
|
|
64
|
+
await db.read();
|
|
65
|
+
return db.data;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async clear(collection) {
|
|
69
|
+
const db = await this.collection(collection, {});
|
|
70
|
+
db.data = {};
|
|
71
|
+
await db.write();
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async update(collection, key, updater) {
|
|
76
|
+
const db = await this.collection(collection, {});
|
|
77
|
+
await db.read();
|
|
78
|
+
|
|
79
|
+
if (typeof updater === 'function') {
|
|
80
|
+
db.data[key] = updater(db.data[key]);
|
|
81
|
+
} else {
|
|
82
|
+
db.data[key] = { ...db.data[key], ...updater };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
await db.write();
|
|
86
|
+
return db.data[key];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async increment(collection, key, field, amount = 1) {
|
|
90
|
+
return await this.update(collection, key, (data) => {
|
|
91
|
+
data = data || {};
|
|
92
|
+
data[field] = (data[field] || 0) + amount;
|
|
93
|
+
return data;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async push(collection, key, value) {
|
|
98
|
+
return await this.update(collection, key, (data) => {
|
|
99
|
+
data = data || [];
|
|
100
|
+
if (Array.isArray(data)) {
|
|
101
|
+
data.push(value);
|
|
102
|
+
}
|
|
103
|
+
return data;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async pull(collection, key, value) {
|
|
108
|
+
return await this.update(collection, key, (data) => {
|
|
109
|
+
if (Array.isArray(data)) {
|
|
110
|
+
return data.filter(item => item !== value);
|
|
111
|
+
}
|
|
112
|
+
return data;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export default Database;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export { serialize } from './serialize.js';
|
|
2
|
+
export { Database } from './database.js';
|
|
3
|
+
export { Logger } from './logger.js';
|
|
4
|
+
export * from './sticker.js';
|
|
5
|
+
|
|
6
|
+
// Utility functions
|
|
7
|
+
export function sleep(ms) {
|
|
8
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function formatTime(seconds) {
|
|
12
|
+
const days = Math.floor(seconds / 86400);
|
|
13
|
+
const hours = Math.floor((seconds % 86400) / 3600);
|
|
14
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
15
|
+
const secs = Math.floor(seconds % 60);
|
|
16
|
+
|
|
17
|
+
if (days > 0) return `${days}d ${hours}h ${minutes}m`;
|
|
18
|
+
if (hours > 0) return `${hours}h ${minutes}m ${secs}s`;
|
|
19
|
+
if (minutes > 0) return `${minutes}m ${secs}s`;
|
|
20
|
+
return `${secs}s`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function formatBytes(bytes) {
|
|
24
|
+
if (bytes === 0) return '0 B';
|
|
25
|
+
const k = 1024;
|
|
26
|
+
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
27
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
28
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function parseCommand(text, prefix = '!') {
|
|
32
|
+
if (!text.startsWith(prefix)) return null;
|
|
33
|
+
|
|
34
|
+
const [command, ...args] = text.slice(prefix.length).trim().split(/\s+/);
|
|
35
|
+
return {
|
|
36
|
+
command: command.toLowerCase(),
|
|
37
|
+
args,
|
|
38
|
+
text: args.join(' ')
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isUrl(text) {
|
|
43
|
+
return /^https?:\/\//i.test(text);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function extractUrls(text) {
|
|
47
|
+
const urlRegex = /https?:\/\/[^\s]+/gi;
|
|
48
|
+
return text.match(urlRegex) || [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function randomString(length = 10) {
|
|
52
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
53
|
+
let result = '';
|
|
54
|
+
for (let i = 0; i < length; i++) {
|
|
55
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function randomNumber(min, max) {
|
|
61
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function pickRandom(array) {
|
|
65
|
+
return array[Math.floor(Math.random() * array.length)];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function chunk(array, size) {
|
|
69
|
+
const chunks = [];
|
|
70
|
+
for (let i = 0; i < array.length; i += size) {
|
|
71
|
+
chunks.push(array.slice(i, i + size));
|
|
72
|
+
}
|
|
73
|
+
return chunks;
|
|
74
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
export class Logger {
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
this.level = options.level || 'info';
|
|
6
|
+
this.prefix = options.prefix || '';
|
|
7
|
+
this.levels = {
|
|
8
|
+
debug: 0,
|
|
9
|
+
info: 1,
|
|
10
|
+
success: 1,
|
|
11
|
+
warn: 2,
|
|
12
|
+
error: 3
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
shouldLog(level) {
|
|
17
|
+
return this.levels[level] >= this.levels[this.level];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
format(level, ...args) {
|
|
21
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
22
|
+
const prefix = this.prefix ? `[${this.prefix}] ` : '';
|
|
23
|
+
return `${chalk.gray(timestamp)} ${prefix}${args.join(' ')}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
debug(...args) {
|
|
27
|
+
if (this.shouldLog('debug')) {
|
|
28
|
+
console.log(chalk.gray('[DEBUG]'), this.format('debug', ...args));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
info(...args) {
|
|
33
|
+
if (this.shouldLog('info')) {
|
|
34
|
+
console.log(chalk.blue('[INFO]'), this.format('info', ...args));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
success(...args) {
|
|
39
|
+
if (this.shouldLog('success')) {
|
|
40
|
+
console.log(chalk.green('[SUCCESS]'), this.format('success', ...args));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
warn(...args) {
|
|
45
|
+
if (this.shouldLog('warn')) {
|
|
46
|
+
console.log(chalk.yellow('[WARN]'), this.format('warn', ...args));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
error(...args) {
|
|
51
|
+
if (this.shouldLog('error')) {
|
|
52
|
+
console.error(chalk.red('[ERROR]'), this.format('error', ...args));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
command(command, from) {
|
|
57
|
+
console.log(
|
|
58
|
+
chalk.cyan('[CMD]'),
|
|
59
|
+
chalk.yellow(command),
|
|
60
|
+
chalk.gray('from'),
|
|
61
|
+
chalk.green(from)
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default Logger;
|