From b4d2a93a984f8224f09206c0da6d3547e8a83a55 Mon Sep 17 00:00:00 2001 From: Mint <117024989+LovelyMint@users.noreply.github.com> Date: Sun, 13 Nov 2022 19:01:03 +0900 Subject: [PATCH 1/6] More env checks --- src/providers/Environment.ts | 38 ++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/providers/Environment.ts b/src/providers/Environment.ts index a2e041a..e29a957 100644 --- a/src/providers/Environment.ts +++ b/src/providers/Environment.ts @@ -1,24 +1,54 @@ import * as path from 'path'; import * as dotenv from 'dotenv'; +import validator from 'validator'; 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']; class Environment { public init(): void { dotenv.config({ path: path.resolve(__dirname, '../../.env') }); for (let param of requiredENV) { - if (this.isUndefinedOrEmpty(process.env[param])) - throw new Error(`.env ${param} is undefined`); + if (this.isUndefinedOrEmpty(process.env[param])) throw new Error(`.env ${param} is undefined`); } // NODE_ENV Checks 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 + // DISCORD_TOKEN Checks + if (this.get().DISCORD_TOKEN.length != 59) + throw new Error('.env DISCORD_TOKEN is not a valid discord bot token'); + + // DEVELOPER_IDS Checks + if (this.get().DEVELOPER_IDS) { + let developerIds = this.get().DEVELOPER_IDS.split(','); + for (let id of developerIds) { + if (isNaN(parseInt(id))) throw new Error(`.env DEVELOPER_IDS contains an invalid id: ${id}`); + } + } + + // PRIVATE_BOT Checks + if (this.get().PRIVATE_BOT.toLowerCase() != 'true' && this.get().PRIVATE_BOT.toLowerCase() != 'false') + throw new Error('.env PRIVATE_BOT must be either "true" or "false"'); + + // SUPPORT_URL Checks + if (this.get().SUPPORT_URL && !validator.isURL(this.get().SUPPORT_URL)) + throw new Error('.env SUPPORT_URL is not a valid URL'); + + // WEB_HOST Checks + if (this.get().WEB_HOST && this.get().WEB_HOST.toLowerCase() != 'localhost' && !validator.isIP(this.get().WEB_HOST)) + throw new Error('.env WEB_HOST must be either localhost or a valid IP address'); + + // WEB_PORT Checks + if (this.get().WEB_PORT && !validator.isPort(this.get().WEB_PORT)) + throw new Error('.env WEB_PORT must be a valid port number'); + + // YOUTUBE_COOKIE_BASE64 Checks + if (this.get().YOUTUBE_COOKIE_BASE64 && !validator.isBase64(this.get().YOUTUBE_COOKIE_BASE64)) + throw new Error('.env YOUTUBE_COOKIE_BASE64 must be a valid base64 string'); Logger.log('info', `Running in ${process.env.NODE_ENV} environment`); } From 9c9434f4baecc2201bc11251b40a3f0e57ba9dc5 Mon Sep 17 00:00:00 2001 From: Mint <117024989+LovelyMint@users.noreply.github.com> Date: Sun, 13 Nov 2022 19:01:29 +0900 Subject: [PATCH 2/6] Don't start web server if env is not there --- src/providers/App.ts | 4 ++++ src/providers/Express.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/providers/App.ts b/src/providers/App.ts index 4959e06..b39d96f 100644 --- a/src/providers/App.ts +++ b/src/providers/App.ts @@ -44,6 +44,10 @@ class App { } public loadExpress(): void { + if (!Environment.get().WEB_HOST || !Environment.get().WEB_PORT) { + Logger.log('warn', 'WEB_HOST or WEB_PORT is not defined in .env, not starting web server'); + return; + } Logger.log('info', 'Loading Express'); Express.init(); } diff --git a/src/providers/Express.ts b/src/providers/Express.ts index 70157bb..aea9bfd 100644 --- a/src/providers/Express.ts +++ b/src/providers/Express.ts @@ -30,7 +30,7 @@ class Express { } public end(): void { - if(this.server != null) + if(this.server) this.server.close(); } From 306dafc75acf4a13f7e143fb406476dbbea7c14b Mon Sep 17 00:00:00 2001 From: Mint <117024989+LovelyMint@users.noreply.github.com> Date: Sun, 13 Nov 2022 19:01:50 +0900 Subject: [PATCH 3/6] Fixed env not working with non lowercase value --- src/discord/Invite.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/discord/Invite.ts b/src/discord/Invite.ts index 513352d..a74bbf9 100644 --- a/src/discord/Invite.ts +++ b/src/discord/Invite.ts @@ -13,8 +13,11 @@ const EMBEDS = { INVITE_INFO: (data: HybridInteractionMessage, locale: I18n) => { const user = data.getUser(); let message; - if (!(Environment.get().PRIVATE_BOT === 'true') || user != null && Users.isDeveloper(user.id)) - message = locale.__('invite.info', { BOT_NAME: DiscordProvider.client.user!.username, LINK: `https://discord.com/api/oauth2/authorize?client_id=${DiscordProvider.client.user?.id}&permissions=8&scope=bot%20applications.commands`}); + if (!(Environment.get().PRIVATE_BOT.toLowerCase() === 'true') || (user != null && Users.isDeveloper(user.id))) + message = locale.__('invite.info', { + BOT_NAME: DiscordProvider.client.user!.username, + LINK: `https://discord.com/api/oauth2/authorize?client_id=${DiscordProvider.client.user?.id}&permissions=8&scope=bot%20applications.commands` + }); return makeInfoEmbed({ title: `Invite`, From 7d1c37f444404983d2f71af04b9406a7b535df26 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 13 Nov 2022 17:24:19 +0700 Subject: [PATCH 4/6] Handle if developers id is not defined --- src/services/Users.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/services/Users.ts b/src/services/Users.ts index fe1dddd..e6e3de0 100644 --- a/src/services/Users.ts +++ b/src/services/Users.ts @@ -3,8 +3,11 @@ import Environment from '../providers/Environment'; class Users { public static isDeveloper(user: User | Snowflake) { - const developers: Snowflake[] = Environment.get().DEVELOPER_IDS.split(','); + if(!Environment.get().DEVELOPERS) + return false; + + const developers: Snowflake[] = Environment.get().DEVELOPER_IDS.split(','); let userID; if (user instanceof User) userID = user.id; From b88ead6d938d967c7837035a033ddb85740b1da2 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 13 Nov 2022 17:27:04 +0700 Subject: [PATCH 5/6] Update README.md --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3bf4cf7..389be20 100644 --- a/README.md +++ b/README.md @@ -44,15 +44,15 @@ Take a look inside [.env.example](https://github.com/kirameki-cafe/Yumi/blob/mai - ``NODE_ENV`` Environment type, ``development`` or ``production`` - ``DATABASE_URL`` Connection URL to your database. [More info](https://www.prisma.io/docs/getting-started/setup-prisma/start-from-scratch/relational-databases/connect-your-database-typescript-postgres) - ``DISCORD_TOKEN`` Your discord bot token -- ``DEVELOPER_IDS`` List of Discord Snowflake User ID that can access developer only modules, separated by ``,`` +- ``DEVELOPER_IDS`` (Optional) List of Discord Snowflake User ID that can access developer only modules, separated by ``,`` - ``PRIVATE_BOT`` Should the bot invite be public? ``true`` or ``false`` - ``SUPPORT_URL`` (Optional) URL to your support server (If not set, support module will show not available message) -- ``OSU_API_KEY`` [osu! API v1 key](https://github.com/ppy/osu-api/wiki) -- ``YOUTUBE_COOKIE_BASE64`` YouTube cookies encoded in base64 [How to get cookies?](https://github.com/play-dl/play-dl/tree/main/instructions#youtube-cookies=) * -- ``SPOTIFY_CLIENT_ID`` Spotify client ID -- ``SPOTIFY_CLIENT_SECRET`` Spotify client secret -- ``SPOTIFY_REFRESH_TOKEN`` Spotify refresh token ** -- ``SPOTIFY_CLIENT_MARKET`` Spotify market country code +- ``OSU_API_KEY`` (Optional) [osu! API v1 key](https://github.com/ppy/osu-api/wiki) +- ``YOUTUBE_COOKIE_BASE64`` (Optional) YouTube cookies encoded in base64 [How to get cookies?](https://github.com/play-dl/play-dl/tree/main/instructions#youtube-cookies=) * +- ``SPOTIFY_CLIENT_ID`` (Optional) Spotify client ID +- ``SPOTIFY_CLIENT_SECRET`` (Optional) Spotify client secret +- ``SPOTIFY_REFRESH_TOKEN`` (Optional) Spotify refresh token ** +- ``SPOTIFY_CLIENT_MARKET`` (Optional) Spotify market country code \* Encode the cookies from the request headers base64 and put it in here instead of creating new file with ``play.authorization();`` code From 684b64985f83e49f47a05b44a2362bb19fadc0b9 Mon Sep 17 00:00:00 2001 From: Mint <117024989+LovelyMint@users.noreply.github.com> Date: Sun, 13 Nov 2022 19:30:55 +0900 Subject: [PATCH 6/6] Handle osu! command when the module is not initialized --- src/discord/osu.ts | 64 +++++++++++++++++------------------------ src/providers/App.ts | 4 +++ src/providers/osuAPI.ts | 8 ++---- 3 files changed, 32 insertions(+), 44 deletions(-) diff --git a/src/discord/osu.ts b/src/discord/osu.ts index bf0623a..3782c62 100644 --- a/src/discord/osu.ts +++ b/src/discord/osu.ts @@ -73,6 +73,12 @@ const EMBEDS = { title: `No osu! beatmap id provided`, user: data.getUser() }); + }, + NOT_INITIALIZED: (data: HybridInteractionMessage) => { + return makeErrorEmbed({ + title: `osu! feature is disabled`, + user: data.getUser() + }); } }; @@ -93,6 +99,12 @@ export default class osu extends DiscordModule { const Guild = await Prisma.client.guild.findFirst({ where: { id: data.getGuild()!.id } }); if (!Guild) return; + + if (!osuAPI.client) + return await sendHybridInteractionMessageResponse(data, { + embeds: [EMBEDS.NOT_INITIALIZED(data)] + }); + const funct = { user: async (data: HybridInteractionMessage) => { let user; @@ -111,7 +123,7 @@ export default class osu extends DiscordModule { embeds: [EMBEDS.INVALID_USER_MENTIONED(data)] }); - let result = await osuAPI.client.getUser({ u: user }); + let result = await osuAPI.client!.getUser({ u: user }); if (result instanceof Array && result.length === 0) return await sendHybridInteractionMessageResponse(data, { @@ -123,12 +135,8 @@ export default class osu extends DiscordModule { } const level = { - number: (Math.round((result.level + Number.EPSILON) * 100) / 100) - .toFixed(2) - .split('.')[0], - progression: (Math.round((result.level + Number.EPSILON) * 100) / 100) - .toFixed(2) - .split('.')[1] + number: (Math.round((result.level + Number.EPSILON) * 100) / 100).toFixed(2).split('.')[0], + progression: (Math.round((result.level + Number.EPSILON) * 100) / 100).toFixed(2).split('.')[1] }; const embed = makeInfoEmbed({ icon: '', @@ -142,10 +150,7 @@ export default class osu extends DiscordModule { )}**, totaling in **${this.numberWithCommas( parseFloat( ( - Math.round( - (result.secondsPlayed / (60 * 60) + Number.EPSILON) * - 100 - ) / 100 + Math.round((result.secondsPlayed / (60 * 60) + Number.EPSILON) * 100) / 100 ).toFixed(2) ) )} ${result.secondsPlayed < 60 ? 'hour' : 'hours'}** of songs played @@ -236,9 +241,7 @@ export default class osu extends DiscordModule { name: `❤ Account Information`, value: `Joined: , ` + )}:R>, ` } //, /*{ name: `💌 Recent Events (Coming soon)`, @@ -255,8 +258,7 @@ export default class osu extends DiscordModule { // TODO: Fix for ppl with no image embed.setThumbnail( - `https://a.ppy.sh/${result.id}` || - 'https://osu.ppy.sh/images/layout/avatar-guest.png' + `https://a.ppy.sh/${result.id}` || 'https://osu.ppy.sh/images/layout/avatar-guest.png' ); const row = new ActionRowBuilder().addComponents([ @@ -289,7 +291,7 @@ export default class osu extends DiscordModule { embeds: [EMBEDS.INVALID_BEATMAP_ID_MENTIONED(data)] }); - let result = await osuAPI.client.getBeatmaps({ b: beatmap }); + let result = await osuAPI.client!.getBeatmaps({ b: beatmap }); if (result instanceof Array && result.length === 0) return await sendHybridInteractionMessageResponse(data, { @@ -331,17 +333,13 @@ export default class osu extends DiscordModule { { name: '⭐ Star Difficulty', value: `**${( - Math.round((bm_result.difficulty.rating + Number.EPSILON) * 100) / - 100 + Math.round((bm_result.difficulty.rating + Number.EPSILON) * 100) / 100 ).toFixed(2)}**`, inline: true }, { name: `⌛ Length`, - value: `**${( - Math.round((bm_result.length.total / 60 + Number.EPSILON) * 100) / - 100 - ) + value: `**${(Math.round((bm_result.length.total / 60 + Number.EPSILON) * 100) / 100) .toFixed(2) .replace('.', ':')}**`, inline: true @@ -389,19 +387,13 @@ export default class osu extends DiscordModule { Genre: **${bm_result.genre}** Submission Date: , + )}:R>, Last updated: , + )}:R>, Approved: , + )}:R>, \u200b` }, { @@ -433,9 +425,7 @@ export default class osu extends DiscordModule { url: `https://osu.ppy.sh/beatmapsets/${bm_result.beatmapSetId}${url_mode}/${bm_result.id}`, iconURL: `https://upload.wikimedia.org/wikipedia/commons/e/e3/Osulogo.png` }); - embed2.setImage( - `https://assets.ppy.sh/beatmaps/${bm_result.beatmapSetId}/covers/cover.jpg` - ); + embed2.setImage(`https://assets.ppy.sh/beatmaps/${bm_result.beatmapSetId}/covers/cover.jpg`); const row = new ActionRowBuilder(); if (bm_result.hasDownload) @@ -455,9 +445,7 @@ export default class osu extends DiscordModule { new ButtonBuilder() .setEmoji('💬') .setLabel(' Open discussion') - .setURL( - `https://osu.ppy.sh/beatmapsets/${bm_result.beatmapSetId}/discussion` - ) + .setURL(`https://osu.ppy.sh/beatmapsets/${bm_result.beatmapSetId}/discussion`) .setStyle(ButtonStyle.Link) ]); diff --git a/src/providers/App.ts b/src/providers/App.ts index b39d96f..570f43c 100644 --- a/src/providers/App.ts +++ b/src/providers/App.ts @@ -39,6 +39,10 @@ class App { } public load_osu(): void { + if (!Environment.get().OSU_API_KEY) { + Logger.log('warn', 'OSU_API_KEY is not defined in .env, osu! features will be disabled'); + return; + } Logger.log('info', 'Loading osu! Client'); osu.init(); } diff --git a/src/providers/osuAPI.ts b/src/providers/osuAPI.ts index 65314c4..8a8b7e4 100644 --- a/src/providers/osuAPI.ts +++ b/src/providers/osuAPI.ts @@ -2,14 +2,10 @@ import { Api } from 'node-osu'; import Environment from './Environment'; class osuAPI { - public client: Api; + public client: Api | null; constructor() { - this.client = new Api(Environment.get().OSU_API_KEY, { - notFoundAsError: false, - completeScores: true, - parseNumeric: true - }); + this.client = null; } public init(): void {