Reorg third party services & config

This commit is contained in:
Max Isom 2021-09-19 19:50:25 -04:00
parent 79e7e88fab
commit efcdeb78c8
No known key found for this signature in database
GPG key ID: 25C9B1A7F6798880
10 changed files with 99 additions and 64 deletions

View file

@ -10,6 +10,7 @@ import handleGuildCreate from './events/guild-create';
import handleVoiceStateUpdate from './events/voice-state-update'; import handleVoiceStateUpdate from './events/voice-state-update';
import errorMsg from './utils/error-msg'; import errorMsg from './utils/error-msg';
import {isUserInVoice} from './utils/channels'; import {isUserInVoice} from './utils/channels';
import Config from './services/config';
@injectable() @injectable()
export default class { export default class {
@ -18,10 +19,10 @@ export default class {
private readonly token: string; private readonly token: string;
private readonly commands!: Collection<string, Command>; private readonly commands!: Collection<string, Command>;
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.client = client;
this.naturalLanguage = naturalLanguage; this.naturalLanguage = naturalLanguage;
this.token = token; this.token = config.DISCORD_TOKEN;
this.commands = new Collection(); this.commands = new Collection();
} }

View file

@ -1,31 +1,20 @@
import Spotify from 'spotify-web-api-node';
import makeDir from 'make-dir'; import makeDir from 'make-dir';
import path from 'path'; import path from 'path';
import container from './inversify.config'; import container from './inversify.config';
import {TYPES} from './types'; import {TYPES} from './types';
import Bot from './bot'; import Bot from './bot';
import {sequelize} from './utils/db'; import {sequelize} from './utils/db';
import Config from './services/config';
let bot = container.get<Bot>(TYPES.Bot); const bot = container.get<Bot>(TYPES.Bot);
const spotify = container.get<Spotify>(TYPES.Lib.Spotify);
const refreshSpotifyToken = async () => {
const auth = await spotify.clientCredentialsGrant();
spotify.setAccessToken(auth.body.access_token);
return auth.body.expires_in;
};
(async () => { (async () => {
const spotifyRefreshIntervalSeconds = await refreshSpotifyToken();
setInterval(async () => refreshSpotifyToken(), (spotifyRefreshIntervalSeconds / 2) * 1000);
// Create data directories if necessary // Create data directories if necessary
await makeDir(container.get(TYPES.Config.DATA_DIR)); const config = container.get<Config>(TYPES.Config);
await makeDir(container.get(TYPES.Config.CACHE_DIR));
await makeDir(path.join(container.get(TYPES.Config.CACHE_DIR), 'tmp')); await makeDir(config.DATA_DIR);
await makeDir(config.CACHE_DIR);
await makeDir(path.join(config.CACHE_DIR, 'tmp'));
await sequelize.sync({}); await sequelize.sync({});

View file

@ -3,16 +3,7 @@ import {Container} from 'inversify';
import {TYPES} from './types'; import {TYPES} from './types';
import Bot from './bot'; import Bot from './bot';
import {Client} from 'discord.js'; import {Client} from 'discord.js';
import YouTube from 'youtube.ts'; import ConfigProvider from './services/config';
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';
// Managers // Managers
import PlayerManager from './managers/player'; import PlayerManager from './managers/player';
@ -36,6 +27,7 @@ import Shortcuts from './commands/shortcuts';
import Shuffle from './commands/shuffle'; import Shuffle from './commands/shuffle';
import Skip from './commands/skip'; import Skip from './commands/skip';
import Unskip from './commands/unskip'; import Unskip from './commands/unskip';
import ThirdParty from './services/third-party';
let container = new Container(); let container = new Container();
@ -70,13 +62,9 @@ container.bind<NaturalLanguage>(TYPES.Services.NaturalLanguage).to(NaturalLangua
}); });
// Config values // Config values
container.bind<string>(TYPES.Config.DISCORD_TOKEN).toConstantValue(DISCORD_TOKEN); container.bind(TYPES.Config).toConstantValue(new ConfigProvider());
container.bind<string>(TYPES.Config.YOUTUBE_API_KEY).toConstantValue(YOUTUBE_API_KEY);
container.bind<string>(TYPES.Config.DATA_DIR).toConstantValue(DATA_DIR);
container.bind<string>(TYPES.Config.CACHE_DIR).toConstantValue(CACHE_DIR);
// Static libraries // Static libraries
container.bind<YouTube>(TYPES.Lib.YouTube).toConstantValue(new YouTube(YOUTUBE_API_KEY)); container.bind(TYPES.ThirdParty).to(ThirdParty);
container.bind<Spotify>(TYPES.Lib.Spotify).toConstantValue(new Spotify({clientId: SPOTIFY_CLIENT_ID, clientSecret: SPOTIFY_CLIENT_SECRET}));
export default container; export default container;

View file

@ -2,6 +2,7 @@ import {inject, injectable} from 'inversify';
import {TYPES} from '../types'; import {TYPES} from '../types';
import Player from '../services/player'; import Player from '../services/player';
import {Client} from 'discord.js'; import {Client} from 'discord.js';
import Config from '../services/config';
@injectable() @injectable()
export default class { export default class {
@ -9,9 +10,9 @@ export default class {
private readonly cacheDir: string; private readonly cacheDir: string;
private readonly discordClient: Client; 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.guildPlayers = new Map();
this.cacheDir = cacheDir; this.cacheDir = config.CACHE_DIR;
this.discordClient = client; this.discordClient = client;
} }

36
src/services/config.ts Normal file
View file

@ -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;
}
}
}

