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 { ResourceManager } = require('./resource_manager')
|
||||
const { PlaylistsManager } = require('./playlists_manager')
|
||||
const { Player } = require('./player')
|
||||
const { PlayerManager } = require('./player_manager')
|
||||
const config = require('../config.json')
|
||||
|
||||
class GuildsManager {
|
||||
|
@ -26,7 +26,7 @@ class GuildsManager {
|
|||
const current_config = config[message.guild.id] || config['default']
|
||||
this.#guilds[guild_id] = {
|
||||
input: new InputManager(this, current_config),
|
||||
player: new Player(this),
|
||||
player: new PlayerManager(this),
|
||||
resource: new ResourceManager(this),
|
||||
playlists: await new PlaylistsManager(this),
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ const LOOP_NAMES = {
|
|||
/**
|
||||
* @class
|
||||
*/
|
||||
class Player {
|
||||
class PlayerManager {
|
||||
#voice
|
||||
#dispatcher
|
||||
#playlist
|
||||
|
@ -174,4 +174,4 @@ class Player {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = { Player }
|
||||
module.exports = { PlayerManager }
|
|
@ -119,6 +119,10 @@ class Playlist {
|
|||
return this.#tracks.length === 0
|
||||
}
|
||||
|
||||
get_length() {
|
||||
return this.#tracks.length
|
||||
}
|
||||
|
||||
/**
|
||||
* @method
|
||||
* @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 { Track } = require('./track')
|
||||
const { youtube_instance } = require("./youtube")
|
||||
|
||||
/**
|
||||
* @class
|
||||
|
@ -15,40 +14,13 @@ class ResourceManager {
|
|||
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
|
||||
* @param {ActionParams}
|
||||
* @return {Track[]} An array of objects representing the search results.
|
||||
*/
|
||||
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) {
|
||||
message.channel.send(':warning: Something went wrong. Please try again')
|
||||
} else {
|
||||
|
@ -100,26 +72,11 @@ class ResourceManager {
|
|||
}
|
||||
|
||||
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()] })
|
||||
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 />
|
||||
* Note that it only works for YouTube videos for now.
|
||||
|
@ -127,7 +84,7 @@ class ResourceManager {
|
|||
* @returns {Track}
|
||||
*/
|
||||
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