levix-bot 3.3.0-rc.2 → 4.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 (118) hide show
  1. package/README.md +13 -9
  2. package/app.cjs +49 -46
  3. package/package.json +9 -1
  4. package/public/dashboard/assets/index-BBBXWzM0.js +391 -0
  5. package/public/dashboard/assets/index-pYaivHON.css +1 -0
  6. package/public/dashboard/index.html +19 -0
  7. package/public/dashboard.css +391 -66
  8. package/public/dashboard.js +593 -42
  9. package/scheduler.cjs +9 -14
  10. package/src/auth/use-database-auth-state.js +2 -4
  11. package/src/bootstrap/core.js +2 -5
  12. package/src/bootstrap/panel.js +10 -5
  13. package/src/cli.js +3 -5
  14. package/src/commands/autoschedule.cjs +1 -4
  15. package/src/commands/blacklist.cjs +22 -20
  16. package/src/commands/block.cjs +2 -4
  17. package/src/commands/calc.cjs +9 -16
  18. package/src/commands/digit.cjs +97 -0
  19. package/src/commands/gemini.cjs +147 -161
  20. package/src/commands/group/add.cjs +7 -16
  21. package/src/commands/group/antiSpam.cjs +5 -11
  22. package/src/commands/group/antilink.cjs +34 -28
  23. package/src/commands/group/approveall.cjs +3 -11
  24. package/src/commands/group/clearwarns.cjs +2 -5
  25. package/src/commands/group/demote.cjs +3 -8
  26. package/src/commands/group/kick.cjs +12 -15
  27. package/src/commands/group/media.cjs +17 -24
  28. package/src/commands/group/members.cjs +1 -2
  29. package/src/commands/group/promote.cjs +4 -12
  30. package/src/commands/group/removeall.cjs +2 -5
  31. package/src/commands/group/setpp.cjs +1 -1
  32. package/src/commands/group/setrules.cjs +2 -8
  33. package/src/commands/group/setwarn.cjs +4 -2
  34. package/src/commands/group/tagadmins.cjs +2 -5
  35. package/src/commands/group/warn.cjs +26 -24
  36. package/src/commands/group/warns.cjs +1 -2
  37. package/src/commands/group/welcome.cjs +13 -17
  38. package/src/commands/group.cjs +7 -10
  39. package/src/commands/help.cjs +6 -12
  40. package/src/commands/lang.cjs +104 -0
  41. package/src/commands/listschedules.cjs +2 -6
  42. package/src/commands/loop.cjs +6 -13
  43. package/src/commands/memory.cjs +19 -32
  44. package/src/commands/mod.cjs +97 -35
  45. package/src/commands/perm.cjs +18 -56
  46. package/src/commands/ping.cjs +1 -5
  47. package/src/commands/poll.cjs +1 -1
  48. package/src/commands/rand.cjs +2 -5
  49. package/src/commands/schedule.cjs +4 -8
  50. package/src/commands/score.cjs +8 -2
  51. package/src/commands/setprefix.cjs +2 -2
  52. package/src/commands/shortlink.cjs +7 -15
  53. package/src/commands/shutdown.cjs +1 -1
  54. package/src/commands/sticker.cjs +222 -0
  55. package/src/commands/stt.cjs +136 -68
  56. package/src/commands/todo.cjs +9 -15
  57. package/src/commands/tts.cjs +10 -16
  58. package/src/commands/unblock.cjs +2 -6
  59. package/src/commands/weather.cjs +5 -12
  60. package/src/config/ai-persona.md +5 -3
  61. package/src/config/available-models.json +1156 -0
  62. package/src/config/baileys.config.js +2 -1
  63. package/src/config/brand.esm.js +1 -10
  64. package/src/config/constants.js +1 -1
  65. package/src/config/defaults.cjs +66 -66
  66. package/src/config/lock.cjs +2 -2
  67. package/src/config/modelCapabilities.cjs +172 -0
  68. package/src/config/runtime-config.cjs +8 -13
  69. package/src/config/secrets.cjs +18 -10
  70. package/src/config/settings.cjs +94 -10
  71. package/src/config/vault.cjs +107 -0
  72. package/src/core/connection.js +13 -12
  73. package/src/core/events.js +41 -23
  74. package/src/core/proxy.js +7 -7
  75. package/src/core/session.js +91 -48
  76. package/src/core/socket.js +12 -15
  77. package/src/db/db.cjs +14 -6
  78. package/src/db/store.cjs +221 -87
  79. package/src/domain/caddy.js +1 -1
  80. package/src/domain/command.js +40 -19
  81. package/src/domain/detect.js +1 -1
  82. package/src/domain/system.js +2 -5
  83. package/src/handlers/command.handler.js +22 -43
  84. package/src/handlers/group.handler.js +13 -13
  85. package/src/handlers/message.handler.js +11 -21
  86. package/src/index.js +8 -4
  87. package/src/middleware/antispam.middleware.js +46 -23
  88. package/src/middleware/blacklist.middleware.js +14 -9
  89. package/src/middleware/forward-tracking.middleware.js +3 -2
  90. package/src/middleware/permissions.middleware.js +7 -10
  91. package/src/panel/login-throttle.cjs +43 -0
  92. package/src/panel/session-auth.cjs +17 -0
  93. package/src/routes/dashboard.api.esm.js +394 -72
  94. package/src/services/aiAgent.cjs +136 -49
  95. package/src/services/aiProviders.cjs +193 -47
  96. package/src/services/aiRouter.cjs +137 -0
  97. package/src/services/aiTools.cjs +305 -128
  98. package/src/services/llmDiscovery.cjs +481 -0
  99. package/src/services/modelRegistry.cjs +412 -0
  100. package/src/utils/datetime.cjs +2 -2
  101. package/src/utils/lid-helper.esm.js +7 -7
  102. package/src/utils/logger.cjs +1 -1
  103. package/src/utils/markdownParser.cjs +159 -0
  104. package/src/utils/memory.cjs +43 -13
  105. package/src/utils/messageContent.cjs +40 -0
  106. package/src/utils/normalizeJid.esm.js +7 -7
  107. package/src/utils/numerals.cjs +75 -0
  108. package/src/utils/permissions.esm.js +10 -15
  109. package/src/utils/providerUrl.cjs +281 -0
  110. package/src/utils/recurrence.cjs +7 -11
  111. package/src/utils/sendBotMessage.esm.js +4 -14
  112. package/src/utils/statusMessage.cjs +5 -13
  113. package/src/utils/storage.cjs +10 -0
  114. package/src/utils/storage.esm.js +11 -0
  115. package/src/utils/textDecode.cjs +4 -9
  116. package/src/utils/thumbnail.cjs +8 -14
  117. package/src/utils/typing.esm.js +12 -12
  118. package/views/dashboard.ejs +134 -106
