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.
Files changed (148) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +105 -0
  3. package/SETUP.md +242 -0
  4. package/app.cjs +337 -0
  5. package/bin/levix.js +7 -0
  6. package/deploy/install.sh +182 -0
  7. package/deploy/levix.service +56 -0
  8. package/deploy/nginx/levix.leviro.net.conf +79 -0
  9. package/package.json +78 -0
  10. package/public/brand/apple-touch-icon.png +0 -0
  11. package/public/brand/banner.jpg +0 -0
  12. package/public/brand/banner.webp +0 -0
  13. package/public/brand/icon-16.png +0 -0
  14. package/public/brand/icon-192.png +0 -0
  15. package/public/brand/icon-32.png +0 -0
  16. package/public/brand/icon-512.png +0 -0
  17. package/public/brand/icon-512.webp +0 -0
  18. package/public/brand/mark.png +0 -0
  19. package/public/brand/mark.svg +16 -0
  20. package/public/brand/mark.webp +0 -0
  21. package/public/brand/wordmark.png +0 -0
  22. package/public/brand/wordmark.webp +0 -0
  23. package/public/dashboard.css +881 -0
  24. package/public/dashboard.js +1057 -0
  25. package/public/logo.svg +16 -0
  26. package/public/qrcode.min.js +1 -0
  27. package/public/socket.io.min.js +7 -0
  28. package/scheduler.cjs +197 -0
  29. package/src/auth/auth-storage.cjs +74 -0
  30. package/src/auth/use-database-auth-state.js +130 -0
  31. package/src/bootstrap/core.js +74 -0
  32. package/src/bootstrap/events.cjs +43 -0
  33. package/src/bootstrap/panel.js +97 -0
  34. package/src/cli.js +153 -0
  35. package/src/commands/autoschedule.cjs +84 -0
  36. package/src/commands/blacklist.cjs +116 -0
  37. package/src/commands/block.cjs +70 -0
  38. package/src/commands/calc.cjs +387 -0
  39. package/src/commands/debt.cjs +175 -0
  40. package/src/commands/deleteschedule.cjs +36 -0
  41. package/src/commands/gemini.cjs +727 -0
  42. package/src/commands/group/add.cjs +136 -0
  43. package/src/commands/group/all.cjs +43 -0
  44. package/src/commands/group/antiSpam.cjs +96 -0
  45. package/src/commands/group/antilink.cjs +167 -0
  46. package/src/commands/group/approveall.cjs +54 -0
  47. package/src/commands/group/clearwarns.cjs +41 -0
  48. package/src/commands/group/deletenote.cjs +33 -0
  49. package/src/commands/group/demote.cjs +62 -0
  50. package/src/commands/group/kick.cjs +85 -0
  51. package/src/commands/group/media.cjs +120 -0
  52. package/src/commands/group/members.cjs +59 -0
  53. package/src/commands/group/note.cjs +32 -0
  54. package/src/commands/group/promote.cjs +60 -0
  55. package/src/commands/group/removeall.cjs +42 -0
  56. package/src/commands/group/rules.cjs +31 -0
  57. package/src/commands/group/save.cjs +36 -0
  58. package/src/commands/group/setname.cjs +41 -0
  59. package/src/commands/group/setpp.cjs +51 -0
  60. package/src/commands/group/setrules.cjs +46 -0
  61. package/src/commands/group/setwarn.cjs +58 -0
  62. package/src/commands/group/tagadmins.cjs +50 -0
  63. package/src/commands/group/warn.cjs +105 -0
  64. package/src/commands/group/warns.cjs +66 -0
  65. package/src/commands/group/welcome.cjs +109 -0
  66. package/src/commands/group.cjs +160 -0
  67. package/src/commands/help.cjs +230 -0
  68. package/src/commands/listschedules.cjs +47 -0
  69. package/src/commands/loop.cjs +163 -0
  70. package/src/commands/memory.cjs +201 -0
  71. package/src/commands/mod.cjs +155 -0
  72. package/src/commands/notes.cjs +28 -0
  73. package/src/commands/perm.cjs +236 -0
  74. package/src/commands/ping.cjs +17 -0
  75. package/src/commands/poll.cjs +80 -0
  76. package/src/commands/prayer.cjs +64 -0
  77. package/src/commands/qr.cjs +39 -0
  78. package/src/commands/rand.cjs +130 -0
  79. package/src/commands/restart.cjs +25 -0
  80. package/src/commands/schedule.cjs +86 -0
  81. package/src/commands/score.cjs +130 -0
  82. package/src/commands/setprefix.cjs +40 -0
  83. package/src/commands/shortlink.cjs +74 -0
  84. package/src/commands/shutdown.cjs +45 -0
  85. package/src/commands/status.cjs +75 -0
  86. package/src/commands/stt.cjs +150 -0
  87. package/src/commands/todo.cjs +88 -0
  88. package/src/commands/tts.cjs +170 -0
  89. package/src/commands/unblock.cjs +57 -0
  90. package/src/commands/weather.cjs +79 -0
  91. package/src/config/ai-persona.md +17 -0
  92. package/src/config/baileys.config.js +51 -0
  93. package/src/config/brand.cjs +36 -0
  94. package/src/config/brand.esm.js +17 -0
  95. package/src/config/constants.js +16 -0
  96. package/src/config/defaults.cjs +91 -0
  97. package/src/config/lock.cjs +110 -0
  98. package/src/config/paths.cjs +90 -0
  99. package/src/config/runtime-config.cjs +219 -0
  100. package/src/config/secrets.cjs +130 -0
  101. package/src/config/settings.cjs +386 -0
  102. package/src/core/connection.js +199 -0
  103. package/src/core/events.js +61 -0
  104. package/src/core/socket.js +64 -0
  105. package/src/db/db.cjs +282 -0
  106. package/src/db/store.cjs +837 -0
  107. package/src/db/store.esm.js +17 -0
  108. package/src/domain/caddy.js +120 -0
  109. package/src/domain/command.js +380 -0
  110. package/src/domain/detect.js +173 -0
  111. package/src/domain/nginx.js +138 -0
  112. package/src/domain/system.js +148 -0
  113. package/src/handlers/command.handler.js +414 -0
  114. package/src/handlers/group.handler.js +73 -0
  115. package/src/handlers/message.handler.js +149 -0
  116. package/src/index.js +217 -0
  117. package/src/middleware/antispam.middleware.js +68 -0
  118. package/src/middleware/blacklist.middleware.js +29 -0
  119. package/src/middleware/forward-tracking.middleware.js +49 -0
  120. package/src/middleware/permissions.middleware.js +114 -0
  121. package/src/routes/dashboard.api.esm.js +779 -0
  122. package/src/services/aiAgent.cjs +317 -0
  123. package/src/services/aiTools.cjs +635 -0
  124. package/src/utils/datetime.cjs +61 -0
  125. package/src/utils/lid-helper.esm.js +143 -0
  126. package/src/utils/logger.cjs +72 -0
  127. package/src/utils/memory.cjs +385 -0
  128. package/src/utils/normalizeJid.cjs +65 -0
  129. package/src/utils/normalizeJid.esm.js +37 -0
  130. package/src/utils/openBrowser.cjs +129 -0
  131. package/src/utils/permissions.cjs +154 -0
  132. package/src/utils/permissions.esm.js +375 -0
  133. package/src/utils/recentMessageCache.esm.js +70 -0
  134. package/src/utils/requestOrigin.cjs +102 -0
  135. package/src/utils/sendBotMessage.cjs +43 -0
  136. package/src/utils/sendBotMessage.esm.js +139 -0
  137. package/src/utils/statusMessage.cjs +179 -0
  138. package/src/utils/storage-hub.cjs +49 -0
  139. package/src/utils/storage-hub.esm.js +24 -0
  140. package/src/utils/storage.cjs +80 -0
  141. package/src/utils/storage.esm.js +88 -0
  142. package/src/utils/textDecode.cjs +197 -0
  143. package/src/utils/thumbnail.cjs +591 -0
  144. package/src/utils/typing.esm.js +102 -0
  145. package/views/dashboard.ejs +329 -0
  146. package/views/login.ejs +105 -0
  147. package/views/qr.ejs +108 -0
  148. 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
+ };