├── .gitattributes ├── LICENSE ├── README.md ├── cf-worker └── index.js ├── config.php ├── functions.php └── index.php /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=PHP 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Kimiato 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 🚫停止更新(Stop updating) 3 | ❗可能目前已经不可用(May no longer be available) 4 | --- 5 | # DriveDirectLink 6 | DriveDirectLink 网盘直链下载,支持谷歌,蓝奏云,360网盘 7 | ## 谷歌网盘直链下载(使用cf-workers代理) 8 | ### 使用方法 9 | 格式如下: 10 | ``` 11 | https://网站地址/?id=文件ID 12 | ``` 13 | 例如 14 | google drive 分享链接 15 | ``` 16 | https://drive.google.com/open?id=1TSlvfrRrGrT8a_84iFDIkSwrxU_53D6T 17 | ``` 18 | 直链地址 19 | ``` 20 | https://网站地址/?id=1TSlvfrRrGrT8a_84iFDIkSwrxU_53D6T 21 | ``` 22 | ## 蓝奏云 23 | ### 获取直链地址 24 | 此方法会获取并展示直链地址,而不会直接下载 25 | 格式如下: 26 | ``` 27 | https://网站地址/?lz=文件ID 28 | ``` 29 | 例如 30 | 蓝奏云分享链接 31 | ``` 32 | https://www.lanzous.com/ibvifch 33 | ``` 34 | 直链获取地址 35 | ``` 36 | https://网站地址/?lz=ibvifch 37 | ``` 38 | 注意,有时候会无法获取,刷新即可 39 | ### 直链下载地址 40 | 此方法会直接跳转下载,但是经过测试发现,国外机器下载直链容易跳验证码,酌情使用 41 | 格式如下 42 | ``` 43 | https://网站地址/?lzd=文件ID 44 | ``` 45 | 例如 46 | 蓝奏云分享链接 47 | ``` 48 | https://www.lanzous.com/ibvifch 49 | ``` 50 | 直链下载地址 51 | ``` 52 | https://网站地址/?lzd=ibvifch 53 | ``` 54 | ## 360网盘 55 | ### 使用方法 56 | 格式如下: 57 | ``` 58 | https://网站地址/?360=文件ID 59 | ``` 60 | 例如 61 | 360 分享链接 62 | ``` 63 | https://yunpan.360.cn/surl_yYgjWMz8GhU 64 | ``` 65 | 直链地址 66 | ``` 67 | https://网站地址/?360=surl_yYgjWMz8GhU 68 | ``` 69 | --- 70 | ## 4月26日更新说明 71 | 更新谷歌网盘直链解析用cf-worker代理 72 | 不用梯子也可以直接直链下载网盘的内容了 73 | 添加360网盘解析 74 | 增加config文件,方便修改配置 75 | ### 如何使用cf-worker 76 | 申请cf-worker 77 | 将cf-worker文件夹中的index.js内容复制到cf-worker的左侧代码(Script)区域 78 | 然后将你的cf-worker链接填入config.php文件修改 79 | -------------------------------------------------------------------------------- /cf-worker/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @link https://github.com/reruin/workers/blob/e4e0876c572d3834d7e1c6bbc88aa3c279313d87/link/index.js 3 | * @license https://github.com/reruin/workers/blob/e4e0876c572d3834d7e1c6bbc88aa3c279313d87/LICENSE 4 | */ 5 | const googleDriveCtrl = async (ctx , view) => { 6 | const id = ctx.params.id 7 | const host = 'https://drive.google.com/' 8 | let result = { id } 9 | //if( ctx.query.output == 'json' ){ 10 | let resp = await request.get(`${host}file/d/${id}/view`) 11 | result.name = (resp.body.match(/=0 ) return result 14 | //} 15 | 16 | let downloadUrl = '' 17 | let code = (resp.body.match(/viewerData\s*=\s*(\{[\w\W]+?\});<\/script>/) || ['',''])[1] 18 | let preview_url = '' 19 | code = code.replace(/[\r\n]/g,'').replace(/'/g,'"') 20 | .replace('config','"config"') 21 | .replace('configJson','"configJson"') 22 | .replace('itemJson','"itemJson"') 23 | .replace(/,\}/g,'}') 24 | .replace(/\\x22/g,'"') 25 | .replace(/\\x27/g,"'") 26 | .replace(/\\x5b/g,'[') 27 | .replace(/\\x5d/g,']') 28 | .replace(/\\(r|n)/g,'') 29 | .replace(/\\\\u/g,'\\u').toString(16) 30 | 31 | if(code){ 32 | try{ 33 | code = JSON.parse(code) 34 | //获取码率 35 | // 37/1920x1080/9/0/115, 36 | // "size|url,size|url" 37 | const rates = {} , urls = [] 38 | code.itemJson[19][0][15][1].split(',').forEach(i => { 39 | let [c,_,r] = i.split(/[\/x]/) 40 | rates[c] = parseInt(r) 41 | }) 42 | code.itemJson[19][0][18][1].split(',').forEach( i => { 43 | let [rate , url] = i.split('|') 44 | urls.push({size:rates[rate] , url:decodeURIComponent(url)}) 45 | }) 46 | result.urls = urls.sort((a,b)=>a.size { 76 | const id = ctx.params.id 77 | const host = 'https://www.lanzous.com' 78 | const newHeaders = { 79 | 'user-agent':'Mozilla/5.0 (Linux; Android 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/37.0.0.0 Mobile Safari/537.36 MicroMessenger/6.3.25.861' 80 | } 81 | let result = { id } 82 | let { body } = await request.get(`${host}/tp/${id}` , {headers:{...newHeaders , referrer:''}}) 83 | let url = (body.match(/(?<=dpost\s*\+\s*["']\?)[^"']+/) || [false])[0] 84 | let base = (body.match(/(?<=urlp[^\=]*=\s*')[^']+/)|| [false])[0] 85 | result.name = (body.match(/(?<="md">)[^<]+/) || [''])[0].replace(/\s*$/,'') 86 | result.ext = (result.name.match(/\.([0-9a-z]+)$/) || ['',''])[1] 87 | if(url && base) result.url = base + '?' + url 88 | return result 89 | } 90 | 91 | const joyCtrl = async (ctx) => { 92 | const id = ctx.params.id 93 | const host = atob('aHR0cDovL3d3dy45MXBvcm4uY29t') 94 | const rnd = (min , max) => Math.floor(min+Math.random()*(max-min)) 95 | const ip = rnd(50,250) + "." + rnd(50,250) + "." + rnd(50,250)+ "." + rnd(50,250) 96 | const newHeaders = { 97 | 'PHPSESSID':'fff', 98 | 'CLIENT-IP':ip, 99 | 'HTTP_X_FORWARDED_FOR':ip, 100 | 'user-agent':ctx.req.headers.get('user-agent') 101 | } 102 | 103 | const decodeUrl = async (body) => { 104 | // eval / new Function is not allowed for security reasons. 105 | // let scrs = await request.get(`${host}/js/md5.js`) 106 | ;var encode_version = 'sojson.v5', lbbpm = '__0x33ad7', __0x33ad7=['QMOTw6XDtVE=','w5XDgsORw5LCuQ==','wojDrWTChFU=','dkdJACw=','w6zDpXDDvsKVwqA=','ZifCsh85fsKaXsOOWg==','RcOvw47DghzDuA==','w7siYTLCnw=='];(function(_0x94dee0,_0x4a3b74){var _0x588ae7=function(_0x32b32e){while(--_0x32b32e){_0x94dee0['push'](_0x94dee0['shift']());}};_0x588ae7(++_0x4a3b74);}(__0x33ad7,0x8f));var _0x5b60=function(_0x4d4456,_0x5a24e3){_0x4d4456=_0x4d4456-0x0;var _0xa82079=__0x33ad7[_0x4d4456];if(_0x5b60['initialized']===undefined){(function(){var _0xef6e0=typeof window!=='undefined'?window:typeof process==='object'&&typeof require==='function'&&typeof global==='object'?global:this;var _0x221728='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';_0xef6e0['atob']||(_0xef6e0['atob']=function(_0x4bb81e){var _0x1c1b59=String(_0x4bb81e)['replace'](/=+$/,'');for(var _0x5e3437=0x0,_0x2da204,_0x1f23f4,_0x3f19c1=0x0,_0x3fb8a7='';_0x1f23f4=_0x1c1b59['charAt'](_0x3f19c1++);~_0x1f23f4&&(_0x2da204=_0x5e3437%0x4?_0x2da204*0x40+_0x1f23f4:_0x1f23f4,_0x5e3437++%0x4)?_0x3fb8a7+=String['fromCharCode'](0xff&_0x2da204>>(-0x2*_0x5e3437&0x6)):0x0){_0x1f23f4=_0x221728['indexOf'](_0x1f23f4);}return _0x3fb8a7;});}());var _0x43712e=function(_0x2e9442,_0x305a3a){var _0x3702d8=[],_0x234ad1=0x0,_0xd45a92,_0x5a1bee='',_0x4a894e='';_0x2e9442=atob(_0x2e9442);for(var _0x67ab0e=0x0,_0x1753b1=_0x2e9442['length'];_0x67ab0e<_0x1753b1;_0x67ab0e++){_0x4a894e+='%'+('00'+_0x2e9442['charCodeAt'](_0x67ab0e)['toString'](0x10))['slice'](-0x2);}_0x2e9442=decodeURIComponent(_0x4a894e);for(var _0x246dd5=0x0;_0x246dd5<0x100;_0x246dd5++){_0x3702d8[_0x246dd5]=_0x246dd5;}for(_0x246dd5=0x0;_0x246dd5<0x100;_0x246dd5++){_0x234ad1=(_0x234ad1+_0x3702d8[_0x246dd5]+_0x305a3a['charCodeAt'](_0x246dd5%_0x305a3a['length']))%0x100;_0xd45a92=_0x3702d8[_0x246dd5];_0x3702d8[_0x246dd5]=_0x3702d8[_0x234ad1];_0x3702d8[_0x234ad1]=_0xd45a92;}_0x246dd5=0x0;_0x234ad1=0x0;for(var _0x39e824=0x0;_0x39e824<_0x2e9442['length'];_0x39e824++){_0x246dd5=(_0x246dd5+0x1)%0x100;_0x234ad1=(_0x234ad1+_0x3702d8[_0x246dd5])%0x100;_0xd45a92=_0x3702d8[_0x246dd5];_0x3702d8[_0x246dd5]=_0x3702d8[_0x234ad1];_0x3702d8[_0x234ad1]=_0xd45a92;_0x5a1bee+=String['fromCharCode'](_0x2e9442['charCodeAt'](_0x39e824)^_0x3702d8[(_0x3702d8[_0x246dd5]+_0x3702d8[_0x234ad1])%0x100]);}return _0x5a1bee;};_0x5b60['rc4']=_0x43712e;_0x5b60['data']={};_0x5b60['initialized']=!![];}var _0x4be5de=_0x5b60['data'][_0x4d4456];if(_0x4be5de===undefined){if(_0x5b60['once']===undefined){_0x5b60['once']=!![];}_0xa82079=_0x5b60['rc4'](_0xa82079,_0x5a24e3);_0x5b60['data'][_0x4d4456]=_0xa82079;}else{_0xa82079=_0x4be5de;}return _0xa82079;};if(typeof encode_version!=='undefined'&&encode_version==='sojson.v5'){function strencode(_0x50cb35,_0x1e821d){var _0x59f053={'MDWYS':'0|4|1|3|2','uyGXL':function _0x3726b1(_0x2b01e8,_0x53b357){return _0x2b01e8(_0x53b357);},'otDTt':function _0x4f6396(_0x33a2eb,_0x5aa7c9){return _0x33a2eb<_0x5aa7c9;},'tPPtN':function _0x3a63ea(_0x1546a9,_0x3fa992){return _0x1546a9%_0x3fa992;}};var _0xd6483c=_0x59f053[_0x5b60('0x0','cEiQ')][_0x5b60('0x1','&]Gi')]('|'),_0x1a3127=0x0;while(!![]){switch(_0xd6483c[_0x1a3127++]){case'0':_0x50cb35=_0x59f053[_0x5b60('0x2','ofbL')](atob,_0x50cb35);continue;case'1':code='';continue;case'2':return _0x59f053[_0x5b60('0x3','mLzQ')](atob,code);case'3':for(i=0x0;_0x59f053[_0x5b60('0x4','J2rX')](i,_0x50cb35[_0x5b60('0x5','Z(CX')]);i++){k=_0x59f053['tPPtN'](i,len);code+=String['fromCharCode'](_0x50cb35[_0x5b60('0x6','s4(u')](i)^_0x1e821d['charCodeAt'](k));}continue;case'4':len=_0x1e821d[_0x5b60('0x7','!Mys')];continue;}break;}}}else{alert('');}; 107 | let args = (body.match(/(?<=strencode\()[^\)]+/) || [''])[0] 108 | .split(',') 109 | .map( i => i.replace(/(^"|"$)/g,'')) 110 | 111 | let source = strencode.apply(null , args) , url = '' 112 | if(source){ 113 | url = (source.match(/src\s*=\s*["']([^"']+)/) || ['',''])[1] 114 | } 115 | return url; 116 | } 117 | let { body } = await request.get(`${host}/view_video.php?viewkey=${id}`, {headers:newHeaders}) 118 | let result = { id } 119 | result.name =(body.match(/viewvideo-title">([^<]+)/) || ['',''])[1].replace(/[\r\n]/g,'').replace(/(^[\s]*|[\s]*$)/g,'') 120 | result.ext = 'mp4' 121 | result.url = await decodeUrl(body) 122 | return result 123 | } 124 | 125 | /* 126 | * 辅助 fetch 127 | */ 128 | const request = { 129 | async get(url , options = {}){ 130 | let mergeOptions = { 131 | ...options, 132 | method:'GET', 133 | headers:Object.assign( {} , options.headers || {} ) 134 | } 135 | if( mergeOptions.headers['cookie'] ){ 136 | mergeOptions.redentials = 'include' 137 | } 138 | if( mergeOptions.body ){ 139 | mergeOptions.body = JSON.stringify(mergeOptions.body); 140 | } 141 | if( mergeOptions.json ){ 142 | mergeOptions.headers['accept'] = 'application/json' 143 | } 144 | 145 | let response = await fetch(url , mergeOptions) 146 | if(mergeOptions.raw === true){ 147 | return response 148 | } 149 | let resp = { ...response , headers:{} } 150 | if(response.headers){ 151 | let headers = {} 152 | for(let i of response.headers.keys()){ 153 | headers[i] = response.headers.get(i) 154 | } 155 | resp.headers = headers 156 | } 157 | if( mergeOptions.json === true ){ 158 | resp.body = await response.json() 159 | }else{ 160 | resp.body = await response.text() 161 | } 162 | return resp 163 | } 164 | } 165 | 166 | const utils = { 167 | isPlainObject(obj){ 168 | if (typeof obj !== 'object' || obj === null) return false 169 | 170 | let proto = obj 171 | while (Object.getPrototypeOf(proto) !== null) { 172 | proto = Object.getPrototypeOf(proto) 173 | } 174 | return Object.getPrototypeOf(obj) === proto 175 | }, 176 | isType(v, type){ 177 | return Object.prototype.toString.call( v ) === `[object ${type}]` 178 | }, 179 | getRange(r , total){ 180 | if(!r) return [0 , total-1] 181 | let [, start, end] = r.match(/(\d*)-(\d*)/) || []; 182 | start = start ? parseInt(start) : 0 183 | end = end ? parseInt(end) : total - 1 184 | return [start , end] 185 | }, 186 | parserHeaders(headers){ 187 | let ret = {} 188 | for(let pair of headers.entries()){ 189 | ret[pair[0].toLowerCase()] = pair[1] 190 | } 191 | console.log(ret) 192 | return ret 193 | } 194 | } 195 | 196 | /* 197 | * 迷你框架 ctx 198 | */ 199 | class Context { 200 | constructor(event){ 201 | let req = new URL(event.request.url) 202 | let query = {} , params = {} , headers = {} 203 | 204 | for(let pair of req.searchParams.entries()) { 205 | query[pair[0]] = pair[1] 206 | } 207 | 208 | for(let pair of event.request.headers.entries()){ 209 | headers[pair[0].toLowerCase()] = pair[1] 210 | } 211 | 212 | this.event = event 213 | this.query = query 214 | this.params = params 215 | this.querystring = req.search 216 | this.pathname = req.pathname 217 | this.method = event.request.method 218 | this.host = req.host 219 | this.protocol = req.protocol 220 | this.headers = this.header = headers 221 | this._resHeaders = { } 222 | this._status = 200 223 | this._data = null 224 | } 225 | set(key , value){ 226 | this._resHeaders[key] = value 227 | } 228 | get res(){ 229 | return this.event.respondWith 230 | } 231 | get req(){ 232 | return this.event.request 233 | } 234 | set status(code){ 235 | this._status = code 236 | } 237 | get data(){ 238 | return this._data 239 | } 240 | set body(data){ 241 | if( utils.isType(data , 'Promise')){ 242 | //get readableStream object and merge haders 243 | this._data = data.then(r => new Response(r.body , { 244 | status:this._status, 245 | headers:{...utils.parserHeaders(r.headers),...this._resHeaders} 246 | })).catch(e => { 247 | console.log(e); 248 | }); 249 | //this._data = new Response(data , {status:this._status,headers:this._headers}) 250 | return 251 | } 252 | if(utils.isPlainObject(data)){ 253 | data = JSON.stringify(data) 254 | this.set('Content-Type',"application/json") 255 | }else{ 256 | this.set('Content-Type',"text/html; charset=utf-8") 257 | } 258 | this._data = new Response(data , {status:this._status,headers:this._resHeaders}) 259 | } 260 | redirect(url,code = 302){ 261 | this._data = Response.redirect( url.replace(/^\//,`${this.protocol}//${this.host}/`) , code) 262 | } 263 | } 264 | 265 | /* 266 | * 迷你框架,内置路由中间件 267 | */ 268 | class App { 269 | constructor(){ 270 | this.routes = [] 271 | this.middlewares = [] 272 | } 273 | use(module){ 274 | this.middlewares.push( module ) 275 | } 276 | listen(){ 277 | addEventListener('fetch', event => { 278 | let ctx = new Context(event) 279 | event.respondWith(this.process(ctx)) 280 | }) 281 | } 282 | async process(ctx){ 283 | await this.compose([].concat(this.middlewares , this._routerMiddleware.bind(this)))(ctx); 284 | console.log( '>>>',typeof ctx.data) 285 | return ctx.data 286 | } 287 | router(method , expr , handler){ 288 | this.routes.push( { ...this._routeToReg(expr) , method:method.toUpperCase() , handler} ) 289 | } 290 | async _routerMiddleware(ctx , next){ 291 | let params = {} , handler 292 | let { pathname , method } = ctx 293 | for(let route of this.routes){ 294 | if( route.method == method ){ 295 | let hit = route.expr.exec( pathname ) 296 | if( hit ){ 297 | hit = hit.slice(1) 298 | route.key.forEach((i , idx) => { 299 | params[i] = hit[idx] 300 | }) 301 | handler = route.handler 302 | break; 303 | } 304 | } 305 | } 306 | ctx.params = params 307 | if(handler) await handler(ctx) 308 | return next() 309 | } 310 | _routeToReg(route){ 311 | let optionalParam = /\((.*?)\)/g , 312 | namedParam = /(\(\?)?:\w+/g, 313 | splatParam = /\*\w+/g, 314 | escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; 315 | let route_new = route.replace(escapeRegExp, '\\$&') 316 | .replace(optionalParam, '(?:$1)?') 317 | .replace(namedParam, function(match, optional) { 318 | return optional ? match : '([^/?]+)'; 319 | }) 320 | .replace(splatParam, '([^?]*?)'); 321 | let expr = new RegExp('^' + route_new + '(?:\\?([\\s\\S]*))?$'); 322 | let res = expr.exec(route).slice(1) 323 | res.pop() 324 | return { expr , key: res.map( i => i.replace(/^\:/,''))}; 325 | } 326 | compose(middlewares) { 327 | return (context) => middlewares.reduceRight( (a, b) => () => Promise.resolve(b(context,a)), () => {})(context) 328 | } 329 | } 330 | 331 | /* 332 | * 视图中间件,仅用于此项目 333 | */ 334 | const View = (options) => { 335 | return (ctx , next) => { 336 | if( ctx.render ) return next() 337 | ctx.render = async (data) => { 338 | let type = ctx.query.output 339 | if( !data.url ){ 340 | console.log(data) 341 | if( type == 'json'){ 342 | ctx.body = {status : -1 , message : "error" , ...data} 343 | }else{ 344 | ctx.body = data.message || '404' 345 | } 346 | }else{ 347 | if(type == 'json'){ 348 | ctx.body = {status : 0, result:data} 349 | } 350 | else if(type == 'redirect'){ 351 | ctx.redirect( data.url ) 352 | } 353 | else if(type == 'preview'){ 354 | if( ['mp3','ogg','m4a','acc'].includes(data.ext)){ 355 | ctx.body = `