[READ-ONLY] Mirror of https://github.com/probablykasper/thumbnail-grabber. Chrome extension for grabbing thumbnails/covers
browser-extension chrome chrome-extension chrome-extention extension
0

Configure Feed

Select the types of activity you want to include in your feed.

Added support for YouTube Music

Kasper (Dec 11, 2020, 8:34 PM +0100) 75559536 67a5f957

+105 -38
+77 -34
src/content-script.js
··· 69 69 }, 5000); 70 70 } 71 71 72 + async function getYouTubeThumbnail(id) { 73 + const urls = [ 74 + `https://img.youtube.com/vi/${id}/maxresdefault.jpg`, 75 + `https://img.youtube.com/vi/${id}/sddefault.jpg`, 76 + `https://img.youtube.com/vi/${id}/hqdefault.jpg`, 77 + ]; 78 + const urlPromises = urls.map(url => 79 + new Promise(async (resolve, reject) => { 80 + try { 81 + await xhr(url, 'GET', 'blob'); 82 + resolve(url); 83 + } catch(err) { 84 + reject(err); 85 + } 86 + }) 87 + ); 88 + let error = ''; 89 + for (const urlPromise of urlPromises) { 90 + try { 91 + const url = await urlPromise; 92 + return url; 93 + break; 94 + } catch(err) { 95 + error = err; 96 + continue; 97 + } 98 + } 99 + throw `No thumbnail found (${error})` 100 + } 101 + 102 + function googleUserContentUrl(urlObj) { 103 + // This is a `googleusercontent.com` url, which ends in something 104 + // like `=w60-h60-l90-rj`. Not sure what `l90` or `rj` mean, but it seems 105 + // to be the same without them. We replace all of this with `=s0` to get 106 + // the original size of the image. 107 + // Info about `googleusercontent.com` urls: 108 + // https://sites.google.com/site/picasaresources/Home/Picasa-FAQ/google-photos-1/how-to/how-to-get-a-direct-link-to-an-image 109 + var imgPathname = urlObj.pathname.split('=')[0]+'=s0' 110 + return urlObj.origin + imgPathname + urlObj.search + urlObj.hash 111 + } 112 + 72 113 var img = thumbnailGrabber.querySelector('img'); 73 114 var imgUrl, filename; 74 115 async function setup(newUrl) { ··· 90 131 const oembed = await xhr(oembedUrl, 'POST', 'json'); 91 132 imgUrl = oembed.thumbnail_url; 92 133 } 134 + } else if (site == 'youtube music') { 135 + if (newUrl != location.href) throw 'For YouTube Music, you need to be at the URL' 136 + filename = 'Cover'; 137 + var coverImg = document.querySelector('.ytmusic-player-bar.image'); 138 + var iurl = new URL(coverImg.src); 139 + if (iurl.hostname == 'i.ytimg.com') { 140 + if (!iurl.pathname.startsWith('/vi/')) { 141 + throw "i.ytimg.com url doesn't start with /vi/" 142 + } 143 + const id = iurl.pathname.substr(4, 11); 144 + imgUrl = await getYouTubeThumbnail(id); 145 + } else { 146 + imgUrl = googleUserContentUrl(iurl) 147 + } 148 + } else if (site == 'youtube music playlist') { 149 + if (newUrl != location.href) throw 'For YouTube Music, you need to be at the URL' 150 + filename = 'Cover'; 151 + var coverImg = document.querySelector('#img'); 152 + var iurl = new URL(coverImg.src); 153 + if (iurl.hostname == 'i.ytimg.com') { 154 + if (iurl.pathname.startsWith('/vi/')) { 155 + const id = iurl.pathname.substr(4, 11); 156 + imgUrl = await getYouTubeThumbnail(id); 157 + } 158 + } else { 159 + imgUrl = googleUserContentUrl(iurl) 160 + } 93 161 } else if (site == 'youtube') { 94 162 filename = 'Thumbnail'; 95 163 if (newUrl == location.href) { ··· 98 166 imgUrl = schemaObject.thumbnailUrl[0]; 99 167 } else { 100 168 const id = new URL(newUrl).searchParams.get('v'); 101 - const urls = [ 102 - `https://img.youtube.com/vi/${id}/maxresdefault.jpg`, 103 - `https://img.youtube.com/vi/${id}/sddefault.jpg`, 104 - `https://img.youtube.com/vi/${id}/hqdefault.jpg`, 105 - ]; 106 - const urlPromises = urls.map(url => 107 - new Promise(async (resolve, reject) => { 108 - try { 109 - await xhr(url, 'GET', 'blob'); 110 - resolve(url); 111 - } catch(err) { 112 - reject(err); 113 - } 114 - }) 115 - ); 116 - let success; 117 - let error = ''; 118 - for (const urlPromise of urlPromises) { 119 - try { 120 - const url = await urlPromise; 121 - imgUrl = url; 122 - success = true; 123 - break; 124 - } catch(err) { 125 - error = err; 126 - continue; 127 - } 128 - } 129 - if (!success) throw `No thumbnail found (${error})` 169 + imgUrl = await getYouTubeThumbnail(id); 130 170 } 131 171 } else { 132 172 throw 'Not supported on this URL'; 133 173 } 134 174 const fileExt = imgUrl.split('.').pop(-1); 135 - filename = filename+'.'+fileExt; 175 + if (fileExt == 'jpg' || fileExt == 'png') { 176 + filename = filename+'.'+fileExt; 177 + } else { 178 + filename = filename+'.jpg'; 179 + } 136 180 img.src = imgUrl; 137 181 } 138 182 ··· 154 198 e.ctrlKey == false && 155 199 e.shiftKey == false 156 200 ) { 201 + e.preventDefault(); 157 202 close(); 158 203 } 159 204 } ··· 291 336 292 337 async function copy(url) { 293 338 const imgBlob = await xhr(url, 'GET', 'blob'); 294 - if (url.endsWith('.jpg') || url.endsWith('.jpeg')) { 295 - await convertToPng(imgBlob); 296 - } else if (url.endsWith('.png')) { 339 + if (url.endsWith('.png')) { 297 340 await copyToClipboard(imgBlob); 298 341 } else { 299 - throw 'Format not supported'; 342 + await convertToPng(imgBlob); 300 343 } 301 344 }
+28 -4
src/modules/url-util.js
··· 3 3 // to manifest.json.permissions 4 4 { 5 5 name: 'soundcloud', 6 - matchPattern: '*://*.soundcloud.com/*/*', 6 + matchPattern: '*://soundcloud.com/*/*', 7 7 checkUrl: (url) => 8 - url.hostname.endsWith('soundcloud.com') 8 + url.hostname.endsWith('soundcloud.com') // allow www 9 9 && url.pathname.split('/').length-1 >= 2 10 10 }, 11 11 { 12 + name: 'youtube music', 13 + matchPattern: '*://music.youtube.com/watch?*', 14 + checkUrl: (url) => 15 + url.hostname == 'music.youtube.com' 16 + && url.pathname == '/watch' 17 + && url.searchParams.has('v') 18 + }, 19 + { 20 + name: 'youtube music playlist', 21 + matchPattern: '*://music.youtube.com/playlist?*', 22 + checkUrl: (url) => 23 + url.hostname == 'music.youtube.com' 24 + && url.pathname == '/playlist' 25 + && url.searchParams.has('list') 26 + }, 27 + { 12 28 name: 'youtube', 13 - matchPattern: '*://*.youtube.com/watch?*', 29 + matchPattern: '*://youtube.com/watch?*', 14 30 checkUrl: (url) => 31 + // allow www 15 32 url.hostname.endsWith('youtube.com') 16 33 && url.pathname == '/watch' 17 34 && url.searchParams.has('v') 18 - } 35 + }, 36 + // { 37 + // name: 'spotify', 38 + // matchPattern: '*://open.spotify.com/album/*', 39 + // checkUrl: (url) => 40 + // url.hostname.endsWith('open.spotify.com') 41 + // && url.pathname.startsWith('/album') 42 + // }, 19 43 ]; 20 44 21 45 const matchPatterns = [];