diff --git a/goindex-acrou/go2index/index-multiple-accounts.js b/goindex-acrou/go2index/index-multiple-accounts.js new file mode 100644 index 0000000..e3c8296 --- /dev/null +++ b/goindex-acrou/go2index/index-multiple-accounts.js @@ -0,0 +1,963 @@ +// =======Options START======= +var authConfig = { + siteName: "GoIndex-theme-acrou", // 网站名称 + version: "1.1.1", // 程序版本 + theme: "acrou", + // 强烈推荐使用自己的 client_id 和 client_secret + /** + * 设置要显示的多个云端硬盘;按格式添加多个 + * [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的盘将不支持搜索功能(不影响其他盘)。 + */ + + 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: false, + }; + + + var accounts = [ + { + id: "root", + name: "Drive 1", + user: "admin", + pass: "1234", + protect_file_link: true, + client_id: "", + client_secret: "", + refresh_token: "" + }, + { + id: "root", + name: "Drive 2", + user: "", + pass: "", + protect_file_link: false, + client_id: "", + client_secret: "", + refresh_token: "" + }, + { + id: "", + name: "folder1", + pass: "", + }, + ] + + var themeOptions = { + cdn: "https://cdn.jsdelivr.net/gh/Aicirou/goindex-theme-acrou", + // 主题版本号 + version: "2.0.5", + //可选默认系统语言:en/zh-chs/zh-cht + languages: "en", + render: { + /** + * 是否渲染HEAD.md文件 + * Render HEAD.md file + */ + head_md: false, + /** + * 是否渲染README.md文件 + * Render README.md file + */ + readme_md: false, + /** + * 是否渲染文件/文件夹描述 + * Render file/folder description or not + */ + desc: false, + }, + /** + * 播放器选项 + * Player options + */ + player: { + /** + * 播放器api(不指定则使用浏览器默认播放器) + * Player api(Use browser default player if not specified) + */ + api: "https://api.jsonpop.cn/demo/blplyaer/?url=", + }, + }; + // =======Options END======= + + /** + * 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 < this.accounts.length; i++) { + let authConfig = { + ...this.authConfig, + ...this.accounts[i] + } + const gd = new googleDrive(authConfig, i); + 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 = decodeURI(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.accounts.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.accounts = accounts[order]; + console.log(this.accounts); + this.protect_file_link = this.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.accounts["id"]] = "/"; + this.paths["/"] = this.accounts["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.accounts["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.accounts.user || "", + pass = this.accounts.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 = thisaccountsid; + // 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 = gdaccountsid; + 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, ""); + }; \ No newline at end of file