@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.
@@ -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;