From 78ab2ed4950ee5a2ce54f85cdcccc827f33b367c Mon Sep 17 00:00:00 2001 From: qxz Date: Tue, 19 May 2020 19:01:24 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=94=AF=E6=8C=81=E7=9A=84go?= =?UTF-8?q?index=E7=89=88=E6=9C=AC=E5=8F=B7=EF=BC=8C=E6=B7=BB=E5=8A=A0inde?= =?UTF-8?q?x=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go2index/index.js | 871 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 872 insertions(+), 1 deletion(-) create mode 100644 go2index/index.js diff --git a/go2index/index.js b/go2index/index.js new file mode 100644 index 0000000..5127c2f --- /dev/null +++ b/go2index/index.js @@ -0,0 +1,871 @@ +var authConfig = { + "siteName": "Achirou's Cloud", // 网站名称 + "version" : "1.1.0", // 程序版本 + "theme" : "acrou", + /*"client_id": "202264815644.apps.googleusercontent.com", + "client_secret": "X4Z3ca8xfWDb1Voo-F9a7ZxJ",*/ + // 强烈推荐使用自己的 client_id 和 client_secret + "client_id": "", + "client_secret": "", + "refresh_token": "", // 授权 token + /** + * 设置要显示的多个云端硬盘;按格式添加多个 + * [id]: 可以是 团队盘id、子文件夹id、或者"root"(代表个人盘根目录); + * [name]: 显示的名称 + * [user]: Basic Auth 的用户名 + * [pass]: Basic Auth 的密码 + * [protect_file_link]: Basic Auth 是否用于保护文件链接,默认值(不设置时)为 false,即不保护文件链接(方便 直链下载/外部播放 等) + * 每个盘的 Basic Auth 都可以单独设置。Basic Auth 默认保护该盘下所有文件夹/子文件夹路径 + * 【注意】默认不保护文件链接,这样可以方便 直链下载/外部播放; + * 如果要保护文件链接,需要将 protect_file_link 设置为 true,此时如果要进行外部播放等操作,需要将 host 替换为 user:pass@host 的 形式 + * 不需要 Basic Auth 的盘,保持 user 和 pass 同时为空即可。(直接不设置也可以) + * 【注意】对于id设置为为子文件夹id的盘将不支持搜索功能(不影响其他盘)。 + */ + "roots": [ + { + id: "", + name: "TeamDrive", + pass: "", + }, + { + id: "root", + name: "PrivateDrive", + user: '', + pass: "", + protect_file_link: true + }, + { + id: "", + name: "folder1", + pass: "", + } + ], + "default_gd": 0, + /** + * 文件列表页面每页显示的数量。【推荐设置值为 100 到 1000 之间】; + * 如果设置大于1000,会导致请求 drive api 时出错; + * 如果设置的值过小,会导致文件列表页面滚动条增量加载(分页加载)失效; + * 此值的另一个作用是,如果目录内文件数大于此设置值(即需要多页展示的),将会对首次列目录结果进行缓存。 + */ + "files_list_page_size": 50, + /** + * 搜索结果页面每页显示的数量。【推荐设置值为 50 到 1000 之间】; + * 如果设置大于1000,会导致请求 drive api 时出错; + * 如果设置的值过小,会导致搜索结果页面滚动条增量加载(分页加载)失效; + * 此值的大小影响搜索操作的响应速度。 + */ + "search_result_list_page_size": 50, + // 确认有 cors 用途的可以开启 + "enable_cors_file_down": false, + /** + * 上面的 basic auth 已经包含了盘内全局保护的功能。所以默认不再去认证 .password 文件内的密码; + * 如果在全局认证的基础上,仍需要给某些目录单独进行 .password 文件内的密码验证的话,将此选项设置为 true; + * 【注意】如果开启了 .password 文件密码验证,每次列目录都会额外增加查询目录内 .password 文件是否存在的开销。 + */ + "enable_password_file_verify": true +}; + +var themeOptions = { + //可选默认系统语言:en/zh-chs/zh-cht + languages: 'en' +} + +/** + * global functions + */ +const FUNCS = { + /** + * 转换成针对谷歌搜索词法相对安全的搜索关键词 + */ + formatSearchKeyword: function (keyword) { + let nothing = ""; + let space = " "; + if (!keyword) return nothing; + return keyword.replace(/(!=)|['"=<>/\\:]/g, nothing) + .replace(/[,,|(){}]/g, space) + .trim() + } + +}; + +/** + * global consts + * @type {{folder_mime_type: string, default_file_fields: string, gd_root_type: {share_drive: number, user_drive: number, sub_folder: number}}} + */ +const CONSTS = new (class { + default_file_fields = 'parents,id,name,mimeType,modifiedTime,createdTime,fileExtension,size'; + gd_root_type = { + user_drive: 0, + share_drive: 1, + sub_folder: 2 + }; + folder_mime_type = 'application/vnd.google-apps.folder'; +})(); + + +// gd instances +var gds = []; + +function html(current_drive_order = 0, model = {}) { + return ` + + + + + + ${authConfig.siteName} + + + + +
+ + + +`; +}; + +addEventListener('fetch', event => { + event.respondWith(handleRequest(event.request)); +}); + +/** + * Fetch and log a request + * @param {Request} request + */ +async function handleRequest(request) { + if (gds.length === 0) { + for (let i = 0; i < authConfig.roots.length; i++) { + const gd = new googleDrive(authConfig, i); + await gd.init(); + gds.push(gd) + } + // 这个操作并行,提高效率 + let tasks = []; + gds.forEach(gd => { + tasks.push(gd.initRootType()); + }); + for (let task of tasks) { + await task; + } + } + + // 从 path 中提取 drive order + // 并根据 drive order 获取对应的 gd instance + let gd; + let url = new URL(request.url); + let path = url.pathname; + + /** + * 重定向至起始页 + * @returns {Response} + */ + function redirectToIndexPage() { + return new Response('', {status: 301, headers: {'Location': `/${authConfig.default_gd}:/`}}); + } + + if (path == '/') return redirectToIndexPage(); + if (path.toLowerCase() == '/favicon.ico') { + // 后面可以找一个 favicon + return new Response('', {status: 404}) + } + + // 特殊命令格式 + const command_reg = /^\/(?\d+):(?[a-zA-Z0-9]+)(\/.*)?$/g; + const match = command_reg.exec(path); + let command; + if (match) { + const num = match.groups.num; + const order = Number(num); + if (order >= 0 && order < gds.length) { + gd = gds[order]; + } else { + return redirectToIndexPage() + } + // basic auth + for (const r = gd.basicAuthResponse(request); r;) return r; + command = match.groups.command; + + // 搜索 + if (command === 'search') { + if (request.method === 'POST') { + // 搜索结果 + return handleSearch(request, gd); + } else { + const params = url.searchParams; + // 搜索页面 + return new Response(html(gd.order, { + q: params.get("q") || '', + is_search_page: true, + root_type: gd.root_type + }), + { + status: 200, + headers: {'Content-Type': 'text/html; charset=utf-8'} + }); + } + } else if (command === 'id2path' && request.method === 'POST') { + return handleId2Path(request, gd) + } else if (command === 'view'){ + const params = url.searchParams; + return gd.view(params.get("url"), request.headers.get('Range')); + } else if (command!=='down' && request.method === 'GET'){ + return new Response(html(gd.order, {root_type: gd.root_type}), { + status: 200, + headers: {'Content-Type': 'text/html; charset=utf-8'} + }); + } + } + const reg = new RegExp(`^(/\\d+:)${command}/`, 'g'); + path = path.replace(reg,(p1,p2)=>{ + return p2+'/' + }) + // 期望的 path 格式 + const common_reg = /^\/\d+:\/.*$/g; + try { + if (!path.match(common_reg)) { + return redirectToIndexPage(); + } + let split = path.split("/"); + let order = Number(split[1].slice(0, -1)); + if (order >= 0 && order < gds.length) { + gd = gds[order]; + } else { + return redirectToIndexPage() + } + } catch (e) { + return redirectToIndexPage() + } + + // basic auth + // for (const r = gd.basicAuthResponse(request); r;) return r; + const basic_auth_res = gd.basicAuthResponse(request); + path = path.replace(gd.url_path_prefix, '') || '/'; + if (request.method == 'POST') { + return basic_auth_res || apiRequest(request, gd); + } + + let action = url.searchParams.get('a'); + + if (path.substr(-1) == '/' || action != null) { + return basic_auth_res || new Response(html(gd.order, {root_type: gd.root_type}), { + status: 200, + headers: {'Content-Type': 'text/html; charset=utf-8'} + }); + } else { + if (path.split('/').pop().toLowerCase() == ".password") { + return basic_auth_res || new Response("", {status: 404}); + } + let file = await gd.file(path); + let range = request.headers.get('Range'); + if (gd.root.protect_file_link && basic_auth_res) return basic_auth_res; + const is_down = !(command && command=='down'); + return gd.down(file.id, range, is_down); + } +} + + +async function apiRequest(request, gd) { + let url = new URL(request.url); + let path = url.pathname; + path = path.replace(gd.url_path_prefix, '') || '/'; + + let option = {status: 200, headers: {'Access-Control-Allow-Origin': '*'}} + + if (path.substr(-1) == '/') { + let deferred_pass = gd.password(path); + let body = await request.text(); + body = JSON.parse(body); + // 这样可以提升首次列目录时的速度。缺点是,如果password验证失败,也依然会产生列目录的开销 + let deferred_list_result = gd.list(path, body.page_token, Number(body.page_index)); + + // check .password file, if `enable_password_file_verify` is true + if (authConfig['enable_password_file_verify']) { + let password = await gd.password(path); + // console.log("dir password", password); + if (password && password.replace("\n", "") !== body.password) { + let html = `{"error": {"code": 401,"message": "password error."}}`; + return new Response(html, option); + } + } + + let list_result = await deferred_list_result; + return new Response(JSON.stringify(list_result), option); + } else { + let file = await gd.file(path); + let range = request.headers.get('Range'); + return new Response(JSON.stringify(file)); + } +} + +// 处理 search +async function handleSearch(request, gd) { + const option = {status: 200, headers: {'Access-Control-Allow-Origin': '*'}}; + let body = await request.text(); + body = JSON.parse(body); + let search_result = await + gd.search(body.q || '', body.page_token, Number(body.page_index)); + return new Response(JSON.stringify(search_result), option); +} + +/** + * 处理 id2path + * @param request 需要 id 参数 + * @param gd + * @returns {Promise} 【注意】如果从前台接收的id代表的项目不在目标gd盘下,那么response会返回给前台一个空字符串"" + */ +async function handleId2Path(request, gd) { + const option = {status: 200, headers: {'Access-Control-Allow-Origin': '*'}}; + let body = await request.text(); + body = JSON.parse(body); + let path = await gd.findPathById(body.id); + return new Response(path || '', option); +} + +class googleDrive { + constructor(authConfig, order) { + // 每个盘对应一个order,对应一个gd实例 + this.order = order; + this.root = authConfig.roots[order]; + this.root.protect_file_link = this.root.protect_file_link || false; + this.url_path_prefix = `/${order}:`; + this.authConfig = authConfig; + // TODO: 这些缓存的失效刷新策略,后期可以制定一下 + // path id + this.paths = []; + // path file + this.files = []; + // path pass + this.passwords = []; + // id <-> path + this.id_path_cache = {}; + this.id_path_cache[this.root['id']] = '/'; + this.paths["/"] = this.root['id']; + /*if (this.root['pass'] != "") { + this.passwords['/'] = this.root['pass']; + }*/ + // this.init(); + } + + /** + * 初次授权;然后获取 user_drive_real_root_id + * @returns {Promise} + */ + async init() { + await this.accessToken(); + /*await (async () => { + // 只获取1次 + if (authConfig.user_drive_real_root_id) return; + const root_obj = await (gds[0] || this).findItemById('root'); + if (root_obj && root_obj.id) { + authConfig.user_drive_real_root_id = root_obj.id + } + })();*/ + // 等待 user_drive_real_root_id ,只获取1次 + if (authConfig.user_drive_real_root_id) return; + const root_obj = await (gds[0] || this).findItemById('root'); + if (root_obj && root_obj.id) { + authConfig.user_drive_real_root_id = root_obj.id + } + } + + /** + * 获取根目录类型,设置到 root_type + * @returns {Promise} + */ + async initRootType() { + const root_id = this.root['id']; + const types = CONSTS.gd_root_type; + if (root_id === 'root' || root_id === authConfig.user_drive_real_root_id) { + this.root_type = types.user_drive; + } else { + const obj = await this.getShareDriveObjById(root_id); + this.root_type = obj ? types.share_drive : types.sub_folder; + } + } + + /** + * Returns a response that requires authorization, or null + * @param request + * @returns {Response|null} + */ + basicAuthResponse(request) { + const user = this.root.user || '', + pass = this.root.pass || '', + _401 = new Response('Unauthorized', { + headers: {'WWW-Authenticate': `Basic realm="goindex:drive:${this.order}"`}, + status: 401 + }); + if (user || pass) { + const auth = request.headers.get('Authorization') + if (auth) { + try { + const [received_user, received_pass] = atob(auth.split(' ').pop()).split(':'); + return (received_user === user && received_pass === pass) ? null : _401; + } catch (e) {} + } + } else return null; + return _401; + } + + async view(url, range = '', inline = true) { + let requestOption = await this.requestOption(); + requestOption.headers['Range'] = range; + let res = await fetch(url, requestOption); + const {headers} = res = new Response(res.body, res) + this.authConfig.enable_cors_file_down && headers.append('Access-Control-Allow-Origin', '*'); + inline === true && headers.set('Content-Disposition', 'inline'); + return res; + } + + async down(id, range = '', inline = false) { + let url = `https://www.googleapis.com/drive/v3/files/${id}?alt=media`; + let requestOption = await this.requestOption(); + requestOption.headers['Range'] = range; + let res = await fetch(url, requestOption); + const {headers} = res = new Response(res.body, res) + this.authConfig.enable_cors_file_down && headers.append('Access-Control-Allow-Origin', '*'); + inline === true && headers.set('Content-Disposition', 'inline'); + return res; + } + + async file(path) { + if (typeof this.files[path] == 'undefined') { + this.files[path] = await this._file(path); + } + return this.files[path]; + } + + async _file(path) { + let arr = path.split('/'); + let name = arr.pop(); + name = decodeURIComponent(name).replace(/\'/g, "\\'"); + let dir = arr.join('/') + '/'; + // console.log(name, dir); + let parent = await this.findPathId(dir); + // console.log(parent); + let url = 'https://www.googleapis.com/drive/v3/files'; + let params = {'includeItemsFromAllDrives': true, 'supportsAllDrives': true}; + params.q = `'${parent}' in parents and name = '${name}' and trashed = false`; + params.fields = "files(id, name, mimeType, size ,createdTime, modifiedTime, iconLink, thumbnailLink)"; + url += '?' + this.enQuery(params); + let requestOption = await this.requestOption(); + let response = await fetch(url, requestOption); + let obj = await response.json(); + // console.log(obj); + return obj.files[0]; + } + + // 通过reqeust cache 来缓存 + async list(path, page_token = null, page_index = 0) { + if (this.path_children_cache == undefined) { + // { :[ {nextPageToken:'',data:{}}, {nextPageToken:'',data:{}} ...], ...} + this.path_children_cache = {}; + } + + if (this.path_children_cache[path] + && this.path_children_cache[path][page_index] + && this.path_children_cache[path][page_index].data + ) { + let child_obj = this.path_children_cache[path][page_index]; + return { + nextPageToken: child_obj.nextPageToken || null, + curPageIndex: page_index, + data: child_obj.data + }; + } + + let id = await this.findPathId(path); + let result = await this._ls(id, page_token, page_index); + let data = result.data; + // 对有多页的,进行缓存 + if (result.nextPageToken && data.files) { + if (!Array.isArray(this.path_children_cache[path])) { + this.path_children_cache[path] = [] + } + this.path_children_cache[path][Number(result.curPageIndex)] = { + nextPageToken: result.nextPageToken, + data: data + }; + } + + return result + } + + + async _ls(parent, page_token = null, page_index = 0) { + // console.log("_ls", parent); + + if (parent == undefined) { + return null; + } + let obj; + let params = {'includeItemsFromAllDrives': true, 'supportsAllDrives': true}; + params.q = `'${parent}' in parents and trashed = false AND name !='.password'`; + params.orderBy = 'folder,name,modifiedTime desc'; + params.fields = "nextPageToken, files(id, name, mimeType, size , modifiedTime, thumbnailLink, description)"; + params.pageSize = this.authConfig.files_list_page_size; + + if (page_token) { + params.pageToken = page_token; + } + let url = 'https://www.googleapis.com/drive/v3/files'; + url += '?' + this.enQuery(params); + let requestOption = await this.requestOption(); + let response = await fetch(url, requestOption); + obj = await response.json(); + + return { + nextPageToken: obj.nextPageToken || null, + curPageIndex: page_index, + data: obj + }; + + /*do { + if (pageToken) { + params.pageToken = pageToken; + } + let url = 'https://www.googleapis.com/drive/v3/files'; + url += '?' + this.enQuery(params); + let requestOption = await this.requestOption(); + let response = await fetch(url, requestOption); + obj = await response.json(); + files.push(...obj.files); + pageToken = obj.nextPageToken; + } while (pageToken);*/ + + } + + async password(path) { + if (this.passwords[path] !== undefined) { + return this.passwords[path]; + } + + // console.log("load", path, ".password", this.passwords[path]); + + let file = await this.file(path + '.password'); + if (file == undefined) { + this.passwords[path] = null; + } else { + let url = `https://www.googleapis.com/drive/v3/files/${file.id}?alt=media`; + let requestOption = await this.requestOption(); + let response = await this.fetch200(url, requestOption); + this.passwords[path] = await response.text(); + } + + return this.passwords[path]; + } + + + /** + * 通过 id 获取 share drive 信息 + * @param any_id + * @returns {Promise} 任何非正常情况都返回 null + */ + async getShareDriveObjById(any_id) { + if (!any_id) return null; + if ('string' !== typeof any_id) return null; + + let url = `https://www.googleapis.com/drive/v3/drives/${any_id}`; + let requestOption = await this.requestOption(); + let res = await fetch(url, requestOption); + let obj = await res.json(); + if (obj && obj.id) return obj; + + return null + } + + + /** + * 搜索 + * @returns {Promise<{data: null, nextPageToken: null, curPageIndex: number}>} + */ + async search(origin_keyword, page_token = null, page_index = 0) { + const types = CONSTS.gd_root_type; + const is_user_drive = this.root_type === types.user_drive; + const is_share_drive = this.root_type === types.share_drive; + + const empty_result = { + nextPageToken: null, + curPageIndex: page_index, + data: null + }; + + if (!is_user_drive && !is_share_drive) { + return empty_result; + } + let keyword = FUNCS.formatSearchKeyword(origin_keyword); + if (!keyword) { + // 关键词为空,返回 + return empty_result; + } + let words = keyword.split(/\s+/); + let name_search_str = `name contains '${words.join("' AND name contains '")}'`; + + // corpora 为 user 是个人盘 ,为 drive 是团队盘。配合 driveId + let params = {}; + if (is_user_drive) { + params.corpora = 'user' + } + if (is_share_drive) { + params.corpora = 'drive'; + params.driveId = this.root.id; + // This parameter will only be effective until June 1, 2020. Afterwards shared drive items will be included in the results. + params.includeItemsFromAllDrives = true; + params.supportsAllDrives = true; + } + if (page_token) { + params.pageToken = page_token; + } + params.q = `trashed = false AND name !='.password' AND (${name_search_str})`; + params.fields = "nextPageToken, files(id, name, mimeType, size , modifiedTime, thumbnailLink, description)"; + params.pageSize = this.authConfig.search_result_list_page_size; + // params.orderBy = 'folder,name,modifiedTime desc'; + + let url = 'https://www.googleapis.com/drive/v3/files'; + url += '?' + this.enQuery(params); + // console.log(params) + let requestOption = await this.requestOption(); + let response = await fetch(url, requestOption); + let res_obj = await response.json(); + + return { + nextPageToken: res_obj.nextPageToken || null, + curPageIndex: page_index, + data: res_obj + }; + } + + + /** + * 一层一层的向上获取这个文件或文件夹的上级文件夹的 file 对象。注意:会很慢!!! + * 最多向上寻找到当前 gd 对象的根目录 (root id) + * 只考虑一条单独的向上链。 + * 【注意】如果此id代表的项目不在目标gd盘下,那么此函数会返回null + * + * @param child_id + * @param contain_myself + * @returns {Promise<[]>} + */ + async findParentFilesRecursion(child_id, contain_myself = true) { + const gd = this; + const gd_root_id = gd.root.id; + const user_drive_real_root_id = authConfig.user_drive_real_root_id; + const is_user_drive = gd.root_type === CONSTS.gd_root_type.user_drive; + + // 自下向上查询的终点目标id + const target_top_id = is_user_drive ? user_drive_real_root_id : gd_root_id; + const fields = CONSTS.default_file_fields; + + // [{},{},...] + const parent_files = []; + let meet_top = false; + + async function addItsFirstParent(file_obj) { + if (!file_obj) return; + if (!file_obj.parents) return; + if (file_obj.parents.length < 1) return; + + // ['','',...] + let p_ids = file_obj.parents; + if (p_ids && p_ids.length > 0) { + // its first parent + const first_p_id = p_ids[0]; + if (first_p_id === target_top_id) { + meet_top = true; + return; + } + const p_file_obj = await gd.findItemById(first_p_id); + if (p_file_obj && p_file_obj.id) { + parent_files.push(p_file_obj); + await addItsFirstParent(p_file_obj); + } + } + } + + const child_obj = await gd.findItemById(child_id); + if (contain_myself) { + parent_files.push(child_obj); + } + await addItsFirstParent(child_obj); + + return meet_top ? parent_files : null + } + + /** + * 获取相对于本盘根目录的path + * @param child_id + * @returns {Promise} 【注意】如果此id代表的项目不在目标gd盘下,那么此方法会返回空字符串"" + */ + async findPathById(child_id) { + if (this.id_path_cache[child_id]) { + return this.id_path_cache[child_id]; + } + + const p_files = await this.findParentFilesRecursion(child_id); + if (!p_files || p_files.length < 1) return ''; + + let cache = []; + // 把查出来的每一级的path和id都缓存一下 + p_files.forEach((value, idx) => { + const is_folder = idx === 0 ? (p_files[idx].mimeType === CONSTS.folder_mime_type) : true; + let path = '/' + p_files.slice(idx).map(it => it.name).reverse().join('/'); + if (is_folder) path += '/'; + cache.push({id: p_files[idx].id, path: path}) + }); + + cache.forEach((obj) => { + this.id_path_cache[obj.id] = obj.path; + this.paths[obj.path] = obj.id + }); + + /*const is_folder = p_files[0].mimeType === CONSTS.folder_mime_type; + let path = '/' + p_files.map(it => it.name).reverse().join('/'); + if (is_folder) path += '/';*/ + + return cache[0].path; + } + + + // 根据id获取file item + async findItemById(id) { + const is_user_drive = this.root_type === CONSTS.gd_root_type.user_drive; + let url = `https://www.googleapis.com/drive/v3/files/${id}?fields=${CONSTS.default_file_fields}${is_user_drive ? '' : '&supportsAllDrives=true'}`; + let requestOption = await this.requestOption(); + let res = await fetch(url, requestOption); + return await res.json() + } + + async findPathId(path) { + let c_path = '/'; + let c_id = this.paths[c_path]; + + let arr = path.trim('/').split('/'); + for (let name of arr) { + c_path += name + '/'; + + if (typeof this.paths[c_path] == 'undefined') { + let id = await this._findDirId(c_id, name); + this.paths[c_path] = id; + } + + c_id = this.paths[c_path]; + if (c_id == undefined || c_id == null) { + break; + } + } + // console.log(this.paths); + return this.paths[path]; + } + + async _findDirId(parent, name) { + name = decodeURIComponent(name).replace(/\'/g, "\\'"); + + // console.log("_findDirId", parent, name); + + if (parent == undefined) { + return null; + } + + let url = 'https://www.googleapis.com/drive/v3/files'; + let params = {'includeItemsFromAllDrives': true, 'supportsAllDrives': true}; + params.q = `'${parent}' in parents and mimeType = 'application/vnd.google-apps.folder' and name = '${name}' and trashed = false`; + params.fields = "nextPageToken, files(id, name, mimeType)"; + url += '?' + this.enQuery(params); + let requestOption = await this.requestOption(); + let response = await fetch(url, requestOption); + let obj = await response.json(); + if (obj.files[0] == undefined) { + return null; + } + return obj.files[0].id; + } + + async accessToken() { + console.log("accessToken"); + if (this.authConfig.expires == undefined || this.authConfig.expires < Date.now()) { + const obj = await this.fetchAccessToken(); + if (obj.access_token != undefined) { + this.authConfig.accessToken = obj.access_token; + this.authConfig.expires = Date.now() + 3500 * 1000; + } + } + return this.authConfig.accessToken; + } + + async fetchAccessToken() { + console.log("fetchAccessToken"); + const url = "https://www.googleapis.com/oauth2/v4/token"; + const headers = { + 'Content-Type': 'application/x-www-form-urlencoded' + }; + const post_data = { + 'client_id': this.authConfig.client_id, + 'client_secret': this.authConfig.client_secret, + 'refresh_token': this.authConfig.refresh_token, + 'grant_type': 'refresh_token' + } + + let requestOption = { + 'method': 'POST', + 'headers': headers, + 'body': this.enQuery(post_data) + }; + + const response = await fetch(url, requestOption); + return await response.json(); + } + + async fetch200(url, requestOption) { + let response; + for (let i = 0; i < 3; i++) { + response = await fetch(url, requestOption); + console.log(response.status); + if (response.status != 403) { + break; + } + await this.sleep(800 * (i + 1)); + } + return response; + } + + async requestOption(headers = {}, method = 'GET') { + const accessToken = await this.accessToken(); + headers['authorization'] = 'Bearer ' + accessToken; + return {'method': method, 'headers': headers}; + } + + enQuery(data) { + const ret = []; + for (let d in data) { + ret.push(encodeURIComponent(d) + '=' + encodeURIComponent(data[d])); + } + return ret.join('&'); + } + + sleep(ms) { + return new Promise(function (resolve, reject) { + let i = 0; + setTimeout(function () { + console.log('sleep' + ms); + i++; + if (i >= 2) reject(new Error('i>=2')); + else resolve(i); + }, ms); + }) + } +} + +String.prototype.trim = function (char) { + if (char) { + return this.replace(new RegExp('^\\' + char + '+|\\' + char + '+$', 'g'), ''); + } + return this.replace(/^\s+|\s+$/g, ''); +}; diff --git a/package.json b/package.json index d17ca14..8caac7e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "go2index-theme-acrou", "version": "2.0.0", - "g2index": "1.0.0", + "g2index": "1.1.0", "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build",