View file

@ -11,6 +11,8 @@ import {Except} from 'type-fest';
import {QueuedSong, QueuedPlaylist} from '../services/player'; import {QueuedSong, QueuedPlaylist} from '../services/player';
import {TYPES} from '../types'; import {TYPES} from '../types';
import {cleanUrl} from '../utils/url'; import {cleanUrl} from '../utils/url';
import ThirdParty from './third-party';
import Config from './config';
type QueuedSongWithoutChannel = Except<QueuedSong, 'addedInChannelId'>; type QueuedSongWithoutChannel = Except<QueuedSong, 'addedInChannelId'>;
@ -20,10 +22,10 @@ export default class {
private readonly youtubeKey: string; private readonly youtubeKey: string;
private readonly spotify: Spotify; 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) { constructor(@inject(TYPES.ThirdParty) thirdParty: ThirdParty, @inject(TYPES.Config) config: Config) {
this.youtube = youtube; this.youtube = thirdParty.youtube;
this.youtubeKey = youtubeKey; this.youtubeKey = config.YOUTUBE_API_KEY;
this.spotify = spotify; this.spotify = thirdParty.spotify;
} }
async youtubeVideoSearch(query: string): Promise<QueuedSongWithoutChannel|null> { async youtubeVideoSearch(query: string): Promise<QueuedSongWithoutChannel|null> {
@ -214,7 +216,7 @@ export default class {
private async spotifyToYouTube(track: SpotifyApi.TrackObjectSimplified, _: QueuedPlaylist | null): Promise<QueuedSongWithoutChannel | null> { private async spotifyToYouTube(track: SpotifyApi.TrackObjectSimplified, _: QueuedPlaylist | null): Promise<QueuedSongWithoutChannel | null> {
try { try {
const {items} = await this.youtube.videos.search({q: `"${track.name}" "${track.artists[0].name}"`, maxResults: 10}); 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) { if (!videoResult) {
throw new Error('No video found for query.'); throw new Error('No video found for query.');

View file

@ -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);
}
}

View file

@ -1,17 +1,9 @@
export const TYPES = { export const TYPES = {
Bot: Symbol('Bot'), Bot: Symbol('Bot'),
Client: Symbol('Client'), Client: Symbol('Client'),
Config: { Config: Symbol('Config'),
DISCORD_TOKEN: Symbol('DISCORD_TOKEN'),
YOUTUBE_API_KEY: Symbol('YOUTUBE_API_KEY'),
DATA_DIR: Symbol('DATA_DIR'),
CACHE_DIR: Symbol('CACHE_DIR')
},
Command: Symbol('Command'), Command: Symbol('Command'),
Lib: { ThirdParty: Symbol('ThirdParty'),
YouTube: Symbol('YouTube'),
Spotify: Symbol('Spotify')
},
Managers: { Managers: {
Player: Symbol('PlayerManager') Player: Symbol('PlayerManager')
}, },

View file

@ -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');

View file

@ -1,6 +1,6 @@
import {Sequelize} from 'sequelize-typescript'; import {Sequelize} from 'sequelize-typescript';
import path from 'path'; import path from 'path';
import {DATA_DIR} from '../utils/config'; import {DATA_DIR} from '../services/config';
import {Settings, Shortcut} from '../models'; import {Settings, Shortcut} from '../models';
export const sequelize = new Sequelize({ export const sequelize = new Sequelize({