Skip to content

Commit 21604c1

Browse files
committed
add: multi bot account support
1 parent 8bf0732 commit 21604c1

18 files changed

+157
-95
lines changed

example.app.yml

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77
# or by going to src/services/ConfigDataService.ts
88

99
bot:
10-
TOKEN: "Bot Token"
10+
# You can add more than 1 token to clone the bot
11+
# Note that performance will be dreaded and you can't set each config for each cloned bot
12+
# Config data of each bot will be the same
13+
TOKEN:
14+
- "Bot Token 1"
15+
# - "Bot token 2"
1116
OWNER_ID: "Your User ID"
1217

1318
lavalink:

example.full.app.yml

+6-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
# something: ${DATA}
66

77
bot:
8-
TOKEN: "Bot Token"
8+
# You can add more than 1 token to clone the bot
9+
# Note that performance will be dreaded and you can't set each config for each cloned bot
10+
# Config data of each bot will be the same
11+
TOKEN:
12+
- "Bot Token 1"
13+
# - "Bot token 2"
914
EMBED_COLOR: "#2B2D31"
1015
OWNER_ID: "Your User ID"
1116
ADMIN: ["<your_trusted_admin_discord_id_here>"]

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"build:languages": "node --no-deprecation ./scripts/copyLanguagePackage.mjs",
1212
"build:manifest": "node --no-deprecation ./scripts/copyManifest.mjs",
1313
"start": "node --no-deprecation ./dist/index.js",
14-
"start:shard": "node --no-deprecation ./dist/shard.js",
14+
"start:shard": "node --no-deprecation ./dist/shard/index.js",
1515
"dev": "npx nodemon ./src/index.ts",
1616
"build:prettier": "npx prettier -w ./src",
1717
"start:pm2": "npx pm2-runtime start ecosystem.config.cjs --env production"

src/@types/Config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export interface Config {
77
}
88

99
export interface Bot {
10-
TOKEN: string;
10+
TOKEN: string[];
1111
EMBED_COLOR: string;
1212
OWNER_ID: string;
1313
ADMIN: string[];

src/database/keyChecker.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export class keyChecker {
2020
}
2121

2222
execute() {
23-
const logger = new LoggerService(this.client);
23+
const logger = new LoggerService(this.client, this.client.clientIndex);
2424
const objReqKey = Object.keys(this.sampleConfig);
2525
const res = this.checkEngine();
2626

src/events/player/playerException.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,6 @@ export default class {
3030

3131
const currentPlayer = client.rainlink.players.get(player.guildId) as RainlinkPlayer;
3232
if (!currentPlayer) return;
33-
if (currentPlayer.state !== RainlinkPlayerState.DESTROYED) await player.destroy();
33+
if (!currentPlayer.sudoDestroy) await player.destroy();
3434
}
3535
}

src/events/track/trackEnd.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,6 @@ export default class {
3333

3434
const currentPlayer = client.rainlink.players.get(player.guildId) as RainlinkPlayer;
3535
if (!currentPlayer) return;
36-
if (currentPlayer.state !== RainlinkPlayerState.DESTROYED) await player.destroy();
36+
if (!currentPlayer.sudoDestroy) await player.destroy();
3737
}
3838
}

src/events/track/trackResolveError.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,6 @@ export default class {
5050

5151
const currentPlayer = client.rainlink.players.get(player.guildId) as RainlinkPlayer;
5252
if (!currentPlayer) return;
53-
if (currentPlayer.state !== RainlinkPlayerState.DESTROYED) await player.destroy();
53+
if (!currentPlayer.sudoDestroy) await player.destroy();
5454
}
5555
}

src/events/track/trackStuck.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,6 @@ export default class {
4848
new ClearMessageService(client, channel, player);
4949
const currentPlayer = client.rainlink.players.get(player.guildId) as RainlinkPlayer;
5050
if (!currentPlayer) return;
51-
if (currentPlayer.state !== RainlinkPlayerState.DESTROYED) await player.destroy();
51+
if (!currentPlayer.sudoDestroy) await player.destroy();
5252
}
5353
}

