-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
122 lines (103 loc) · 4.39 KB
/
Copy pathindex.js
File metadata and controls
122 lines (103 loc) · 4.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
const createLevelCommand = require('./commands/level');
const createLeaderboardCommand = require('./commands/leaderboard');
const createLevelConfigCommand = require('./commands/level-config');
const createLevelRolesCommand = require('./commands/level-roles');
const LevelSchema = require('./models/Level');
const LevelConfigSchema = require('./models/LevelConfig');
const LevelRoleSchema = require('./models/LevelRole');
// Simple level curve: level = floor(sqrt(xp / 100))
function levelForXp(xp) {
return Math.floor(Math.sqrt((xp || 0) / 100));
}
async function load(ctx) {
ctx.logger.info('Levels plugin loading...');
// --- Models (namespaced to this plugin) ---------------------------------
const LevelModel = ctx.defineModel('Level', LevelSchema);
const LevelConfigModel = ctx.defineModel('LevelConfig', LevelConfigSchema);
const LevelRoleModel = ctx.defineModel('LevelRole', LevelRoleSchema);
// --- Commands (models injected; commands never reach outside the plugin) -
ctx.registerCommand(createLevelCommand(LevelModel));
ctx.registerCommand(createLeaderboardCommand(LevelModel));
ctx.registerCommand(createLevelConfigCommand(LevelConfigModel));
ctx.registerCommand(createLevelRolesCommand(LevelRoleModel));
// Per-user XP cooldown, kept in-memory. The real ADB ctx.db has no generic
// get/set KV store, so cooldown state lives in the plugin process.
// ponytail: in-memory per-process cooldown; move to a TTL store if the bot
// ever runs sharded and XP double-awards across shards.
const lastXpAt = new Map(); // key `${guildId}:${userId}` -> epoch ms
async function getConfig(guildId) {
const cfg = await LevelConfigModel.findOne({ guildId });
return {
xpPerMessage: cfg?.xpPerMessage ?? 5,
xpCooldown: cfg?.xpCooldown ?? 60,
xpPerMinuteLimit: cfg?.xpPerMinuteLimit ?? 100,
levelUpChannelId: cfg?.levelUpChannelId ?? null,
};
}
// --- Award XP on message ------------------------------------------------
ctx.registerEvent('messageCreate', async (message) => {
if (message.author?.bot || !message.guild) return;
try {
const config = await getConfig(message.guild.id);
const now = Date.now();
const key = `${message.guild.id}:${message.author.id}`;
const last = lastXpAt.get(key) || 0;
if (now - last < config.xpCooldown * 1000) return;
lastXpAt.set(key, now);
const existing = await LevelModel.findOne({
guildId: message.guild.id,
userId: message.author.id,
});
const oldXp = existing?.xp || 0;
const newXp = oldXp + config.xpPerMessage;
await LevelModel.findOneAndUpdate(
{ guildId: message.guild.id, userId: message.author.id },
{ $set: { lastMessageAt: new Date() }, $inc: { xp: config.xpPerMessage } },
{ upsert: true, new: true }
);
const oldLevel = levelForXp(oldXp);
const newLevel = levelForXp(newXp);
if (newLevel > oldLevel) {
await LevelModel.updateOne(
{ guildId: message.guild.id, userId: message.author.id },
{ $set: { level: newLevel } }
);
// Notify other plugins.
await ctx.hooks.emitHook('onLevelUp', {
user: message.author,
newLevel,
guild: message.guild,
});
// Announce in configured channel, if any.
if (config.levelUpChannelId) {
const channel = message.guild.channels.cache.get(config.levelUpChannelId);
if (channel) {
channel.send(`${message.author} has reached level ${newLevel}! :tada:`);
}
}
// Grant role rewards for the new level.
const roleRewards = await LevelRoleModel.find({
guildId: message.guild.id,
level: newLevel,
});
for (const reward of roleRewards) {
const role = message.guild.roles.cache.get(reward.roleId);
if (role && message.member && !message.member.roles.cache.has(role.id)) {
await message.member.roles.add(role);
try {
await message.author.send(
`You earned the ${role.name} role for reaching level ${newLevel}!`
);
} catch (e) {
// DMs closed — ignore.
}
}
}
}
} catch (error) {
ctx.logger.error('Error in levels plugin:', error);
}
});
ctx.logger.info('Levels plugin loaded!');
}
module.exports = { load, levelForXp };