@@ -15,7 +15,46 @@ const resolutionMatch = {
1515 "426" : 240
1616}
1717
18- const requestApiInfo = ( videoId , password ) => {
18+ const genericHeaders = {
19+ Accept : 'application/vnd.vimeo.*+json; version=3.4.10' ,
20+ 'User-Agent' : 'Vimeo/11.13.0 (com.vimeo; build:250619.102023.0; iOS 18.5.0) Alamofire/5.9.0 VimeoNetworking/5.0.0' ,
21+ Authorization : 'Basic MTMxNzViY2Y0NDE0YTQ5YzhjZTc0YmU0NjVjNDQxYzNkYWVjOWRlOTpHKzRvMmgzVUh4UkxjdU5FRW80cDNDbDhDWGR5dVJLNUJZZ055dHBHTTB4V1VzaG41bEx1a2hiN0NWYWNUcldSSW53dzRUdFRYZlJEZmFoTTArOTBUZkJHS3R4V2llYU04Qnl1bERSWWxUdXRidjNqR2J4SHFpVmtFSUcyRktuQw==' ,
22+ 'Accept-Language' : 'en-US,en;q=0.9' ,
23+ }
24+
25+ let bearer = '' ;
26+
27+ const getBearer = async ( refresh = false ) => {
28+ if ( bearer && ! refresh ) return bearer ;
29+
30+ const oauthResponse = await fetch (
31+ `https://api.vimeo.com/oauth/authorize/client?sizes=216,288,300,360,640,960,1280,1920&cdm_type=fairplay` ,
32+ {
33+ method : 'POST' ,
34+ body : JSON . stringify ( {
35+ scope : 'public private purchased create edit delete interact upload stats' ,
36+ grant_type : 'client_credentials' ,
37+ // device_identifier is a long ass base64 string of seemingly
38+ // random data, but it doesn't seem to be required, so we just omit it lol
39+ device_identifier : '' ,
40+ } ) ,
41+ headers : {
42+ ...genericHeaders ,
43+ 'Content-Type' : 'application/json' ,
44+ }
45+ }
46+ )
47+ . then ( a => a . json ( ) )
48+ . catch ( ( ) => { } ) ;
49+
50+ if ( ! oauthResponse || ! oauthResponse . access_token ) {
51+ return ;
52+ }
53+
54+ return bearer = oauthResponse . access_token ;
55+ }
56+
57+ const requestApiInfo = ( bearer , videoId , password ) => {
1958 if ( password ) {
2059 videoId += `:${ password } `
2160 }
@@ -24,10 +63,8 @@ const requestApiInfo = (videoId, password) => {
2463 `https://api.vimeo.com/videos/${ videoId } ` ,
2564 {
2665 headers : {
27- Accept : 'application/vnd.vimeo.*+json; version=3.4.2' ,
28- 'User-Agent' : 'Vimeo/10.19.0 (com.vimeo; build:101900.57.0; iOS 17.5.1) Alamofire/5.9.0 VimeoNetworking/5.0.0' ,
29- Authorization : 'Basic MTMxNzViY2Y0NDE0YTQ5YzhjZTc0YmU0NjVjNDQxYzNkYWVjOWRlOTpHKzRvMmgzVUh4UkxjdU5FRW80cDNDbDhDWGR5dVJLNUJZZ055dHBHTTB4V1VzaG41bEx1a2hiN0NWYWNUcldSSW53dzRUdFRYZlJEZmFoTTArOTBUZkJHS3R4V2llYU04Qnl1bERSWWxUdXRidjNqR2J4SHFpVmtFSUcyRktuQw==' ,
30- 'Accept-Language' : 'en'
66+ ...genericHeaders ,
67+ Authorization : `Bearer ${ bearer } ` ,
3168 }
3269 }
3370 )
@@ -151,9 +188,28 @@ export default async function(obj) {
151188 if ( quality < 240 ) quality = 240 ;
152189 if ( ! quality || obj . isAudioOnly ) quality = 9000 ;
153190
154- const info = await requestApiInfo ( obj . id , obj . password ) ;
191+ const bearerToken = await getBearer ( ) ;
192+ if ( ! bearerToken ) {
193+ return { error : "fetch.fail" } ;
194+ }
195+
196+ let info = await requestApiInfo ( bearerToken , obj . id , obj . password ) ;
155197 let response ;
156198
199+ // auth error, try to refresh the token
200+ if ( info ?. error_code === 8003 ) {
201+ const newBearer = await getBearer ( true ) ;
202+ if ( ! newBearer ) {
203+ return { error : "fetch.fail" } ;
204+ }
205+ info = await requestApiInfo ( newBearer , obj . id , obj . password ) ;
206+ }
207+
208+ // if there's still no info, then return a generic error
209+ if ( ! info || info . error_code ) {
210+ return { error : "fetch.empty" } ;
211+ }
212+
157213 if ( obj . isAudioOnly ) {
158214 response = await getHLS ( info . config_url , { ...obj , quality } ) ;
159215 }
0 commit comments