mirror of
https://github.com/BluemediaGER/muse.git
synced 2024-11-23 09:15:29 +01:00
Basic play functionality
This commit is contained in:
parent
652cc2e5ef
commit
8eb4c8a6c0
|
@ -64,10 +64,12 @@
|
||||||
"@discordjs/opus": "^0.1.0",
|
"@discordjs/opus": "^0.1.0",
|
||||||
"discord.js": "^12.0.1",
|
"discord.js": "^12.0.1",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
|
"hasha": "^5.2.0",
|
||||||
"make-dir": "^3.0.2",
|
"make-dir": "^3.0.2",
|
||||||
"node-emoji": "^1.10.0",
|
"node-emoji": "^1.10.0",
|
||||||
"sequelize": "^5.21.5",
|
"sequelize": "^5.21.5",
|
||||||
"sequelize-typescript": "^1.1.0",
|
"sequelize-typescript": "^1.1.0",
|
||||||
"sqlite3": "^4.1.1"
|
"sqlite3": "^4.1.1",
|
||||||
|
"ytdl-core": "^2.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
21
src/commands/play.ts
Normal file
21
src/commands/play.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import {CommandHandler} from '../interfaces';
|
||||||
|
import {getMostPopularVoiceChannel} from '../utils/channels';
|
||||||
|
import getYouTubeStream from '../utils/get-youtube-stream';
|
||||||
|
|
||||||
|
const play: CommandHandler = {
|
||||||
|
name: 'play',
|
||||||
|
description: 'plays a song',
|
||||||
|
execute: async (msg, args) => {
|
||||||
|
const url = args[0];
|
||||||
|
|
||||||
|
const channel = getMostPopularVoiceChannel(msg.guild!);
|
||||||
|
|
||||||
|
const conn = await channel.join();
|
||||||
|
|
||||||
|
const stream = await getYouTubeStream(url);
|
||||||
|
|
||||||
|
conn.play(stream, {type: 'webm/opus'});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default play;
|
|
@ -2,7 +2,7 @@ import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import makeDir from 'make-dir';
|
import makeDir from 'make-dir';
|
||||||
import Discord from 'discord.js';
|
import Discord from 'discord.js';
|
||||||
import {DISCORD_TOKEN, DISCORD_CLIENT_ID, DATA_DIR} from './utils/config';
|
import {DISCORD_TOKEN, DISCORD_CLIENT_ID, DATA_DIR, CACHE_DIR} from './utils/config';
|
||||||
import {Settings} from './models';
|
import {Settings} from './models';
|
||||||
import {sequelize} from './utils/db';
|
import {sequelize} from './utils/db';
|
||||||
import {CommandHandler} from './interfaces';
|
import {CommandHandler} from './interfaces';
|
||||||
|
@ -56,6 +56,7 @@ client.on('message', async (msg: Discord.Message) => {
|
||||||
client.on('ready', async () => {
|
client.on('ready', async () => {
|
||||||
// Create directory if necessary
|
// Create directory if necessary
|
||||||
await makeDir(DATA_DIR);
|
await makeDir(DATA_DIR);
|
||||||
|
await makeDir(CACHE_DIR);
|
||||||
|
|
||||||
await sequelize.sync({});
|
await sequelize.sync({});
|
||||||
|
|
||||||
|
|
38
src/utils/channels.ts
Normal file
38
src/utils/channels.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import {Guild, VoiceChannel} from 'discord.js';
|
||||||
|
|
||||||
|
export const getMostPopularVoiceChannel = (guild: Guild, min = 0): VoiceChannel => {
|
||||||
|
interface PopularResult {
|
||||||
|
n: number;
|
||||||
|
channel: VoiceChannel | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const voiceChannels: PopularResult[] = [];
|
||||||
|
|
||||||
|
for (const [_, channel] of guild.channels.cache) {
|
||||||
|
if (channel.type === 'voice' && channel.members.size >= min) {
|
||||||
|
voiceChannels.push({
|
||||||
|
channel: channel as VoiceChannel,
|
||||||
|
n: channel.members.size
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (voiceChannels.length === 0) {
|
||||||
|
throw new Error('No voice channels meet minimum size');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find most popular channel
|
||||||
|
const popularChannel = voiceChannels.reduce((popular: PopularResult, elem: PopularResult) => {
|
||||||
|
if (elem.n > popular.n) {
|
||||||
|
return elem;
|
||||||
|
}
|
||||||
|
|
||||||
|
return popular;
|
||||||
|
}, {n: -1, channel: null});
|
||||||
|
|
||||||
|
if (popularChannel.channel) {
|
||||||
|
return popularChannel.channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error();
|
||||||
|
};
|
|
@ -5,3 +5,4 @@ dotenv.config();
|
||||||
export const DISCORD_TOKEN: string = process.env.DISCORD_TOKEN ? process.env.DISCORD_TOKEN : '';
|
export const DISCORD_TOKEN: string = process.env.DISCORD_TOKEN ? process.env.DISCORD_TOKEN : '';
|
||||||
export const DISCORD_CLIENT_ID: string = process.env.DISCORD_CLIENT_ID ? process.env.DISCORD_CLIENT_ID : '';
|
export const DISCORD_CLIENT_ID: string = process.env.DISCORD_CLIENT_ID ? process.env.DISCORD_CLIENT_ID : '';
|
||||||
export const DATA_DIR = path.resolve(process.env.DATA_DIR ? process.env.DATA_DIR : './data');
|
export const DATA_DIR = path.resolve(process.env.DATA_DIR ? process.env.DATA_DIR : './data');
|
||||||
|
export const CACHE_DIR = path.join(DATA_DIR, 'cache');
|
||||||
|
|
51
src/utils/get-youtube-stream.ts
Normal file
51
src/utils/get-youtube-stream.ts
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import {promises as fs, createReadStream, createWriteStream} from 'fs';
|
||||||
|
import {Readable, PassThrough} from 'stream';
|
||||||
|
import path from 'path';
|
||||||
|
import hasha from 'hasha';
|
||||||
|
import ytdl from 'ytdl-core';
|
||||||
|
import {CACHE_DIR} from './config';
|
||||||
|
|
||||||
|
const nextBestFormat = (formats: ytdl.videoFormat[]): ytdl.videoFormat => {
|
||||||
|
formats = formats
|
||||||
|
.filter(format => format.averageBitrate)
|
||||||
|
.sort((a, b) => b.averageBitrate - a.averageBitrate);
|
||||||
|
return formats.find(format => !format.bitrate) ?? formats[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: are some videos not available in webm/opus?
|
||||||
|
export default async (url: string): Promise<Readable> => {
|
||||||
|
const hash = hasha(url);
|
||||||
|
const cachedPath = path.join(CACHE_DIR, `${hash}.webm`);
|
||||||
|
|
||||||
|
const info = await ytdl.getInfo(url);
|
||||||
|
|
||||||
|
const {formats} = info;
|
||||||
|
|
||||||
|
const filter = (format: ytdl.videoFormat): boolean => format.codecs === 'opus' && format.container === 'webm' && format.audioSampleRate !== undefined && parseInt(format.audioSampleRate, 10) === 48000;
|
||||||
|
|
||||||
|
let format = formats.find(filter);
|
||||||
|
|
||||||
|
if (!format) {
|
||||||
|
format = nextBestFormat(info.formats);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Test if file exists
|
||||||
|
await fs.access(cachedPath);
|
||||||
|
|
||||||
|
// If so, return cached stream
|
||||||
|
return createReadStream(cachedPath);
|
||||||
|
} catch (_) {
|
||||||
|
// Not yet cached, must download
|
||||||
|
const cacheTempPath = path.join('/tmp', `${hash}.webm`);
|
||||||
|
const cacheStream = createWriteStream(cacheTempPath);
|
||||||
|
|
||||||
|
const pass = new PassThrough();
|
||||||
|
|
||||||
|
pass.pipe(cacheStream).on('finish', async () => {
|
||||||
|
await fs.rename(cacheTempPath, cachedPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
return ytdl.downloadFromInfo(info, {format}).pipe(pass);
|
||||||
|
}
|
||||||
|
};
|
45
yarn.lock
45
yarn.lock
|
@ -1059,6 +1059,19 @@ has-unicode@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
|
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
|
||||||
integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=
|
integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=
|
||||||
|
|
||||||
|
hasha@^5.2.0:
|
||||||
|
version "5.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/hasha/-/hasha-5.2.0.tgz#33094d1f69c40a4a6ac7be53d5fe3ff95a269e0c"
|
||||||
|
integrity sha512-2W+jKdQbAdSIrggA8Q35Br8qKadTrqCTC8+XZvBWepKDK6m9XkX6Iz1a2yh2KP01kzAR/dpuMeUnocoLYDcskw==
|
||||||
|
dependencies:
|
||||||
|
is-stream "^2.0.0"
|
||||||
|
type-fest "^0.8.0"
|
||||||
|
|
||||||
|
html-entities@^1.1.3:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f"
|
||||||
|
integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=
|
||||||
|
|
||||||
http-signature@~1.2.0:
|
http-signature@~1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
|
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
|
||||||
|
@ -1271,6 +1284,11 @@ is-stream@^1.0.0, is-stream@^1.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
||||||
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
|
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
|
||||||
|
|
||||||
|
is-stream@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3"
|
||||||
|
integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==
|
||||||
|
|
||||||
is-typedarray@~1.0.0:
|
is-typedarray@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
|
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
|
||||||
|
@ -1394,6 +1412,14 @@ lru-cache@^4.0.1:
|
||||||
pseudomap "^1.0.2"
|
pseudomap "^1.0.2"
|
||||||
yallist "^2.1.2"
|
yallist "^2.1.2"
|
||||||
|
|
||||||
|
m3u8stream@^0.6.3:
|
||||||
|
version "0.6.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/m3u8stream/-/m3u8stream-0.6.5.tgz#a41713cb7144f8fe1eb944d32a8848be8cb2c951"
|
||||||
|
integrity sha512-QZCzhcfUliZfsOboi68QkNcMejPKTEhxE+s1TApvHubDeR8ythm4ViWuYFqgUwZeoHe8q0nsPxOvA3lQvdSzyg==
|
||||||
|
dependencies:
|
||||||
|
miniget "^1.6.1"
|
||||||
|
sax "^1.2.4"
|
||||||
|
|
||||||
make-dir@^1.0.0:
|
make-dir@^1.0.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
|
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
|
||||||
|
@ -1430,6 +1456,11 @@ mimic-fn@^2.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||||
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
|
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
|
||||||
|
|
||||||
|
miniget@^1.6.0, miniget@^1.6.1:
|
||||||
|
version "1.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/miniget/-/miniget-1.7.0.tgz#a29eb79ebff479e9efafd271616981c603987875"
|
||||||
|
integrity sha512-yrgaDSMRzrfYTkudB4Y6xK8pCb7oAH2bvfv6iPY2m6CedZfs9yK4b/ofh0Vzv08hCYXH/HHkoS8an6fkWtOAQA==
|
||||||
|
|
||||||
minimatch@^3.0.4:
|
minimatch@^3.0.4:
|
||||||
version "3.0.4"
|
version "3.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
||||||
|
@ -2016,7 +2047,7 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||||
|
|
||||||
sax@^1.2.4:
|
sax@^1.1.3, sax@^1.2.4:
|
||||||
version "1.2.4"
|
version "1.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||||
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
|
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
|
||||||
|
@ -2399,7 +2430,7 @@ type-fest@^0.11.0:
|
||||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1"
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1"
|
||||||
integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==
|
integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==
|
||||||
|
|
||||||
type-fest@^0.8.1:
|
type-fest@^0.8.0, type-fest@^0.8.1:
|
||||||
version "0.8.1"
|
version "0.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
|
||||||
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
|
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
|
||||||
|
@ -2577,3 +2608,13 @@ yn@3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
||||||
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
|
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
|
||||||
|
|
||||||
|
ytdl-core@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ytdl-core/-/ytdl-core-2.0.0.tgz#09bafc2beeab1eb9c69ceb9ca8f406be12396613"
|
||||||
|
integrity sha512-cYdcxXhgldJeOaWaN/mxT+j07XFZdR+qUqfz5eddx4WcyWCha66FfJvmQBOPqyQo1TCyj/IkgZqdGADWlNdTcQ==
|
||||||
|
dependencies:
|
||||||
|
html-entities "^1.1.3"
|
||||||
|
m3u8stream "^0.6.3"
|
||||||
|
miniget "^1.6.0"
|
||||||
|
sax "^1.1.3"
|
||||||
|
|
Loading…
Reference in a new issue