Compare commits
8 Commits
d33770cb4e
...
0d498bd8af
Author | SHA1 | Date |
---|---|---|
Arthur POULET | 0d498bd8af | |
Arthur POULET | cd3c1c8be7 | |
Arthur POULET | 634759481d | |
Thibaut Broggi | a4d5638624 | |
Arthur POULET | b0cbc3bb4d | |
Arthur POULET | 659ce829b9 | |
Arthur POULET | d43a3424fe | |
Arthur POULET | 7003d24278 |
|
@ -1,7 +1,7 @@
|
||||||
const { InputManager } = require ('./input_manager')
|
const { InputManager } = require ('./input_manager')
|
||||||
const { ResourceManager } = require('./resource_manager')
|
const { ResourceManager } = require('./resource_manager')
|
||||||
const { PlaylistsManager } = require('./playlists_manager')
|
const { PlaylistsManager } = require('./playlists_manager')
|
||||||
const { Player } = require('./player')
|
const { PlayerManager } = require('./player_manager')
|
||||||
const config = require('../config.json')
|
const config = require('../config.json')
|
||||||
|
|
||||||
class GuildsManager {
|
class GuildsManager {
|
||||||
|
@ -26,7 +26,7 @@ class GuildsManager {
|
||||||
const current_config = config[message.guild.id] || config['default']
|
const current_config = config[message.guild.id] || config['default']
|
||||||
this.#guilds[guild_id] = {
|
this.#guilds[guild_id] = {
|
||||||
input: new InputManager(this, current_config),
|
input: new InputManager(this, current_config),
|
||||||
player: new Player(this),
|
player: new PlayerManager(this),
|
||||||
resource: new ResourceManager(this),
|
resource: new ResourceManager(this),
|
||||||
playlists: await new PlaylistsManager(this),
|
playlists: await new PlaylistsManager(this),
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ const LOOP_NAMES = {
|
||||||
/**
|
/**
|
||||||
* @class
|
* @class
|
||||||
*/
|
*/
|
||||||
class Player {
|
class PlayerManager {
|
||||||
#voice
|
#voice
|
||||||
#dispatcher
|
#dispatcher
|
||||||
#playlist
|
#playlist
|
||||||
|
@ -174,4 +174,4 @@ class Player {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { Player }
|
module.exports = { PlayerManager }
|
|
@ -119,6 +119,10 @@ class Playlist {
|
||||||
return this.#tracks.length === 0
|
return this.#tracks.length === 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_length() {
|
||||||
|
return this.#tracks.length
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @method
|
* @method
|
||||||
* @returns {string} a string representation of the playlist
|
* @returns {string} a string representation of the playlist
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
const fetch = require('node-fetch')
|
|
||||||
const ytdl = require('ytdl-core')
|
|
||||||
const { GuildAction } = require('./guild_action')
|
const { GuildAction } = require('./guild_action')
|
||||||
const { Track } = require('./track')
|
const { Track } = require('./track')
|
||||||
|
const { youtube_instance } = require("./youtube")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class
|
* @class
|
||||||
|
@ -15,40 +14,13 @@ class ResourceManager {
|
||||||
this.#awaiting_answers = []
|
this.#awaiting_answers = []
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} search The search pattern that will be sent to Youtube
|
|
||||||
* @return {Track[]} An array of objects representing the search results. <code>source</code> is set to <code>"youtube"</code>
|
|
||||||
*/
|
|
||||||
async #search_youtube(search) {
|
|
||||||
try {
|
|
||||||
const req = await fetch('https://www.youtube.com/results?search_query='
|
|
||||||
+ encodeURIComponent(search))
|
|
||||||
const body = await req.text()
|
|
||||||
const json = JSON.parse(body.match(/ytInitialData = (.*);<\/script>/)[1])
|
|
||||||
const results = json.contents.twoColumnSearchResultsRenderer.primaryContents.sectionListRenderer.contents[0].itemSectionRenderer.contents
|
|
||||||
return results
|
|
||||||
.filter(e => e.videoRenderer)
|
|
||||||
.map(e => {
|
|
||||||
return new Track({
|
|
||||||
url: 'https://www.youtube.com/watch?v=' + e.videoRenderer.videoId,
|
|
||||||
title: e.videoRenderer.title.runs[0].text,
|
|
||||||
length: e.videoRenderer.lengthText.simpleText,
|
|
||||||
source: 'youtube'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} catch(e) {
|
|
||||||
console.error(e)
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do a research and send the search results back to the Discord channel it's been asked for
|
* Do a research and send the search results back to the Discord channel it's been asked for
|
||||||
* @param {ActionParams}
|
* @param {ActionParams}
|
||||||
* @return {Track[]} An array of objects representing the search results.
|
* @return {Track[]} An array of objects representing the search results.
|
||||||
*/
|
*/
|
||||||
async search({ message, params }) {
|
async search({ message, params }) {
|
||||||
const tracks = await this.#search_youtube(params.join(' '))
|
const tracks = await youtube_instance.search_track(params.join(' '))
|
||||||
if (tracks.length === 0) {
|
if (tracks.length === 0) {
|
||||||
message.channel.send(':warning: Something went wrong. Please try again')
|
message.channel.send(':warning: Something went wrong. Please try again')
|
||||||
} else {
|
} else {
|
||||||
|
@ -100,26 +72,11 @@ class ResourceManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
async quick_search({ message, params }) {
|
async quick_search({ message, params }) {
|
||||||
const tracks = await this.#search_youtube(params.join(' '))
|
const tracks = await youtube_instance.search_track(params.join(' '))
|
||||||
const action = new GuildAction({ message, params: [tracks[0].get_url()] })
|
const action = new GuildAction({ message, params: [tracks[0].get_url()] })
|
||||||
this.#gmanager.get_manager(message.guild.id, 'player').add(action)
|
this.#gmanager.get_manager(message.guild.id, 'player').add(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the metadata of a YouTube video
|
|
||||||
* @param {string} url
|
|
||||||
* @returns {Track}
|
|
||||||
*/
|
|
||||||
async #get_youtube_metadata(url) {
|
|
||||||
const video_infos = (await ytdl.getBasicInfo(url)).videoDetails
|
|
||||||
return new Track({
|
|
||||||
url: video_infos.video_url,
|
|
||||||
title: video_infos.title,
|
|
||||||
length: parseInt(video_infos.lengthSeconds),
|
|
||||||
source: 'youtube'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given the url of a song, get is metadata. <br />
|
* Given the url of a song, get is metadata. <br />
|
||||||
* Note that it only works for YouTube videos for now.
|
* Note that it only works for YouTube videos for now.
|
||||||
|
@ -127,7 +84,7 @@ class ResourceManager {
|
||||||
* @returns {Track}
|
* @returns {Track}
|
||||||
*/
|
*/
|
||||||
async get_metadata(url) {
|
async get_metadata(url) {
|
||||||
return this.#get_youtube_metadata(url)
|
return youtube_instance.get_metadata(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
const fetch = require("node-fetch")
|
||||||
|
const { Track } = require("./track")
|
||||||
|
const { Playlist } = require("./playlist")
|
||||||
|
const ytdl = require('ytdl-core')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Centralize all youtube requests within a single interface.
|
||||||
|
* It allows searching infos (tracks, playlists, metadata) over youtube.
|
||||||
|
*/
|
||||||
|
class Youtube {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} search The search pattern that will be sent to Youtube
|
||||||
|
* @return {Track[]} An array of objects representing the search results. <code>source</code> is set to <code>"youtube"</code>
|
||||||
|
*/
|
||||||
|
async search_track(search) {
|
||||||
|
try {
|
||||||
|
const req = await fetch('https://www.youtube.com/results?search_query='
|
||||||
|
+ encodeURIComponent(search))
|
||||||
|
const body = await req.text()
|
||||||
|
const json = JSON.parse(body.match(/ytInitialData = (.*);<\/script>/)[1])
|
||||||
|
const results = json.contents.twoColumnSearchResultsRenderer.primaryContents.sectionListRenderer.contents[0].itemSectionRenderer.contents
|
||||||
|
return results
|
||||||
|
.filter(e => e.videoRenderer)
|
||||||
|
.map(e => {
|
||||||
|
return new Track({
|
||||||
|
url: 'https://www.youtube.com/watch?v=' + e.videoRenderer.videoId,
|
||||||
|
title: e.videoRenderer.title.runs[0].text,
|
||||||
|
length: e.videoRenderer.lengthText.simpleText,
|
||||||
|
source: 'youtube',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve all tracks of the playlist on youtube
|
||||||
|
* @param {string} playlist_query an url or id of the youtube playlist. It will recognize the list=xxx pattern.
|
||||||
|
* @returns {Playlist}
|
||||||
|
*/
|
||||||
|
async get_playlist(playlist_query) {
|
||||||
|
const playlist_id = playlist_query.match(/list=(\w+)/)?.[1] || playlist
|
||||||
|
const req = await fetch(`https://www.youtube.com/playlist?list=${playlist_id}`)
|
||||||
|
const body = await req.text()
|
||||||
|
const json = JSON.parse(body.match(/ytInitialData = (.*);<\/script>/)[1])
|
||||||
|
// console.log(JSON.stringify(json, null, 2))
|
||||||
|
const results = json.contents.twoColumnBrowseResultsRenderer.tabs[0].tabRenderer.content.sectionListRenderer.contents[0].itemSectionRenderer.contents[0].playlistVideoListRenderer.contents
|
||||||
|
|
||||||
|
const tracks = results
|
||||||
|
.filter(video => video.playlistVideoRenderer.lengthText)
|
||||||
|
.map((video) => new Track({
|
||||||
|
title: video.playlistVideoRenderer.title.runs[0].text,
|
||||||
|
url: 'https://www.youtube.com/watch?v=' + video.playlistVideoRenderer.videoId,
|
||||||
|
length: video.playlistVideoRenderer.lengthText.simpleText,
|
||||||
|
source: 'youtube',
|
||||||
|
}))
|
||||||
|
return new Playlist({ tracks })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the metadata of a YouTube video
|
||||||
|
* @param {string} url
|
||||||
|
* @returns {Track}
|
||||||
|
*/
|
||||||
|
async get_metadata(url) {
|
||||||
|
const video_infos = (await ytdl.getBasicInfo(url)).videoDetails
|
||||||
|
return new Track({
|
||||||
|
url: video_infos.video_url,
|
||||||
|
title: video_infos.title,
|
||||||
|
length: parseInt(video_infos.lengthSeconds),
|
||||||
|
source: 'youtube'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const youtube_instance = new Youtube()
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
Youtube,
|
||||||
|
youtube_instance,
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
const { Youtube } = require('../src/youtube');
|
||||||
|
|
||||||
|
describe('playlist basic manipulations', () => {
|
||||||
|
const youtube = new Youtube();
|
||||||
|
test('search_track', async () => {
|
||||||
|
const tracks = await youtube.search_track("FROOT: Full Instrumental Album (MARINA AND THE DIAMONDS)")
|
||||||
|
expect(tracks.length).not.toEqual(0)
|
||||||
|
expect(tracks[0].get_url()).toEqual('https://www.youtube.com/watch?v=0WvTtIfX04c')
|
||||||
|
expect(tracks[0].get_title()).toEqual('FROOT: Full Instrumental Album (MARINA AND THE DIAMONDS)')
|
||||||
|
expect(tracks[0].get_length_as_string()).toEqual('48:27')
|
||||||
|
expect(tracks[0].get_source()).toEqual('youtube')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('get_playlist', async () => {
|
||||||
|
const playlist = await youtube.get_playlist("https://www.youtube.com/playlist?list=PLtKVsbX6mq9RvO__n08wujegVI0NsuNiu")
|
||||||
|
const track = playlist.get_current_track()
|
||||||
|
expect(playlist.get_length()).toEqual(35)
|
||||||
|
expect(track.get_url()).toEqual('https://www.youtube.com/watch?v=jfAbX-Fg9N0')
|
||||||
|
expect(track.get_title()).toEqual('Attack on Titan: Original Soundtrack I - attack ON titan | High Quality | Hiroyuki Sawano')
|
||||||
|
expect(track.get_length_as_string()).toEqual('4:21')
|
||||||
|
expect(track.get_source()).toEqual('youtube')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('get_metadata', async () => {
|
||||||
|
const track = await youtube.get_metadata("https://www.youtube.com/watch?v=0WvTtIfX04c")
|
||||||
|
expect(track.get_url()).toEqual('https://www.youtube.com/watch?v=0WvTtIfX04c')
|
||||||
|
expect(track.get_title()).toEqual('FROOT: Full Instrumental Album (MARINA AND THE DIAMONDS)')
|
||||||
|
expect(track.get_length_as_string()).toEqual('48:27')
|
||||||
|
expect(track.get_source()).toEqual('youtube')
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue