|
1 | 1 | import * as util from '../../util.js'; |
| 2 | +import SizedMap from '../../SizedMap.js'; |
2 | 3 |
|
| 4 | +import ytdl from '../../../lib/ytdl-core.min.js'; |
| 5 | +const ytdlCache = new SizedMap(100, 'cache-youtube', 1000 * 60 * 60); // 1hr |
| 6 | + |
| 7 | + |
| 8 | +// ytdl-core requires the User-Agent header to be empty to scrape the watch |
| 9 | +// page correctly. XMLHttpRequest doesn't allow removing this header, |
| 10 | +// so we have to use the `webRequest` API. |
| 11 | +// Removing the header from the list of headers doesn't work either, |
| 12 | +// we have to set it to a blank value. |
| 13 | +chrome.webRequest.onBeforeSendHeaders.addListener((details) => { |
| 14 | + let header = details.requestHeaders.find(h => h.name === 'User-Agent'); |
| 15 | + if (header) { header.value = ''; } |
| 16 | + return { requestHeaders: details.requestHeaders }; |
| 17 | +}, { |
| 18 | + urls: [ |
| 19 | + 'https://www.youtube.com/watch?v=*', |
| 20 | + 'https://www.youtube.com/get_video_info*' |
| 21 | + ], |
| 22 | + types: ['xmlhttprequest'], |
| 23 | +}, ['blocking', 'requestHeaders']); |
3 | 24 |
|
4 | 25 | export default { |
5 | 26 | patterns: [ |
6 | 27 | '*://www.youtube.com/watch?v=*', |
7 | 28 | '*://m.youtube.com/watch?v=*', |
8 | 29 | '*://youtu.be/*', |
| 30 | + '*://music.youtube.com/watch?v=*', |
| 31 | + '*://gaming.youtube.com/watch?v=*', |
9 | 32 | ], |
10 | 33 | getVideo: async (url) => { |
11 | | - const r = /(?:v=|youtu\.be\/)([^?&$]+)/.exec(url); |
12 | | - let id; |
13 | | - if (r) { |
14 | | - id = r[1]; |
15 | | - } else { |
16 | | - throw Error('Could not get video ID of URL: ' + url); |
| 34 | + const id = ytdl.getURLVideoID(url); |
| 35 | + if (ytdlCache.has(id)) { |
| 36 | + return id; |
17 | 37 | } |
18 | | - const meta = await util.ajax('https://www.youtube.com/get_video_info', { |
19 | | - data: { ps: 'default', gl: 'US', hl: 'en', video_id: id }, |
20 | | - cache: { |
21 | | - transform: (response) => { |
22 | | - if (response.status === 'fail') { |
23 | | - throw Error('failed to get video info: ' + response.reason); |
24 | | - } |
25 | | - return { |
26 | | - length: parseInt(response.length_seconds, 10), |
27 | | - title: response.title, |
28 | | - views: parseInt(response.view_count, 10), |
29 | | - user: { name: response.author }, |
30 | | - }; |
31 | | - }, |
32 | | - ttl: 1000 * 60 * 30 // 30 min |
33 | | - }, |
34 | | - }); |
35 | | - return { |
| 38 | + const info = await ytdl.getBasicInfo(url); |
| 39 | + const meta = { |
36 | 40 | // Canonical YouTube URL. |
37 | | - url: 'https://www.youtube.com/watch?v=' + id, |
| 41 | + url: info.video_url, |
| 42 | + length: parseInt(info.length_seconds, 10), |
| 43 | + title: info.title, |
| 44 | + views: parseInt(info.view_count, 10), |
| 45 | + user: { |
| 46 | + url: info.author.channel_url, |
| 47 | + name: info.author.name, |
| 48 | + thumbnail: info.author.avatar, |
| 49 | + verified: info.author.verified, |
| 50 | + }, |
38 | 51 |
|
39 | 52 | // Using medium quality gives a screenshot without black bars. |
40 | | - thumbnail: 'https://i.ytimg.com/vi/' + id + |
| 53 | + thumbnail: 'https://i.ytimg.com/vi/' + info.video_id + |
41 | 54 | '/mqdefault.jpg?custom=true&w=196&h=110&stc=true&jpg444=true&' + |
42 | 55 | 'jpgq=90&sp=68', |
43 | 56 |
|
44 | | - game: null, |
45 | | - ...meta, |
| 57 | + game: info.media && info.media.game ? { |
| 58 | + name: info.media.game, |
| 59 | + url: info.media.game_url, |
| 60 | + thumbnail: info.media.image, |
| 61 | + } : null, |
46 | 62 | }; |
| 63 | + ytdlCache.push(id, meta); |
| 64 | + return meta; |
47 | 65 | }, |
48 | 66 | getAllVideos: async () => { |
49 | 67 | const body = await util.ajax('https://www.youtube.com/feed/subscriptions', { |
@@ -110,8 +128,7 @@ export default { |
110 | 128 | url: videoUrl, |
111 | 129 | thumbnail, |
112 | 130 | title: item.title.simpleText, |
113 | | - desc: |
114 | | - item.descriptionSnippet && item.descriptionSnippet.simpleText, |
| 131 | + desc: item.descriptionSnippet && item.descriptionSnippet.simpleText, |
115 | 132 | length: length ? util.timeToSeconds(length.simpleText) : null, |
116 | 133 | views: views ? parseInt(views.replace(/,/g, ''), 10) : null, |
117 | 134 | timestamp, |
|
0 commit comments