Skip to content

Commit

Permalink
[Add multi node support for autofix] Merge #45
Browse files Browse the repository at this point in the history
  • Loading branch information
RainyXeon authored Feb 12, 2024
2 parents e57cade + 25b39a9 commit ee5f727
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 78 deletions.
11 changes: 10 additions & 1 deletion app.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,16 @@ lavalink:
DEFAULT_VOLUME: 100 # Must not over 1000 or bot crash

# You can add more Lavalink servers!
NODES: [{ url: "IP:Port", name: "Name", auth: "Password", secure: false }]
NODES:
- url: "IP:Port"
name: "Name" #only a-z A-Z 0-9 and _
auth: "Password"
secure: false

# - url: "IP:Port"
# name: "Name"
# auth: "Password"
# secure: false

features:
DATABASE:
Expand Down
58 changes: 58 additions & 0 deletions src/@types/ByteBlaze.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Metadata } from "./Metadata.js";
import { Config } from "./Config.js";
import { DatabaseTable } from "../database/@types.js";
import {
ActionRowBuilder,
ButtonBuilder,
Client,
Collection,
ColorResolvable,
Message,
} from "discord.js";
import { I18n } from "@hammerhq/localization";
import { LavalinkDataType, LavalinkUsingDataType } from "./Lavalink.js";
import { Kazagumo } from "../lib/Kazagumo.js";
import { Command } from "../structures/Command.js";
import { PremiumUser } from "./User.js";
import { PlayerButton } from "./Button.js";
import { GlobalMsg } from "../structures/CommandHandler.js";
import { RequestInterface } from "../webserver/RequestInterface.js";
import { KazagumoPlayer } from "../lib/main.js";
import { IconType } from "./Emoji.js";
import { ClusterClient } from "discord-hybrid-sharding";

export interface ByteBlaze extends Client {
metadata: Metadata;
config: Config;
logger: any;
db: DatabaseTable;
owner: string;
color: ColorResolvable;
i18n: I18n;
prefix: string;
isDatabaseConnected: boolean;
shardStatus: boolean;
lavalinkList: LavalinkDataType[];
lavalinkUsing: LavalinkUsingDataType[];
lavalinkUsed: LavalinkUsingDataType[];
manager: Kazagumo;
commands: Collection<string, Command>;
premiums: Collection<string, PremiumUser>;
interval: Collection<string, NodeJS.Timer>;
sentQueue: Collection<string, boolean>;
nplayingMsg: Collection<string, Message>;
aliases: Collection<string, string>;
plButton: Collection<string, PlayerButton>;
leaveDelay: Collection<string, NodeJS.Timeout>;
nowPlaying: Collection<string, { interval: NodeJS.Timeout; msg: GlobalMsg }>;
websocket: WebSocket | undefined;
wsMessage?: Collection<string, RequestInterface>;
UpdateMusic?: (player: KazagumoPlayer) => Promise<void | Message<true>>;
UpdateQueueMsg?: (player: KazagumoPlayer) => Promise<void | Message<true>>;
enSwitch?: ActionRowBuilder<ButtonBuilder>;
diSwitch?: ActionRowBuilder<ButtonBuilder>;
enSwitchMod?: ActionRowBuilder<ButtonBuilder>;
icons: IconType;
cluster?: ClusterClient<Client>;
REGEX: RegExp[];
}
32 changes: 19 additions & 13 deletions src/autofix/AutoFixLavalink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ const regex =

