mirror of
https://github.com/kirameki-cafe/Yumi.git
synced 2026-09-13 18:59:19 +00:00
Formatted code and clean up
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
import Logger from '../libs/Logger';
|
||||
|
||||
import Environment from './Environment';
|
||||
import Configuration from './Configuration';
|
||||
import Prisma from './Prisma';
|
||||
import Discord from './Discord';
|
||||
import osu from './osuAPI';
|
||||
import Configuration from './Configuration';
|
||||
|
||||
import Logger from '../libs/Logger';
|
||||
class App {
|
||||
|
||||
public readonly versionNumber = `0.09`;
|
||||
public readonly version = `${this.versionNumber}${Environment.get().NODE_ENV === "development" ? ' / Development Build' : ''}`;
|
||||
public readonly version = `${this.versionNumber}${
|
||||
Environment.get().NODE_ENV === 'development' ? ' / Development Build' : ''
|
||||
}`;
|
||||
|
||||
public loadConfig(): void {
|
||||
Logger.log('info', 'Loading configuration');
|
||||
@@ -37,4 +37,4 @@ class App {
|
||||
}
|
||||
}
|
||||
|
||||
export default new App;
|
||||
export default new App();
|
||||
|
||||
+13
-20
@@ -1,30 +1,27 @@
|
||||
import { Guild } from "@prisma/client";
|
||||
import { Snowflake } from "discord-api-types/v10";
|
||||
import Prisma from "./Prisma";
|
||||
import { Guild } from '@prisma/client';
|
||||
import { Snowflake } from 'discord-api-types/v10';
|
||||
import Prisma from './Prisma';
|
||||
|
||||
class Cache {
|
||||
|
||||
private cache: any;
|
||||
|
||||
constructor () {
|
||||
constructor() {
|
||||
this.cache = {
|
||||
Guilds: {
|
||||
|
||||
}
|
||||
Guilds: {}
|
||||
};
|
||||
}
|
||||
|
||||
public async updateGuildsCache() {
|
||||
const Guilds = await Prisma.client.guild.findMany();
|
||||
for(let Guild of Guilds) {
|
||||
for (let Guild of Guilds) {
|
||||
this.setGuildData(Guild.id, Guild);
|
||||
}
|
||||
}
|
||||
|
||||
public async updateGuildCache(guildID: Snowflake) {
|
||||
const DBGuild = await Prisma.client.guild.findFirst({ where:{id: guildID} });
|
||||
|
||||
if(DBGuild === null) return;
|
||||
const DBGuild = await Prisma.client.guild.findFirst({ where: { id: guildID } });
|
||||
|
||||
if (DBGuild === null) return;
|
||||
this.setGuildData(guildID, DBGuild);
|
||||
}
|
||||
|
||||
@@ -32,9 +29,8 @@ class Cache {
|
||||
this.cache.Guilds[id] = data;
|
||||
}
|
||||
|
||||
public async getGuild(id: string): Promise<(Guild | undefined)> {
|
||||
if(typeof this.cache.Guilds[id] !== 'undefined')
|
||||
return this.cache.Guilds[id];
|
||||
public async getGuild(id: string): Promise<Guild | undefined> {
|
||||
if (typeof this.cache.Guilds[id] !== 'undefined') return this.cache.Guilds[id];
|
||||
|
||||
const Guild = await Prisma.client.guild.findFirst({
|
||||
where: {
|
||||
@@ -42,18 +38,15 @@ class Cache {
|
||||
}
|
||||
});
|
||||
|
||||
if(Guild === null)
|
||||
return undefined;
|
||||
if (Guild === null) return undefined;
|
||||
|
||||
this.setGuildData(Guild.id, Guild);
|
||||
return this.cache.Guilds[id];
|
||||
|
||||
}
|
||||
|
||||
public getGuilds(): void {
|
||||
return this.cache.Guilds;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new Cache();
|
||||
export default new Cache();
|
||||
|
||||
@@ -1,48 +1,50 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const ConfigurationData: any = [];
|
||||
class Configuration {
|
||||
|
||||
public init(): void {
|
||||
const dir = path.join(process.cwd(), 'configs/');
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir);
|
||||
}
|
||||
this.copyExampleIfNotExists("Ping.json");
|
||||
this.copyExampleIfNotExists("ServiceAnnouncement.json");
|
||||
this.copyExampleIfNotExists('Ping.json');
|
||||
this.copyExampleIfNotExists('ServiceAnnouncement.json');
|
||||
|
||||
this.loadConfig("Ping.json");
|
||||
this.loadConfig("ServiceAnnouncement.json");
|
||||
this.loadConfig('Ping.json');
|
||||
this.loadConfig('ServiceAnnouncement.json');
|
||||
}
|
||||
|
||||
public loadConfig(configFileName: string): void {
|
||||
const dir = path.join(process.cwd(), 'configs/');
|
||||
if (!fs.existsSync(path.join(dir, configFileName)))
|
||||
if (!fs.existsSync(path.join(dir, configFileName)))
|
||||
throw new Error(`Config file ${configFileName} does not exist`);
|
||||
|
||||
const config = JSON.parse(fs.readFileSync(path.join(dir, configFileName)).toString());
|
||||
ConfigurationData[configFileName.replace(/\.[^/.]+$/, "")] = config;
|
||||
ConfigurationData[configFileName.replace(/\.[^/.]+$/, '')] = config;
|
||||
}
|
||||
|
||||
public getConfig(key?: string) {
|
||||
if(!key)
|
||||
return ConfigurationData;
|
||||
if (!key) return ConfigurationData;
|
||||
else {
|
||||
if(ConfigurationData[key])
|
||||
return ConfigurationData[key];
|
||||
else
|
||||
throw new Error(`No configuration found for ${key}`);
|
||||
if (ConfigurationData[key]) return ConfigurationData[key];
|
||||
else throw new Error(`No configuration found for ${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
private copyExampleIfNotExists(file: string): void {
|
||||
const dir = path.join(process.cwd(), 'configs/');
|
||||
if (!fs.existsSync(path.join(dir, file))) {
|
||||
fs.copyFileSync(path.join(process.cwd(), 'configs_example/', `${file.replace('.json', '.example.json')}`), path.join(dir, file));
|
||||
fs.copyFileSync(
|
||||
path.join(
|
||||
process.cwd(),
|
||||
'configs_example/',
|
||||
`${file.replace('.json', '.example.json')}`
|
||||
),
|
||||
path.join(dir, file)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new Configuration();
|
||||
|
||||
+160
-122
@@ -1,56 +1,59 @@
|
||||
import { Guild, Client, GuildMember, Intents, Interaction, Message, TextChannel} from "discord.js";
|
||||
import { Guild as GuildPrisma } from ".prisma/client";
|
||||
import { Guild, Client, GuildMember, Intents, Interaction, Message, TextChannel } from 'discord.js';
|
||||
|
||||
import Logger from "../libs/Logger";
|
||||
import Environment from "./Environment";
|
||||
import Logger from '../libs/Logger';
|
||||
import Environment from './Environment';
|
||||
|
||||
import Discord_Core from "../discord/Core";
|
||||
import Discord_Settings from "../discord/Settings";
|
||||
import Discord_Ping from "../discord/Ping";
|
||||
import Discord_Help from "../discord/Help";
|
||||
import Discord_Invite from "../discord/Invite";
|
||||
import Discord_Say from "../discord/Say";
|
||||
import Discord_InteractionManager from "../discord/InteractionManager";
|
||||
import Discord_MembershipScreening from "../discord/MembershipScreening";
|
||||
import Discord_osu from "../discord/osu";
|
||||
import Discord_UserInfo from "../discord/UserInfo";
|
||||
import Discord_Stats from "../discord/Stats";
|
||||
import Discord_Core from '../discord/Core';
|
||||
import Discord_Settings from '../discord/Settings';
|
||||
import Discord_Ping from '../discord/Ping';
|
||||
import Discord_Help from '../discord/Help';
|
||||
import Discord_Invite from '../discord/Invite';
|
||||
import Discord_Say from '../discord/Say';
|
||||
import Discord_InteractionManager from '../discord/InteractionManager';
|
||||
import Discord_MembershipScreening from '../discord/MembershipScreening';
|
||||
import Discord_osu from '../discord/osu';
|
||||
import Discord_UserInfo from '../discord/UserInfo';
|
||||
import Discord_Stats from '../discord/Stats';
|
||||
|
||||
import Discord_MusicPlayer_Play from "../discord/MusicPlayer/Play";
|
||||
import Discord_MusicPlayer_Skip from "../discord/MusicPlayer/Skip";
|
||||
import Discord_MusicPlayer_Join from "../discord/MusicPlayer/Join";
|
||||
import Discord_MusicPlayer_Leave from "../discord/MusicPlayer/Leave";
|
||||
import Discord_MusicPlayer_Queue from "../discord/MusicPlayer/Queue";
|
||||
import Discord_MusicPlayer_Search from "../discord/MusicPlayer/Search";
|
||||
import Discord_MusicPlayer_NowPlaying from "../discord/MusicPlayer/NowPlaying";
|
||||
import Discord_MusicPlayer_Loop from "../discord/MusicPlayer/Loop";
|
||||
import Discord_MusicPlayer_Pause from "../discord/MusicPlayer/Pause";
|
||||
import Discord_MusicPlayer_Resume from "../discord/MusicPlayer/Resume";
|
||||
import Discord_MusicPlayer_Play from '../discord/MusicPlayer/Play';
|
||||
import Discord_MusicPlayer_Skip from '../discord/MusicPlayer/Skip';
|
||||
import Discord_MusicPlayer_Join from '../discord/MusicPlayer/Join';
|
||||
import Discord_MusicPlayer_Leave from '../discord/MusicPlayer/Leave';
|
||||
import Discord_MusicPlayer_Queue from '../discord/MusicPlayer/Queue';
|
||||
import Discord_MusicPlayer_Search from '../discord/MusicPlayer/Search';
|
||||
import Discord_MusicPlayer_NowPlaying from '../discord/MusicPlayer/NowPlaying';
|
||||
import Discord_MusicPlayer_Loop from '../discord/MusicPlayer/Loop';
|
||||
import Discord_MusicPlayer_Pause from '../discord/MusicPlayer/Pause';
|
||||
import Discord_MusicPlayer_Resume from '../discord/MusicPlayer/Resume';
|
||||
|
||||
import Discord_Developer_ServiceAnnouncement from "../discord/developer/ServiceAnnouncement";
|
||||
import Discord_Developer_Debug from "../discord/developer/Debug";
|
||||
import Discord_Developer_ServiceAnnouncement from '../discord/developer/ServiceAnnouncement';
|
||||
import Discord_Developer_Debug from '../discord/developer/Debug';
|
||||
|
||||
import Cache from "./Cache";
|
||||
import DiscordModule from "../utils/DiscordModule";
|
||||
import { Map } from "typescript";
|
||||
import Cache from './Cache';
|
||||
import DiscordModule from '../utils/DiscordModule';
|
||||
import { Map } from 'typescript';
|
||||
|
||||
class Discord {
|
||||
|
||||
public client: Client;
|
||||
private loaded_module = new Map<string, DiscordModule>();
|
||||
|
||||
constructor () {
|
||||
this.client = new Client({
|
||||
constructor() {
|
||||
this.client = new Client({
|
||||
//partials: ['MESSAGE', 'CHANNEL', 'REACTION'],
|
||||
intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES, Intents.FLAGS.GUILD_MEMBERS, Intents.FLAGS.GUILD_PRESENCES, Intents.FLAGS.GUILD_VOICE_STATES]
|
||||
intents: [
|
||||
Intents.FLAGS.GUILDS,
|
||||
Intents.FLAGS.GUILD_MESSAGES,
|
||||
Intents.FLAGS.GUILD_MEMBERS,
|
||||
Intents.FLAGS.GUILD_PRESENCES,
|
||||
Intents.FLAGS.GUILD_VOICE_STATES
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
public init(): void {
|
||||
|
||||
Logger.info('Logging in to discord');
|
||||
this.client.login(Environment.get().DISCORD_TOKEN);
|
||||
|
||||
|
||||
const modules: DiscordModule[] = [
|
||||
new Discord_Core(),
|
||||
new Discord_Help(),
|
||||
@@ -80,191 +83,226 @@ class Discord {
|
||||
new Discord_Developer_Debug()
|
||||
];
|
||||
|
||||
for(const _module of modules) {
|
||||
if(_module.id) {
|
||||
if(this.loaded_module.has(_module.id))
|
||||
Logger.error(`Module ${_module.constructor.name} is trying to assign a conflicting module id ${_module.id}. ${this.loaded_module.get(_module.id)!.constructor.name} is already assigned to this id.`);
|
||||
else
|
||||
this.loaded_module.set(_module.id, _module);
|
||||
}
|
||||
else
|
||||
Logger.error(`Invalid module ${_module.constructor.name}. The module does not have an id.`);
|
||||
for (const _module of modules) {
|
||||
if (_module.id) {
|
||||
if (this.loaded_module.has(_module.id))
|
||||
Logger.error(
|
||||
`Module ${
|
||||
_module.constructor.name
|
||||
} is trying to assign a conflicting module id ${_module.id}. ${
|
||||
this.loaded_module.get(_module.id)!.constructor.name
|
||||
} is already assigned to this id.`
|
||||
);
|
||||
else this.loaded_module.set(_module.id, _module);
|
||||
} else
|
||||
Logger.error(
|
||||
`Invalid module ${_module.constructor.name}. The module does not have an id.`
|
||||
);
|
||||
}
|
||||
|
||||
Logger.info(`Loaded ${this.loaded_module.size} Discord Modules`);
|
||||
|
||||
for(const module of this.loaded_module) {
|
||||
for (const module of this.loaded_module) {
|
||||
let thisModule: DiscordModule = module[1];
|
||||
thisModule.Init();
|
||||
}
|
||||
|
||||
// On bot logged in
|
||||
this.client.on("ready", () => {
|
||||
for(const module of this.loaded_module) {
|
||||
this.client.on('ready', () => {
|
||||
for (const module of this.loaded_module) {
|
||||
let thisModule: DiscordModule = module[1];
|
||||
thisModule.Ready();
|
||||
}
|
||||
});
|
||||
|
||||
// Member join guild event to modules
|
||||
this.client.on("guildMemberAdd", (member: GuildMember) => {
|
||||
for(const module of this.loaded_module) {
|
||||
this.client.on('guildMemberAdd', (member: GuildMember) => {
|
||||
for (const module of this.loaded_module) {
|
||||
let thisModule: DiscordModule = module[1];
|
||||
thisModule.GuildMemberAdd(member);
|
||||
}
|
||||
});
|
||||
|
||||
// Interaction create event to modules
|
||||
this.client.on("interactionCreate", (interaction: Interaction) => {
|
||||
for(const module of this.loaded_module) {
|
||||
this.client.on('interactionCreate', (interaction: Interaction) => {
|
||||
for (const module of this.loaded_module) {
|
||||
let thisModule: DiscordModule = module[1];
|
||||
|
||||
if(interaction.guild) {
|
||||
|
||||
if (interaction.guild) {
|
||||
thisModule.GuildInteractionCreate(interaction);
|
||||
|
||||
if(interaction.isCommand() && interaction.commandName && thisModule.commandInteractionName) {
|
||||
if (
|
||||
interaction.isCommand() &&
|
||||
interaction.commandName &&
|
||||
thisModule.commandInteractionName
|
||||
) {
|
||||
thisModule.GuildCommandInteractionCreate(interaction);
|
||||
if(interaction.commandName.toLowerCase() === thisModule.commandInteractionName)
|
||||
if (
|
||||
interaction.commandName.toLowerCase() ===
|
||||
thisModule.commandInteractionName
|
||||
)
|
||||
thisModule.GuildModuleCommandInteractionCreate(interaction);
|
||||
}
|
||||
else if(interaction.isButton()) {
|
||||
} else if (interaction.isButton()) {
|
||||
thisModule.GuildButtonInteractionCreate(interaction);
|
||||
}
|
||||
else if(interaction.isSelectMenu()) {
|
||||
} else if (interaction.isSelectMenu()) {
|
||||
thisModule.GuildSelectMenuInteractionCreate(interaction);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//thisModule.InteractionCreate(interaction);
|
||||
}
|
||||
});
|
||||
|
||||
// Message create event to modules
|
||||
this.client.on("messageCreate", (message: Message) => {
|
||||
for(const module of this.loaded_module) {
|
||||
this.client.on('messageCreate', (message: Message) => {
|
||||
for (const module of this.loaded_module) {
|
||||
let thisModule: DiscordModule = module[1];
|
||||
thisModule.GuildMessageCreate(message);
|
||||
}
|
||||
});
|
||||
|
||||
// Joined guild event to modules
|
||||
this.client.on("guildCreate", (guild: Guild) => {
|
||||
for(const module of this.loaded_module) {
|
||||
this.client.on('guildCreate', (guild: Guild) => {
|
||||
for (const module of this.loaded_module) {
|
||||
let thisModule: DiscordModule = module[1];
|
||||
thisModule.GuildCreate(guild);
|
||||
}
|
||||
});
|
||||
|
||||
// Handling guild commands
|
||||
this.client.on("messageCreate", async (message: Message) => {
|
||||
|
||||
if(!(message.channel instanceof TextChannel)) return;
|
||||
if(message.author.bot) return;
|
||||
if(typeof message.guild?.id === 'undefined') return;
|
||||
this.client.on('messageCreate', async (message: Message) => {
|
||||
if (!(message.channel instanceof TextChannel)) return;
|
||||
if (message.author.bot) return;
|
||||
if (typeof message.guild?.id === 'undefined') return;
|
||||
|
||||
let GuildCache = await Cache.getGuild(message.guild.id);
|
||||
if(typeof GuildCache === 'undefined' || typeof GuildCache.prefix === 'undefined') return;
|
||||
if (typeof GuildCache === 'undefined' || typeof GuildCache.prefix === 'undefined')
|
||||
return;
|
||||
|
||||
if(!message.content.startsWith(GuildCache.prefix)) return;
|
||||
if (!message.content.startsWith(GuildCache.prefix)) return;
|
||||
|
||||
let noPrefixMessage = message.content.replace(GuildCache.prefix, '');
|
||||
let symbols = [
|
||||
'!','@','#','$','%','^','&','*','(',')','-','=','_','+','\\','/','<','>','[',']','{','}','`','"',"'",',','.','~','|',';',':','?','、','。'
|
||||
'!',
|
||||
'@',
|
||||
'#',
|
||||
'$',
|
||||
'%',
|
||||
'^',
|
||||
'&',
|
||||
'*',
|
||||
'(',
|
||||
')',
|
||||
'-',
|
||||
'=',
|
||||
'_',
|
||||
'+',
|
||||
'\\',
|
||||
'/',
|
||||
'<',
|
||||
'>',
|
||||
'[',
|
||||
']',
|
||||
'{',
|
||||
'}',
|
||||
'`',
|
||||
'"',
|
||||
"'",
|
||||
',',
|
||||
'.',
|
||||
'~',
|
||||
'|',
|
||||
';',
|
||||
':',
|
||||
'?',
|
||||
'、',
|
||||
'。'
|
||||
];
|
||||
|
||||
const isTag = (prefix: string) => {
|
||||
return prefix.startsWith('<@!') && prefix.endsWith('>') ||
|
||||
prefix.startsWith('<:') && prefix.endsWith('>') ||
|
||||
prefix.startsWith('<a:') && prefix.endsWith('>') ||
|
||||
prefix.startsWith('<#') && prefix.endsWith('>');
|
||||
}
|
||||
return (
|
||||
(prefix.startsWith('<@!') && prefix.endsWith('>')) ||
|
||||
(prefix.startsWith('<:') && prefix.endsWith('>')) ||
|
||||
(prefix.startsWith('<a:') && prefix.endsWith('>')) ||
|
||||
(prefix.startsWith('<#') && prefix.endsWith('>'))
|
||||
);
|
||||
};
|
||||
|
||||
if((GuildCache.prefix.indexOf(' ') >= 0)) {
|
||||
if(noPrefixMessage.charAt(0) !== ' ') return;
|
||||
}
|
||||
else {
|
||||
if(symbols.includes(GuildCache.prefix.charAt(GuildCache.prefix.length - 1))) {
|
||||
|
||||
if(noPrefixMessage.charAt(0) === ' ') {
|
||||
if (GuildCache.prefix.indexOf(' ') >= 0) {
|
||||
if (noPrefixMessage.charAt(0) !== ' ') return;
|
||||
} else {
|
||||
if (symbols.includes(GuildCache.prefix.charAt(GuildCache.prefix.length - 1))) {
|
||||
if (noPrefixMessage.charAt(0) === ' ') {
|
||||
//Handle for "@Bot <command>"
|
||||
if(isTag(GuildCache.prefix)) {}
|
||||
else
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (isTag(GuildCache.prefix)) {
|
||||
} else return;
|
||||
} else {
|
||||
//Handle for "@Bot<command>"
|
||||
if(isTag(GuildCache.prefix)) return;
|
||||
if (isTag(GuildCache.prefix)) return;
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
if(noPrefixMessage.charAt(0) !== ' ') return;
|
||||
} else {
|
||||
if (noPrefixMessage.charAt(0) !== ' ') return;
|
||||
}
|
||||
//if(noPrefixMessage.charAt(0) !== ' ' && !symbols.includes(noPrefixMessage.charAt(0))) return;
|
||||
}
|
||||
|
||||
if(noPrefixMessage === '') return;
|
||||
if(noPrefixMessage.charAt(0) === ' ') {
|
||||
if (noPrefixMessage === '') return;
|
||||
if (noPrefixMessage.charAt(0) === ' ') {
|
||||
/*if(symbols.includes(GuildCache.prefix.charAt(GuildCache.prefix.length - 1))) {
|
||||
if((GuildCache.prefix.indexOf(' ') >= 0)) return;
|
||||
}*/
|
||||
|
||||
|
||||
noPrefixMessage = noPrefixMessage.substring(1);
|
||||
}
|
||||
|
||||
let args = noPrefixMessage.split(" ");
|
||||
args = args.filter(e => e !== '');
|
||||
let args = noPrefixMessage.split(' ');
|
||||
args = args.filter((e) => e !== '');
|
||||
|
||||
let command = args[0];
|
||||
|
||||
args.shift();
|
||||
|
||||
if(args.length === 0)
|
||||
args = [];
|
||||
if (args.length === 0) args = [];
|
||||
|
||||
for(const module of this.loaded_module) {
|
||||
for (const module of this.loaded_module) {
|
||||
let thisModule: DiscordModule = module[1];
|
||||
thisModule.GuildOnCommand(command, args, message);
|
||||
|
||||
if(thisModule.commands && thisModule.commands.includes(command))
|
||||
|
||||
if (thisModule.commands && thisModule.commands.includes(command))
|
||||
thisModule.GuildOnModuleCommand(args, message);
|
||||
}
|
||||
});
|
||||
|
||||
// Handling mentions
|
||||
this.client.on("messageCreate", async (message: Message) => {
|
||||
|
||||
this.client.on('messageCreate', async (message: Message) => {
|
||||
// TODO: Handle DMs commands soon
|
||||
if(!(message.channel instanceof TextChannel)) return;
|
||||
if(message.author.bot) return;
|
||||
if (!(message.channel instanceof TextChannel)) return;
|
||||
if (message.author.bot) return;
|
||||
|
||||
if(typeof message.guild?.id === 'undefined') return;
|
||||
if(!message.mentions.users) return;
|
||||
if (typeof message.guild?.id === 'undefined') return;
|
||||
if (!message.mentions.users) return;
|
||||
|
||||
if(message.mentions.users.first()?.id !== this.client.user?.id) return;
|
||||
if(!message.content.startsWith(`<@!${this.client.user?.id}>`)) return;
|
||||
if (message.mentions.users.first()?.id !== this.client.user?.id) return;
|
||||
if (!message.content.startsWith(`<@!${this.client.user?.id}>`)) return;
|
||||
|
||||
let args = message.content.split(" ");
|
||||
let args = message.content.split(' ');
|
||||
let command = args[1];
|
||||
|
||||
args.shift();
|
||||
args.shift();
|
||||
args = args.filter(e => e !== '');
|
||||
args = args.filter((e) => e !== '');
|
||||
|
||||
if(args.length === 0)
|
||||
args = [];
|
||||
if (args.length === 0) args = [];
|
||||
|
||||
for(const module of this.loaded_module) {
|
||||
for (const module of this.loaded_module) {
|
||||
let thisModule: DiscordModule = module[1];
|
||||
thisModule.GuildOnCommand(command, args, message);
|
||||
|
||||
if(thisModule.commands && thisModule.commands.includes(command))
|
||||
|
||||
if (thisModule.commands && thisModule.commands.includes(command))
|
||||
thisModule.GuildOnModuleCommand(args, message);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new Discord();
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
import playdl, { YouTubeVideo } from "play-dl";
|
||||
import { VoiceChannel, Snowflake, TextChannel, StageChannel, Guild } from "discord.js";
|
||||
import { AudioPlayer, VoiceConnection, createAudioPlayer, joinVoiceChannel, createAudioResource, VoiceConnectionStatus, AudioPlayerStatus, AudioPlayerState, NoSubscriberBehavior, VoiceConnectionState, AudioPlayerError, DiscordGatewayAdapterCreator } from "@discordjs/voice";
|
||||
import { EventEmitter } from "stream";
|
||||
import { VoiceChannel, Snowflake, TextChannel, StageChannel, Guild } from 'discord.js';
|
||||
import {
|
||||
AudioPlayer,
|
||||
VoiceConnection,
|
||||
createAudioPlayer,
|
||||
joinVoiceChannel,
|
||||
createAudioResource,
|
||||
VoiceConnectionStatus,
|
||||
AudioPlayerStatus,
|
||||
AudioPlayerState,
|
||||
NoSubscriberBehavior,
|
||||
VoiceConnectionState,
|
||||
AudioPlayerError,
|
||||
DiscordGatewayAdapterCreator
|
||||
} from '@discordjs/voice';
|
||||
import playdl, { YouTubeVideo } from 'play-dl';
|
||||
import { EventEmitter } from 'stream';
|
||||
|
||||
import DiscordProvider from "./Discord";
|
||||
import Environment from "./Environment";
|
||||
import DiscordProvider from './Discord';
|
||||
import Environment from './Environment';
|
||||
|
||||
export type ValidTracks = YouTubeVideo;
|
||||
|
||||
@@ -13,10 +26,10 @@ if (Environment.get().YOUTUBE_COOKIE_BASE64) {
|
||||
youtube: {
|
||||
cookie: Buffer.from(Environment.get().YOUTUBE_COOKIE_BASE64, 'base64').toString()
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
export class Queue {
|
||||
public track: (ValidTracks)[] = [];
|
||||
public track: ValidTracks[] = [];
|
||||
}
|
||||
|
||||
export class YouTubeLink {
|
||||
@@ -56,14 +69,14 @@ export class VoiceDisconnectedEvent {
|
||||
}
|
||||
|
||||
export enum DiscordMusicPlayerLoopMode {
|
||||
None = "none",
|
||||
Current = "current"
|
||||
None = 'none',
|
||||
Current = 'current'
|
||||
}
|
||||
export class DiscordMusicPlayerInstance {
|
||||
public queue: Queue;
|
||||
public player: AudioPlayer;
|
||||
public textChannel?: TextChannel;
|
||||
public voiceChannel: (VoiceChannel | StageChannel);
|
||||
public voiceChannel: VoiceChannel | StageChannel;
|
||||
public voiceConnection?: VoiceConnection;
|
||||
public previousTrack?: ValidTracks;
|
||||
|
||||
@@ -72,7 +85,7 @@ export class DiscordMusicPlayerInstance {
|
||||
|
||||
public readonly events: EventEmitter;
|
||||
|
||||
constructor({ voiceChannel }: { voiceChannel: (VoiceChannel | StageChannel) }) {
|
||||
constructor({ voiceChannel }: { voiceChannel: VoiceChannel | StageChannel }) {
|
||||
this.queue = new Queue();
|
||||
this.player = createAudioPlayer({
|
||||
behaviors: {
|
||||
@@ -85,8 +98,10 @@ export class DiscordMusicPlayerInstance {
|
||||
|
||||
this.player.on(AudioPlayerStatus.Idle, async (oldStage, newStage) => {
|
||||
//The player stopped
|
||||
if (newStage.status === AudioPlayerStatus.Idle && oldStage.status !== AudioPlayerStatus.Idle) {
|
||||
|
||||
if (
|
||||
newStage.status === AudioPlayerStatus.Idle &&
|
||||
oldStage.status !== AudioPlayerStatus.Idle
|
||||
) {
|
||||
// Loop mode is set to current song
|
||||
if (this.loopMode === DiscordMusicPlayerLoopMode.Current) {
|
||||
if (this.queue.track.length !== 0) {
|
||||
@@ -99,14 +114,12 @@ export class DiscordMusicPlayerInstance {
|
||||
// There are more songs in the queue, remove finished song and play the next one
|
||||
if (this.queue.track.length !== 0) {
|
||||
let previousTrack = this.queue.track.shift();
|
||||
if(previousTrack)
|
||||
this.previousTrack = previousTrack;
|
||||
if (previousTrack) this.previousTrack = previousTrack;
|
||||
|
||||
if (this.queue.track.length > 0) {
|
||||
this.playTrack(this.queue.track[0]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
@@ -133,40 +146,49 @@ export class DiscordMusicPlayerInstance {
|
||||
return true;
|
||||
}
|
||||
|
||||
public joinVoiceChannel(voiceChannel: (VoiceChannel | StageChannel), textChannel?: TextChannel) {
|
||||
public joinVoiceChannel(voiceChannel: VoiceChannel | StageChannel, textChannel?: TextChannel) {
|
||||
const permissions = voiceChannel.permissionsFor(DiscordProvider.client.user!);
|
||||
|
||||
if (!permissions || !voiceChannel.joinable || !permissions.has("CONNECT"))
|
||||
throw new Error("No permissions");
|
||||
if (!permissions || !voiceChannel.joinable || !permissions.has('CONNECT'))
|
||||
throw new Error('No permissions');
|
||||
|
||||
if (textChannel)
|
||||
this.textChannel = textChannel;
|
||||
if (textChannel) this.textChannel = textChannel;
|
||||
|
||||
this.voiceConnection = joinVoiceChannel({
|
||||
channelId: this.voiceChannel.id,
|
||||
guildId: this.voiceChannel.guild.id,
|
||||
adapterCreator: this.voiceChannel.guild.voiceAdapterCreator as DiscordGatewayAdapterCreator
|
||||
adapterCreator: this.voiceChannel.guild
|
||||
.voiceAdapterCreator as DiscordGatewayAdapterCreator
|
||||
});
|
||||
|
||||
this.voiceConnection.on(VoiceConnectionStatus.Ready, (oldState: VoiceConnectionState, newState: VoiceConnectionState) => {
|
||||
let currentVC = DiscordProvider.client.guilds.cache.get(voiceChannel.guild.id)?.me?.voice.channel;
|
||||
if (currentVC && currentVC.id !== this.voiceChannel.id) {
|
||||
this.voiceChannel = currentVC;
|
||||
}
|
||||
});
|
||||
|
||||
this.voiceConnection.on(VoiceConnectionStatus.Disconnected, (oldState: VoiceConnectionState, newState: VoiceConnectionState) => {
|
||||
setTimeout(() => {
|
||||
if (!DiscordProvider.client.guilds.cache.get(voiceChannel.guildId!)!.me!.voice.channelId) {
|
||||
this.events.emit('disconnect', new VoiceDisconnectedEvent(this));
|
||||
this.voiceConnection.on(
|
||||
VoiceConnectionStatus.Ready,
|
||||
(oldState: VoiceConnectionState, newState: VoiceConnectionState) => {
|
||||
let currentVC = DiscordProvider.client.guilds.cache.get(voiceChannel.guild.id)?.me
|
||||
?.voice.channel;
|
||||
if (currentVC && currentVC.id !== this.voiceChannel.id) {
|
||||
this.voiceChannel = currentVC;
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
this.voiceConnection.on(
|
||||
VoiceConnectionStatus.Disconnected,
|
||||
(oldState: VoiceConnectionState, newState: VoiceConnectionState) => {
|
||||
setTimeout(() => {
|
||||
if (
|
||||
!DiscordProvider.client.guilds.cache.get(voiceChannel.guildId!)!.me!.voice
|
||||
.channelId
|
||||
) {
|
||||
this.events.emit('disconnect', new VoiceDisconnectedEvent(this));
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public async leaveVoiceChannel() {
|
||||
if (this.player)
|
||||
this.player.pause();
|
||||
if (this.player) this.player.pause();
|
||||
|
||||
if (DiscordProvider.client.guilds.cache.get(this.voiceChannel.guild.id)?.me?.voice) {
|
||||
this.voiceConnection?.disconnect();
|
||||
@@ -174,18 +196,18 @@ export class DiscordMusicPlayerInstance {
|
||||
}
|
||||
|
||||
public async pausePlayer() {
|
||||
if(this.paused || !this.player) return;
|
||||
if(!this.player.pause(true)) throw new Error('Unable to pause player');
|
||||
if (this.paused || !this.player) return;
|
||||
if (!this.player.pause(true)) throw new Error('Unable to pause player');
|
||||
this.paused = true;
|
||||
}
|
||||
|
||||
public async resumePlayer() {
|
||||
if(!this.paused || !this.player) return;
|
||||
if(!this.player.unpause()) throw new Error('Unable to resume player');
|
||||
if (!this.paused || !this.player) return;
|
||||
if (!this.player.unpause()) throw new Error('Unable to resume player');
|
||||
this.paused = false;
|
||||
}
|
||||
|
||||
public addTrackToQueue(track: (ValidTracks)) {
|
||||
public addTrackToQueue(track: ValidTracks) {
|
||||
if (this.queue.track.length === 0) {
|
||||
this.queue.track.push(track);
|
||||
this.playTrack(this.queue.track[0]);
|
||||
@@ -196,16 +218,16 @@ export class DiscordMusicPlayerInstance {
|
||||
}
|
||||
|
||||
public async playTrack(track: ValidTracks) {
|
||||
if (!this.voiceConnection) throw new Error("No voice connection");
|
||||
|
||||
if (!this.voiceConnection) throw new Error('No voice connection');
|
||||
|
||||
try {
|
||||
const stream = await playdl.stream(track.url);
|
||||
const resource = createAudioResource(stream.stream, {
|
||||
inputType: stream.type
|
||||
});
|
||||
|
||||
this.player.play(resource)
|
||||
this.voiceConnection.subscribe(this.player)
|
||||
|
||||
this.player.play(resource);
|
||||
this.voiceConnection.subscribe(this.player);
|
||||
} catch (error: any) {
|
||||
this.events.emit('error', new PlayerErrorEvent(this, error));
|
||||
this.skipTrack();
|
||||
@@ -213,14 +235,13 @@ export class DiscordMusicPlayerInstance {
|
||||
}
|
||||
|
||||
public async skipTrack() {
|
||||
if (!this.voiceConnection) throw new Error("No voice connection");
|
||||
if (!this.voiceConnection) throw new Error('No voice connection');
|
||||
|
||||
if (this.queue.track.length > 1) {
|
||||
this.previousTrack = this.queue.track[0];
|
||||
this.queue.track.shift();
|
||||
this.playTrack(this.queue.track[0]);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.previousTrack = this.queue.track[0];
|
||||
this.queue.track.shift();
|
||||
this.player.stop();
|
||||
@@ -248,8 +269,7 @@ export class DiscordMusicPlayerInstance {
|
||||
|
||||
if (this.voiceConnection) {
|
||||
this.voiceConnection.removeAllListeners();
|
||||
if (this.voiceConnection.state.status !== 'destroyed')
|
||||
this.voiceConnection.destroy();
|
||||
if (this.voiceConnection.state.status !== 'destroyed') this.voiceConnection.destroy();
|
||||
}
|
||||
|
||||
if (this.player) {
|
||||
@@ -266,16 +286,17 @@ export class DiscordMusicPlayerInstance {
|
||||
const stream = 'https://fakestream:42069/fake/stream/fake/audio/fake.mp3';
|
||||
const resource = createAudioResource(stream);
|
||||
this.player.play(resource);
|
||||
this.player.emit('error', new AudioPlayerError(new Error("Music player was manually crashed"), null!));
|
||||
this.player.emit(
|
||||
'error',
|
||||
new AudioPlayerError(new Error('Music player was manually crashed'), null!)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DiscordMusicPlayer {
|
||||
|
||||
public GuildQueue = new Map();
|
||||
|
||||
public getGuildInstance(guildId: Snowflake): (DiscordMusicPlayerInstance | null) {
|
||||
public getGuildInstance(guildId: Snowflake): DiscordMusicPlayerInstance | null {
|
||||
if (!this.isGuildInstanceExists(guildId)) return null;
|
||||
return this.GuildQueue.get(guildId);
|
||||
}
|
||||
@@ -284,12 +305,11 @@ class DiscordMusicPlayer {
|
||||
return this.GuildQueue.has(guildId);
|
||||
}
|
||||
|
||||
public createGuildInstance(guildId: Snowflake, voiceChannel: (VoiceChannel | StageChannel)) {
|
||||
public createGuildInstance(guildId: Snowflake, voiceChannel: VoiceChannel | StageChannel) {
|
||||
this.GuildQueue.set(guildId, new DiscordMusicPlayerInstance({ voiceChannel }));
|
||||
}
|
||||
|
||||
public async destoryGuildInstance(guild: (Guild | Snowflake)) {
|
||||
|
||||
public async destoryGuildInstance(guild: Guild | Snowflake) {
|
||||
let guildId: Snowflake = guild instanceof Guild ? guild.id : guild;
|
||||
|
||||
if (this.isGuildInstanceExists(guildId)) {
|
||||
@@ -299,39 +319,48 @@ class DiscordMusicPlayer {
|
||||
}
|
||||
|
||||
public async searchYouTubeByQuery(query: string) {
|
||||
const searched: YouTubeVideo[] = await playdl.search(query, { source: { youtube: "video" } });
|
||||
const searched: YouTubeVideo[] = await playdl.search(query, {
|
||||
source: { youtube: 'video' }
|
||||
});
|
||||
if (searched.length == 0) return null;
|
||||
return searched;
|
||||
}
|
||||
|
||||
public async searchYouTubeByYouTubeLink(youtubeLink: YouTubeLink) {
|
||||
|
||||
// Search the url
|
||||
const searched: YouTubeVideo[] = await playdl.search("https://www.youtube.com/watch?v=" + youtubeLink.videoId, { source: { youtube: "video" } });
|
||||
const searched: YouTubeVideo[] = await playdl.search(
|
||||
'https://www.youtube.com/watch?v=' + youtubeLink.videoId,
|
||||
{ source: { youtube: 'video' } }
|
||||
);
|
||||
for (let video of searched) {
|
||||
if (video.id === youtubeLink.videoId)
|
||||
return video;
|
||||
if (video.id === youtubeLink.videoId) return video;
|
||||
}
|
||||
|
||||
// Serch the video Id
|
||||
const searched2: YouTubeVideo[] = await playdl.search(youtubeLink.videoId, { source: { youtube: "video" } });
|
||||
const searched2: YouTubeVideo[] = await playdl.search(youtubeLink.videoId, {
|
||||
source: { youtube: 'video' }
|
||||
});
|
||||
for (let video of searched2) {
|
||||
if (video.id === youtubeLink.videoId)
|
||||
return video;
|
||||
if (video.id === youtubeLink.videoId) return video;
|
||||
}
|
||||
|
||||
// Last resort, search the title
|
||||
const videoInfo = await playdl.video_basic_info("https://www.youtube.com/watch?v=" + youtubeLink.videoId);
|
||||
const videoInfo = await playdl.video_basic_info(
|
||||
'https://www.youtube.com/watch?v=' + youtubeLink.videoId
|
||||
);
|
||||
if (videoInfo?.video_details?.title) {
|
||||
const searched: YouTubeVideo[] = await playdl.search(videoInfo.video_details.title, { source: { youtube: "video" } });
|
||||
const searched: YouTubeVideo[] = await playdl.search(videoInfo.video_details.title, {
|
||||
source: { youtube: 'video' }
|
||||
});
|
||||
for (let video of searched) {
|
||||
if (video.id === youtubeLink.videoId)
|
||||
return video;
|
||||
if (video.id === youtubeLink.videoId) return video;
|
||||
}
|
||||
}
|
||||
|
||||
let yt_info = await playdl.video_info("https://www.youtube.com/watch?v=" + youtubeLink.videoId);
|
||||
if(yt_info) {
|
||||
let yt_info = await playdl.video_info(
|
||||
'https://www.youtube.com/watch?v=' + youtubeLink.videoId
|
||||
);
|
||||
if (yt_info) {
|
||||
return new YouTubeVideo({
|
||||
id: yt_info.video_details.id,
|
||||
url: yt_info.video_details.url,
|
||||
@@ -341,7 +370,7 @@ class DiscordMusicPlayer {
|
||||
durationRaw: yt_info.video_details.durationRaw,
|
||||
durationInSec: yt_info.video_details.durationInSec,
|
||||
uploadedAt: yt_info.video_details.uploadedAt,
|
||||
upcoming: yt_info.video_details.upcoming,
|
||||
upcoming: yt_info.video_details.upcoming,
|
||||
views: yt_info.video_details.views,
|
||||
thumbnails: yt_info.video_details.thumbnails,
|
||||
channel: yt_info.video_details.channel,
|
||||
@@ -374,16 +403,17 @@ class DiscordMusicPlayer {
|
||||
}
|
||||
|
||||
public parseYouTubeLink(query: string): YouTubeLink {
|
||||
if (query.startsWith('https://www.youtube.com/watch?v=') || query.startsWith('http://www.youtube.com/watch?v=')) {
|
||||
if (
|
||||
query.startsWith('https://www.youtube.com/watch?v=') ||
|
||||
query.startsWith('http://www.youtube.com/watch?v=')
|
||||
) {
|
||||
let data = this.parseURLQuery(query);
|
||||
if (!data.v)
|
||||
throw new Error('YouTube link is invalid');
|
||||
if (!data.v) throw new Error('YouTube link is invalid');
|
||||
return {
|
||||
videoId: data.v,
|
||||
list: (data.list ? (data.list !== "RDMM" ? data.list : undefined) : undefined)
|
||||
}
|
||||
}
|
||||
else if (query.startsWith('https://youtu.be/') || query.startsWith('http://youtu.be/')) {
|
||||
list: data.list ? (data.list !== 'RDMM' ? data.list : undefined) : undefined
|
||||
};
|
||||
} else if (query.startsWith('https://youtu.be/') || query.startsWith('http://youtu.be/')) {
|
||||
//Get youtube video id after the url
|
||||
let videoId = query.split('/')[3];
|
||||
|
||||
@@ -392,16 +422,17 @@ class DiscordMusicPlayer {
|
||||
|
||||
return {
|
||||
videoId: videoId
|
||||
}
|
||||
}
|
||||
else if (query.startsWith('https://www.youtube.com/playlist?list=') || query.startsWith('http://www.youtube.com/playlist?list=')) {
|
||||
};
|
||||
} else if (
|
||||
query.startsWith('https://www.youtube.com/playlist?list=') ||
|
||||
query.startsWith('http://www.youtube.com/playlist?list=')
|
||||
) {
|
||||
let listId = query.split('?list=')[1];
|
||||
return {
|
||||
videoId: "",
|
||||
videoId: '',
|
||||
list: listId
|
||||
}
|
||||
}
|
||||
else {
|
||||
};
|
||||
} else {
|
||||
throw new Error('YouTube link is invalid');
|
||||
}
|
||||
}
|
||||
@@ -418,8 +449,7 @@ class DiscordMusicPlayer {
|
||||
}
|
||||
return queryObject;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const DiscordMusicPlayer_Instance = new DiscordMusicPlayer();
|
||||
export default DiscordMusicPlayer_Instance;
|
||||
export default DiscordMusicPlayer_Instance;
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
import * as path from "path";
|
||||
import * as dotenv from "dotenv";
|
||||
import * as path from 'path';
|
||||
import * as dotenv from 'dotenv';
|
||||
|
||||
import Logger from "../libs/Logger";
|
||||
import Logger from '../libs/Logger';
|
||||
|
||||
const requiredENV = [
|
||||
'NODE_ENV',
|
||||
'DATABASE_URL',
|
||||
'DISCORD_TOKEN',
|
||||
'PRIVATE_BOT',
|
||||
'OSU_API_KEY'
|
||||
];
|
||||
const requiredENV = ['NODE_ENV', 'DATABASE_URL', 'DISCORD_TOKEN', 'PRIVATE_BOT', 'OSU_API_KEY'];
|
||||
|
||||
class Environment {
|
||||
|
||||
public init(): void {
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../.env") });
|
||||
dotenv.config({ path: path.resolve(__dirname, '../../.env') });
|
||||
|
||||
for (let param of requiredENV) {
|
||||
if (this.isUndefinedOrEmpty(process.env[param]))
|
||||
@@ -22,7 +15,7 @@ class Environment {
|
||||
}
|
||||
|
||||
// NODE_ENV Checks
|
||||
if (this.get().NODE_ENV != "production" && this.get().NODE_ENV != "development")
|
||||
if (this.get().NODE_ENV != 'production' && this.get().NODE_ENV != 'development')
|
||||
throw new Error('.env NODE_ENV must be either "production" or "development"');
|
||||
|
||||
// TODO: Discord token check
|
||||
@@ -31,7 +24,6 @@ class Environment {
|
||||
}
|
||||
|
||||
public get(): any {
|
||||
|
||||
const NODE_ENV = process.env.NODE_ENV;
|
||||
|
||||
const DISCORD_TOKEN = process.env.DISCORD_TOKEN;
|
||||
@@ -40,7 +32,7 @@ class Environment {
|
||||
|
||||
const OSU_API_KEY = process.env.OSU_API_KEY;
|
||||
const YOUTUBE_COOKIE_BASE64 = process.env.YOUTUBE_COOKIE_BASE64;
|
||||
|
||||
|
||||
return {
|
||||
NODE_ENV,
|
||||
|
||||
@@ -54,18 +46,14 @@ class Environment {
|
||||
}
|
||||
|
||||
private isUndefinedOrEmpty(value: String | undefined): boolean {
|
||||
if(typeof value === 'undefined')
|
||||
return true;
|
||||
if (typeof value === 'undefined') return true;
|
||||
|
||||
if(value === undefined)
|
||||
return true;
|
||||
if (value === undefined) return true;
|
||||
|
||||
if(value === '')
|
||||
return true;
|
||||
if (value === '') return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new Environment();
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
class Prisma {
|
||||
|
||||
public client: PrismaClient;
|
||||
|
||||
constructor () {
|
||||
this.client = new PrismaClient;
|
||||
constructor() {
|
||||
this.client = new PrismaClient();
|
||||
}
|
||||
|
||||
public init(): void {
|
||||
@@ -15,7 +14,6 @@ class Prisma {
|
||||
public end(): void {
|
||||
this.client.$disconnect();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new Prisma();
|
||||
export default new Prisma();
|
||||
|
||||
@@ -2,10 +2,9 @@ import { Api } from 'node-osu';
|
||||
import Environment from './Environment';
|
||||
|
||||
class osuAPI {
|
||||
|
||||
public client: Api;
|
||||
|
||||
constructor () {
|
||||
constructor() {
|
||||
this.client = new Api(Environment.get().OSU_API_KEY, {
|
||||
notFoundAsError: false,
|
||||
completeScores: true,
|
||||
@@ -21,9 +20,7 @@ class osuAPI {
|
||||
});
|
||||
}
|
||||
|
||||
public end(): void {
|
||||
}
|
||||
|
||||
public end(): void {}
|
||||
}
|
||||
|
||||
export default new osuAPI();
|
||||
export default new osuAPI();
|
||||
|
||||
Reference in New Issue
Block a user