src/index.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { Manager } from "./manager.js";
2+
import { ConfigDataService } from "./services/ConfigDataService.js";
3+
const configData = new ConfigDataService().data;
24

35
(() => {
4-
new Manager();
6+
configData.bot.TOKEN.forEach((token, index) => {
7+
new Manager(configData, index, configData.features.MESSAGE_CONTENT.enable).login(token);
8+
});
59
})();

src/manager.ts

+55-57
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
import { DatabaseService } from "./database/index.js";
1313
import { I18n, I18nArgs } from "@hammerhq/localization";
1414
import { resolve } from "path";
15-
import { ConfigDataService } from "./services/ConfigDataService.js";
1615
import { LoggerService } from "./services/LoggerService.js";
1716
import { ClusterClient, getInfo } from "discord-hybrid-sharding";
1817
import { join, dirname } from "path";
@@ -37,62 +36,53 @@ import { RainlinkPlayer } from "./rainlink/main.js";
3736
import { IconType } from "./@types/Emoji.js";
3837
import { TopggService } from "./services/TopggService.js";
3938
config();
40-
const __dirname = dirname(fileURLToPath(import.meta.url));
41-
const configData = new ConfigDataService().data;
42-
const REGEX = [
43-
/(?:https?:\/\/)?(?:www\.)?youtu(?:\.be\/|be.com\/\S*(?:watch|embed)(?:(?:(?=\/[-a-zA-Z0-9_]{11,}(?!\S))\/)|(?:\S*v=|v\/)))([-a-zA-Z0-9_]{11,})/,
44-
/^.*(youtu.be\/|list=)([^#\&\?]*).*/,
45-
/^(?:spotify:|https:\/\/[a-z]+\.spotify\.com\/(track\/|user\/(.*)\/playlist\/|playlist\/))(.*)$/,
46-
/^https?:\/\/(?:www\.)?deezer\.com\/[a-z]+\/(track|album|playlist)\/(\d+)$/,
47-
/^(?:(https?):\/\/)?(?:(?:www|m)\.)?(soundcloud\.com|snd\.sc)\/(.*)$/,
48-
/(?:https:\/\/music\.apple\.com\/)(?:.+)?(artist|album|music-video|playlist)\/([\w\-\.]+(\/)+[\w\-\.]+|[^&]+)\/([\w\-\.]+(\/)+[\w\-\.]+|[^&]+)/,
49-
/^https?:\/\/(?:www\.|secure\.|sp\.)?nicovideo\.jp\/watch\/([a-z]{2}[0-9]+)/,
50-
/(?:https:\/\/spotify\.link)\/([A-Za-z0-9]+)/,
51-
/^https:\/\/deezer\.page\.link\/[a-zA-Z0-9]{12}$/,
52-
];
5339

5440
export class Manager extends Client {
55-
metadata: Metadata;
56-
config: Config;
57-
logger: LoggerService;
58-
db!: DatabaseTable;
59-
owner: string;
60-
color: ColorResolvable;
61-
i18n: I18n;
62-
prefix: string;
63-
isDatabaseConnected: boolean;
64-
shardStatus: boolean;
65-
lavalinkList: LavalinkDataType[];
66-
lavalinkUsing: LavalinkUsingDataType[];
67-
lavalinkUsed: LavalinkUsingDataType[];
68-
rainlink: Rainlink;
69-
commands: Collection<string, Command>;
70-
interval: Collection<string, NodeJS.Timer>;
71-
sentQueue: Collection<string, boolean>;
72-
nplayingMsg: Collection<string, { coll: InteractionCollector<ButtonInteraction<"cached">>; msg: Message }>;
73-
aliases: Collection<string, string>;
74-
plButton: Collection<string, PlayerButton>;
75-
leaveDelay: Collection<string, NodeJS.Timeout>;
76-
nowPlaying: Collection<string, { interval: NodeJS.Timeout; msg: GlobalMsg }>;
77-
wsl: Collection<string, { send: (data: Record<string, unknown>) => void }>;
78-
UpdateMusic!: (player: RainlinkPlayer) => Promise<void | Message<true>>;
79-
UpdateQueueMsg!: (player: RainlinkPlayer) => Promise<void | Message<true>>;
80-
enSwitch!: ActionRowBuilder<ButtonBuilder>;
81-
diSwitch!: ActionRowBuilder<ButtonBuilder>;
82-
enSwitchMod!: ActionRowBuilder<ButtonBuilder>;
83-
topgg?: TopggService;
84-
icons: IconType;
85-
cluster?: ClusterClient<Client>;
86-
REGEX: RegExp[];
87-
constructor() {
41+
public metadata: Metadata;
42+
public logger: LoggerService;
43+
public db!: DatabaseTable;
44+
public owner: string;
45+
public color: ColorResolvable;
46+
public i18n: I18n;
47+
public prefix: string;
48+
public isDatabaseConnected: boolean;
49+
public shardStatus: boolean;
50+
public lavalinkList: LavalinkDataType[];
51+
public lavalinkUsing: LavalinkUsingDataType[];
52+
public lavalinkUsed: LavalinkUsingDataType[];
53+
public rainlink: Rainlink;
54+
public commands: Collection<string, Command>;
55+
public interval: Collection<string, NodeJS.Timer>;
56+
public sentQueue: Collection<string, boolean>;
57+
public nplayingMsg: Collection<string, { coll: InteractionCollector<ButtonInteraction<"cached">>; msg: Message }>;
58+
public aliases: Collection<string, string>;
59+
public plButton: Collection<string, PlayerButton>;
60+
public leaveDelay: Collection<string, NodeJS.Timeout>;
61+
public nowPlaying: Collection<string, { interval: NodeJS.Timeout; msg: GlobalMsg }>;
62+
public wsl: Collection<string, { send: (data: Record<string, unknown>) => void }>;
63+
public UpdateMusic!: (player: RainlinkPlayer) => Promise<void | Message<true>>;
64+
public UpdateQueueMsg!: (player: RainlinkPlayer) => Promise<void | Message<true>>;
65+
public enSwitch!: ActionRowBuilder<ButtonBuilder>;
66+
public diSwitch!: ActionRowBuilder<ButtonBuilder>;
67+
public enSwitchMod!: ActionRowBuilder<ButtonBuilder>;
68+
public topgg?: TopggService;
69+
public icons: IconType;
70+
public cluster?: ClusterClient<Client>;
71+
public REGEX: RegExp[];
72+
73+
constructor(
74+
public config: Config,
75+
public clientIndex: number,
76+
isMsgEnable: boolean
77+
) {
8878
super({
8979
shards: process.env.IS_SHARING == "true" ? getInfo().SHARD_LIST : "auto",
9080
shardCount: process.env.IS_SHARING == "true" ? getInfo().TOTAL_SHARDS : 1,
9181
allowedMentions: {
9282
parse: ["roles", "users", "everyone"],
9383
repliedUser: false,
9484
},
95-
intents: configData.features.MESSAGE_CONTENT.enable
85+
intents: isMsgEnable
9686
? [
9787
GatewayIntentBits.Guilds,
9888
GatewayIntentBits.GuildVoiceStates,
@@ -103,10 +93,9 @@ export class Manager extends Client {
10393
});
10494

10595
// Initial basic bot config
106-
// process.argv[1].replace(/^.*[\\\/]/, "") + " " +
107-
this.logger = new LoggerService(this);
96+
const __dirname = dirname(fileURLToPath(import.meta.url));
97+
this.logger = new LoggerService(this, clientIndex);
10898
this.logger.info("ClientManager", "Booting client...");
109-
this.config = configData;
11099
this.metadata = new ManifestService().data.metadata.bot;
111100
this.owner = this.config.bot.OWNER_ID;
112101
this.color = (this.config.bot.EMBED_COLOR || "#2b2d31") as ColorResolvable;
@@ -116,7 +105,17 @@ export class Manager extends Client {
116105
});
117106
this.prefix = this.config.features.MESSAGE_CONTENT.commands.prefix || "d!";
118107
this.shardStatus = false;
119-
this.REGEX = REGEX;
108+
this.REGEX = [
109+
/(?:https?:\/\/)?(?:www\.)?youtu(?:\.be\/|be.com\/\S*(?:watch|embed)(?:(?:(?=\/[-a-zA-Z0-9_]{11,}(?!\S))\/)|(?:\S*v=|v\/)))([-a-zA-Z0-9_]{11,})/,
110+
/^.*(youtu.be\/|list=)([^#\&\?]*).*/,
111+
/^(?:spotify:|https:\/\/[a-z]+\.spotify\.com\/(track\/|user\/(.*)\/playlist\/|playlist\/))(.*)$/,
112+
/^https?:\/\/(?:www\.)?deezer\.com\/[a-z]+\/(track|album|playlist)\/(\d+)$/,
113+
/^(?:(https?):\/\/)?(?:(?:www|m)\.)?(soundcloud\.com|snd\.sc)\/(.*)$/,
114+
/(?:https:\/\/music\.apple\.com\/)(?:.+)?(artist|album|music-video|playlist)\/([\w\-\.]+(\/)+[\w\-\.]+|[^&]+)\/([\w\-\.]+(\/)+[\w\-\.]+|[^&]+)/,
115+
/^https?:\/\/(?:www\.|secure\.|sp\.)?nicovideo\.jp\/watch\/([a-z]{2}[0-9]+)/,
116+
/(?:https:\/\/spotify\.link)\/([A-Za-z0-9]+)/,
117+
/^https:\/\/deezer\.page\.link\/[a-zA-Z0-9]{12}$/,
118+
];
120119

121120
if (!this.config.lavalink.AVOID_SUSPEND)
122121
this.logger.warn(
@@ -160,18 +159,17 @@ export class Manager extends Client {
160159
new DeployService(this);
161160
new initHandler(this);
162161
new DatabaseService(this);
163-
super.login(this.config.bot.TOKEN);
164162
}
165163

166-
configVolCheck(vol: number = this.config.lavalink.DEFAULT_VOLUME) {
164+
protected configVolCheck(vol: number = this.config.lavalink.DEFAULT_VOLUME) {
167165
if (!vol || isNaN(vol) || vol > 100 || vol < 1) {
168166
this.config.lavalink.DEFAULT_VOLUME = 100;
169167
return false;
170168
}
171169
return true;
172170
}
173171

174-
configSearchCheck(data: string[] = this.config.lavalink.AUTOCOMPLETE_SEARCH) {
172+
protected configSearchCheck(data: string[] = this.config.lavalink.AUTOCOMPLETE_SEARCH) {
175173
const defaultSearch = ["yorushika", "yoasobi", "tuyu", "hinkik"];
176174
if (!data || data.length == 0) {
177175
this.config.lavalink.AUTOCOMPLETE_SEARCH = defaultSearch;
@@ -186,12 +184,12 @@ export class Manager extends Client {
186184
return true;
187185
}
188186

189-
stringCheck(data: unknown) {
187+
protected stringCheck(data: unknown) {
190188
if (typeof data === "string" || data instanceof String) return true;
191189
return false;
192190
}
193191

194-
getString(locale: string, section: string, key: string, args?: I18nArgs | undefined) {
192+
public getString(locale: string, section: string, key: string, args?: I18nArgs | undefined) {
195193
const currentString = this.i18n.get(locale, section, key, args);
196194
const locateErr = `Locale '${locale}' not found.`;
197195
const sectionErr = `Section '${section}' not found in locale '${locale}'`;

src/services/ConfigDataService.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ export class ConfigDataService {
4646
throw new Error("Config file not contains lavalink config field, please check app.example.yml for example");
4747
if (!res.bot.OWNER_ID)
4848
throw new Error("Config file not contains OWNER_ID, please check app.example.yml for example");
49-
if (!res.bot.TOKEN) throw new Error("Config file not contains TOKEN, please check app.example.yml for example");
49+
if (!res.bot.TOKEN || res.bot.TOKEN.length == 0)
50+
throw new Error("Config file not contains TOKEN, please check app.example.yml for example");
51+
if (!Array.isArray(res.bot.TOKEN))
52+
throw new Error("TOKEN field not in array, please check app.example.yml for example");
5053
if (!res.lavalink.NODES || res.lavalink.NODES.length == 0)
5154
throw new Error("Config file not contains NODES, please check app.example.yml for example");
5255
}
@@ -83,7 +86,7 @@ export class ConfigDataService {
8386
get defaultConfig(): Config {
8487
return {
8588
bot: {
86-
TOKEN: "",
89+
TOKEN: [],
8790
OWNER_ID: "",
8891
EMBED_COLOR: "#2B2D31",
8992
LANGUAGE: "en",

src/services/DeployService.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export class DeployService {
5252

5353
this.client.logger.deploy(DeployService.name, "Reading interaction files completed, setting up REST...");
5454

55-
const rest = new REST({ version: "10" }).setToken(this.client.config.bot.TOKEN);
55+
const rest = new REST({ version: "10" }).setToken(this.client.config.bot.TOKEN[this.client.clientIndex]);
5656
const client = await rest.get(Routes.user());
5757

5858
this.client.logger.deploy(

src/services/LoggerService.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { createLogger, transports, format, Logger } from "winston";
22
const { timestamp, prettyPrint, printf } = format;
3-
import { fileURLToPath } from "url";
43
import chalk from "chalk";
54
import util from "node:util";
65
import { Manager } from "../manager.js";
@@ -15,7 +14,10 @@ type InfoDataType = {
1514
export class LoggerService {
1615
private preLog: Logger;
1716
private padding = 28;
18-
constructor(private client: Manager) {
17+
constructor(
18+
private client: Manager,
19+
private tag: number
20+
) {
1921
this.preLog = createLogger({
2022
levels: {
2123
error: 0,
@@ -151,10 +153,13 @@ export class LoggerService {
151153

152154
private get consoleFormat() {
153155
const colored = chalk.hex("#86cecb")("|");
156+
const timeStamp = (info: InfoDataType) => chalk.hex("#00ddc0")(info.timestamp);
157+
const botTag = chalk.hex("#2aabf3")(`bot_${this.tag}`);
158+
const msg = (info: InfoDataType) => chalk.hex("#86cecb")(info.message);
154159
return format.combine(
155160
timestamp(),
156161
printf((info: InfoDataType) => {
157-
return `${chalk.hex("#00ddc0")(info.timestamp)} ${colored} ${this.filter(info)} ${colored} ${chalk.hex("#86cecb")(info.message)}`;
162+
return `${timeStamp(info)} ${colored} ${botTag} ${colored} ${this.filter(info)} ${colored} ${msg(info)}`;
158163
})
159164
);
160165
}

src/shard.ts

-21
This file was deleted.

src/shard/bootloader.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { Manager } from "../manager.js";
2+
import { ConfigDataService } from "../services/ConfigDataService.js";
3+
const configData = new ConfigDataService().data;
4+
const index = Number(process.env.BYTEBLAZE_CURRENT_INDEX);
5+
const token = String(process.env.BYTEBLAZE_CURRENT_TOKEN);
6+
new Manager(configData, index, configData.features.MESSAGE_CONTENT.enable).login(token);

0 commit comments

Comments
 (0)