djs-builder 0.7.9 → 0.7.10

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/function/level.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const { Schema, model } = require("mongoose");
2
2
 
3
+ ////////////////////////////////! � User Level Schema
3
4
  const levelSchema = new Schema({
4
5
  userId: String,
5
6
  guildId: String,
@@ -7,15 +8,188 @@ const levelSchema = new Schema({
7
8
  voice: { type: Number, default: 0 },
8
9
  totalXP: { type: Number, default: 0 },
9
10
  level: { type: Number, default: 0 },
11
+ lastMessageTimestamp: { type: Date, default: null },
12
+ });
13
+
14
+ ////////////////////////////////! ⚙️ Leveling Sub-Schema
15
+ const levelingSchema = new Schema({
16
+ type: { type: String, default: "line" },
17
+ amount: { type: Number, default: 100 },
18
+ a: { type: Number, default: 5 },
19
+ b: { type: Number, default: 50 },
20
+ c: { type: Number, default: 100 },
21
+ customLevels: { type: Object, default: {} },
22
+ }, { _id: false });
23
+
24
+ ////////////////////////////////! ⚙️ Guild Config Schema
25
+ const guildSchema = new Schema({
26
+ guildId: { type: String, required: true, unique: true },
27
+ initialized: { type: Boolean, default: false },
28
+
29
+ minXP: { type: Number, default: 5 },
30
+ maxXP: { type: Number, default: 12 },
31
+ xpMultiplier: { type: Number, default: 1 },
32
+ maxLevel: { type: Number, default: null },
33
+ cooldown: { type: Number, default: 0 },
34
+ roleReward: { type: Object, default: {} },
35
+ blacklistedChannels: { type: Array, default: [] },
36
+ disabled: { type: Boolean, default: false },
37
+ leveling: { type: levelingSchema, default: () => ({ type: "line", amount: 100, a: 5, b: 50, c: 100, customLevels: {} }) },
38
+
10
39
  });
11
40
 
12
41
  const Level = model("Level", levelSchema);
42
+ const levelGuild = model("levelGuild", guildSchema);
43
+
44
+ ////////////////////////////////! 🗃️ Cache System
45
+ const configCache = new Map();
46
+
47
+ function clearConfigCache(guildId) {
48
+ if (guildId) {
49
+ configCache.delete(guildId);
50
+ } else {
51
+ configCache.clear();
52
+ }
53
+ }
54
+
55
+ async function getCachedConfig(guildId) {
56
+ if (configCache.has(guildId)) {
57
+ return configCache.get(guildId);
58
+ }
59
+ const config = await levelGuild.findOne({ guildId });
60
+ if (config) configCache.set(guildId, config);
61
+ return config;
62
+ }
63
+
64
+ ////////////////////////////////! 🧮 Calculate XP needed for level
65
+ function xpNeeded(level, config = {}) {
66
+ const leveling = config.leveling || { type: "line", amount: 100 };
67
+ const type = leveling.type || "line";
68
+
69
+ if (leveling.customLevels && leveling.customLevels[level]) {
70
+ return leveling.customLevels[level];
71
+ }
72
+
73
+ switch (type) {
74
+ case "exponential":
75
+ return Math.floor(leveling.amount * Math.pow(2, level));
76
+ case "balanced":
77
+ const a = leveling.a || 5;
78
+ const b = leveling.b || 50;
79
+ const c = leveling.c || 100;
80
+ return Math.floor(a * Math.pow(level, 2) + b * level + c);
81
+ case "custom":
82
+ return leveling.customLevels?.[level] || level * 1000;
83
+ case "line":
84
+ default:
85
+ return (level + 1) * (leveling.amount || 100);
86
+ }
87
+ }
88
+
89
+ function calculateLevel(totalXP, config = {}) {
90
+ let level = 0;
91
+ const maxLevel = config.maxLevel || null;
92
+
93
+ while (totalXP >= xpNeeded(level, config)) {
94
+ level++;
95
+ if (maxLevel && level >= maxLevel) {
96
+ return maxLevel;
97
+ }
98
+ }
99
+ return level;
100
+ }
13
101
 
14
102
  function randomXP(min, max) {
15
103
  return Math.floor(Math.random() * (max - min + 1)) + min;
16
104
  }
17
105
 
106
+ ////////////////////////////////* 🎮 Main setLevel Function
107
+
108
+ async function setLevel(client, guilds, callbacks = {}) {
109
+ client.levelGuilds = new Set(guilds.map(g => g.id));
110
+ client.levelDashboardGuilds = new Set(guilds.filter(g => g.Dashboard === true).map(g => g.id));
111
+
112
+ for (const guild of guilds) {
113
+ const trueGuild = client.guilds.cache.get(guild.id);
114
+ if (!trueGuild) continue;
115
+
116
+ const existingConfig = await levelGuild.findOne({ guildId: guild.id });
117
+
118
+ const { id, Dashboard, ...guildConfig } = guild;
119
+
120
+ if (!existingConfig) {
121
+ const newConfig = new levelGuild({ guildId: id, initialized: true, ...guildConfig });
122
+ await newConfig.save();
123
+ configCache.set(id, newConfig);
124
+ } else {
125
+ if (guild.Dashboard) {
126
+ await levelGuild.findOneAndUpdate(
127
+ { guildId: id },
128
+ { initialized: true },
129
+ { new: true }
130
+ );
131
+ } else {
132
+ await levelGuild.findOneAndDelete({ guildId: id });
133
+ const newConfig = new levelGuild({ guildId: id, initialized: true, ...guildConfig });
134
+ await newConfig.save();
135
+ }
136
+ clearConfigCache(id);
137
+ }
138
+ }
139
+
140
+ client.on("messageCreate", async (msg) => {
141
+ if (msg.author.bot || !msg.guild) return;
142
+
143
+ // ✅ Check if guild is in active level system
144
+ if (!client.levelGuilds || !client.levelGuilds.has(msg.guild.id)) return;
145
+
146
+ const guildConfig = await getCachedConfig(msg.guild.id);
147
+ if (!guildConfig) return;
148
+
149
+ const guildOptions = guilds.find(g => g.id === msg.guild.id) || {};
150
+
151
+ const result = await addXP(msg.author.id, msg.guild.id, {
152
+ ...guildOptions,
153
+ channelId: msg.channel.id,
154
+ member: msg.member,
155
+ xpType: "text",
156
+ });
157
+
158
+ if (result.disabled || result.blacklisted || result.onCooldown) return;
159
+
160
+ if (callbacks.onXP) {
161
+ try { await callbacks.onXP(msg, result); } catch (e) { console.error("setLevel onXP error:", e); }
162
+ }
163
+
164
+ if (result.leveledUp && callbacks.onLevelUp) {
165
+ try { await callbacks.onLevelUp(msg, result); } catch (e) { console.error("setLevel onLevelUp error:", e); }
166
+ }
167
+
168
+ if (result.newRole?.length > 0 && callbacks.onRoleReward) {
169
+ for (const role of result.newRole) {
170
+ try { await callbacks.onRoleReward(msg, result, role); } catch (e) { console.error("setLevel onRoleReward error:", e); }
171
+ }
172
+ }
173
+ });
174
+
175
+
176
+ }
177
+
178
+ ////////////////////////////////! 🎮 Main addXP Function
179
+
180
+
18
181
  async function addXP(userId, guildId, options = {}) {
182
+ ////////////////////////////////! 🗃️ Get config from Dashboard or options
183
+ let config = options.Dashboard
184
+ ? await getCachedConfig(guildId) || {}
185
+ : options;
186
+
187
+
188
+ ////////////////////////////////! ❌ Check if disabled
189
+ if (config.disabled) {
190
+ return { disabled: true, newLevel: 0, oldLevel: 0, totalXP: 0, leveledUp: false };
191
+ }
192
+
19
193
  let data = await Level.findOne({ userId, guildId });
20
194
  if (!data) {
21
195
  data = new Level({ userId, guildId });
@@ -23,12 +197,50 @@ async function addXP(userId, guildId, options = {}) {
23
197
 
24
198
  const oldLevel = data.level || 0;
25
199
  let leveledUp = false;
200
+ let roleRewardAdded = false;
201
+ let newRole = [];
202
+
203
+ ////////////////////////////////! 🚫 Blacklist check
204
+ if (options.channelId && config.blacklistedChannels?.includes(options.channelId)) {
205
+ return {
206
+ newLevel: data.level,
207
+ oldLevel,
208
+ totalXP: data.totalXP,
209
+ leveledUp: false,
210
+ blacklisted: true,
211
+ };
212
+ }
213
+
214
+
215
+ ////////////////////////////////! ⏱️ Cooldown check
216
+ const cooldownDuration = config.cooldown ?? options.cooldown ?? 0;
217
+
218
+ if (cooldownDuration > 0) {
219
+ const now = new Date();
220
+ if (data.lastMessageTimestamp) {
221
+ const timeDiff = (now - data.lastMessageTimestamp) / 1000;
222
+ if (timeDiff < cooldownDuration) {
223
+ return {
224
+ newLevel: data.level,
225
+ oldLevel,
226
+ totalXP: data.totalXP,
227
+ leveledUp: false,
228
+ onCooldown: true,
229
+ remainingCooldown: Math.ceil(cooldownDuration - timeDiff),
230
+ };
231
+ }
232
+ }
233
+ data.lastMessageTimestamp = now;
234
+ }
26
235
 
27
236
  ////////////////////////////////! 🎲 XP
28
- const xpToAdd =
29
- options.amount_add ?? randomXP(options.minXP || 5, options.maxXP || 12);
237
+ let xpToAdd =
238
+ options.amount_add ??
239
+ randomXP(config.minXP || 5, config.maxXP || 12);
240
+
241
+ xpToAdd = Math.floor(xpToAdd * (config.xpMultiplier || 1));
30
242
 
31
- data[options.type || "text"] += xpToAdd;
243
+ data[options.xpType || "text"] += xpToAdd;
32
244
  data.totalXP += xpToAdd;
33
245
 
34
246
  ////////////////////////////////! 🎯 Level boost
@@ -37,24 +249,70 @@ async function addXP(userId, guildId, options = {}) {
37
249
  leveledUp = true;
38
250
  }
39
251
 
40
- ////////////////////////////////! check level
41
- const needed = (data.level + 1) * 100;
42
- if (data.totalXP >= needed) {
43
- data.level++;
252
+ ////////////////////////////////! 🔋 xp boost
253
+ if (options.totalXP_add) {
254
+ data.totalXP += options.totalXP_add;
255
+ }
256
+
257
+ if (options.text_add) {
258
+ data.text += options.text_add;
259
+ }
260
+
261
+ if (options.voice_add) {
262
+ data.voice += options.voice_add;
263
+ }
264
+
265
+ ////////////////////////////////! ✅ Calculate correct level
266
+ const newCalculatedLevel = calculateLevel(data.totalXP, config);
267
+
268
+ if (newCalculatedLevel > data.level) {
269
+ if (config.roleReward) {
270
+ const rewardLevels = Object.keys(config.roleReward)
271
+ .map(Number)
272
+ .filter(lvl => lvl === newCalculatedLevel)
273
+ .sort((a, b) => b - a);
274
+
275
+ if (rewardLevels.length > 0) {
276
+ const highestRewardLevel = rewardLevels[0];
277
+ newRole.push({
278
+ level: highestRewardLevel,
279
+ roleId: config.roleReward[highestRewardLevel]
280
+ });
281
+ }
282
+ }
283
+ data.level = newCalculatedLevel;
44
284
  leveledUp = true;
45
285
  }
46
286
 
47
287
  await data.save();
48
288
 
289
+ ////////////////////////////////! 🎖️ Auto add roles
290
+ if (options.member && newRole.length > 0) {
291
+ for (const role of newRole) {
292
+ try {
293
+ await options.member.roles.add(role.roleId);
294
+ roleRewardAdded = true;
295
+ } catch (err) {
296
+ roleRewardAdded = "Failed to add role: " + err.message;
297
+ }
298
+ }
299
+ }
300
+
49
301
  return {
50
302
  newLevel: data.level,
51
303
  oldLevel,
52
304
  totalXP: data.totalXP,
53
305
  leveledUp,
306
+ xpAdded: xpToAdd,
307
+ xpForNextLevel: xpNeeded(data.level, config),
308
+ remainingXP: xpNeeded(data.level, config) - data.totalXP,
309
+ roleRewardAdded,
310
+ newRole,
311
+ multiplierUsed: config.xpMultiplier || 1,
54
312
  };
55
313
  }
56
314
 
57
- ////////////////////////////////! user data 🧑‍🤝‍🧑
315
+ ////////////////////////////////! 🧑‍🤝‍🧑 User data
58
316
  async function UserLevel(userId, guildId) {
59
317
  return (
60
318
  (await Level.findOne({ userId, guildId })) || {
@@ -66,11 +324,80 @@ async function UserLevel(userId, guildId) {
66
324
  );
67
325
  }
68
326
 
69
- ////////////////////////////////! 🏆 top
327
+ ////////////////////////////////! 🏆 Leaderboard
70
328
  async function leaderboard(guildId, type = "totalXP", limit = 10) {
71
329
  return await Level.find({ guildId })
72
330
  .sort({ [type]: -1 })
73
331
  .limit(limit);
74
332
  }
75
333
 
76
- module.exports = { Level,addXP, UserLevel, leaderboard };
334
+ ////////////////////////////////! 🏅 Get user rank
335
+ async function getUserRank(userId, guildId, type = "totalXP") {
336
+ const allUsers = await Level.find({ guildId }).sort({ [type]: -1 });
337
+ const rank = allUsers.findIndex(u => u.userId === userId) + 1;
338
+ return rank || null;
339
+ }
340
+
341
+
342
+ ////////////////////////////////! 🗑️ Delete user data
343
+ async function resetUser(userId, guildId) {
344
+ return await Level.findOneAndDelete({ userId, guildId });
345
+ }
346
+
347
+ ////////////////////////////////! 🔥 Reset all users in a guild
348
+ async function resetGuild(guildId) {
349
+ return await Level.deleteMany({ guildId });
350
+ }
351
+
352
+ ////////////////////////////////! 📋 Get level info
353
+ function getLevelInfo(level, config = {}) {
354
+ return {
355
+ level,
356
+ xpNeeded: xpNeeded(level, config),
357
+ xpForNext: xpNeeded(level + 1, config),
358
+ xpDifference: xpNeeded(level + 1, config) - xpNeeded(level, config),
359
+ };
360
+ }
361
+
362
+ ////////////////////////////////! ⚙️ Get Guild Config
363
+ async function getGuildConfig(guildId) {
364
+ return await getCachedConfig(guildId);
365
+ }
366
+
367
+ ////////////////////////////////! ✏️ Update Guild Config
368
+ async function updateGuildConfig(guildId, updates) {
369
+ const result = await levelGuild.findOneAndUpdate(
370
+ { guildId },
371
+ { guildId, ...updates },
372
+ { upsert: true, new: true }
373
+ );
374
+
375
+ clearConfigCache(guildId);
376
+ return result;
377
+ }
378
+
379
+ ////////////////////////////////! 🗑️ Delete Guild Config
380
+ async function deleteGuildConfig(guildId) {
381
+ const result = await levelGuild.findOneAndDelete({ guildId });
382
+ clearConfigCache(guildId);
383
+ return result;
384
+ }
385
+
386
+ module.exports = {
387
+ Level,
388
+ levelGuild,
389
+ addXP,
390
+ UserLevel,
391
+ leaderboard,
392
+ getUserRank,
393
+ resetUser,
394
+ resetGuild,
395
+ xpNeeded,
396
+ calculateLevel,
397
+ getLevelInfo,
398
+ getGuildConfig,
399
+ updateGuildConfig,
400
+ deleteGuildConfig,
401
+ clearConfigCache,
402
+ setLevel
403
+ };
@@ -514,7 +514,24 @@ async function starter(client, options) {
514
514
  }
515
515
 
516
516
  const { Wait, CreateBar, CreateRow, GetUser , CreateModal, CreateComponents } = require("../function/function");
517
- const { Level, addXP, UserLevel, leaderboard } = require("../function/level");
517
+ const {
518
+ Level,
519
+ levelGuild,
520
+ addXP,
521
+ UserLevel,
522
+ leaderboard,
523
+ getUserRank,
524
+ resetUser,
525
+ resetGuild,
526
+ xpNeeded,
527
+ calculateLevel,
528
+ getLevelInfo,
529
+ getGuildConfig,
530
+ updateGuildConfig,
531
+ deleteGuildConfig,
532
+ clearConfigCache,
533
+ setLevel
534
+ } = require("../function/level");
518
535
  const { log , Log , getLogConfigData} = require("../function/log");
519
536
  const { dashboard } = require("../function/dash");
520
537
 
@@ -542,7 +559,22 @@ const {
542
559
  } = require("../function/giveaway");
543
560
 
544
561
  module.exports = {
545
- Level,
562
+ Level,
563
+ levelGuild,
564
+ addXP,
565
+ UserLevel,
566
+ leaderboard,
567
+ getUserRank,
568
+ resetUser,
569
+ resetGuild,
570
+ xpNeeded,
571
+ calculateLevel,
572
+ getLevelInfo,
573
+ getGuildConfig,
574
+ updateGuildConfig,
575
+ deleteGuildConfig,
576
+ clearConfigCache,
577
+ setLevel,
546
578
  giveaway,
547
579
  starter,
548
580
  dashboard,
@@ -556,9 +588,6 @@ module.exports = {
556
588
  CreateModal,
557
589
  CreateComponents,
558
590
  GetUser,
559
- addXP,
560
- UserLevel,
561
- leaderboard,
562
591
  Gstart,
563
592
  Gcheck,
564
593
  Greroll,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "djs-builder",
3
- "version": "0.7.9",
4
- "note": "🎉 Package Update v0.7.9! \n\n- 🧩 NEW: CreateComponents Function!\n • Build advanced Discord UI (Text, Separator, Media, File, Button, Menu, Section)\n • Container mode for Components V2 | Array mode for standard messages\n • Full documentation with examples\n\n- 🔳 NEW: CreateModal Function!\n • Create Discord Modals with Text Inputs, Menus, Files, and Labels\n\n- 🌐 NEW: Dashboard System!\n • Discord OAuth2 Login\n • Server, Level, Giveaway & Blacklist Management\n • Logs Management Page (Enable/Disable events, custom channels & colors)\n • Standalone usage - works with or without Starter\n\n- 🛡️ Logging System Enhancements!\n • Dashboard Integration with Smart Cache System\n • Read-only Mode for code-based configs\n • Channel Fallback & Better Slash Command support\n\n- 🔧 Bug Fixes: Schema validation, cache updates, sidebar navigation\n\n- <:npm:1107014411375353968> Learn more on [NPM](https://www.npmjs.com/package/djs-builder)\n- <:Discord:906936109114753024> DISCORD SERVER : [LINK](https://discord.gg/uYcKCZk3)",
3
+ "version": "0.7.10",
4
+ "note": "🎉 Package Update v0.7.10! \n\n- 🎮 NEW: setLevel Function!\n • All-in-one level system setup\n • Automatic message listener\n • Callbacks: onLevelUp, onRoleReward, onXP\n • Dashboard Mode & Code Mode support\n • Auto role rewards on level up\n • Voice XP support via addVoiceXP\n\n- 📊 Level System Improvements!\n • getGuildConfig with full output example\n • updateGuildConfig for programmatic changes\n • XP Calculation Types: line, exponential, balanced, custom\n • Cooldown & Blacklist support\n\n- 🌐 Dashboard System!\n • Discord OAuth2 Login\n • Server, Level, Giveaway & Blacklist Management\n • Logs Management Page\n\n- <:npm:1107014411375353968> Learn more on [NPM](https://www.npmjs.com/package/djs-builder)\n- <:Discord:906936109114753024> DISCORD SERVER : [LINK](https://discord.gg/uYcKCZk3)",
5
5
  "description": "🎉 Full-featured Discord.js utilities: CreateComponents, CreateModal, Dashboard, Logging & more! 🥏",
6
6
  "main": "handler/starter.js",
7
7
  "dependencies": {
@@ -725,6 +725,23 @@
725
725
  </div>
726
726
  </div>
727
727
 
728
+ <!-- Confirm Modal -->
729
+ <div class="modal" id="confirmModal">
730
+ <div class="modal-content" style="max-width: 400px;">
731
+ <div class="modal-header">
732
+ <h3 id="confirmTitle"><i class="ri-question-line" style="color: var(--warning);"></i> تأكيد</h3>
733
+ <button class="modal-close" onclick="closeConfirmModal()">&times;</button>
734
+ </div>
735
+ <div class="modal-body">
736
+ <p id="confirmMessage" style="font-size: 15px; text-align: center; padding: 10px 0;"></p>
737
+ </div>
738
+ <div class="modal-footer" style="justify-content: center;">
739
+ <button class="btn btn-secondary" onclick="closeConfirmModal()">إلغاء</button>
740
+ <button class="btn btn-primary" id="confirmBtn" style="background: var(--danger);"><i class="ri-check-line"></i> تأكيد</button>
741
+ </div>
742
+ </div>
743
+ </div>
744
+
728
745
  <div class="toast" id="toast"><i class="ri-checkbox-circle-fill"></i><span id="toastText"></span></div>
729
746
 
730
747
  <script>
@@ -766,6 +783,23 @@
766
783
  setTimeout(() => toast.classList.remove('show'), 3000);
767
784
  }
768
785
 
786
+ // Confirm Modal Functions
787
+ let confirmCallback = null;
788
+ function showConfirm(message, callback, title = 'تأكيد') {
789
+ document.getElementById('confirmMessage').textContent = message;
790
+ document.getElementById('confirmTitle').innerHTML = '<i class="ri-question-line" style="color: var(--warning);"></i> ' + title;
791
+ confirmCallback = callback;
792
+ document.getElementById('confirmModal').classList.add('show');
793
+ }
794
+ function closeConfirmModal() {
795
+ document.getElementById('confirmModal').classList.remove('show');
796
+ confirmCallback = null;
797
+ }
798
+ document.getElementById('confirmBtn').addEventListener('click', function() {
799
+ if (confirmCallback) confirmCallback();
800
+ closeConfirmModal();
801
+ });
802
+
769
803
  // Modal functions
770
804
  function selectBlType(type) {
771
805
  document.querySelectorAll('.type-btn').forEach(btn => {
@@ -867,24 +901,24 @@
867
901
  } catch (e) { showToast('حدث خطأ', 'error'); }
868
902
  }
869
903
 
870
- async function removeFromGlobalBlacklist(type, id) {
871
- if (!confirm('هل أنت متأكد من الإزالة؟')) return;
872
-
873
- try {
874
- const res = await fetch('/api/blacklist', {
875
- method: 'DELETE',
876
- headers: { 'Content-Type': 'application/json' },
877
- body: JSON.stringify({ type, id })
878
- });
879
- const data = await res.json();
880
-
881
- if (data.success) {
882
- showToast('تمت الإزالة بنجاح');
883
- document.querySelector(`.bl-item[data-id="${id}"]`).remove();
884
- } else {
885
- showToast(data.error || 'خطأ', 'error');
886
- }
887
- } catch (e) { showToast('خطأ', 'error'); }
904
+ function removeFromGlobalBlacklist(type, id) {
905
+ showConfirm('هل أنت متأكد من الإزالة؟', async () => {
906
+ try {
907
+ const res = await fetch('/api/blacklist', {
908
+ method: 'DELETE',
909
+ headers: { 'Content-Type': 'application/json' },
910
+ body: JSON.stringify({ type, id })
911
+ });
912
+ const data = await res.json();
913
+
914
+ if (data.success) {
915
+ showToast('تمت الإزالة بنجاح');
916
+ document.querySelector(`.bl-item[data-id="${id}"]`).remove();
917
+ } else {
918
+ showToast(data.error || 'خطأ', 'error');
919
+ }
920
+ } catch (e) { showToast('خطأ', 'error'); }
921
+ }, 'إزالة');
888
922
  }
889
923
 
890
924
  // Load blacklist on init