levix-bot 2.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 +105 -0
- package/SETUP.md +242 -0
- package/app.cjs +337 -0
- package/bin/levix.js +7 -0
- package/deploy/install.sh +182 -0
- package/deploy/levix.service +56 -0
- package/deploy/nginx/levix.leviro.net.conf +79 -0
- package/package.json +78 -0
- package/public/brand/apple-touch-icon.png +0 -0
- package/public/brand/banner.jpg +0 -0
- package/public/brand/banner.webp +0 -0
- package/public/brand/icon-16.png +0 -0
- package/public/brand/icon-192.png +0 -0
- package/public/brand/icon-32.png +0 -0
- package/public/brand/icon-512.png +0 -0
- package/public/brand/icon-512.webp +0 -0
- package/public/brand/mark.png +0 -0
- package/public/brand/mark.svg +16 -0
- package/public/brand/mark.webp +0 -0
- package/public/brand/wordmark.png +0 -0
- package/public/brand/wordmark.webp +0 -0
- package/public/dashboard.css +881 -0
- package/public/dashboard.js +1057 -0
- package/public/logo.svg +16 -0
- package/public/qrcode.min.js +1 -0
- package/public/socket.io.min.js +7 -0
- package/scheduler.cjs +197 -0
- package/src/auth/auth-storage.cjs +74 -0
- package/src/auth/use-database-auth-state.js +130 -0
- package/src/bootstrap/core.js +74 -0
- package/src/bootstrap/events.cjs +43 -0
- package/src/bootstrap/panel.js +97 -0
- package/src/cli.js +153 -0
- package/src/commands/autoschedule.cjs +84 -0
- package/src/commands/blacklist.cjs +116 -0
- package/src/commands/block.cjs +70 -0
- package/src/commands/calc.cjs +387 -0
- package/src/commands/debt.cjs +175 -0
- package/src/commands/deleteschedule.cjs +36 -0
- package/src/commands/gemini.cjs +727 -0
- package/src/commands/group/add.cjs +136 -0
- package/src/commands/group/all.cjs +43 -0
- package/src/commands/group/antiSpam.cjs +96 -0
- package/src/commands/group/antilink.cjs +167 -0
- package/src/commands/group/approveall.cjs +54 -0
- package/src/commands/group/clearwarns.cjs +41 -0
- package/src/commands/group/deletenote.cjs +33 -0
- package/src/commands/group/demote.cjs +62 -0
- package/src/commands/group/kick.cjs +85 -0
- package/src/commands/group/media.cjs +120 -0
- package/src/commands/group/members.cjs +59 -0
- package/src/commands/group/note.cjs +32 -0
- package/src/commands/group/promote.cjs +60 -0
- package/src/commands/group/removeall.cjs +42 -0
- package/src/commands/group/rules.cjs +31 -0
- package/src/commands/group/save.cjs +36 -0
- package/src/commands/group/setname.cjs +41 -0
- package/src/commands/group/setpp.cjs +51 -0
- package/src/commands/group/setrules.cjs +46 -0
- package/src/commands/group/setwarn.cjs +58 -0
- package/src/commands/group/tagadmins.cjs +50 -0
- package/src/commands/group/warn.cjs +105 -0
- package/src/commands/group/warns.cjs +66 -0
- package/src/commands/group/welcome.cjs +109 -0
- package/src/commands/group.cjs +160 -0
- package/src/commands/help.cjs +230 -0
- package/src/commands/listschedules.cjs +47 -0
- package/src/commands/loop.cjs +163 -0
- package/src/commands/memory.cjs +201 -0
- package/src/commands/mod.cjs +155 -0
- package/src/commands/notes.cjs +28 -0
- package/src/commands/perm.cjs +236 -0
- package/src/commands/ping.cjs +17 -0
- package/src/commands/poll.cjs +80 -0
- package/src/commands/prayer.cjs +64 -0
- package/src/commands/qr.cjs +39 -0
- package/src/commands/rand.cjs +130 -0
- package/src/commands/restart.cjs +25 -0
- package/src/commands/schedule.cjs +86 -0
- package/src/commands/score.cjs +130 -0
- package/src/commands/setprefix.cjs +40 -0
- package/src/commands/shortlink.cjs +74 -0
- package/src/commands/shutdown.cjs +45 -0
- package/src/commands/status.cjs +75 -0
- package/src/commands/stt.cjs +150 -0
- package/src/commands/todo.cjs +88 -0
- package/src/commands/tts.cjs +170 -0
- package/src/commands/unblock.cjs +57 -0
- package/src/commands/weather.cjs +79 -0
- package/src/config/ai-persona.md +17 -0
- package/src/config/baileys.config.js +51 -0
- package/src/config/brand.cjs +36 -0
- package/src/config/brand.esm.js +17 -0
- package/src/config/constants.js +16 -0
- package/src/config/defaults.cjs +91 -0
- package/src/config/lock.cjs +110 -0
- package/src/config/paths.cjs +90 -0
- package/src/config/runtime-config.cjs +219 -0
- package/src/config/secrets.cjs +130 -0
- package/src/config/settings.cjs +386 -0
- package/src/core/connection.js +199 -0
- package/src/core/events.js +61 -0
- package/src/core/socket.js +64 -0
- package/src/db/db.cjs +282 -0
- package/src/db/store.cjs +837 -0
- package/src/db/store.esm.js +17 -0
- package/src/domain/caddy.js +120 -0
- package/src/domain/command.js +380 -0
- package/src/domain/detect.js +173 -0
- package/src/domain/nginx.js +138 -0
- package/src/domain/system.js +148 -0
- package/src/handlers/command.handler.js +414 -0
- package/src/handlers/group.handler.js +73 -0
- package/src/handlers/message.handler.js +149 -0
- package/src/index.js +217 -0
- package/src/middleware/antispam.middleware.js +68 -0
- package/src/middleware/blacklist.middleware.js +29 -0
- package/src/middleware/forward-tracking.middleware.js +49 -0
- package/src/middleware/permissions.middleware.js +114 -0
- package/src/routes/dashboard.api.esm.js +779 -0
- package/src/services/aiAgent.cjs +317 -0
- package/src/services/aiTools.cjs +635 -0
- package/src/utils/datetime.cjs +61 -0
- package/src/utils/lid-helper.esm.js +143 -0
- package/src/utils/logger.cjs +72 -0
- package/src/utils/memory.cjs +385 -0
- package/src/utils/normalizeJid.cjs +65 -0
- package/src/utils/normalizeJid.esm.js +37 -0
- package/src/utils/openBrowser.cjs +129 -0
- package/src/utils/permissions.cjs +154 -0
- package/src/utils/permissions.esm.js +375 -0
- package/src/utils/recentMessageCache.esm.js +70 -0
- package/src/utils/requestOrigin.cjs +102 -0
- package/src/utils/sendBotMessage.cjs +43 -0
- package/src/utils/sendBotMessage.esm.js +139 -0
- package/src/utils/statusMessage.cjs +179 -0
- package/src/utils/storage-hub.cjs +49 -0
- package/src/utils/storage-hub.esm.js +24 -0
- package/src/utils/storage.cjs +80 -0
- package/src/utils/storage.esm.js +88 -0
- package/src/utils/textDecode.cjs +197 -0
- package/src/utils/thumbnail.cjs +591 -0
- package/src/utils/typing.esm.js +102 -0
- package/views/dashboard.ejs +329 -0
- package/views/login.ejs +105 -0
- package/views/qr.ejs +108 -0
- package/views/setup.ejs +122 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// عشوائي — !rand
|
|
2
|
+
//
|
|
3
|
+
// !rand رقم من 1 لـ 100
|
|
4
|
+
// !rand 6 رقم من 1 لـ 6
|
|
5
|
+
// !rand 10 50 رقم بين 10 و 50
|
|
6
|
+
// !rand dice نرد 1d6
|
|
7
|
+
// !rand dice 2d20 نردين وجه 20
|
|
8
|
+
// !rand coin ملك / كتابة
|
|
9
|
+
// !rand pick شاي | قهوة | عصير
|
|
10
|
+
//
|
|
11
|
+
// Used for settling arguments in the group. No dependencies, no state.
|
|
12
|
+
|
|
13
|
+
const { sendBotMessage } = require("../utils/sendBotMessage.cjs");
|
|
14
|
+
|
|
15
|
+
const MAX_DICE = 20;
|
|
16
|
+
const MAX_SIDES = 1000;
|
|
17
|
+
const MAX_CHOICES = 30;
|
|
18
|
+
|
|
19
|
+
function randomInt(min, max) {
|
|
20
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function toNumber(token) {
|
|
24
|
+
const normalized = String(token ?? "")
|
|
25
|
+
.trim()
|
|
26
|
+
.replace(/[٠-٩]/g, (d) => String("٠١٢٣٤٥٦٧٨٩".indexOf(d)));
|
|
27
|
+
if (!normalized) return null; // Number("") is 0 — not what we mean here
|
|
28
|
+
const value = Number(normalized);
|
|
29
|
+
return Number.isFinite(value) ? Math.trunc(value) : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const HELP_TEXT =
|
|
33
|
+
"🎲 *عشوائي*\n\n" +
|
|
34
|
+
"`!rand` رقم من 1 لـ 100\n" +
|
|
35
|
+
"`!rand <أقصى>` رقم من 1 للرقم ده\n" +
|
|
36
|
+
"`!rand <أدنى> <أقصى>` رقم بين الاتنين\n" +
|
|
37
|
+
"`!rand dice [عدد d أوجه]` نرد — مثال `!rand dice 2d6`\n" +
|
|
38
|
+
"`!rand coin` ملك ولا كتابة\n" +
|
|
39
|
+
"`!rand pick خيار | خيار | خيار` يختار واحد";
|
|
40
|
+
|
|
41
|
+
function rollDice(spec) {
|
|
42
|
+
const match = String(spec || "1d6")
|
|
43
|
+
.toLowerCase()
|
|
44
|
+
.match(/^(\d+)?d(\d+)$/);
|
|
45
|
+
const count = match?.[1] ? Number(match[1]) : 1;
|
|
46
|
+
const sides = match?.[2] ? Number(match[2]) : 6;
|
|
47
|
+
|
|
48
|
+
if (!match || count < 1 || count > MAX_DICE || sides < 2 || sides > MAX_SIDES) {
|
|
49
|
+
return {
|
|
50
|
+
error:
|
|
51
|
+
`❌ صيغة النرد غلط. استخدم \`عددdأوجه\` — مثال \`2d6\`.\n` +
|
|
52
|
+
`(حد أقصى ${MAX_DICE} نرد و ${MAX_SIDES} وجه)`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const rolls = Array.from({ length: count }, () => randomInt(1, sides));
|
|
57
|
+
const total = rolls.reduce((sum, value) => sum + value, 0);
|
|
58
|
+
return { rolls, total, count, sides };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
name: "rand",
|
|
63
|
+
aliases: ["random", "roll", "عشوائي", "قرعة"],
|
|
64
|
+
description: "أرقام عشوائية، نرد، عملة، أو اختيار من قائمة.",
|
|
65
|
+
usage: "rand [<أقصى> | <أدنى> <أقصى> | dice [2d6] | coin | pick أ | ب | ج]",
|
|
66
|
+
chat: "all",
|
|
67
|
+
|
|
68
|
+
async execute(sock, msg, args) {
|
|
69
|
+
const chatId = msg.key.remoteJid;
|
|
70
|
+
const reply = (text) => sendBotMessage(sock, chatId, { text }, { replyTo: msg });
|
|
71
|
+
|
|
72
|
+
const mode = (args[0] || "").toLowerCase();
|
|
73
|
+
|
|
74
|
+
if (["help", "?", "مساعدة"].includes(mode)) return reply(HELP_TEXT);
|
|
75
|
+
|
|
76
|
+
if (["coin", "flip", "عملة", "معملة"].includes(mode)) {
|
|
77
|
+
const heads = Math.random() < 0.5;
|
|
78
|
+
return reply(`🪙 *${heads ? "ملك 👑" : "كتابة ✍️"}*`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (["dice", "die", "نرد", "زهر"].includes(mode)) {
|
|
82
|
+
const result = rollDice(args[1]);
|
|
83
|
+
if (result.error) return reply(result.error);
|
|
84
|
+
const detail =
|
|
85
|
+
result.rolls.length > 1 ? `\n(${result.rolls.join(" + ")})` : "";
|
|
86
|
+
return reply(
|
|
87
|
+
`🎲 *${result.total}* — ${result.count}d${result.sides}${detail}`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (["pick", "choose", "اختار", "اختر"].includes(mode)) {
|
|
92
|
+
const choices = args
|
|
93
|
+
.slice(1)
|
|
94
|
+
.join(" ")
|
|
95
|
+
.split(/\s*[|،,]\s*/)
|
|
96
|
+
.map((choice) => choice.trim())
|
|
97
|
+
.filter(Boolean);
|
|
98
|
+
|
|
99
|
+
if (choices.length < 2) {
|
|
100
|
+
return reply("❌ اديني خيارين على الأقل مفصولين بـ `|`.\nمثال: `!rand pick شاي | قهوة`");
|
|
101
|
+
}
|
|
102
|
+
if (choices.length > MAX_CHOICES) {
|
|
103
|
+
return reply(`❌ الخيارات كتير أوي (الحد ${MAX_CHOICES}).`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const winner = choices[randomInt(0, choices.length - 1)];
|
|
107
|
+
return reply(`🎯 اخترت: *${winner}*\n_(من ${choices.length} خيارات)_`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Plain number forms: !rand · !rand 6 · !rand 10 50
|
|
111
|
+
const first = toNumber(args[0]);
|
|
112
|
+
const second = toNumber(args[1]);
|
|
113
|
+
|
|
114
|
+
if (args[0] && first === null) return reply(HELP_TEXT);
|
|
115
|
+
|
|
116
|
+
let min = 1;
|
|
117
|
+
let max = 100;
|
|
118
|
+
if (first !== null && second !== null) {
|
|
119
|
+
min = Math.min(first, second);
|
|
120
|
+
max = Math.max(first, second);
|
|
121
|
+
} else if (first !== null) {
|
|
122
|
+
min = first < 1 ? first : 1;
|
|
123
|
+
max = first < 1 ? 1 : first;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (min === max) return reply(`🎲 *${min}*`);
|
|
127
|
+
|
|
128
|
+
return reply(`🎲 *${randomInt(min, max)}*\n_(بين ${min} و ${max})_`);
|
|
129
|
+
},
|
|
130
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// file: /commands/restart.js
|
|
2
|
+
const { delay } = require("@whiskeysockets/baileys");
|
|
3
|
+
const logger = require("../utils/logger.cjs");
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
name: "restart",
|
|
7
|
+
description: "Restarts the bot.",
|
|
8
|
+
usage: "restart",
|
|
9
|
+
chat: "all",
|
|
10
|
+
|
|
11
|
+
async execute(sock, msg) {
|
|
12
|
+
logger.warn("Received !restart command. Restarting bot...");
|
|
13
|
+
|
|
14
|
+
// Send a confirmation message before exiting
|
|
15
|
+
await sock.sendMessage(msg.key.remoteJid, {
|
|
16
|
+
text: "✅ جاري إعادة تشغيل البوت... سأعود بعد لحظات.",
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// A small delay to ensure the message is sent before the process exits
|
|
20
|
+
await delay(2000); // 2-second delay
|
|
21
|
+
|
|
22
|
+
// This will stop the current process. PM2 will automatically restart it.
|
|
23
|
+
process.exit(0);
|
|
24
|
+
},
|
|
25
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// file: commands/schedule.js
|
|
2
|
+
const {
|
|
3
|
+
scheduleNewJob,
|
|
4
|
+
saveScheduledJob,
|
|
5
|
+
} = require("../../scheduler.cjs");
|
|
6
|
+
const { zonedTimeToDate, defaultTimezone } = require("../utils/datetime.cjs");
|
|
7
|
+
|
|
8
|
+
const DATETIME_RE = /^(\d{1,2}):(\d{2})\s+(\d{1,2})-(\d{1,2})-(\d{4})$/;
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
name: "schedule",
|
|
12
|
+
description: "Schedules a message to be sent in the future.",
|
|
13
|
+
usage: 'schedule "HH:mm DD-MM-YYYY" "الرسالة"',
|
|
14
|
+
chat: "all",
|
|
15
|
+
|
|
16
|
+
async execute(sock, msg, args) {
|
|
17
|
+
const creatorJid = msg.key.participant || msg.key.remoteJid;
|
|
18
|
+
const targetJid = msg.key.remoteJid;
|
|
19
|
+
|
|
20
|
+
// We expect the format: !schedule "HH:mm DD-MM-YYYY" "Your message here"
|
|
21
|
+
const commandBody = args.join(" ");
|
|
22
|
+
const parts = commandBody.match(/(["'])(?:(?=(\\?))\2.)*?\1/g);
|
|
23
|
+
|
|
24
|
+
if (!parts || parts.length < 2) {
|
|
25
|
+
return await sock.sendMessage(creatorJid, {
|
|
26
|
+
text: 'الصيغة غير صحيحة. يرجى استخدام:\n`!schedule "HH:mm DD-MM-YYYY" "رسالتك"`',
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const dateTimeString = parts[0].slice(1, -1);
|
|
31
|
+
const message = parts[1].slice(1, -1);
|
|
32
|
+
|
|
33
|
+
// Parse date and time: "22:30 07-06-2025"
|
|
34
|
+
const match = DATETIME_RE.exec(dateTimeString.trim());
|
|
35
|
+
if (!match) {
|
|
36
|
+
return await sock.sendMessage(creatorJid, {
|
|
37
|
+
text: "صيغة الوقت غير صحيحة. استخدم `HH:mm DD-MM-YYYY` (مثال: `22:30 07-06-2025`).",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const [, hour, minute, day, month, year] = match.map(Number);
|
|
42
|
+
|
|
43
|
+
// الوقت اللي المستخدم كتبه بتوقيت البوت، مش بتوقيت السيرفر. الكود القديم
|
|
44
|
+
// كان بيبنيه بتوقيت السيرفر المحلي وبعدين الجدولة تتم بتوقيت القاهرة،
|
|
45
|
+
// فالرسالة كانت بتتبعت بفارق ساعات على أي سيرفر شغال UTC.
|
|
46
|
+
const scheduleDate = zonedTimeToDate({
|
|
47
|
+
year,
|
|
48
|
+
month,
|
|
49
|
+
day,
|
|
50
|
+
hour,
|
|
51
|
+
minute,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (isNaN(scheduleDate.getTime()) || scheduleDate <= new Date()) {
|
|
55
|
+
return await sock.sendMessage(creatorJid, {
|
|
56
|
+
text: "التاريخ أو الوقت غير صالح أو في الماضي. يرجى استخدام صيغة `HH:mm DD-MM-YYYY` بتاريخ مستقبلي.",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const newJob = {
|
|
61
|
+
id: Date.now().toString(),
|
|
62
|
+
type: "once",
|
|
63
|
+
date: scheduleDate.toISOString(),
|
|
64
|
+
message: message,
|
|
65
|
+
targetJid: targetJid,
|
|
66
|
+
creatorJid: creatorJid,
|
|
67
|
+
status: "pending",
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Schedule the job to run in the current session
|
|
71
|
+
if (!scheduleNewJob(sock, newJob)) {
|
|
72
|
+
return await sock.sendMessage(creatorJid, {
|
|
73
|
+
text: "معرفتش أجدول الرسالة دي. راجع الوقت وجرب تاني.",
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
saveScheduledJob(newJob);
|
|
78
|
+
|
|
79
|
+
await sock.sendMessage(creatorJid, {
|
|
80
|
+
text: `✅ تم جدولة رسالتك بنجاح ليتم إرسالها في:\n*${scheduleDate.toLocaleString(
|
|
81
|
+
"ar-EG",
|
|
82
|
+
{ timeZone: defaultTimezone() }
|
|
83
|
+
)}*`,
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
const logger = require("../utils/logger.cjs");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extracts quoted message info with forwarding score
|
|
5
|
+
* @param {object} msg - WhatsApp message object
|
|
6
|
+
* @returns {object|null} - { stanzaId, quotedMessage, forwardingScore, isForwarded }
|
|
7
|
+
*/
|
|
8
|
+
function getQuotedInfo(msg) {
|
|
9
|
+
const m = msg.message;
|
|
10
|
+
if (!m) return null;
|
|
11
|
+
|
|
12
|
+
// List of all possible message types that can contain contextInfo
|
|
13
|
+
const messageTypes = [
|
|
14
|
+
"extendedTextMessage",
|
|
15
|
+
"imageMessage",
|
|
16
|
+
"videoMessage",
|
|
17
|
+
"documentMessage",
|
|
18
|
+
"documentWithCaptionMessage",
|
|
19
|
+
"audioMessage",
|
|
20
|
+
"stickerMessage",
|
|
21
|
+
"contactMessage",
|
|
22
|
+
"locationMessage",
|
|
23
|
+
"liveLocationMessage",
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
for (const type of messageTypes) {
|
|
27
|
+
const messageContent = m[type];
|
|
28
|
+
if (!messageContent?.contextInfo) continue;
|
|
29
|
+
|
|
30
|
+
const contextInfo = messageContent.contextInfo;
|
|
31
|
+
const stanzaId = contextInfo.stanzaId;
|
|
32
|
+
const quotedMessage = contextInfo.quotedMessage;
|
|
33
|
+
|
|
34
|
+
// Skip if no stanzaId (not a reply)
|
|
35
|
+
if (!stanzaId) continue;
|
|
36
|
+
|
|
37
|
+
// Initialize result
|
|
38
|
+
let forwardingScore = 0;
|
|
39
|
+
let isForwarded = false;
|
|
40
|
+
|
|
41
|
+
// IMPORTANT: Try to extract forwarding info from the quoted message
|
|
42
|
+
if (quotedMessage) {
|
|
43
|
+
// Check all possible message types in quotedMessage
|
|
44
|
+
for (const quotedType of messageTypes) {
|
|
45
|
+
const quotedContent = quotedMessage[quotedType];
|
|
46
|
+
|
|
47
|
+
// Check if this message type exists and has contextInfo
|
|
48
|
+
if (quotedContent && quotedContent.contextInfo) {
|
|
49
|
+
const quotedContextInfo = quotedContent.contextInfo;
|
|
50
|
+
|
|
51
|
+
// Extract forwarding score and isForwarded flag
|
|
52
|
+
if (quotedContextInfo.forwardingScore !== undefined && quotedContextInfo.forwardingScore !== null) {
|
|
53
|
+
forwardingScore = quotedContextInfo.forwardingScore;
|
|
54
|
+
}
|
|
55
|
+
if (quotedContextInfo.isForwarded !== undefined && quotedContextInfo.isForwarded !== null) {
|
|
56
|
+
isForwarded = quotedContextInfo.isForwarded;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Don't break - check all types in case there are multiple
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Also check conversation type (simple text without media)
|
|
64
|
+
if (quotedMessage.conversation !== undefined) {
|
|
65
|
+
// For simple conversation, forwarding info might be in parent contextInfo
|
|
66
|
+
if (contextInfo.forwardingScore !== undefined && contextInfo.forwardingScore !== null) {
|
|
67
|
+
forwardingScore = contextInfo.forwardingScore;
|
|
68
|
+
}
|
|
69
|
+
if (contextInfo.isForwarded !== undefined && contextInfo.isForwarded !== null) {
|
|
70
|
+
isForwarded = contextInfo.isForwarded;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
stanzaId,
|
|
77
|
+
quotedMessage,
|
|
78
|
+
forwardingScore,
|
|
79
|
+
isForwarded,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = {
|
|
87
|
+
name: "score",
|
|
88
|
+
aliases: ["نقاط", "احصائية"],
|
|
89
|
+
description: "عرض عدد مرات إعادة توجيه رسالة معينة",
|
|
90
|
+
usage: "score (رد على الرسالة)",
|
|
91
|
+
chat: "all",
|
|
92
|
+
|
|
93
|
+
async execute(sock, msg, args, body, groupMetadata) {
|
|
94
|
+
const chatId = msg.key.remoteJid;
|
|
95
|
+
|
|
96
|
+
const quotedInfo = getQuotedInfo(msg);
|
|
97
|
+
|
|
98
|
+
if (!quotedInfo || !quotedInfo.stanzaId) {
|
|
99
|
+
return await sock.sendMessage(chatId, {
|
|
100
|
+
text:
|
|
101
|
+
"❌ يرجى الرد على رسالة لعرض نقاطها.\n\n" +
|
|
102
|
+
"*الاستخدام:* قم بالرد على رسالة واكتب `!score`",
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const { forwardingScore, isForwarded } = quotedInfo;
|
|
108
|
+
|
|
109
|
+
// Display the forward score
|
|
110
|
+
let responseText = "📊 *نقاط الرسالة*\n\n";
|
|
111
|
+
|
|
112
|
+
if (isForwarded || forwardingScore > 0) {
|
|
113
|
+
responseText += `🔢 *عدد مرات إعادة التوجيه:* ${forwardingScore}\n`;
|
|
114
|
+
responseText += `✅ *حالة الرسالة:* تم إعادة توجيهها`;
|
|
115
|
+
} else {
|
|
116
|
+
responseText += `🔢 *عدد مرات إعادة التوجيه:* 0\n`;
|
|
117
|
+
responseText += `ℹ️ *حالة الرسالة:* رسالة أصلية (لم يتم إعادة توجيهها)`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
await sock.sendMessage(chatId, {
|
|
121
|
+
text: responseText,
|
|
122
|
+
});
|
|
123
|
+
} catch (error) {
|
|
124
|
+
logger.error({ err: error }, "Error in !score command");
|
|
125
|
+
await sock.sendMessage(chatId, {
|
|
126
|
+
text: "❌ حدث خطأ أثناء جلب نقاط الرسالة.",
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// file: /commands/setprefix.cjs
|
|
2
|
+
const logger = require("../utils/logger.cjs");
|
|
3
|
+
// Same validation and storage the dashboard uses, so both doors agree.
|
|
4
|
+
const runtimeConfig = require("../config/runtime-config.cjs");
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
name: "setprefix",
|
|
8
|
+
aliases: ["prefix"],
|
|
9
|
+
description: "Set custom prefix for the bot (Owner only)",
|
|
10
|
+
usage: "setprefix <البادئة الجديدة>",
|
|
11
|
+
chat: "all",
|
|
12
|
+
|
|
13
|
+
async execute(sock, msg, args) {
|
|
14
|
+
const chatId = msg.key.remoteJid;
|
|
15
|
+
|
|
16
|
+
if (args.length === 0) {
|
|
17
|
+
// Show current prefix
|
|
18
|
+
const currentPrefix = runtimeConfig.getPrefix();
|
|
19
|
+
|
|
20
|
+
await sock.sendMessage(chatId, {
|
|
21
|
+
text: `البادئة الحالية للبوت: \`${currentPrefix}\`\n\nلتغيير البادئة، استخدم:\n\`${currentPrefix}setprefix <البادئة_الجديدة>\`\n\nمثال: \`${currentPrefix}setprefix /\`\n\n⚠️ ملاحظة: يمكن للمالك فقط تغيير البادئة.`
|
|
22
|
+
});
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let newPrefix;
|
|
27
|
+
try {
|
|
28
|
+
newPrefix = runtimeConfig.setPrefix(args[0]);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
await sock.sendMessage(chatId, { text: `❌ ${error.message}` });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
logger.info(`[Prefix] Bot prefix changed to: ${newPrefix}`);
|
|
35
|
+
|
|
36
|
+
await sock.sendMessage(chatId, {
|
|
37
|
+
text: `✅ تم تغيير البادئة بنجاح إلى: \`${newPrefix}\`\n\nالآن استخدم \`${newPrefix}help\` لعرض الأوامر.\n\n⚠️ البادئة الجديدة تعمل الآن في كل المحادثات.`
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// file: /commands/shortlink.js
|
|
2
|
+
|
|
3
|
+
const axios = require("axios");
|
|
4
|
+
const logger = require("../utils/logger.cjs");
|
|
5
|
+
const { createStatus } = require("../utils/statusMessage.cjs");
|
|
6
|
+
|
|
7
|
+
// A simple regex to validate if the input is a URL
|
|
8
|
+
const urlRegex = new RegExp(/^(https?:\/\/[^\s/$.?#].[^\s]*)$/i);
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
name: "shortlink",
|
|
12
|
+
description: "Shortens a long URL using the is.gd service.",
|
|
13
|
+
usage: "shortlink <الرابط>",
|
|
14
|
+
chat: "all", // This command can be used anywhere
|
|
15
|
+
|
|
16
|
+
async execute(sock, msg, args) {
|
|
17
|
+
// 1. Check if the user provided any arguments
|
|
18
|
+
if (!args || args.length === 0) {
|
|
19
|
+
return await sock.sendMessage(msg.key.remoteJid, {
|
|
20
|
+
text: "يرجى إرسال الرابط الذي تريد اختصاره.\n\n*مثال:*\n`!shortlink https://github.com/WhiskeySockets/Baileys`",
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const longUrl = args[0];
|
|
25
|
+
|
|
26
|
+
// 2. Validate if the provided argument is a valid URL
|
|
27
|
+
if (!urlRegex.test(longUrl)) {
|
|
28
|
+
return await sock.sendMessage(msg.key.remoteJid, {
|
|
29
|
+
text: "الرابط الذي أرسلته غير صالح. يرجى التأكد من أنه يبدأ بـ `http://` أو `https://`.",
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 3. Define the API endpoint for is.gd
|
|
34
|
+
// We use `encodeURIComponent` to ensure the URL is properly formatted for the API request
|
|
35
|
+
const API_URL = `https://is.gd/create.php?format=simple&url=${encodeURIComponent(
|
|
36
|
+
longUrl
|
|
37
|
+
)}`;
|
|
38
|
+
|
|
39
|
+
// One message: "shortening..." becomes the short link itself.
|
|
40
|
+
const status = await createStatus(
|
|
41
|
+
sock,
|
|
42
|
+
msg.key.remoteJid,
|
|
43
|
+
"🔗 بختصر الرابط...",
|
|
44
|
+
{ replyTo: msg },
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
// 4. Make the GET request to the API
|
|
49
|
+
const response = await axios.get(API_URL);
|
|
50
|
+
|
|
51
|
+
// 5. The API returns the shortened URL as plain text in the response body
|
|
52
|
+
const shortUrl = response.data;
|
|
53
|
+
|
|
54
|
+
const reply =
|
|
55
|
+
`✅ تم اختصار الرابط بنجاح!\n\n` + `🔗 *الرابط المختصر:*\n${shortUrl}`;
|
|
56
|
+
|
|
57
|
+
await status.finish(reply);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
logger.error(
|
|
60
|
+
error.response ? error.response.data : error.message,
|
|
61
|
+
"[Error] in !shortlink command:"
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// The API returns a plain text error message if something goes wrong
|
|
65
|
+
const errorMessage = error.response
|
|
66
|
+
? error.response.data
|
|
67
|
+
: "حدث خطأ غير متوقع.";
|
|
68
|
+
|
|
69
|
+
await sock.sendMessage(msg.key.remoteJid, {
|
|
70
|
+
text: `*عذرًا، حدث خطأ:*\n\n` + `\`\`\`${errorMessage}\`\`\``,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// file: /commands/shutdown.js (Now with shell command execution)
|
|
2
|
+
const { delay } = require("@whiskeysockets/baileys");
|
|
3
|
+
const { exec } = require("child_process"); // 1. Import the 'exec' function
|
|
4
|
+
const logger = require("../utils/logger.cjs");
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
name: "shutdown",
|
|
8
|
+
description: "Stops the bot process permanently using PM2.",
|
|
9
|
+
usage: "shutdown",
|
|
10
|
+
chat: "all",
|
|
11
|
+
|
|
12
|
+
async execute(sock, msg) {
|
|
13
|
+
const commandToExecute = "pm2 stop wa-bot"; // The exact command to run on the server
|
|
14
|
+
|
|
15
|
+
logger.warn(`Received !shutdown command. Executing: ${commandToExecute}`);
|
|
16
|
+
|
|
17
|
+
// 2. Send a final message to the user before shutting down
|
|
18
|
+
await sock.sendMessage(msg.key.remoteJid, {
|
|
19
|
+
text: "🔌 تم استلام أمر الإيقاف النهائي. سيتم إيقاف البوت الآن... وداعًا! 👋",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// A small delay to ensure the WhatsApp message is sent out
|
|
23
|
+
await delay(2000);
|
|
24
|
+
|
|
25
|
+
// 3. Execute the shell command
|
|
26
|
+
exec(commandToExecute, (error, stdout, stderr) => {
|
|
27
|
+
if (error) {
|
|
28
|
+
// This log is crucial if something goes wrong
|
|
29
|
+
logger.error(
|
|
30
|
+
{
|
|
31
|
+
err: error.message,
|
|
32
|
+
stderr: stderr,
|
|
33
|
+
},
|
|
34
|
+
`Failed to execute shutdown command: ${commandToExecute}`
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// We can't reliably send a message here because the process might be stopping.
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// This log will appear just before the process is stopped by PM2
|
|
42
|
+
logger.info(`PM2 stop command executed successfully. STDOUT: ${stdout}`);
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// file: /commands/status.js
|
|
2
|
+
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const baileysVersion = require("@whiskeysockets/baileys/package.json").version;
|
|
5
|
+
const logger = require("../utils/logger.cjs");
|
|
6
|
+
const { checkFfmpeg } = require("../utils/thumbnail.cjs");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A helper function to format seconds into a human-readable string.
|
|
10
|
+
* e.g., 86461 seconds -> "1 day, 0 hours, 1 minute, 1 second"
|
|
11
|
+
* @param {number} seconds - The total seconds to format.
|
|
12
|
+
* @returns {string} The formatted uptime string.
|
|
13
|
+
*/
|
|
14
|
+
function formatUptime(seconds) {
|
|
15
|
+
const d = Math.floor(seconds / (3600 * 24));
|
|
16
|
+
const h = Math.floor((seconds % (3600 * 24)) / 3600);
|
|
17
|
+
const m = Math.floor((seconds % 3600) / 60);
|
|
18
|
+
const s = Math.floor(seconds % 60);
|
|
19
|
+
|
|
20
|
+
return `${d} يوم, ${h} ساعة, ${m} دقيقة, ${s} ثانية`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
name: "status",
|
|
25
|
+
description: "Displays the bot's system status.",
|
|
26
|
+
usage: "status",
|
|
27
|
+
chat: "all", // Can be used in any chat
|
|
28
|
+
|
|
29
|
+
async execute(sock, msg) {
|
|
30
|
+
try {
|
|
31
|
+
// --- GATHERING DATA ---
|
|
32
|
+
|
|
33
|
+
// 1. Uptime
|
|
34
|
+
const uptimeSeconds = process.uptime();
|
|
35
|
+
const formattedUptime = formatUptime(uptimeSeconds);
|
|
36
|
+
|
|
37
|
+
// 2. RAM Usage
|
|
38
|
+
// process.memoryUsage().rss is the Resident Set Size in bytes
|
|
39
|
+
const rssBytes = process.memoryUsage().rss;
|
|
40
|
+
const rssMb = (rssBytes / 1024 / 1024).toFixed(2); // Convert to MB
|
|
41
|
+
|
|
42
|
+
// 3. Node.js Version
|
|
43
|
+
const nodeVersion = process.version;
|
|
44
|
+
|
|
45
|
+
// 4. Operating System Info
|
|
46
|
+
const platform = os.platform();
|
|
47
|
+
const osRelease = os.release();
|
|
48
|
+
|
|
49
|
+
// 5. ffmpeg — media previews (jpegThumbnail) depend on it, and a broken
|
|
50
|
+
// binary is otherwise invisible.
|
|
51
|
+
const ffmpeg = await checkFfmpeg();
|
|
52
|
+
const ffmpegLine = ffmpeg.ok
|
|
53
|
+
? `✅ ${ffmpeg.version || "شغال"}`
|
|
54
|
+
: `❌ مش شغال (${ffmpeg.path})`;
|
|
55
|
+
|
|
56
|
+
// --- BUILDING THE REPLY ---
|
|
57
|
+
const reply = `*🤖 Bot Status 🤖*
|
|
58
|
+
|
|
59
|
+
*⏰ وقت التشغيل:* ${formattedUptime}
|
|
60
|
+
* 📊 استخدام الذاكرة:* ${rssMb} MB
|
|
61
|
+
*🟢 Node.js Version:* ${nodeVersion}
|
|
62
|
+
*📚 Baileys Version:* v${baileysVersion}
|
|
63
|
+
*💻 نظام التشغيل:* ${platform}
|
|
64
|
+
*💿 إصدار نظام التشغيل:* ${osRelease}
|
|
65
|
+
*🎞️ ffmpeg (الصور المصغرة):* ${ffmpegLine}`;
|
|
66
|
+
|
|
67
|
+
await sock.sendMessage(msg.key.remoteJid, { text: reply });
|
|
68
|
+
} catch (error) {
|
|
69
|
+
logger.error(error, "[Error] في أمر !status:");
|
|
70
|
+
await sock.sendMessage(msg.key.remoteJid, {
|
|
71
|
+
text: "حدث خطأ أثناء جلب حالة النظام.",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
};
|