forked from black-river-gaming/albion-killbot
/
bot.js
180 lines (157 loc) · 6.04 KB
/
bot.js
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
const Discord = require("discord.js");
const moment = require("moment");
const logger = require("./logger")("bot");
const config = require("./config");
const messages = require("./messages");
const commands = require("./commands");
const database = require("./database");
const { sleep, fileSizeFormatter } = require("./utils");
const events = require("./queries/events");
const battles = require("./queries/battles");
const dailyRanking = require("./queries/dailyRanking");
const guilds = require("./queries/guilds");
const { hasSubscription } = require("./subscriptions");
const COMMAND_PREFIX = "!";
const client = new Discord.Client({
autoReconnect: true,
});
client.commands = commands;
client.on("ready", async () => {
logger.info(`Connected to Discord as ${client.user.tag}`);
});
client.on("disconnect", async () => {
logger.info("Disconnected from Discord.");
});
client.on("reconnecting", async () => {
logger.info("Trying to reconnect to Discord.");
});
client.on("error", async e => {
logger.info(`Discord error: ${e}.`);
});
client.on("message", async message => {
if (message.author.bot) return;
if (!message.content || !message.content.startsWith(COMMAND_PREFIX)) return;
if (!message.member) return;
// For now, bot only accepts commands from server admins
if (!message.member.hasPermission("ADMINISTRATOR")) return;
// This is needed to inherit configs to guild object
const guild = message.guild;
guild.config = await config.getConfig(guild);
if (!hasSubscription(guild.config)) return;
const args = message.content
.slice(COMMAND_PREFIX.length)
.trim()
.split(/ +/g);
const cmd = args.shift().toLowerCase();
if (commands[cmd]) {
commands[cmd].run(client, guild, message, args);
}
});
client.on("guildCreate", async guild => {
logger.info(`Joined guild "${guild.name}". Creating default settings.`);
guild.config = await config.getConfig(guild);
guild.config.channel = exports.getDefaultChannel(guild).id;
config.setConfig(guild);
const l = messages.getI18n(guild);
exports.sendGuildMessage(guild, l.__("JOIN"));
});
client.on("guildDelete", guild => {
logger.info(`Left guild "${guild.name}". Deleting settings.`);
config.deleteConfig(guild);
});
exports.getDefaultChannel = guild => {
// get "original" default channel
if (guild.channels.cache.has(guild.id)) return guild.channels.cache.get(guild.id);
// Check for a "general" channel, which is often default chat
const generalChannel = guild.channels.cache.find(channel => channel.name === "general");
if (generalChannel) return generalChannel;
// Now we get into the heavy stuff: first channel in order where the bot can speak
// hold on to your hats!
return guild.channels.cache
.filter(c => c.type === "text" && c.permissionsFor(guild.client.user).has("SEND_MESSAGES"))
.sort((a, b) => a.position - b.position)
.first();
};
const msgErrors = {};
exports.sendGuildMessage = async (guild, message, category = "general") => {
if (!guild.config) guild.config = await config.getConfig(guild);
if (!guild.config.categories) guild.config.categories = {};
if (guild.config.categories[category] === false) return;
let channelId = guild.config.channel[category];
if (!channelId) channelId = guild.config.channel.general;
// Old structure backport
if (typeof guild.config.channel === "string") channelId = guild.config.channel;
const l = messages.getI18n(guild);
let channel = client.channels.cache.find(c => c.id === channelId);
if (!channel) {
logger.warn(`Channel not configured for guild ${guild.name}.`);
channel = exports.getDefaultChannel(guild);
}
if (!channel) return;
try {
await channel.send(message);
msgErrors[guild.id] = 0;
} catch (e) {
logger.error(`Unable to send message to guild ${guild.name}/${channel.name}: ${e}`);
if (
e.code === Discord.Constants.APIErrors.UNKNOWN_CHANNEL ||
e.code === Discord.Constants.APIErrors.MISSING_ACCESS ||
e.code === Discord.Constants.APIErrors.MISSING_PERMISSIONS
) {
if (!msgErrors[guild.id]) msgErrors[guild.id] = 0;
msgErrors[guild.id]++;
// If more than 50 msg errors occur in succession, bot will leave and warn owner
if (msgErrors[guild.id] > 50) {
logger.warn(`Leaving guild ${guild.name} due to excessive message errors. Warning owner.`);
await guild.owner.send(l.__("LEAVE", { guild: guild.name }));
await guild.leave();
msgErrors[guild.id] = 0;
}
}
}
};
exports.client = client;
exports.run = async token => {
database.connect();
await client.login(token);
// Events that fires daily (Default: 12:00 pm)
const runDaily = async (func, name, hour = 12, minute = 0) => {
if (!func) return logger.warn("There is an undefined function. Please check your settings.");
const exit = false;
while (!exit) {
await sleep(60000);
const now = moment();
if (now.hour() === hour && now.minute() === minute) {
try {
await func(exports);
} catch (e) {
logger.error(`Error in function ${name}: ${e}`);
}
}
}
};
// Events that run on an interval (Default: 30 seconds)
const runInterval = async (func, name, interval = 30000) => {
if (!func) return logger.warn("There is an undefined function. Please check your settings.");
const exit = false;
while (!exit) {
await sleep(interval);
try {
await func(exports);
} catch (e) {
logger.error(`Error in function ${name}: ${e}`);
}
}
};
runDaily(guilds.showRanking, "Show Monthly Ranking");
runDaily(dailyRanking.scanDaily, "Show PvP Ranking (daily)", 0, 0);
runDaily(dailyRanking.clear, "Clear PvP Ranking", 0, 5);
runInterval(dailyRanking.scan, "Show PvP Ranking", 3600000);
runInterval(events.get, "Get Events", 30000);
runInterval(events.scan, "Show Events", 5000);
runInterval(battles.get, "Get Battles", 60000);
runInterval(battles.scan, "Show Battles", 60000);
runInterval(() => {
logger.debug(`Memory usage (approx): ${fileSizeFormatter(process.memoryUsage().heapUsed)}`);
}, 60000);
};