export class AutoFixLavalink {
client: Manager;
constructor(client: Manager) {
lavalinkName: string;
constructor(client: Manager, lavalinkName: string) {
this.client = client;
this.lavalinkName = lavalinkName;
this.execute();
}

Expand Down Expand Up @@ -37,7 +39,8 @@ export class AutoFixLavalink {
this.client.logger.lavalink("----- Terminated autofix lavalink. -----");
return;
}
const nodeInfo = await this.applyNewLavalink();

await this.applyNewLavalink();

this.client.logger.lavalink(
"Now used new lavalink, please wait 1 second to make it connect."
Expand All @@ -64,36 +67,39 @@ export class AutoFixLavalink {
}

async removeCurrentLavalink() {
const lavalinkIndex = this.client.lavalinkUsing.findIndex(
(data) => data.name == this.lavalinkName
);
const targetLavalink = this.client.lavalinkUsing[lavalinkIndex];
if (
this.client.manager.shoukaku.nodes.size == 0 &&
this.client.lavalinkUsing.length != 0
) {
this.client.lavalinkUsed.push(this.client.lavalinkUsing[0]);
this.client.lavalinkUsing.splice(0, 1);
this.client.lavalinkUsing.splice(lavalinkIndex, 1);
} else if (
this.client.manager.shoukaku.nodes.size !== 0 &&
this.client.lavalinkUsing.length !== 0
) {
this.client.lavalinkUsed.push(this.client.lavalinkUsing[0]);
await this.client.manager.shoukaku.removeNode(
this.client.lavalinkUsing[0].name
const isLavalinkExist = this.client.manager.shoukaku.nodes.has(
targetLavalink.name
);
this.client.lavalinkUsing.splice(0, 1);
if (isLavalinkExist)
await this.client.manager.shoukaku.removeNode(targetLavalink.name);
this.client.lavalinkUsing.splice(lavalinkIndex, 1);
}
}

async applyNewLavalink() {
const online_list: LavalinkDataType[] = [];
const onlineList: LavalinkDataType[] = [];

this.client.lavalinkList.filter(async (data) => {
if (data.online == true) return online_list.push(data);
if (data.online == true) return onlineList.push(data);
});

const nodeInfo =
online_list[Math.floor(Math.random() * online_list.length)];
const nodeInfo = onlineList[Math.floor(Math.random() * onlineList.length)];

const newNodeInfo = {
name: `${nodeInfo.host}:${nodeInfo.port}`,
name: `${nodeInfo.host.replace(/[^A-Z0-9]+/gi, "_")}`,
url: `${nodeInfo.host}:${nodeInfo.port}`,
auth: nodeInfo.pass,
secure: nodeInfo.secure,
Expand Down
2 changes: 1 addition & 1 deletion src/events/node/disconnect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default class {
if (players) players.map((player: KazagumoPlayer) => player.destroy());
client.logger.debug(`Lavalink ${name}: Disconnected`);
if (client.config.features.AUTOFIX_LAVALINK.enable) {
new AutoFixLavalink(client);
new AutoFixLavalink(client, name);
}
}
}
15 changes: 15 additions & 0 deletions src/lib/Kazagumo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,12 @@ export class Kazagumo extends EventEmitter {
) {
super();

if (!this.precheckNode(nodes))
throw new KazagumoError(
1,
"Node name must not have the same name and just include a-z, A-Z, 0-9 and _"
);

this.shoukaku = new Shoukaku(connector, nodes, options);

if (this.KazagumoOptions.plugins) {
Expand All @@ -307,6 +313,15 @@ export class Kazagumo extends EventEmitter {
this.players = new Map<string, KazagumoPlayer>();
}

protected precheckNode(node: NodeOption[]) {
const regex = /^[a-zA-Z0-9_.-]*$/;
for (const data of node) {
if (!regex.test(data.name)) return false;
if (node.filter((e) => e.name === data.name).length > 1) return false;
}
return true;
}

// Modified version of Shoukaku#joinVoiceChannel
// Credit to @deivu
protected async createVoiceConnection(
Expand Down
67 changes: 7 additions & 60 deletions src/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,30 @@ import {
GatewayIntentBits,
Collection,
ColorResolvable,
ActionRowBuilder,
ButtonBuilder,
Message,
} from "discord.js";
import { DatabaseService } from "./database/index.js";
import { I18n } from "@hammerhq/localization";
import { resolve } from "path";
import { LavalinkDataType, LavalinkUsingDataType } from "./@types/Lavalink.js";
import { ConfigDataService } from "./services/ConfigDataService.js";
import { LoggerService } from "./services/LoggerService.js";
import { ClusterClient, getInfo } from "discord-hybrid-sharding";
import { Kazagumo, KazagumoPlayer } from "./lib/main.js";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
import { WebServer } from "./webserver/index.js";
import WebSocket from "ws";
import { Metadata } from "./@types/Metadata.js";
import { ManifestService } from "./services/ManifestService.js";
import { Config } from "./@types/Config.js";
import { PremiumUser } from "./@types/User.js";
import { IconType } from "./@types/Emoji.js";
import { NormalModeIcons } from "./assets/NormalModeIcons.js";
import { SafeModeIcons } from "./assets/SafeModeIcons.js";
import { config } from "dotenv";
import { DatabaseTable } from "./database/@types.js";
import { initHandler } from "./handlers/index.js";
import { KazagumoInit } from "./structures/Kazagumo.js";
import utils from "node:util";
import { RequestInterface } from "./webserver/RequestInterface.js";
import { DeployService } from "./services/DeployService.js";
import { PlayerButton } from "./@types/Button.js";
import { Command } from "./structures/Command.js";
import { GlobalMsg } from "./structures/CommandHandler.js";
config();
import { ByteBlaze } from "./@types/ByteBlaze.js";

config();
const __dirname = dirname(fileURLToPath(import.meta.url));
const loggerService = new LoggerService().init();
const configData = new ConfigDataService().data;

const REGEX = [
/(?:https?:\/\/)?(?:www\.)?youtu(?:\.be\/|be.com\/\S*(?:watch|embed)(?:(?:(?=\/[-a-zA-Z0-9_]{11,}(?!\S))\/)|(?:\S*v=|v\/)))([-a-zA-Z0-9_]{11,})/,
/^.*(youtu.be\/|list=)([^#\&\?]*).*/,
Expand All @@ -54,52 +39,14 @@ const REGEX = [
/^https:\/\/deezer\.page\.link\/[a-zA-Z0-9]{12}$/,
];

loggerService.info("Booting client...");
export declare interface Manager extends ByteBlaze {}

export class Manager extends Client {
// Interface
token: string;
metadata: Metadata;
config: Config;
logger: any;
db!: DatabaseTable;
owner: string;
color: ColorResolvable;
i18n: I18n;
prefix: string;
isDatabaseConnected: boolean;
shardStatus: boolean;
lavalinkList: LavalinkDataType[];
lavalinkUsing: LavalinkUsingDataType[];
lavalinkUsed: LavalinkUsingDataType[];
manager: Kazagumo;
commands: Collection<string, Command>;
premiums: Collection<string, PremiumUser>;
interval: Collection<string, NodeJS.Timer>;
sentQueue: Collection<string, boolean>;
nplayingMsg: Collection<string, Message>;
aliases: Collection<string, string>;
plButton: Collection<string, PlayerButton>;
leaveDelay: Collection<string, NodeJS.Timeout>;
nowPlaying: Collection<string, { interval: NodeJS.Timeout; msg: GlobalMsg }>;
websocket?: WebSocket;
wsMessage?: Collection<string, RequestInterface>;
UpdateMusic!: (player: KazagumoPlayer) => Promise<void | Message<true>>;
UpdateQueueMsg!: (player: KazagumoPlayer) => Promise<void | Message<true>>;
enSwitch!: ActionRowBuilder<ButtonBuilder>;
diSwitch!: ActionRowBuilder<ButtonBuilder>;
enSwitchMod!: ActionRowBuilder<ButtonBuilder>;
icons: IconType;
cluster?: ClusterClient<Client>;
REGEX: RegExp[];

// Main class
constructor() {
super({
// shards: getInfo().SHARD_LIST, // An array of shards that will get spawned
// shardCount: getInfo().TOTAL_SHARDS, // Total number of shards
shards: process.env.IS_SHARING == "true" ? getInfo().SHARD_LIST : "auto",
shardCount: process.env.IS_SHARING == "true" ? getInfo().TOTAL_SHARDS : 1,
shardCount:
process.env.IS_SHARING == "true" ? getInfo().TOTAL_SHARDS : undefined,
allowedMentions: {
parse: ["roles", "users", "everyone"],
repliedUser: false,
Expand All @@ -119,10 +66,10 @@ export class Manager extends Client {
});

// Initial basic bot config
loggerService.info("Booting client...");
this.logger = loggerService;
this.config = configData;
this.metadata = new ManifestService().data.metadata.bot;
this.token = this.config.bot.TOKEN;
this.owner = this.config.bot.OWNER_ID;
this.color = (this.config.bot.EMBED_COLOR || "#2b2d31") as ColorResolvable;
this.i18n = new I18n({
Expand Down Expand Up @@ -203,7 +150,7 @@ export class Manager extends Client {
new DeployService(this);
new initHandler(this);
new DatabaseService(this);
super.login(this.token);
super.login(this.config.bot.TOKEN);
}

configVolCheck(vol: number = this.config.lavalink.DEFAULT_VOLUME) {
Expand Down
6 changes: 3 additions & 3 deletions src/manifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

<metadata>
<bot>
<version>5.0.0</version>
<autofix>5.0.0</autofix>
<kazagumo>3.0.0</kazagumo>
<version>5.0.1</version>
<autofix>5.0.1</autofix>
<kazagumo>3.0.1</kazagumo>
<codename>vocaloid_kaito</codename>
</bot>
</metadata>

0 comments on commit ee5f727

Please sign in to comment.