djs-builder 0.7.0 → 0.7.1-8.1

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: 3 },
30
+ maxXP: { type: Number, default: 8 },
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
+
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
+ };