support youtube
This commit is contained in:
parent
9da80ee210
commit
b0b32d6d20
|
@ -0,0 +1,26 @@
|
|||
import test from 'ava';
|
||||
import {youtube} from '../../src';
|
||||
|
||||
// The Weeknd - I Feel It Coming ft. Daft Punk (Official Video)
|
||||
const VALID_VIDEO = 'qFLhGq0060w';
|
||||
|
||||
// youtube-dl test video "'/\ä↭𝕐
|
||||
const INVALID_VIDEO = 'BaW_jenozKc';
|
||||
|
||||
test('GET TRACK INFO', async (t) => {
|
||||
const response = await youtube.track2deezer(VALID_VIDEO);
|
||||
|
||||
t.is(response.SNG_ID, '136889434');
|
||||
t.is(response.SNG_TITLE, 'I Feel It Coming');
|
||||
t.is(response.ALB_TITLE, 'Starboy');
|
||||
t.is(response.ISRC, 'USUG11601012');
|
||||
});
|
||||
|
||||
test('FAIL INVALID VIDEO', async (t) => {
|
||||
try {
|
||||
await youtube.track2deezer(INVALID_VIDEO);
|
||||
t.fail();
|
||||
} catch (err) {
|
||||
t.true(err.message.includes('No track found for youtube video ' + INVALID_VIDEO));
|
||||
}
|
||||
});
|
|
@ -145,13 +145,14 @@ export const getProfile = (user_id: string): Promise<profileType> =>
|
|||
/**
|
||||
* @param {String} artist artist name
|
||||
* @param {String} song song name
|
||||
* @param {String} nb number of items to fetch
|
||||
*/
|
||||
export const searchAlternative = (artist: string, song: string): Promise<searchType> =>
|
||||
export const searchAlternative = (artist: string, song: string, nb = 10): Promise<searchType> =>
|
||||
request(
|
||||
{
|
||||
query: `artist:'${artist}' track:'${song}'`,
|
||||
types: ['TRACK'],
|
||||
nb: 10,
|
||||
nb,
|
||||
},
|
||||
'mobile_suggest',
|
||||
);
|
||||
|
|
|
@ -2,3 +2,4 @@ export * from './parse';
|
|||
export * from './deezer';
|
||||
export * as tidal from './tidal';
|
||||
export * as spotify from './spotify';
|
||||
export * as youtube from './youtube';
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
import spotifyUri from 'spotify-uri';
|
||||
import * as spotify from './spotify';
|
||||
import * as tidal from './tidal';
|
||||
import * as youtube from './tidal';
|
||||
import PQueue from 'p-queue';
|
||||
import type {albumType, artistInfoType, playlistInfo, trackType} from '../types';
|
||||
|
||||
|
@ -30,7 +31,8 @@ export type urlPartsType = {
|
|||
| 'tidal-track'
|
||||
| 'tidal-album'
|
||||
| 'tidal-playlist'
|
||||
| 'tidal-artist';
|
||||
| 'tidal-artist'
|
||||
| 'youtube-track';
|
||||
};
|
||||
|
||||
const queue = new PQueue({concurrency: 10});
|
||||
|
@ -41,7 +43,7 @@ export const getUrlParts = async (url: string, setToken = false): Promise<urlPar
|
|||
url = 'https://open.spotify.com/' + spotify[1] + '/' + spotify[2];
|
||||
}
|
||||
|
||||
const site = url.match(/deezer|spotify|tidal/);
|
||||
const site = url.match(/deezer|spotify|tidal|youtube/);
|
||||
if (!site) {
|
||||
throw new Error('Unknown URL: ' + url);
|
||||
}
|
||||
|
@ -62,6 +64,13 @@ export const getUrlParts = async (url: string, setToken = false): Promise<urlPar
|
|||
const tidalUrlParts = url.split(/\/(\w+)\/(\d+|\w+-\w+-\w+-\w+-\w+)/);
|
||||
return {type: ('tidal-' + tidalUrlParts[1]) as any, id: tidalUrlParts[2]};
|
||||
|
||||
case 'youtube':
|
||||
let yotubeId = url.split('v=')[1];
|
||||
if (yotubeId.includes('&')) {
|
||||
yotubeId = yotubeId.split('&')[0];
|
||||
}
|
||||
return {type: 'youtube-track', id: yotubeId};
|
||||
|
||||
default:
|
||||
throw new Error('Unable to parse URL: ' + url);
|
||||
}
|
||||
|
@ -163,6 +172,10 @@ export const parseInfo = async (url: string) => {
|
|||
linktype = 'artist';
|
||||
break;
|
||||
|
||||
case 'youtube-track':
|
||||
tracks.push(await youtube.track2deezer(info.id));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error('Unknown type: ' + info.type);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
import axios from 'axios';
|
||||
import {parse} from 'node-html-parser';
|
||||
import {searchAlternative} from '../api';
|
||||
|
||||
const getTrack = async (id: string) => {
|
||||
const response = await axios.get(`https://www.youtube.com/watch?v=${id}&hl=en`);
|
||||
const script = parse(response.data)
|
||||
.querySelectorAll('script')
|
||||
.find((script) => script.childNodes.find((node) => node.rawText.includes('responseText')));
|
||||
|
||||
if (script) {
|
||||
const info = script.text.split('= ');
|
||||
info.shift();
|
||||
if (info) {
|
||||
let json = info.join('= ').trim();
|
||||
if (json.endsWith(';')) {
|
||||
json = json.slice(0, -1);
|
||||
}
|
||||
|
||||
const data = JSON.parse(json).contents.twoColumnWatchNextResults.results.results.contents[1]
|
||||
.videoSecondaryInfoRenderer.metadataRowContainer.metadataRowContainerRenderer;
|
||||
const song = data.rows?.find(
|
||||
(row: any) => row.metadataRowRenderer && row.metadataRowRenderer.title.simpleText === 'Song',
|
||||
);
|
||||
const artist = data.rows?.find(
|
||||
(row: any) => row.metadataRowRenderer && row.metadataRowRenderer.title.simpleText === 'Artist',
|
||||
);
|
||||
|
||||
if (song && artist) {
|
||||
const {TRACK} = await searchAlternative(
|
||||
artist.metadataRowRenderer.contents[0].runs[0].text,
|
||||
song.metadataRowRenderer.contents[0].simpleText,
|
||||
1,
|
||||
);
|
||||
if (TRACK.data[0]) {
|
||||
return TRACK.data[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a youtube video to track by video id
|
||||
* @param {String} id - video id
|
||||
*/
|
||||
export const track2deezer = async (id: string) => {
|
||||
const track = await getTrack(id);
|
||||
if (track) {
|
||||
return track;
|
||||
}
|
||||
|
||||
throw new Error('No track found for youtube video ' + id);
|
||||
};
|
Loading…
Reference in New Issue