From efcdeb78c8b690bc544dac1ed0be96a6693bcff6 Mon Sep 17 00:00:00 2001 From: Max Isom Date: Sun, 19 Sep 2021 19:50:25 -0400 Subject: [PATCH] Reorg third party services & config --- src/bot.ts | 5 +++-- src/index.ts | 25 +++++++------------------ src/inversify.config.ts | 20 ++++---------------- src/managers/player.ts | 5 +++-- src/services/config.ts | 36 ++++++++++++++++++++++++++++++++++++ src/services/get-songs.ts | 12 +++++++----- src/services/third-party.ts | 36 ++++++++++++++++++++++++++++++++++++ src/types.ts | 12 ++---------- src/utils/config.ts | 10 ---------- src/utils/db.ts | 2 +- 10 files changed, 99 insertions(+), 64 deletions(-) create mode 100644 src/services/config.ts create mode 100644 src/services/third-party.ts delete mode 100644 src/utils/config.ts diff --git a/src/bot.ts b/src/bot.ts index 16bdba0..00deb17 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -10,6 +10,7 @@ import handleGuildCreate from './events/guild-create'; import handleVoiceStateUpdate from './events/voice-state-update'; import errorMsg from './utils/error-msg'; import {isUserInVoice} from './utils/channels'; +import Config from './services/config'; @injectable() export default class { @@ -18,10 +19,10 @@ export default class { private readonly token: string; private readonly commands!: Collection; - constructor(@inject(TYPES.Client) client: Client, @inject(TYPES.Services.NaturalLanguage) naturalLanguage: NaturalLanguage, @inject(TYPES.Config.DISCORD_TOKEN) token: string) { + constructor(@inject(TYPES.Client) client: Client, @inject(TYPES.Services.NaturalLanguage) naturalLanguage: NaturalLanguage, @inject(TYPES.Config) config: Config) { this.client = client; this.naturalLanguage = naturalLanguage; - this.token = token; + this.token = config.DISCORD_TOKEN; this.commands = new Collection(); } diff --git a/src/index.ts b/src/index.ts index 2cdd6f1..7b1aac4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,31 +1,20 @@ -import Spotify from 'spotify-web-api-node'; import makeDir from 'make-dir'; import path from 'path'; import container from './inversify.config'; import {TYPES} from './types'; import Bot from './bot'; import {sequelize} from './utils/db'; +import Config from './services/config'; -let bot = container.get(TYPES.Bot); -const spotify = container.get(TYPES.Lib.Spotify); - -const refreshSpotifyToken = async () => { - const auth = await spotify.clientCredentialsGrant(); - - spotify.setAccessToken(auth.body.access_token); - - return auth.body.expires_in; -}; +const bot = container.get(TYPES.Bot); (async () => { - const spotifyRefreshIntervalSeconds = await refreshSpotifyToken(); - - setInterval(async () => refreshSpotifyToken(), (spotifyRefreshIntervalSeconds / 2) * 1000); - // Create data directories if necessary - await makeDir(container.get(TYPES.Config.DATA_DIR)); - await makeDir(container.get(TYPES.Config.CACHE_DIR)); - await makeDir(path.join(container.get(TYPES.Config.CACHE_DIR), 'tmp')); + const config = container.get(TYPES.Config); + + await makeDir(config.DATA_DIR); + await makeDir(config.CACHE_DIR); + await makeDir(path.join(config.CACHE_DIR, 'tmp')); await sequelize.sync({}); diff --git a/src/inversify.config.ts b/src/inversify.config.ts index f211083..0b60f6f 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -3,16 +3,7 @@ import {Container} from 'inversify'; import {TYPES} from './types'; import Bot from './bot'; import {Client} from 'discord.js'; -import YouTube from 'youtube.ts'; -import Spotify from 'spotify-web-api-node'; -import { - DISCORD_TOKEN, - YOUTUBE_API_KEY, - SPOTIFY_CLIENT_ID, - SPOTIFY_CLIENT_SECRET, - DATA_DIR, - CACHE_DIR -} from './utils/config'; +import ConfigProvider from './services/config'; // Managers import PlayerManager from './managers/player'; @@ -36,6 +27,7 @@ import Shortcuts from './commands/shortcuts'; import Shuffle from './commands/shuffle'; import Skip from './commands/skip'; import Unskip from './commands/unskip'; +import ThirdParty from './services/third-party'; let container = new Container(); @@ -70,13 +62,9 @@ container.bind(TYPES.Services.NaturalLanguage).to(NaturalLangua }); // Config values -container.bind(TYPES.Config.DISCORD_TOKEN).toConstantValue(DISCORD_TOKEN); -container.bind(TYPES.Config.YOUTUBE_API_KEY).toConstantValue(YOUTUBE_API_KEY); -container.bind(TYPES.Config.DATA_DIR).toConstantValue(DATA_DIR); -container.bind(TYPES.Config.CACHE_DIR).toConstantValue(CACHE_DIR); +container.bind(TYPES.Config).toConstantValue(new ConfigProvider()); // Static libraries -container.bind(TYPES.Lib.YouTube).toConstantValue(new YouTube(YOUTUBE_API_KEY)); -container.bind(TYPES.Lib.Spotify).toConstantValue(new Spotify({clientId: SPOTIFY_CLIENT_ID, clientSecret: SPOTIFY_CLIENT_SECRET})); +container.bind(TYPES.ThirdParty).to(ThirdParty); export default container; diff --git a/src/managers/player.ts b/src/managers/player.ts index 0afcc71..71fbd55 100644 --- a/src/managers/player.ts +++ b/src/managers/player.ts @@ -2,6 +2,7 @@ import {inject, injectable} from 'inversify'; import {TYPES} from '../types'; import Player from '../services/player'; import {Client} from 'discord.js'; +import Config from '../services/config'; @injectable() export default class { @@ -9,9 +10,9 @@ export default class { private readonly cacheDir: string; private readonly discordClient: Client; - constructor(@inject(TYPES.Config.CACHE_DIR) cacheDir: string, @inject(TYPES.Client) client: Client) { + constructor(@inject(TYPES.Config) config: Config, @inject(TYPES.Client) client: Client) { this.guildPlayers = new Map(); - this.cacheDir = cacheDir; + this.cacheDir = config.CACHE_DIR; this.discordClient = client; } diff --git a/src/services/config.ts b/src/services/config.ts new file mode 100644 index 0000000..80b4a81 --- /dev/null +++ b/src/services/config.ts @@ -0,0 +1,36 @@ +import dotenv from 'dotenv'; +import {injectable} from 'inversify'; +import path from 'path'; +dotenv.config(); + +export const DATA_DIR = path.resolve(process.env.DATA_DIR ? process.env.DATA_DIR : './data'); + +const CONFIG_MAP = { + DISCORD_TOKEN: process.env.DISCORD_TOKEN, + YOUTUBE_API_KEY: process.env.YOUTUBE_API_KEY, + SPOTIFY_CLIENT_ID: process.env.SPOTIFY_CLIENT_ID, + SPOTIFY_CLIENT_SECRET: process.env.SPOTIFY_CLIENT_SECRET, + DATA_DIR, + CACHE_DIR: path.join(DATA_DIR, 'cache') +} as const; + +@injectable() +export default class Config { + readonly DISCORD_TOKEN!: string; + readonly YOUTUBE_API_KEY!: string; + readonly SPOTIFY_CLIENT_ID!: string; + readonly SPOTIFY_CLIENT_SECRET!: string; + readonly DATA_DIR!: string; + readonly CACHE_DIR!: string; + + constructor() { + for (const [key, value] of Object.entries(CONFIG_MAP)) { + if (typeof value === 'undefined') { + console.error(`Missing environment variable for ${key}`); + process.exit(1); + } + + this[key as keyof typeof CONFIG_MAP] = value; + } + } +} diff --git a/src/services/get-songs.ts b/src/services/get-songs.ts index d5ccdef..0a6524f 100644 --- a/src/services/get-songs.ts +++ b/src/services/get-songs.ts @@ -11,6 +11,8 @@ import {Except} from 'type-fest'; import {QueuedSong, QueuedPlaylist} from '../services/player'; import {TYPES} from '../types'; import {cleanUrl} from '../utils/url'; +import ThirdParty from './third-party'; +import Config from './config'; type QueuedSongWithoutChannel = Except; @@ -20,10 +22,10 @@ export default class { private readonly youtubeKey: string; private readonly spotify: Spotify; - constructor(@inject(TYPES.Lib.YouTube) youtube: YouTube, @inject(TYPES.Config.YOUTUBE_API_KEY) youtubeKey: string, @inject(TYPES.Lib.Spotify) spotify: Spotify) { - this.youtube = youtube; - this.youtubeKey = youtubeKey; - this.spotify = spotify; + constructor(@inject(TYPES.ThirdParty) thirdParty: ThirdParty, @inject(TYPES.Config) config: Config) { + this.youtube = thirdParty.youtube; + this.youtubeKey = config.YOUTUBE_API_KEY; + this.spotify = thirdParty.spotify; } async youtubeVideoSearch(query: string): Promise { @@ -214,7 +216,7 @@ export default class { private async spotifyToYouTube(track: SpotifyApi.TrackObjectSimplified, _: QueuedPlaylist | null): Promise { try { const {items} = await this.youtube.videos.search({q: `"${track.name}" "${track.artists[0].name}"`, maxResults: 10}); - const videoResult = items[0]; // Items.find(item => item.type === 'video'); + const videoResult = items[0]; if (!videoResult) { throw new Error('No video found for query.'); diff --git a/src/services/third-party.ts b/src/services/third-party.ts new file mode 100644 index 0000000..00b9269 --- /dev/null +++ b/src/services/third-party.ts @@ -0,0 +1,36 @@ +import {inject, injectable} from 'inversify'; +import SpotifyWebApi from 'spotify-web-api-node'; +import Youtube from 'youtube.ts'; +import {TYPES} from '../types'; +import Config from './config'; + +@injectable() +export default class ThirdParty { + readonly youtube: Youtube; + readonly spotify: SpotifyWebApi; + + private spotifyTokenTimerId?: NodeJS.Timeout; + + constructor(@inject(TYPES.Config) config: Config) { + this.youtube = new Youtube(config.YOUTUBE_API_KEY); + this.spotify = new SpotifyWebApi({ + clientId: config.SPOTIFY_CLIENT_ID, + clientSecret: config.SPOTIFY_CLIENT_SECRET + }); + + void this.refreshSpotifyToken(); + } + + cleanup() { + if (this.spotifyTokenTimerId) { + clearTimeout(this.spotifyTokenTimerId); + } + } + + private async refreshSpotifyToken() { + const auth = await this.spotify.clientCredentialsGrant(); + this.spotify.setAccessToken(auth.body.access_token); + + this.spotifyTokenTimerId = setTimeout(this.refreshSpotifyToken, (auth.body.expires_in / 2) * 1000); + } +} diff --git a/src/types.ts b/src/types.ts index 8202b2f..a64ec49 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,17 +1,9 @@ export const TYPES = { Bot: Symbol('Bot'), Client: Symbol('Client'), - Config: { - DISCORD_TOKEN: Symbol('DISCORD_TOKEN'), - YOUTUBE_API_KEY: Symbol('YOUTUBE_API_KEY'), - DATA_DIR: Symbol('DATA_DIR'), - CACHE_DIR: Symbol('CACHE_DIR') - }, + Config: Symbol('Config'), Command: Symbol('Command'), - Lib: { - YouTube: Symbol('YouTube'), - Spotify: Symbol('Spotify') - }, + ThirdParty: Symbol('ThirdParty'), Managers: { Player: Symbol('PlayerManager') }, diff --git a/src/utils/config.ts b/src/utils/config.ts deleted file mode 100644 index 2e72677..0000000 --- a/src/utils/config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import dotenv from 'dotenv'; -import path from 'path'; -dotenv.config(); - -export const DISCORD_TOKEN: string = process.env.DISCORD_TOKEN ? process.env.DISCORD_TOKEN : ''; -export const YOUTUBE_API_KEY: string = process.env.YOUTUBE_API_KEY ? process.env.YOUTUBE_API_KEY : ''; -export const SPOTIFY_CLIENT_ID: string = process.env.SPOTIFY_CLIENT_ID ? process.env.SPOTIFY_CLIENT_ID : ''; -export const SPOTIFY_CLIENT_SECRET: string = process.env.SPOTIFY_CLIENT_SECRET ? process.env.SPOTIFY_CLIENT_SECRET : ''; -export const DATA_DIR = path.resolve(process.env.DATA_DIR ? process.env.DATA_DIR : './data'); -export const CACHE_DIR = path.join(DATA_DIR, 'cache'); diff --git a/src/utils/db.ts b/src/utils/db.ts index 965de89..01d5e01 100644 --- a/src/utils/db.ts +++ b/src/utils/db.ts @@ -1,6 +1,6 @@ import {Sequelize} from 'sequelize-typescript'; import path from 'path'; -import {DATA_DIR} from '../utils/config'; +import {DATA_DIR} from '../services/config'; import {Settings, Shortcut} from '../models'; export const sequelize = new Sequelize({