@@ -1,11 +1,12 @@
1
1
  // file: /commands/warn.js (Corrected Logic Flow)
2
2
  const { getGroupSettings } = require("../../utils/storage.cjs"); // <-- 1. Import getGroupSettings
3
- const {
4
- getUserWarnings,
5
- saveUserWarnings,
6
- clearUserWarnings,
7
- } = require("../../utils/storage.cjs");
3
+ const { getUserWarnings, saveUserWarnings, clearUserWarnings } = require("../../utils/storage.cjs");
8
4
  const logger = require("../../utils/logger.cjs");
5
+ const {
6
+ hasBotPrivilegesSync,
7
+ isAdminInGroupSync,
8
+ sameUserSync,
9
+ } = require("../../utils/permissions.cjs");
9
10
 
10
11
  module.exports = {
11
12
  name: "warn",
@@ -14,12 +15,11 @@ module.exports = {
14
15
  chat: "group",
15
16
  userAdminRequired: true,
16
17
 
17
- async execute(sock, msg, args) {
18
+ async execute(sock, msg, args, _body, groupMetadata) {
18
19
  try {
19
20
  const groupId = msg.key.remoteJid;
20
21
  const senderId = msg.key.participant;
21
- const mentionedJid =
22
- msg.message?.extendedTextMessage?.contextInfo?.mentionedJid?.[0];
22
+ const mentionedJid = msg.message?.extendedTextMessage?.contextInfo?.mentionedJid?.[0];
23
23
  const reason = args.slice(1).join(" ");
24
24
 
25
25
  // --- 1. Validate Inputs ---
@@ -33,6 +33,18 @@ module.exports = {
33
33
  text: "يجب كتابة سبب التحذير.",
34
34
  });
35
35
  }
36
+ const isBot = [sock.user?.id, sock.user?.lid]
37
+ .filter(Boolean)
38
+ .some((id) => sameUserSync(id, mentionedJid));
39
+ if (
40
+ isBot ||
41
+ hasBotPrivilegesSync(mentionedJid) ||
42
+ isAdminInGroupSync(groupMetadata, mentionedJid)
43
+ ) {
44
+ return await sock.sendMessage(groupId, {
45
+ text: "لا يمكن تحذير أو طرد البوت أو مالكه أو أحد المشرفين بهذه الطريقة.",
46
+ });
47
+ }
36
48
 
37
49
  // --- 2. Add the New Warning ---
38
50
  const userWarnings = getUserWarnings(groupId, mentionedJid);
@@ -46,9 +58,8 @@ module.exports = {
46
58
  const newWarnCount = userWarnings.length;
47
59
 
48
60
  // --- 3. Send Confirmation & Check for Action ---
49
- let replyText =
50
- `✅ تم توجيه تحذير إلى @${mentionedJid.split("@")[0]}.\n` +
51
- `*السبب:* ${reason}`;
61
+ const replyText =
62
+ `✅ تم توجيه تحذير إلى @${mentionedJid.split("@")[0]}.\n` + `*السبب:* ${reason}`;
52
63
 
53
64
  await sock.sendMessage(groupId, {
54
65
  text: replyText,
@@ -57,17 +68,11 @@ module.exports = {
57
68
 
58
69
  const warnConfig = getGroupSettings(groupId)?.warn_system;
59
70
 
60
- if (
61
- warnConfig &&
62
- warnConfig.action === "KICK" &&
63
- newWarnCount >= warnConfig.max_warnings
64
- ) {
71
+ if (warnConfig && warnConfig.action === "KICK" && newWarnCount >= warnConfig.max_warnings) {
65
72
  await sock.sendMessage(groupId, {
66
73
  text: `🚫 لقد وصل العضو @${
67
74
  mentionedJid.split("@")[0]
68
- } إلى الحد الأقصى للتحذيرات (${newWarnCount}/${
69
- warnConfig.max_warnings
70
- }). سيتم حذفه.`,
75
+ } إلى الحد الأقصى للتحذيرات (${newWarnCount}/${warnConfig.max_warnings}). سيتم حذفه.`,
71
76
  mentions: [mentionedJid],
72
77
  });
73
78
 
@@ -76,13 +81,10 @@ module.exports = {
76
81
  await sock.groupParticipantsUpdate(groupId, [mentionedJid], "remove");
77
82
  clearUserWarnings(groupId, mentionedJid);
78
83
  logger.info(
79
- `[Warn Kick] Kicked and cleared warnings for ${mentionedJid} from ${groupId}`
84
+ `[Warn Kick] Kicked and cleared warnings for ${mentionedJid} from ${groupId}`,
80
85
  );
81
86
  } catch (kickError) {
82
- logger.error(
83
- { err: kickError },
84
- "Failed to kick user after max warnings"
85
- );
87
+ logger.error({ err: kickError }, "Failed to kick user after max warnings");
86
88
  await sock.sendMessage(groupId, {
87
89
  text: "حاولت حذف العضو ولكني لا أملك صلاحية كافية لذلك.",
88
90
  });
@@ -12,8 +12,7 @@ module.exports = {
12
12
  async execute(sock, msg) {
13
13
  try {
14
14
  const groupId = msg.key.remoteJid;
15
- const mentionedJid =
16
- msg.message?.extendedTextMessage?.contextInfo?.mentionedJid?.[0];
15
+ const mentionedJid = msg.message?.extendedTextMessage?.contextInfo?.mentionedJid?.[0];
17
16
 
18
17
  if (!mentionedJid) {
19
18
  return await sock.sendMessage(groupId, {
@@ -1,14 +1,12 @@
1
1
  // file: /commands/welcome.js
2
- const {
3
- getGroupSettings,
4
- saveGroupSettings,
5
- } = require("../../utils/storage.cjs");
2
+ const { getGroupSettings, saveGroupSettings } = require("../../utils/storage.cjs");
6
3
  const logger = require("../../utils/logger.cjs");
7
4
 
8
5
  module.exports = {
9
6
  name: "welcome",
10
7
  description: "Manages the group's welcome message system.",
11
- usage: "welcome [status]\nwelcome <on|off>\nwelcome add <نص الترحيب>\nwelcome list\nwelcome delete <رقم>",
8
+ usage:
9
+ "welcome [status]\nwelcome <on|off>\nwelcome add <نص الترحيب>\nwelcome list\nwelcome delete <رقم>",
12
10
  chat: "group",
13
11
  userAdminRequired: true,
14
12
 
@@ -40,7 +38,7 @@ module.exports = {
40
38
  });
41
39
  break;
42
40
 
43
- case "add":
41
+ case "add": {
44
42
  const messageToAdd = args.slice(1).join(" ");
45
43
  if (!messageToAdd || !messageToAdd.includes("${user}")) {
46
44
  return await sock.sendMessage(groupId, {
@@ -52,8 +50,9 @@ module.exports = {
52
50
  text: "✅ تم إضافة رسالة الترحيب بنجاح.",
53
51
  });
54
52
  break;
53
+ }
55
54
 
56
- case "list":
55
+ case "list": {
57
56
  if (welcomeConfig.messages.length === 0) {
58
57
  return await sock.sendMessage(groupId, {
59
58
  text: "لا توجد رسائل ترحيب محفوظة.",
@@ -65,8 +64,9 @@ module.exports = {
65
64
  });
66
65
  await sock.sendMessage(groupId, { text: listReply });
67
66
  break;
67
+ }
68
68
 
69
- case "delete":
69
+ case "delete": {
70
70
  const indexToDelete = parseInt(args[1]?.replace("#", ""), 10) - 1;
71
71
  if (
72
72
  isNaN(indexToDelete) ||
@@ -77,32 +77,28 @@ module.exports = {
77
77
  text: "رقم الرسالة غير صالح. استخدم `!welcome list` لمعرفة الأرقام.",
78
78
  });
79
79
  }
80
- const deletedMessage = welcomeConfig.messages.splice(
81
- indexToDelete,
82
- 1
83
- );
80
+ const deletedMessage = welcomeConfig.messages.splice(indexToDelete, 1);
84
81
  await sock.sendMessage(groupId, {
85
82
  text: `☑️ تم حذف الرسالة بنجاح:\n*${deletedMessage[0]}*`,
86
83
  });
87
84
  break;
85
+ }
88
86
 
89
87
  case "status":
90
- default:
88
+ default: {
91
89
  const statusReply =
92
90
  `*상태 نظام الترحيب:*\n\n` +
93
91
  `الحالة: ${welcomeConfig.enabled ? "مفعل ✅" : "معطل ☑️"}\n` +
94
92
  `عدد الرسائل المحفوظة: ${welcomeConfig.messages.length}`;
95
93
  await sock.sendMessage(groupId, { text: statusReply });
96
94
  break;
95
+ }
97
96
  }
98
97
 
99
98
  // Save the updated settings back to the database
100
99
  saveGroupSettings(groupId, settings);
101
100
  } catch (error) {
102
- logger.error(
103
- { err: error, command: "welcome" },
104
- "Error in !welcome command"
105
- );
101
+ logger.error({ err: error, command: "welcome" }, "Error in !welcome command");
106
102
  await sock.sendMessage(groupId, { text: "حدث خطأ." });
107
103
  }
108
104
  },
@@ -28,7 +28,9 @@ function register(command, label) {
28
28
  if (!command?.name) return;
29
29
  subCommands.set(command.name, command);
30
30
  if (Array.isArray(command.aliases)) {
31
- command.aliases.forEach((alias) => subCommands.set(alias, command));
31
+ command.aliases.forEach((alias) => {
32
+ subCommands.set(alias, command);
33
+ });
32
34
  }
33
35
  } catch (error) {
34
36
  logger.error({ err: error, file: label }, `Failed to load group sub-command from ${label}`);
@@ -54,10 +56,7 @@ if (manifest) {
54
56
  try {
55
57
  register(require(path.join(subCommandsPath, file)), file);
56
58
  } catch (error) {
57
- logger.error(
58
- { err: error, file },
59
- `Failed to load group sub-command from ${file}`
60
- );
59
+ logger.error({ err: error, file }, `Failed to load group sub-command from ${file}`);
61
60
  }
62
61
  }
63
62
  }
@@ -100,9 +99,7 @@ module.exports = {
100
99
 
101
100
  // "group:kick" — the flat key runtime-config uses for sub-commands, so a
102
101
  // level changed from the dashboard applies here too.
103
- const permissionLevel = runtimeConfig.getPermission(
104
- `group:${subCommand.name}`
105
- );
102
+ const permissionLevel = runtimeConfig.getPermission(`group:${subCommand.name}`);
106
103
 
107
104
  let hasPermission = false;
108
105
  switch (permissionLevel) {
@@ -121,7 +118,7 @@ module.exports = {
121
118
  break;
122
119
  default:
123
120
  logger.warn(
124
- `[group] Unknown permission level "${permissionLevel}" for sub-command "${subCommand.name}"`
121
+ `[group] Unknown permission level "${permissionLevel}" for sub-command "${subCommand.name}"`,
125
122
  );
126
123
  }
127
124
 
@@ -148,7 +145,7 @@ module.exports = {
148
145
  } catch (error) {
149
146
  logger.error(
150
147
  { err: error, subCommand: subCommandName },
151
- "Error executing a group sub-command"
148
+ "Error executing a group sub-command",
152
149
  );
153
150
  await sock.sendMessage(msg.key.remoteJid, {
154
151
  text: `❌ *فشل تنفيذ الأمر الفرعي* (\`${error.name || "Error"}\`): ${
@@ -27,8 +27,7 @@ function loadAllCommands() {
27
27
  if (cached) return cached;
28
28
  const commandsPath = path.join(__dirname);
29
29
  const files = getAllCommandFiles(commandsPath).filter(
30
- (file) =>
31
- path.basename(file) !== "help.cjs" && path.basename(file) !== "help.js"
30
+ (file) => path.basename(file) !== "help.cjs" && path.basename(file) !== "help.js",
32
31
  );
33
32
 
34
33
  const commands = { general: [], group: [] };
@@ -110,7 +109,7 @@ const PERMISSION_LABELS = {
110
109
 
111
110
  function permissionFor(command, isGroupCommand) {
112
111
  const level = runtimeConfig.getPermission(
113
- isGroupCommand ? `group:${command.name}` : command.name
112
+ isGroupCommand ? `group:${command.name}` : command.name,
114
113
  );
115
114
  return PERMISSION_LABELS[level] || level;
116
115
  }
@@ -152,8 +151,7 @@ module.exports = {
152
151
 
153
152
  // A command switched off from the dashboard can't run, so it has no
154
153
  // business in the menu.
155
- const live = (list) =>
156
- list.filter((cmd) => !runtimeConfig.isDisabled(cmd.name));
154
+ const live = (list) => list.filter((cmd) => !runtimeConfig.isDisabled(cmd.name));
157
155
 
158
156
  const general = live(commands.general);
159
157
  const group = live(commands.group);
@@ -177,11 +175,9 @@ module.exports = {
177
175
  sock,
178
176
  msg.key.remoteJid,
179
177
  {
180
- text:
181
- `❌ الأمر "*${args[0]}*" غير موجود.\n` +
182
- `اكتب \`${prefix}help\` لعرض كل الأوامر.`,
178
+ text: `❌ الأمر "*${args[0]}*" غير موجود.\n` + `اكتب \`${prefix}help\` لعرض كل الأوامر.`,
183
179
  },
184
- { replyTo: msg }
180
+ { replyTo: msg },
185
181
  );
186
182
  }
187
183
 
@@ -190,9 +186,7 @@ module.exports = {
190
186
 
191
187
  const commandAliases = aliasesOf(command);
192
188
  if (commandAliases.length) {
193
- usageText += `├─ *اختصارات:* ${commandAliases
194
- .map((a) => `${prefix}${a}`)
195
- .join(", ")}\n`;
189
+ usageText += `├─ *اختصارات:* ${commandAliases.map((a) => `${prefix}${a}`).join(", ")}\n`;
196
190
  }
197
191
 
198
192
  if (command.description) {
@@ -0,0 +1,104 @@
1
+ // file: src/commands/lang.cjs
2
+ const logger = require("../utils/logger.cjs");
3
+ const settings = require("../config/settings.cjs");
4
+ const runtimeConfig = require("../config/runtime-config.cjs");
5
+ const { isOwnerJidSync, isBotAdminUserSync } = require("../utils/permissions.cjs");
6
+
7
+ module.exports = {
8
+ name: "lang",
9
+ aliases: ["language", "لغة"],
10
+ description: "View or change the bot response language",
11
+ usage: "lang [en|ar|auto]",
12
+ chat: "all",
13
+
14
+ async execute(sock, msg, args) {
15
+ const chatId = msg.key.remoteJid;
16
+ const sender = msg.key.participant || msg.key.remoteJid;
17
+ const prefix = runtimeConfig.getPrefix();
18
+ const currentLang = settings.get("bot_language") || "auto";
19
+
20
+ const firstArg = args?.[0] ? String(args[0]).toLowerCase().trim() : "";
21
+
22
+ if (!args || args.length === 0) {
23
+ const labels = {
24
+ auto: "تلقائي (حسب لغة رسالتك) / Auto-detect",
25
+ ar: "العربية الفصحى (Modern Standard Arabic)",
26
+ en: "English (الإنجليزية)",
27
+ };
28
+ const text = `🌐 *لغة البوت / Bot language*
29
+
30
+ ⚙️ *اللغة الحالية:* \`${labels[currentLang] || currentLang}\`
31
+ • \`${prefix}lang ar\` — العربية الفصحى دائماً
32
+ • \`${prefix}lang en\` — English always
33
+ • \`${prefix}lang auto\` — تلقائي حسب لغة الرسالة
34
+
35
+ 🔢 *لتحويل الأرقام* استخدم \`${prefix}digit\` — مثال: \`${prefix}digit 12345\``;
36
+ await sock.sendMessage(chatId, { text }, { quoted: msg });
37
+ return;
38
+ }
39
+
40
+ if (firstArg === "ar" || firstArg === "en" || firstArg === "auto") {
41
+ if (args.length > 1) {
42
+ await sock.sendMessage(
43
+ chatId,
44
+ {
45
+ text: `🔢 لتحويل الأرقام استخدم \`${prefix}digit\`:\n• \`${prefix}digit ${firstArg} ${args.slice(1).join(" ")}\`\n• أو \`${prefix}digit ${args.slice(1).join(" ")}\` للتحويل التلقائي.\nTo convert digits, use \`${prefix}digit\`.`,
46
+ },
47
+ { quoted: msg },
48
+ );
49
+ return;
50
+ }
51
+
52
+ const isOwner = isOwnerJidSync(sender);
53
+ const isAdmin = isBotAdminUserSync(sender);
54
+ if (!isOwner && !isAdmin) {
55
+ await sock.sendMessage(
56
+ chatId,
57
+ {
58
+ text: `⚠️ عذراً، تغيير لغة البوت متاح لمالك البوت والمسؤولين فقط.\nSorry, only bot owner and admins can change bot language.\n_لتحويل الأرقام استخدم \`${prefix}digit\`._`,
59
+ },
60
+ { quoted: msg },
61
+ );
62
+ return;
63
+ }
64
+
65
+ settings.set("bot_language", firstArg);
66
+ logger.info(`[Language] Bot response language set to: ${firstArg}`);
67
+
68
+ if (firstArg === "ar") {
69
+ await sock.sendMessage(
70
+ chatId,
71
+ {
72
+ text: "✅ تم ضبط لغة البوت على *العربية الفصحى*. سيتحدث الذكاء الاصطناعي بالعربية الفصحى دائماً.",
73
+ },
74
+ { quoted: msg },
75
+ );
76
+ } else if (firstArg === "en") {
77
+ await sock.sendMessage(
78
+ chatId,
79
+ {
80
+ text: "✅ Bot language set to *English*. The AI assistant will now always respond in English.",
81
+ },
82
+ { quoted: msg },
83
+ );
84
+ } else {
85
+ await sock.sendMessage(
86
+ chatId,
87
+ {
88
+ text: "✅ تم ضبط اللغة على *التلقائي (Auto)*. سيرد البوت بحسب لغة كل رسالة ترسلها له تلقائياً.",
89
+ },
90
+ { quoted: msg },
91
+ );
92
+ }
93
+ return;
94
+ }
95
+
96
+ await sock.sendMessage(
97
+ chatId,
98
+ {
99
+ text: `❌ خيار غير صحيح.\nاستخدم:\n• \`${prefix}lang ar\` / \`${prefix}lang en\` / \`${prefix}lang auto\` للغة البوت\n• \`${prefix}digit 12345\` لتحويل الأرقام`,
100
+ },
101
+ { quoted: msg },
102
+ );
103
+ },
104
+ };
@@ -10,9 +10,7 @@ module.exports = {
10
10
 
11
11
  async execute(sock, msg) {
12
12
  const jobs = getScheduledJobs();
13
- const activeJobs = jobs.filter(
14
- (job) => job.status === "pending" || job.status === "active"
15
- );
13
+ const activeJobs = jobs.filter((job) => job.status === "pending" || job.status === "active");
16
14
 
17
15
  if (activeJobs.length === 0) {
18
16
  return await sock.sendMessage(msg.key.remoteJid, {
@@ -35,9 +33,7 @@ module.exports = {
35
33
  });
36
34
  reply += `*الوقت المحدد:* ${jobDate}\n`;
37
35
  }
38
- reply += `*الوجهة:* ${
39
- job.targetJid.endsWith("@g.us") ? "هذا الجروب" : "محادثة خاصة"
40
- }\n\n`;
36
+ reply += `*الوجهة:* ${job.targetJid.endsWith("@g.us") ? "هذا الجروب" : "محادثة خاصة"}\n\n`;
41
37
  });
42
38
 
43
39
  reply += "*لحذف مهمة، استخدم:*\n`!deleteschedule <ID>`";
@@ -85,17 +85,14 @@ function buildNumbers({ from, to, step }, limit) {
85
85
 
86
86
  function renderLine(template, number, index) {
87
87
  if (!template) return String(number);
88
- return template
89
- .replace(/\{n\}/gi, String(number))
90
- .replace(/\{i\}/gi, String(index));
88
+ return template.replace(/\{n\}/gi, String(number)).replace(/\{i\}/gi, String(index));
91
89
  }
92
90
 
93
91
  module.exports = {
94
92
  name: "loop",
95
93
  aliases: ["repeat", "كرر", "تكرار"],
96
94
  description: "يكرر نص أو أرقام — في رسالة واحدة أو رسائل منفصلة.",
97
- usage:
98
- "loop <عدد|من-إلى[:خطوة]> [النص] [-s رسائل منفصلة] [-n بدون ترقيم]",
95
+ usage: "loop <عدد|من-إلى[:خطوة]> [النص] [-s رسائل منفصلة] [-n بدون ترقيم]",
99
96
  chat: "all",
100
97
 
101
98
  async execute(sock, msg, args) {
@@ -124,7 +121,7 @@ module.exports = {
124
121
  if (requested > limit) {
125
122
  return reply(
126
123
  `❌ ${requested} تكرار كتير أوي.\n` +
127
- `الحد الأقصى ${limit} ${split ? "رسالة منفصلة" : "تكرار في الرسالة الواحدة"}.`
124
+ `الحد الأقصى ${limit} ${split ? "رسالة منفصلة" : "تكرار في الرسالة الواحدة"}.`,
128
125
  );
129
126
  }
130
127
 
@@ -133,9 +130,7 @@ module.exports = {
133
130
 
134
131
  // A bare count with text repeats the text; a range without text prints the
135
132
  // numbers. Either way {n}/{i} are substituted per line.
136
- const lines = numbers.map((number, index) =>
137
- renderLine(template, number, index + 1)
138
- );
133
+ const lines = numbers.map((number, index) => renderLine(template, number, index + 1));
139
134
 
140
135
  if (split) {
141
136
  for (let i = 0; i < lines.length; i++) {
@@ -144,15 +139,13 @@ module.exports = {
144
139
  sock,
145
140
  chatId,
146
141
  { text: body },
147
- { replyTo: i === 0 ? msg : null, typing: false, delayMs: SPLIT_GAP_MS }
142
+ { replyTo: i === 0 ? msg : null, typing: false, delayMs: SPLIT_GAP_MS },
148
143
  );
149
144
  }
150
145
  return;
151
146
  }
152
147
 
153
- let text = lines
154
- .map((line, i) => (numbered ? `${i + 1}. ${line}` : line))
155
- .join("\n");
148
+ let text = lines.map((line, i) => (numbered ? `${i + 1}. ${line}` : line)).join("\n");
156
149
 
157
150
  if (text.length > MAX_OUTPUT_CHARS) {
158
151
  text = `${text.slice(0, MAX_OUTPUT_CHARS)}\n…\n_(اتقصت عشان طولها)_`;
@@ -48,16 +48,13 @@ function formatEntries(entries, { limit = 30 } = {}) {
48
48
  return `*${number}.* ${entry.content}${meta}`;
49
49
  });
50
50
  const skipped = entries.length - shown.length;
51
- return (
52
- (skipped > 0 ? `_(+${skipped} أقدم مش معروضين)_\n\n` : "") + lines.join("\n")
53
- );
51
+ return (skipped > 0 ? `_(+${skipped} أقدم مش معروضين)_\n\n` : "") + lines.join("\n");
54
52
  }
55
53
 
56
54
  module.exports = {
57
55
  name: "memory",
58
56
  aliases: ["mem", "ذاكرة", "remember"],
59
- description:
60
- "الذاكرة الدائمة للبوت (ملفات .md): إضافة، عرض، بحث، حذف، أو تصدير الملف.",
57
+ description: "الذاكرة الدائمة للبوت (ملفات .md): إضافة، عرض، بحث، حذف، أو تصدير الملف.",
61
58
  usage:
62
59
  "memory\nmemory add <المعلومة>\nmemory add global <المعلومة>\nmemory search <كلمة>\nmemory forget <رقم|جزء من النص>\nmemory clear [global]\nmemory file [global]",
63
60
  chat: "all",
@@ -68,10 +65,8 @@ module.exports = {
68
65
  const senderId = isGroup ? msg.key.participant : msg.key.remoteJid;
69
66
 
70
67
  const isOwner = msg.key.fromMe || isOwnerJidSync(senderId);
71
- const privileged =
72
- isOwner ||
73
- isBotAdminUserSync(senderId) ||
74
- (isGroup && isAdminInGroupSync(groupMetadata, senderId));
68
+ const botPrivileged = isOwner || isBotAdminUserSync(senderId);
69
+ const groupAdmin = Boolean(isGroup && isAdminInGroupSync(groupMetadata, senderId));
75
70
 
76
71
  const action = String(args[0] || "").toLowerCase();
77
72
  const { scope, rest } = extractScope(args.slice(1));
@@ -79,19 +74,16 @@ module.exports = {
79
74
  const reply = (text, extra = {}) =>
80
75
  sendBotMessage(sock, chatId, { text, ...extra }, { replyTo: msg });
81
76
 
82
- const denied = () =>
83
- reply("🚫 ده للمشرفين والمالك بس. تقدر تضيف معلومة لذاكرة الشات عادي.");
77
+ const denied = () => reply("🚫 ده للمشرفين والمالك بس. تقدر تضيف معلومة لذاكرة الشات عادي.");
84
78
 
85
79
  try {
86
80
  // ---------------------------------------------------------------- add
87
81
  if (["add", "save", "احفظ", "ضيف", "اضف"].includes(action)) {
88
82
  const content = rest.join(" ").trim();
89
83
  if (!content) {
90
- return reply(
91
- "اكتب المعلومة بعد الأمر.\nمثال: `!memory add الاجتماع كل تلات الساعة ٩`"
92
- );
84
+ return reply("اكتب المعلومة بعد الأمر.\nمثال: `!memory add الاجتماع كل تلات الساعة ٩`");
93
85
  }
94
- if (scope === "global" && !privileged) return denied();
86
+ if (scope === "global" && !botPrivileged) return denied();
95
87
 
96
88
  const entry = memory.addMemory({
97
89
  scope,
@@ -103,7 +95,7 @@ module.exports = {
103
95
 
104
96
  return reply(
105
97
  `🧠 اتحفظت في *${scope === "global" ? "الذاكرة العامة" : "ذاكرة الشات"}*.\n` +
106
- `\`${entry.id}\` — ${content.length > 80 ? content.slice(0, 80) + "…" : content}`
98
+ `\`${entry.id}\` — ${content.length > 80 ? content.slice(0, 80) + "…" : content}`,
107
99
  );
108
100
  }
109
101
 
@@ -120,18 +112,17 @@ module.exports = {
120
112
  return reply(
121
113
  `🔍 *نتايج البحث عن* "${query}"\n\n` +
122
114
  `*ذاكرة الشات (${chatHits.length}):*\n${formatEntries(chatHits, { limit: 10 })}\n\n` +
123
- `*الذاكرة العامة (${globalHits.length}):*\n${formatEntries(globalHits, { limit: 10 })}`
115
+ `*الذاكرة العامة (${globalHits.length}):*\n${formatEntries(globalHits, { limit: 10 })}`,
124
116
  );
125
117
  }
126
118
 
127
119
  // ------------------------------------------------------------- forget
128
120
  if (["forget", "del", "delete", "remove", "امسح", "انسى"].includes(action)) {
129
- if (!privileged) return denied();
121
+ if (scope === "global" ? !botPrivileged : !botPrivileged && !groupAdmin) return denied();
130
122
  const ref = rest.join(" ").trim();
131
123
  if (!ref) {
132
124
  return reply(
133
- "حدد اللي عايز تمسحه: رقمه من `!memory` أو جزء من نصه.\n" +
134
- "مثال: `!memory forget 3`"
125
+ "حدد اللي عايز تمسحه: رقمه من `!memory` أو جزء من نصه.\n" + "مثال: `!memory forget 3`",
135
126
  );
136
127
  }
137
128
  const removed = memory.removeMemory({ scope, chatId, ref });
@@ -140,26 +131,26 @@ module.exports = {
140
131
  ? `🗑️ اتمسحت: ${removed.content.slice(0, 120)}`
141
132
  : `مالقيتش حاجة تطابق "${ref}" في ${
142
133
  scope === "global" ? "الذاكرة العامة" : "ذاكرة الشات"
143
- }.`
134
+ }.`,
144
135
  );
145
136
  }
146
137
 
147
138
  // -------------------------------------------------------------- clear
148
139
  if (["clear", "reset", "wipe", "تصفير"].includes(action)) {
149
- if (!privileged) return denied();
140
+ if (scope === "global" ? !botPrivileged : !botPrivileged && !groupAdmin) return denied();
150
141
  const count = memory.clearMemory({ scope, chatId });
151
142
  return reply(
152
143
  count
153
144
  ? `🧹 اتمسحت *${count}* معلومة من ${
154
145
  scope === "global" ? "الذاكرة العامة" : "ذاكرة الشات"
155
146
  }.`
156
- : "الذاكرة كانت فاضية أصلاً."
147
+ : "الذاكرة كانت فاضية أصلاً.",
157
148
  );
158
149
  }
159
150
 
160
151
  // --------------------------------------------------------------- file
161
152
  if (["file", "export", "md", "ملف"].includes(action)) {
162
- if (!privileged) return denied();
153
+ if (scope === "global" ? !botPrivileged : !botPrivileged && !groupAdmin) return denied();
163
154
  const filePath = memory.memoryFilePath(scope, chatId);
164
155
  if (!fs.existsSync(filePath)) {
165
156
  return reply("مفيش ملف ذاكرة لسه — ابدأ بـ `!memory add ...`.");
@@ -171,11 +162,9 @@ module.exports = {
171
162
  document: fs.readFileSync(filePath),
172
163
  mimetype: "text/markdown",
173
164
  fileName: scope === "global" ? "global.md" : `${chatId}.md`,
174
- caption: `🧠 ملف ${
175
- scope === "global" ? "الذاكرة العامة" : "ذاكرة الشات"
176
- }`,
165
+ caption: `🧠 ملف ${scope === "global" ? "الذاكرة العامة" : "ذاكرة الشات"}`,
177
166
  },
178
- { replyTo: msg }
167
+ { replyTo: msg },
179
168
  );
180
169
  }
181
170
 
@@ -189,13 +178,11 @@ module.exports = {
189
178
  `(${entries.length} معلومة)\n\n` +
190
179
  `${formatEntries(entries)}\n\n` +
191
180
  `_الشات: ${stats.chatCount} · العام: ${stats.globalCount}_\n` +
192
- "`!memory add <معلومة>` · `!memory add global <معلومة>` · `!memory forget <رقم>` · `!memory file`"
181
+ "`!memory add <معلومة>` · `!memory add global <معلومة>` · `!memory forget <رقم>` · `!memory file`",
193
182
  );
194
183
  } catch (error) {
195
184
  logger.error({ err: error }, "[memory] command failed");
196
- return reply(
197
- `❌ *مشكلة في الذاكرة*\n\n*التفاصيل:* ${error.message || error}`
198
- );
185
+ return reply(`❌ *مشكلة في الذاكرة*\n\n*التفاصيل:* ${error.message || error}`);
199
186
  }
200
187
  },
201
188
  };