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