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,387 @@
1
+ // آلة حاسبة — !calc
2
+ //
3
+ // A self-contained expression evaluator. Deliberately NOT `eval()`: the input
4
+ // comes straight from any group member, so we tokenize and parse it ourselves
5
+ // and only ever touch the whitelisted functions below.
6
+ //
7
+ // Supports: + - * / % ^ (and ** ), parentheses, unary minus, implicit
8
+ // multiplication (2(3+4), 3pi), postfix factorial (5!), percentages
9
+ // (15%, «20% من 300»), Arabic-Indic digits, and the math functions listed in
10
+ // FUNCTIONS.
11
+
12
+ const { sendBotMessage } = require("../utils/sendBotMessage.cjs");
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // whitelist
16
+ // ---------------------------------------------------------------------------
17
+
18
+ const CONSTANTS = {
19
+ pi: Math.PI,
20
+ π: Math.PI,
21
+ e: Math.E,
22
+ tau: Math.PI * 2,
23
+ };
24
+
25
+ const DEG = Math.PI / 180;
26
+
27
+ const FUNCTIONS = {
28
+ sqrt: { arity: 1, fn: Math.sqrt },
29
+ cbrt: { arity: 1, fn: Math.cbrt },
30
+ abs: { arity: 1, fn: Math.abs },
31
+ round: { arity: 1, fn: Math.round },
32
+ floor: { arity: 1, fn: Math.floor },
33
+ ceil: { arity: 1, fn: Math.ceil },
34
+ sign: { arity: 1, fn: Math.sign },
35
+ ln: { arity: 1, fn: Math.log },
36
+ log: { arity: 1, fn: Math.log10 },
37
+ log2: { arity: 1, fn: Math.log2 },
38
+ exp: { arity: 1, fn: Math.exp },
39
+ sin: { arity: 1, fn: Math.sin },
40
+ cos: { arity: 1, fn: Math.cos },
41
+ tan: { arity: 1, fn: Math.tan },
42
+ asin: { arity: 1, fn: Math.asin },
43
+ acos: { arity: 1, fn: Math.acos },
44
+ atan: { arity: 1, fn: Math.atan },
45
+ // Degree variants — nobody types sin(rad(30)) in a chat.
46
+ sind: { arity: 1, fn: (x) => Math.sin(x * DEG) },
47
+ cosd: { arity: 1, fn: (x) => Math.cos(x * DEG) },
48
+ tand: { arity: 1, fn: (x) => Math.tan(x * DEG) },
49
+ deg: { arity: 1, fn: (x) => x / DEG },
50
+ rad: { arity: 1, fn: (x) => x * DEG },
51
+ fact: { arity: 1, fn: (x) => factorial(x) },
52
+ pow: { arity: 2, fn: Math.pow },
53
+ root: { arity: 2, fn: (x, n) => (n % 2 === 0 || x >= 0 ? Math.pow(x, 1 / n) : -Math.pow(-x, 1 / n)) },
54
+ hypot: { arity: -1, fn: (...a) => Math.hypot(...a) },
55
+ min: { arity: -1, fn: (...a) => Math.min(...a) },
56
+ max: { arity: -1, fn: (...a) => Math.max(...a) },
57
+ avg: { arity: -1, fn: (...a) => a.reduce((s, x) => s + x, 0) / a.length },
58
+ sum: { arity: -1, fn: (...a) => a.reduce((s, x) => s + x, 0) },
59
+ };
60
+
61
+ function factorial(n) {
62
+ if (!Number.isInteger(n)) throw new CalcError("المضروب (!) يشتغل على أرقام صحيحة بس");
63
+ if (n < 0) throw new CalcError("مفيش مضروب لرقم سالب");
64
+ if (n > 170) return Infinity; // beyond this a double is Infinity anyway
65
+ let result = 1;
66
+ for (let i = 2; i <= n; i++) result *= i;
67
+ return result;
68
+ }
69
+
70
+ class CalcError extends Error {}
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // normalisation
74
+ // ---------------------------------------------------------------------------
75
+
76
+ const ARABIC_DIGITS = "٠١٢٣٤٥٦٧٨٩";
77
+ const PERSIAN_DIGITS = "۰۱۲۳۴۵۶۷۸۹";
78
+
79
+ function normalize(input) {
80
+ let text = String(input || "");
81
+
82
+ // Arabic-Indic / Persian digits -> ASCII
83
+ text = text.replace(/[٠-٩]/g, (d) => String(ARABIC_DIGITS.indexOf(d)));
84
+ text = text.replace(/[۰-۹]/g, (d) => String(PERSIAN_DIGITS.indexOf(d)));
85
+
86
+ // Arabic decimal separator + the symbols people paste from calculators
87
+ text = text
88
+ .replace(/٫/g, ".")
89
+ .replace(/\*\*/g, "^")
90
+ .replace(/[×✕✖]/g, "*")
91
+ .replace(/[÷➗]/g, "/")
92
+ .replace(/[−–—]/g, "-")
93
+ .replace(/(\d)\s*[xX]\s*(\d)/g, "$1*$2") // «3 x 4»
94
+ // thousands separators only — a comma between function arguments stays put
95
+ // (write `max(3, 900)` with a space so it isn't read as 3900)
96
+ .replace(/(?<=\d),(?=\d{3}(?!\d))/g, "");
97
+
98
+ // «20% من 300» / «20% of 300» -> (20/100)*300, so % stays modulo elsewhere.
99
+ text = text.replace(
100
+ /(\d+(?:\.\d+)?)\s*%\s*(?:of|من)\s*/gi,
101
+ (_, value) => `(${value}/100)*`
102
+ );
103
+
104
+ return text.trim();
105
+ }
106
+
107
+ // ---------------------------------------------------------------------------
108
+ // tokenizer
109
+ // ---------------------------------------------------------------------------
110
+
111
+ function tokenize(text) {
112
+ const tokens = [];
113
+ let i = 0;
114
+
115
+ while (i < text.length) {
116
+ const char = text[i];
117
+
118
+ if (/\s/.test(char)) {
119
+ i++;
120
+ continue;
121
+ }
122
+
123
+ if (/[0-9.]/.test(char)) {
124
+ let raw = "";
125
+ while (i < text.length && /[0-9.]/.test(text[i])) raw += text[i++];
126
+ // scientific notation: 1e5, 2.5e-3
127
+ const signed = /[+-]/.test(text[i + 1] || "");
128
+ const exponentDigit = signed ? text[i + 2] : text[i + 1];
129
+ if (/[eE]/.test(text[i] || "") && /[0-9]/.test(exponentDigit || "")) {
130
+ raw += text[i++];
131
+ if (signed) raw += text[i++];
132
+ while (i < text.length && /[0-9]/.test(text[i])) raw += text[i++];
133
+ }
134
+ const value = Number(raw);
135
+ if (!Number.isFinite(value)) throw new CalcError(`رقم غير صالح: \`${raw}\``);
136
+ tokens.push({ type: "num", value });
137
+ continue;
138
+ }
139
+
140
+ if (/[a-zA-Zπ_]/.test(char)) {
141
+ let name = "";
142
+ while (i < text.length && /[a-zA-Z0-9π_]/.test(text[i])) name += text[i++];
143
+ tokens.push({ type: "ident", value: name.toLowerCase() });
144
+ continue;
145
+ }
146
+
147
+ if ("+-*/%^()!,".includes(char)) {
148
+ tokens.push({ type: char });
149
+ i++;
150
+ continue;
151
+ }
152
+
153
+ throw new CalcError(`رمز مش مفهوم: \`${char}\``);
154
+ }
155
+
156
+ return tokens;
157
+ }
158
+
159
+ // ---------------------------------------------------------------------------
160
+ // parser (recursive descent)
161
+ // ---------------------------------------------------------------------------
162
+
163
+ function parse(tokens) {
164
+ let pos = 0;
165
+
166
+ const peek = (offset = 0) => tokens[pos + offset];
167
+ const eat = (type) => {
168
+ if (peek()?.type !== type) return null;
169
+ return tokens[pos++];
170
+ };
171
+ const expect = (type, message) => {
172
+ const token = eat(type);
173
+ if (!token) throw new CalcError(message);
174
+ return token;
175
+ };
176
+
177
+ // A token that can start a value — used for implicit multiplication.
178
+ const startsValue = (token) =>
179
+ !!token && (token.type === "num" || token.type === "ident" || token.type === "(");
180
+
181
+ function parseExpression() {
182
+ let left = parseTerm();
183
+ for (;;) {
184
+ if (eat("+")) left += parseTerm();
185
+ else if (eat("-")) left -= parseTerm();
186
+ else return left;
187
+ }
188
+ }
189
+
190
+ function parseTerm() {
191
+ let left = parseUnary();
192
+ for (;;) {
193
+ if (eat("*")) {
194
+ left *= parseUnary();
195
+ } else if (eat("/")) {
196
+ const right = parseUnary();
197
+ if (right === 0) throw new CalcError("القسمة على صفر ملهاش لازمة 😅");
198
+ left /= right;
199
+ } else if (peek()?.type === "%" && startsValue(peek(1))) {
200
+ // infix `%` is modulo; the postfix «15%» form is handled in parsePostfix
201
+ pos++;
202
+ const right = parseUnary();
203
+ if (right === 0) throw new CalcError("باقي القسمة على صفر مش معرّف");
204
+ left %= right;
205
+ } else if (startsValue(peek())) {
206
+ // implicit multiplication: 2(3+4), 3pi, 2sqrt(9)
207
+ left *= parseUnary();
208
+ } else {
209
+ return left;
210
+ }
211
+ }
212
+ }
213
+
214
+ function parseUnary() {
215
+ if (eat("-")) return -parseUnary();
216
+ if (eat("+")) return parseUnary();
217
+ return parsePower();
218
+ }
219
+
220
+ function parsePower() {
221
+ const base = parsePostfix();
222
+ if (eat("^")) return Math.pow(base, parseUnary()); // right-associative
223
+ return base;
224
+ }
225
+
226
+ function parsePostfix() {
227
+ let value = parsePrimary();
228
+ for (;;) {
229
+ if (eat("!")) {
230
+ value = factorial(value);
231
+ } else if (peek()?.type === "%" && !startsValue(peek(1))) {
232
+ pos++;
233
+ value /= 100;
234
+ } else {
235
+ return value;
236
+ }
237
+ }
238
+ }
239
+
240
+ function parsePrimary() {
241
+ const token = peek();
242
+ if (!token) throw new CalcError("المعادلة ناقصة");
243
+
244
+ if (eat("(")) {
245
+ const value = parseExpression();
246
+ expect(")", "قوس مفتوح ومقفلش");
247
+ return value;
248
+ }
249
+
250
+ if (token.type === "num") {
251
+ pos++;
252
+ return token.value;
253
+ }
254
+
255
+ if (token.type === "ident") {
256
+ pos++;
257
+ const name = token.value;
258
+
259
+ if (FUNCTIONS[name]) {
260
+ const spec = FUNCTIONS[name];
261
+ expect("(", `الدالة \`${name}\` محتاجة أقواس، زي \`${name}(9)\``);
262
+ const args = [];
263
+ if (peek()?.type !== ")") {
264
+ args.push(parseExpression());
265
+ while (eat(",")) args.push(parseExpression());
266
+ }
267
+ expect(")", `قوس \`${name}\` مقفلش`);
268
+ if (spec.arity !== -1 && args.length !== spec.arity) {
269
+ throw new CalcError(
270
+ `الدالة \`${name}\` بتاخد ${spec.arity} ${spec.arity === 1 ? "قيمة" : "قيم"}`
271
+ );
272
+ }
273
+ if (!args.length) throw new CalcError(`الدالة \`${name}\` من غير قيم`);
274
+ return spec.fn(...args);
275
+ }
276
+
277
+ if (name in CONSTANTS) return CONSTANTS[name];
278
+
279
+ throw new CalcError(`مش عارف يعني إيه \`${name}\``);
280
+ }
281
+
282
+ throw new CalcError(`مكان غلط للرمز \`${token.type}\``);
283
+ }
284
+
285
+ const result = parseExpression();
286
+ if (pos < tokens.length) throw new CalcError("في زيادة في آخر المعادلة");
287
+ return result;
288
+ }
289
+
290
+ function evaluate(expression) {
291
+ const normalized = normalize(expression);
292
+ if (!normalized) throw new CalcError("اكتب معادلة الأول");
293
+ if (normalized.length > 300) throw new CalcError("المعادلة طويلة أوي");
294
+
295
+ const value = parse(tokenize(normalized));
296
+ if (typeof value !== "number" || Number.isNaN(value)) {
297
+ throw new CalcError("النتيجة مش رقم صالح");
298
+ }
299
+ return { normalized, value };
300
+ }
301
+
302
+ // ---------------------------------------------------------------------------
303
+ // formatting
304
+ // ---------------------------------------------------------------------------
305
+
306
+ function formatNumber(value) {
307
+ if (!Number.isFinite(value)) return value > 0 ? "∞" : "-∞";
308
+ if (Number.isInteger(value) && Math.abs(value) < 1e15) {
309
+ return value.toLocaleString("en-US");
310
+ }
311
+ const absolute = Math.abs(value);
312
+ if (absolute !== 0 && (absolute >= 1e15 || absolute < 1e-7)) {
313
+ return value.toExponential(6).replace(/e([+-])(\d)$/, "e$10$2");
314
+ }
315
+ // 0.1 + 0.2 should read 0.3, not 0.30000000000000004
316
+ const cleaned = Number(value.toPrecision(12));
317
+ const [whole, fraction] = String(cleaned).split(".");
318
+ const grouped = Number(whole).toLocaleString("en-US");
319
+ return fraction ? `${grouped}.${fraction}` : grouped;
320
+ }
321
+
322
+ const HELP_TEXT =
323
+ "🧮 *الآلة الحاسبة*\n\n" +
324
+ "*الاستخدام:* `!calc <معادلة>`\n" +
325
+ "أو رد على رسالة فيها معادلة واكتب `!calc`\n\n" +
326
+ "*العمليات:* `+` `-` `*` `/` `%` (باقي القسمة) `^` (أُس) `!` (مضروب)\n" +
327
+ "*النِّسب:* `15%` · `20% من 300`\n" +
328
+ "*الثوابت:* `pi` · `e` · `tau`\n" +
329
+ "*الدوال:* sqrt, cbrt, abs, round, floor, ceil, ln, log, log2, exp,\n" +
330
+ "sin, cos, tan, sind, cosd, tand, asin, acos, atan, deg, rad,\n" +
331
+ "pow, root, hypot, min, max, avg, sum, fact\n\n" +
332
+ "*أمثلة:*\n" +
333
+ "`!calc (12 + 8) * 3`\n" +
334
+ "`!calc 2^10`\n" +
335
+ "`!calc 25% من 480`\n" +
336
+ "`!calc sqrt(144) + 5!`\n" +
337
+ "`!calc max(3, 9, 4) * pi`";
338
+
339
+ module.exports = {
340
+ name: "calc",
341
+ aliases: ["calculate", "حساب", "احسب", "="],
342
+ description: "آلة حاسبة: عمليات حسابية، نِسب، أُسس، ودوال رياضية.",
343
+ usage: "calc <معادلة>",
344
+ chat: "all",
345
+
346
+ async execute(sock, msg, args) {
347
+ const chatId = msg.key.remoteJid;
348
+
349
+ // Fall back to the quoted message so «رد على الرقم واكتب !calc» works.
350
+ const quoted = msg.message?.extendedTextMessage?.contextInfo?.quotedMessage;
351
+ const quotedText =
352
+ quoted?.conversation || quoted?.extendedTextMessage?.text || "";
353
+
354
+ const expression = args.join(" ").trim() || quotedText.trim();
355
+
356
+ if (!expression) {
357
+ return sendBotMessage(sock, chatId, { text: HELP_TEXT }, { replyTo: msg });
358
+ }
359
+
360
+ try {
361
+ const { normalized, value } = evaluate(expression);
362
+ return sendBotMessage(
363
+ sock,
364
+ chatId,
365
+ {
366
+ text:
367
+ `🧮 \`${normalized}\`\n` +
368
+ `*=* ${formatNumber(value)}`,
369
+ },
370
+ { replyTo: msg }
371
+ );
372
+ } catch (error) {
373
+ const reason =
374
+ error instanceof CalcError ? error.message : "المعادلة مش مفهومة";
375
+ return sendBotMessage(
376
+ sock,
377
+ chatId,
378
+ { text: `❌ ${reason}\n\nاكتب \`!calc\` لوحده عشان تشوف الأمثلة.` },
379
+ { replyTo: msg }
380
+ );
381
+ }
382
+ },
383
+
384
+ // exported for reuse / quick sanity checks
385
+ _evaluate: evaluate,
386
+ _format: formatNumber,
387
+ };
@@ -0,0 +1,175 @@
1
+ // file: /commands/debt.cjs
2
+ // Aliased on import: the command's own helpers below are named addDebt /
3
+ // listDebts / deleteDebt too.
4
+ const {
5
+ addDebt: storeAddDebt,
6
+ getDebt: storeGetDebt,
7
+ deleteDebt: storeDeleteDebt,
8
+ listDebts: storeListDebts,
9
+ } = require("../utils/storage.cjs");
10
+ const logger = require("../utils/logger.cjs");
11
+ const normalizeJid = require("../utils/normalizeJid.esm.js").default;
12
+
13
+ module.exports = {
14
+ name: "debt",
15
+ aliases: ["دين", "owes"],
16
+ description: "Private debt tracking system",
17
+ usage: "debt add <المبلغ> [الوصف]\ndebt del <id>\ndebt list",
18
+ chat: "private",
19
+
20
+ async execute(sock, msg, args) {
21
+ const remote = msg.key.remoteJid;
22
+ const senderId = normalizeJid(msg.key.participant || remote);
23
+
24
+ // private chat: the person you're talking to
25
+ const targetId = normalizeJid(msg.key.remoteJidAlt);
26
+ const chatKey = "private-" + targetId;
27
+
28
+ const sub = (args[0] || "").toLowerCase();
29
+
30
+ switch (sub) {
31
+ case "add":
32
+ case "اضافة":
33
+ await addDebt(sock, msg, args.slice(1), senderId, targetId, chatKey);
34
+ break;
35
+
36
+ case "del":
37
+ case "حذف":
38
+ await deleteDebt(sock, msg, args.slice(1), senderId, targetId, chatKey);
39
+ break;
40
+
41
+ case "list":
42
+ case "قائمة":
43
+ await listDebts(sock, msg, senderId, targetId, chatKey);
44
+ break;
45
+
46
+ default:
47
+ await sock.sendMessage(remote, {
48
+ text:
49
+ `📊 *نظام الديون (خاص فقط)*\n\n` +
50
+ `الأوامر:\n\n` +
51
+ `• \`!debt add <amount> [description]\` — إضافة دين\n` +
52
+ `• \`!debt list\` — عرض الديون\n` +
53
+ `• \`!debt del <id>\` — حذف دين\n\n` +
54
+ `مثال:\n\`!debt add 50 غداء\``,
55
+ });
56
+ }
57
+ },
58
+ };
59
+
60
+ async function addDebt(sock, msg, args, creditorId, debtorId, chatKey) {
61
+ const remote = msg.key.remoteJid;
62
+
63
+ if (args.length < 1) {
64
+ return sock.sendMessage(remote, {
65
+ text: "❌ الاستخدام: `!debt add <amount> [description]`",
66
+ });
67
+ }
68
+
69
+ const amount = parseFloat(args[0]);
70
+ const description = args.slice(1).join(" ") || "بدون وصف";
71
+
72
+ if (isNaN(amount) || amount <= 0) {
73
+ return sock.sendMessage(remote, {
74
+ text: "❌ المبلغ يجب أن يكون رقم موجب",
75
+ });
76
+ }
77
+
78
+ try {
79
+ const debt = storeAddDebt({
80
+ groupId: chatKey,
81
+ debtorId,
82
+ creditorId,
83
+ amount,
84
+ description,
85
+ });
86
+
87
+ await sock.sendMessage(remote, {
88
+ text:
89
+ `✅ *تم إضافة الدين*\n\n` +
90
+ `المدين: ${debtorId.split("@")[0]}\n` +
91
+ `الدائن: ${creditorId.split("@")[0]}\n` +
92
+ `المبلغ: ${amount}\n` +
93
+ `الوصف: ${description}\n` +
94
+ `رقم السجل: #${debt.id}`,
95
+ });
96
+ } catch (err) {
97
+ logger.error(err, "[Debt] Add error:");
98
+ await sock.sendMessage(remote, {
99
+ text: "❌ حدث خطأ أثناء تسجيل الدين",
100
+ });
101
+ }
102
+ }
103
+
104
+ async function listDebts(sock, msg, creditorId, debtorId, chatKey) {
105
+ const remote = msg.key.remoteJid;
106
+
107
+ try {
108
+ const debts = storeListDebts(chatKey, { settled: false });
109
+
110
+ if (debts.length === 0) {
111
+ return sock.sendMessage(remote, { text: "لا توجد ديون حالياً ✅" });
112
+ }
113
+
114
+ let text = "📋 *قائمة الديون:*\n\n";
115
+
116
+ debts.forEach((d) => {
117
+ const date = new Date(d.created_at).toLocaleDateString("ar-EG");
118
+ text += `*#${d.id}*\n`;
119
+ text += `المدين: ${d.debtor_id.split("@")[0]}\n`;
120
+ text += `الدائن: ${d.creditor_id.split("@")[0]}\n`;
121
+ text += `المبلغ: ${d.amount}\n`;
122
+ text += `الوصف: ${d.description}\n`;
123
+ text += `التاريخ: ${date}\n\n`;
124
+ });
125
+
126
+ await sock.sendMessage(remote, { text });
127
+ } catch (err) {
128
+ logger.error(err, "[Debt] List error:");
129
+ await sock.sendMessage(remote, {
130
+ text: "❌ حدث خطأ أثناء عرض الديون",
131
+ });
132
+ }
133
+ }
134
+
135
+ async function deleteDebt(sock, msg, args, creditorId, debtorId, chatKey) {
136
+ const remote = msg.key.remoteJid;
137
+
138
+ if (args.length < 1) {
139
+ return sock.sendMessage(remote, {
140
+ text: "❌ الاستخدام: `!debt del <id>`",
141
+ });
142
+ }
143
+
144
+ const id = parseInt(args[0]);
145
+ if (isNaN(id)) {
146
+ return sock.sendMessage(remote, {
147
+ text: "❌ رقم السجل غير صحيح",
148
+ });
149
+ }
150
+
151
+ try {
152
+ const debt = storeGetDebt(id);
153
+
154
+ if (!debt) {
155
+ return sock.sendMessage(remote, { text: "❌ لم يتم العثور على الدين" });
156
+ }
157
+
158
+ if (debt.group_id !== chatKey) {
159
+ return sock.sendMessage(remote, {
160
+ text: "❌ هذا الدين لا يخص هذه المحادثة",
161
+ });
162
+ }
163
+
164
+ storeDeleteDebt(id);
165
+
166
+ await sock.sendMessage(remote, {
167
+ text: `🗑️ تم حذف الدين رقم #${id} بنجاح`,
168
+ });
169
+ } catch (err) {
170
+ logger.error(err, "[Debt] Del error:");
171
+ await sock.sendMessage(remote, {
172
+ text: "❌ حدث خطأ أثناء حذف الدين",
173
+ });
174
+ }
175
+ }
@@ -0,0 +1,36 @@
1
+ // file: /commands/deleteschedule.js
2
+ const { deleteScheduledJob, getScheduledJobs } = require("../../scheduler.cjs");
3
+
4
+ module.exports = {
5
+ name: "deleteschedule",
6
+ description: "Deletes a scheduled job by its ID.",
7
+ usage: "deleteschedule <id>",
8
+ chat: "all",
9
+ userAdminRequired: true,
10
+
11
+ async execute(sock, msg, args) {
12
+ const jobIdToDelete = args[0];
13
+
14
+ if (!jobIdToDelete) {
15
+ return await sock.sendMessage(msg.key.remoteJid, {
16
+ text: "يرجى تحديد ID المهمة التي تريد حذفها. يمكنك الحصول على الـ ID باستخدام أمر `!listschedules`.",
17
+ });
18
+ }
19
+
20
+ const jobs = getScheduledJobs();
21
+ const jobExists = jobs.some((job) => job.id === jobIdToDelete);
22
+
23
+ if (!jobExists) {
24
+ return await sock.sendMessage(msg.key.remoteJid, {
25
+ text: `⚠️ لم يتم العثور على مهمة بهذا الـ ID: \`${jobIdToDelete}\``,
26
+ });
27
+ }
28
+
29
+ // This function stops the running task and removes it from the JSON file
30
+ deleteScheduledJob(jobIdToDelete);
31
+
32
+ await sock.sendMessage(msg.key.remoteJid, {
33
+ text: `✅ تم حذف المهمة المجدولة بالـ ID: \`${jobIdToDelete}\` بنجاح.`,
34
+ });
35
+ },
36
+ };