Skip to content

Commit 9331f5b

Browse files
committed
refactor: use URL object to parse urls
1 parent 45ac5dc commit 9331f5b

File tree

7 files changed

+48
-57
lines changed

7 files changed

+48
-57
lines changed

lib/info-extras.js

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
const utils = require('./utils');
22
const qs = require('querystring');
3-
const urllib = require('url');
43
const { URL } = require('url');
54
const { parseTimestamp } = require('m3u8stream');
65

76

8-
const VIDEO_URL = 'https://www.youtube.com/watch?v=';
7+
const BASE_URL = 'https://www.youtube.com/watch?v=';
98
const TITLE_TO_CATEGORY = {
109
song: { name: 'Music', url: 'https://music.youtube.com/' },
1110
};
@@ -42,8 +41,8 @@ exports.getMedia = info => {
4241
media[title] = getText(contents);
4342
let runs = contents.runs;
4443
if (runs && runs[0].navigationEndpoint) {
45-
media[`${title}_url`] = urllib.resolve(VIDEO_URL,
46-
runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url);
44+
media[`${title}_url`] = new URL(
45+
runs[0].navigationEndpoint.commandMetadata.webCommandMetadata.url, BASE_URL).toString();
4746
}
4847
if (title in TITLE_TO_CATEGORY) {
4948
media.category = TITLE_TO_CATEGORY[title].name;
@@ -58,17 +57,17 @@ exports.getMedia = info => {
5857
media.year = getText(meta.subtitle);
5958
let type = getText(meta.callToAction).split(' ')[1];
6059
media[type] = getText(meta.title);
61-
media[`${type}_url`] = urllib.resolve(VIDEO_URL,
62-
meta.endpoint.commandMetadata.webCommandMetadata.url);
60+
media[`${type}_url`] = new URL(
61+
meta.endpoint.commandMetadata.webCommandMetadata.url, BASE_URL).toString();
6362
media.thumbnails = meta.thumbnail.thumbnails;
6463
}
6564
let topic = contents
6665
.filter(meta => meta.richMetadataRenderer.style === 'RICH_METADATA_RENDERER_STYLE_TOPIC');
6766
for (let { richMetadataRenderer } of topic) {
6867
let meta = richMetadataRenderer;
6968
media.category = getText(meta.title);
70-
media.category_url = urllib.resolve(VIDEO_URL,
71-
meta.endpoint.commandMetadata.webCommandMetadata.url);
69+
media.category_url = new URL(
70+
meta.endpoint.commandMetadata.webCommandMetadata.url, BASE_URL).toString();
7271
}
7372
}
7473
}
@@ -100,7 +99,7 @@ exports.getAuthor = info => {
10099
let videoOwnerRenderer = v.videoSecondaryInfoRenderer.owner.videoOwnerRenderer;
101100
channelId = videoOwnerRenderer.navigationEndpoint.browseEndpoint.browseId;
102101
thumbnails = videoOwnerRenderer.thumbnail.thumbnails.map(thumbnail => {
103-
thumbnail.url = urllib.resolve(VIDEO_URL, thumbnail.url);
102+
thumbnail.url = new URL(thumbnail.url, BASE_URL).toString();
104103
return thumbnail;
105104
});
106105
subscriberCount = utils.parseAbbreviatedNumber(getText(videoOwnerRenderer.subscriberCountText));
@@ -117,7 +116,7 @@ exports.getAuthor = info => {
117116
user: videoDetails ? videoDetails.ownerProfileUrl.split('/').slice(-1)[0] : null,
118117
channel_url: `https://www.youtube.com/channel/${id}`,
119118
external_channel_url: videoDetails ? `https://www.youtube.com/channel/${videoDetails.externalChannelId}` : '',
120-
user_url: videoDetails ? urllib.resolve(VIDEO_URL, videoDetails.ownerProfileUrl) : '',
119+
user_url: videoDetails ? new URL(videoDetails.ownerProfileUrl, BASE_URL).toString() : '',
121120
thumbnails,
122121
verified,
123122
subscriber_count: subscriberCount,
@@ -156,7 +155,7 @@ const parseRelatedVideo = (details, rvsParams) => {
156155
channel_url: `https://www.youtube.com/channel/${channelId}`,
157156
user_url: `https://www.youtube.com/user/${user}`,
158157
thumbnails: details.channelThumbnail.thumbnails.map(thumbnail => {
159-
thumbnail.url = urllib.resolve(VIDEO_URL, thumbnail.url);
158+
thumbnail.url = new URL(thumbnail.url, BASE_URL).toString();
160159
return thumbnail;
161160
}),
162161
verified: isVerified(details.ownerBadges),

lib/info.js

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const urllib = require('url');
1+
const { URL } = require('url');
22
const querystring = require('querystring');
33
const sax = require('sax');
44
const miniget = require('miniget');
@@ -12,7 +12,7 @@ const sig = require('./sig');
1212
const Cache = require('./cache');
1313

1414

15-
const VIDEO_URL = 'https://www.youtube.com/watch?v=';
15+
const BASE_URL = 'https://www.youtube.com/watch?v=';
1616

1717

1818
// Cached for storing basic/full info.
@@ -80,7 +80,7 @@ exports.getBasicInfo = async(id, options) => {
8080
age_restricted: !!(media && media.notice_url && AGE_RESTRICTED_URLS.some(url => media.notice_url.includes(url))),
8181

8282
// Give the standard link to the video.
83-
video_url: VIDEO_URL + id,
83+
video_url: BASE_URL + id,
8484
storyboards: extras.getStoryboards(info),
8585
};
8686

@@ -116,7 +116,7 @@ const isNotYetBroadcasted = player_response => {
116116
};
117117

118118

119-
const getWatchHTMLURL = (id, options) => `${VIDEO_URL + id}&hl=${options.lang || 'en'}`;
119+
const getWatchHTMLURL = (id, options) => `${BASE_URL + id}&hl=${options.lang || 'en'}`;
120120
const getWatchHTMLPageBody = (id, options) => {
121121
const url = getWatchHTMLURL(id, options);
122122
return exports.watchPageCache.getOrSet(url, () => miniget(url, options.requestOptions).text());
@@ -323,19 +323,13 @@ const INFO_HOST = 'www.youtube.com';
323323
const INFO_PATH = '/get_video_info';
324324
const VIDEO_EURL = 'https://youtube.googleapis.com/v/';
325325
const getVideoInfoPage = async(id, options) => {
326-
const url = urllib.format({
327-
protocol: 'https',
328-
host: INFO_HOST,
329-
pathname: INFO_PATH,
330-
query: {
331-
video_id: id,
332-
eurl: VIDEO_EURL + id,
333-
ps: 'default',
334-
gl: 'US',
335-
hl: options.lang || 'en',
336-
},
337-
});
338-
let body = await miniget(url, options.requestOptions).text();
326+
const url = new URL(`https://${INFO_HOST}${INFO_PATH}`);
327+
url.searchParams.set('video_id', id);
328+
url.searchParams.set('eurl', VIDEO_EURL + id);
329+
url.searchParams.set('ps', 'default');
330+
url.searchParams.set('gl', 'US');
331+
url.searchParams.set('hl', options.lang || 'en');
332+
let body = await miniget(url.toString(), options.requestOptions).text();
339333
let info = querystring.parse(body);
340334
info.player_response = findPlayerResponse('get_video_info', info);
341335
return info;
@@ -378,7 +372,7 @@ exports.getInfo = async(id, options) => {
378372
if (!info.html5player) {
379373
throw Error('Unable to find html5player file');
380374
}
381-
const html5player = urllib.resolve(VIDEO_URL, info.html5player);
375+
const html5player = new URL(info.html5player, BASE_URL).toString();
382376
funcs.push(sig.decipherFormats(info.formats, html5player, options));
383377
}
384378
if (hasManifest && info.player_response.streamingData.dashManifestUrl) {
@@ -432,7 +426,7 @@ const getDashManifest = (url, options) => new Promise((resolve, reject) => {
432426
}
433427
};
434428
parser.onend = () => { resolve(formats); };
435-
const req = miniget(urllib.resolve(VIDEO_URL, url), options.requestOptions);
429+
const req = miniget(new URL(url, BASE_URL).toString(), options.requestOptions);
436430
req.setEncoding('utf8');
437431
req.on('error', reject);
438432
req.on('data', chunk => { parser.write(chunk); });
@@ -448,8 +442,8 @@ const getDashManifest = (url, options) => new Promise((resolve, reject) => {
448442
* @returns {Promise<Array.<Object>>}
449443
*/
450444
const getM3U8 = async(url, options) => {
451-
url = urllib.resolve(VIDEO_URL, url);
452-
let body = await miniget(url, options.requestOptions).text();
445+
url = new URL(url, BASE_URL);
446+
let body = await miniget(url.toString(), options.requestOptions).text();
453447
let formats = {};
454448
body
455449
.split('\n')

lib/sig.js

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const url = require('url');
1+
const { URL } = require('url');
22
const miniget = require('miniget');
33
const querystring = require('querystring');
44
const Cache = require('./cache');
@@ -205,25 +205,20 @@ exports.setDownloadURL = (format, sig) => {
205205
}
206206

207207
// Make some adjustments to the final url.
208-
const parsedUrl = url.parse(decodedUrl, true);
209-
210-
// Deleting the `search` part is necessary otherwise changes to
211-
// `query` won't reflect when running `url.format()`
212-
delete parsedUrl.search;
213-
214-
let query = parsedUrl.query;
208+
const parsedUrl = new URL(decodedUrl);
215209

216210
// This is needed for a speedier download.
217211
// See https://github.com/fent/node-ytdl-core/issues/127
218-
query.ratebypass = 'yes';
212+
parsedUrl.searchParams.set('ratebypass', 'yes');
213+
219214
if (sig) {
220215
// When YouTube provides a `sp` parameter the signature `sig` must go
221216
// into the parameter it specifies.
222217
// See https://github.com/fent/node-ytdl-core/issues/417
223-
query[format.sp || 'signature'] = sig;
218+
parsedUrl.searchParams.set(format.sp || 'signature', sig);
224219
}
225220

226-
format.url = url.format(parsedUrl);
221+
format.url = parsedUrl.toString();
227222
};
228223

229224

lib/url-utils.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const url = require('url');
1+
const { URL } = require('url');
22

33

44
/**
@@ -27,8 +27,8 @@ const validQueryDomains = new Set([
2727
]);
2828
const validPathDomains = /^https?:\/\/(youtu\.be\/|(www\.)?youtube.com\/(embed|v)\/)/;
2929
exports.getURLVideoID = link => {
30-
const parsed = url.parse(link, true);
31-
let id = parsed.query.v;
30+
const parsed = new URL(link);
31+
let id = parsed.searchParams.get('v');
3232
if (validPathDomains.test(link) && !id) {
3333
const paths = parsed.pathname.split('/');
3434
id = paths[paths.length - 1];
@@ -56,11 +56,14 @@ exports.getURLVideoID = link => {
5656
* @throws {Error} If unable to find a id
5757
* @throws {TypeError} If videoid doesn't match specs
5858
*/
59+
const urlRegex = /^https?:\/\//;
5960
exports.getVideoID = str => {
6061
if (exports.validateID(str)) {
6162
return str;
62-
} else {
63+
} else if (urlRegex.test(str)) {
6364
return exports.getURLVideoID(str);
65+
} else {
66+
throw Error(`No video id found: ${str}`);
6467
}
6568
};
6669

test/download-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const assert = require('assert');
22
const path = require('path');
33
const fs = require('fs');
4-
const url = require('url');
4+
const { URL } = require('url');
55
const streamEqual = require('stream-equal');
66
const sinon = require('sinon');
77
const nock = require('./nock');
@@ -575,7 +575,7 @@ describe('Download video', () => {
575575
'/file03.ts',
576576
'#EXT-X-ENDLIST',
577577
].join('\n'));
578-
const host = url.parse(format.url).host;
578+
const host = new URL(format.url).host;
579579
scope.urlReply(`https://${host}/file01.ts`, 200, 'one', {
580580
'content-length': '3',
581581
});

test/files/refresh.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ const videos = [
117117

118118
const fs = require('fs');
119119
const path = require('path');
120-
const urlParse = require('url').parse;
120+
const { URL } = require('url');
121121
const { PassThrough } = require('stream');
122122
const mukRequire = require('muk-require');
123123
const miniget = require('miniget');
@@ -176,7 +176,7 @@ const refreshVideo = async(video, noRequests) => {
176176
};
177177

178178
const getFilenameFromURL = url => {
179-
let parsed = urlParse(url);
179+
let parsed = new URL(url);
180180
let s = parsed.pathname.split('/');
181181
let filename =
182182
// Special case for livestream manifest files.

test/nock.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const ytdl = require('..');
22
const path = require('path');
33
const fs = require('fs');
4-
const url = require('url');
4+
const { URL } = require('url');
55
const nock = require('nock');
66

77
const YT_HOST = 'https://www.youtube.com';
@@ -144,15 +144,15 @@ exports = module.exports = (id, type, opts = {}) => {
144144
};
145145

146146
exports.filteringPath = (uri, filter1, filter2) => {
147-
let parsed = url.parse(uri);
148-
return nock(`${parsed.protocol}//${parsed.host}`)
147+
let parsed = new URL(uri);
148+
return nock(parsed.origin)
149149
.filteringPath(filter1, filter2)
150-
.get(parsed.path);
150+
.get(parsed.pathname + parsed.search + parsed.hash);
151151
};
152152

153153
exports.url = uri => {
154-
let parsed = url.parse(uri);
155-
return nock(`${parsed.protocol}//${parsed.host}`).get(parsed.path);
154+
let parsed = new URL(uri);
155+
return nock(parsed.origin).get(parsed.pathname + parsed.search + parsed.hash);
156156
};
157157

158158
exports.cleanAll = nock.cleanAll;

0 commit comments

Comments
 (0)