Compare commits

...

8 Commits

6 changed files with 128 additions and 51 deletions

View File

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

View File

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

View File

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

View File

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

85
src/youtube.js Normal file
View File

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

31
test/youtube.test.js Normal file
View File

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