Guest User

goindex

a guest
Jul 21st, 2020
19
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 32.83 KB | None | 0 0
  1. // =======Options START=======
  2. var authConfig = {
  3. siteName: "GDFlix",
  4. version: "v7.7.7",
  5. github_name: "tks18",
  6. github_repo: "gindex-v4-no-backend",
  7. favicon: "https://raw.githubusercontent.com/tks18/infozy/develop/favicon.ico",
  8. client_id: "client_id_here",
  9. client_secret: "client_secret_here",
  10. refresh_token: "refresh_token_here",
  11. roots: [
  12. {
  13. id: "0APo5FwGx5mYeUk9PVA",
  14. name: "GDFlix",
  15. protect_file_link: true,
  16. }
  17. ],
  18. default_gd: 0,
  19. files_list_page_size: 200,
  20. search_result_list_page_size: 50,
  21. enable_cors_file_down: false,
  22. enable_password_file_verify: true,
  23. };
  24. var themeOptions = {
  25. languages: 'en',
  26. netflix_home: true, //If True Dont Forget to Fill in all the Required Details
  27. prefer_netflix_black: false,
  28. loading_image: "https://i.ibb.co/FzvmfZ8/5f1172be55414545150939.gif", // Max Width and Height - 250px. Do not Enter Images more than 250px wide.
  29. footer_data: {
  30. copyright: true,
  31. disclaimer: true,
  32. license: true,
  33. codeofconduct: true,
  34. footer_logo: true,
  35. footer_logo_link: "https://i.ibb.co/XCLv7Sd/Webp-net-gifmaker-3.gif",
  36. },
  37. render: {
  38. head_md: true,
  39. head_md_link: `https://cdn.jsdelivr.net/gh/tks18/gindex-v4@v7.5.2/vuejs/dist/HEAD.md`,
  40. readme_md: true,
  41. readme_md_link: "https://cdn.jsdelivr.net/gh/tks18/gindex-v4@v7.5.2/vuejs/dist/README.md",
  42. desc: true,
  43. }
  44. }
  45. var mainhero = [
  46. {
  47. root: 0,
  48. link: [
  49. {
  50. name: "The 100",
  51. poster: "https://wallpapercave.com/wp/wp1825213.jpg",
  52. subtitle: "Fear isnt their only weapon.",
  53. link: "TV%20Shows/TV%20Shows%20English/T/The%20100%20(2014)",
  54. },
  55. {
  56. name: "Orange is the New Black",
  57. poster: "https://ejournalz.com/wp-content/uploads/2019/09/orange-is-the-new-black-season-6-shows-background-01.jpg",
  58. subtitle: "Every sentence is a story",
  59. link: "TV%20Shows/TV%20Shows%20English/O/Orange%20Is%20the%20New%20Black%20(2013)",
  60. },
  61. {
  62. name: "Peaky Blinders",
  63. poster: "https://images.squarespace-cdn.com/content/v1/5b3a1b92da02bc79ba013a60/1533301640630-YABFHIHFZWXYUJ6U9D4V/ke17ZwdGBToddI8pDm48kF9aEDQaTpZHfWEO2zppK7Z7gQa3H78H3Y0txjaiv_0fDoOvxcdMmMKkDsyUqMSsMWxHk725yiiHCCLfrh8O1z5QPOohDIaIeljMHgDF5CVlOqpeNLcJ80NK65_fV7S1UX7HUUwySjcPdRBGehEKrDf5zebfiuf9u6oCHzr2lsfYZD7bBzAwq_2wCJyqgJebgg/PeakyBlinders-0312+1.jpg",
  64. subtitle: "Crime pays well",
  65. link: "TV%20Shows/TV%20Shows%20English/P/Peaky%20Blinders%20(2013)",
  66. },
  67. ],
  68. },
  69. ];
  70. var categories = [
  71. {
  72. root: 0,
  73. link: [
  74. {
  75. name: "Anime - Cartoons",
  76. link: "Anime - Cartoons",
  77. poster: "https://jw-webmagazine.com/wp-content/uploads/2020/03/Kimetsu-no-YaibaDemon-Slayer.jpg"
  78. },
  79. {
  80. name: "Stand Up",
  81. link: "Stand Up Comedies",
  82. poster: "https://i1.wp.com/sova.ponominalu.ru/wp-content/uploads/2019/09/diplo.jpg?fit=1000%2C631&ssl=1"
  83. },
  84. {
  85. name: "The Cinema",
  86. link: "Movies",
  87. poster: "https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcS-5gU6lKVeKvdBBVQyn8aZY4kRtPBgJPIpDA&usqp=CAU"
  88. },
  89. {
  90. name: "TV Series",
  91. link: "TV Shows",
  92. poster: "https://www.arthipo.com/image/cache/catalog/genel-tasarim/all-posters/dizi-tv-series-posterleri/PSTER-dizi168k-breaking_bad_actors_face_walter_white_jesse_pinkman-1000x1000.jpg"
  93. },
  94. {
  95. name: "Unsorted",
  96. link: "Unsorted",
  97. poster: "https://www.un.org/sites/un2.un.org/files/styles/large-article-image-style-16-9/public/field/image/virunga-poster_0.jpg?itok=sJdai-fJ"
  98. },
  99. ],
  100. },
  101. ];
  102. var trendingPosters = [
  103. {
  104. root: 0,
  105. link: [
  106. {
  107. poster: "https://images.newindianexpress.com/uploads/user/imagelibrary/2020/4/28/w900X450/Paatal_Lok.jpg",
  108. link: "TV%20Shows/TV%20Shows%20Indian/P/Paatal%20Lok%20(2020)",
  109. },
  110. {
  111. poster: "https://akm-img-a-in.tosshub.com/indiatoday/images/story/202006/qc_12-770x433.jpeg?hivJTisfzeBoAqGHnSq1nWZQCeNp_ErN",
  112. link: "TV%20Shows/TV%20Shows%20English/D/Dark%20(2017)",
  113. },
  114. {
  115. poster: "https://cdn.shopify.com/s/files/1/0969/9128/products/Friends_tv_show_poster_-_A3_-_11x15.8_5da52b1c-6c02-4649-abee-b41bf475e692.jpg?v=1561202385",
  116. link: "TV%20Shows/TV%20Shows%20English/F/Friends%20(1994)",
  117. },
  118. {
  119. poster: "https://static-koimoi.akamaized.net/wp-content/new-galleries/2020/04/money-heist-season-4-la-casa-de-papel-makers-revealed-a-big-spoiler-in-their-poster-earlier-no-one-noticed-it-0001.jpg",
  120. link: "TV%20Shows/TV%20Shows%20English/M/Money%20Heist%20(2017)",
  121. },
  122. {
  123. poster: "https://cdn.vox-cdn.com/thumbor/1FsuIbsA0gKrplbv7m6BQKA4_UM=/0x683:810x1139/1600x900/cdn.vox-cdn.com/uploads/chorus_image/image/55660155/strangerthings.0.jpg",
  124. link: "TV%20Shows/TV%20Shows%20English/S/Stranger%20Things%20(2016)",
  125. },
  126. {
  127. poster: "https://cdn.shopify.com/s/files/1/0969/9128/products/91TmR1v-qRL._RI_2f56fbdc-a4e4-4fa8-b022-b45e36874fc7.jpg?v=1556951536",
  128. link: "TV%20Shows/TV%20Shows%20English/T/The%20Office%20(US)%20(2005)",
  129. },
  130. ],
  131. },
  132. ];
  133. var quickLinks = [
  134. {
  135. root: 0,
  136. link: [
  137. {
  138. displayname: "Anime",
  139. link: "Anime - Cartoons",
  140. faIcon: "fas fa-heart",
  141. },
  142. {
  143. displayname: "Movies",
  144. link: "Movies",
  145. faIcon: "fas fa-video",
  146. },
  147. {
  148. displayname: "TV Series",
  149. link: "TV Shows",
  150. faIcon: "fas fa-tv",
  151. },
  152. ],
  153. },
  154. ];
  155. // =======Options END=======
  156.  
  157.  
  158. /**
  159. * global functions
  160. */
  161. const FUNCS = {
  162. formatSearchKeyword: function(keyword) {
  163. let nothing = "";
  164. let space = " ";
  165. if (!keyword) return nothing;
  166. return keyword
  167. .replace(/(!=)|['"=<>/\\:]/g, nothing)
  168. .replace(/[,,|(){}]/g, space)
  169. .trim();
  170. },
  171. };
  172.  
  173. /**
  174. * global consts
  175. * @type {{folder_mime_type: string, default_file_fields: string, gd_root_type: {share_drive: number, user_drive: number, sub_folder: number}}}
  176. */
  177. const CONSTS = new (class {
  178. default_file_fields =
  179. "parents,id,name,mimeType,modifiedTime,createdTime,fileExtension,size";
  180. gd_root_type = {
  181. user_drive: 0,
  182. share_drive: 1,
  183. sub_folder: 2,
  184. };
  185. folder_mime_type = "application/vnd.google-apps.folder";
  186. })();
  187.  
  188. // gd instances
  189. var gds = [];
  190.  
  191. function html(current_drive_order = 0, model = {}) {
  192. return `
  193. <!DOCTYPE html>
  194. <html>
  195. <head>
  196. <meta charset="utf-8">
  197. <meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0, user-scalable=no"/>
  198. <title>${authConfig.siteName}</title>
  199. <style>
  200. @import url(https://cdn.jsdelivr.net/gh/${authConfig.github_name}/${authConfig.github_repo}@${authConfig.version}/vuejs/dist/style.css);
  201. </style>
  202. <link rel="icon" sizes="57x57" href="${authConfig.favicon}" />
  203. <script>
  204. window.gdconfig = JSON.parse('${JSON.stringify({
  205. version: authConfig.version,
  206. themeOptions: themeOptions,
  207. })}');
  208. window.quickLinks = JSON.parse('${JSON.stringify(
  209. quickLinks.map((it) => {
  210. var links = {
  211. root: it.root,
  212. link: it.link
  213. }
  214. return links;
  215. })
  216. )}');
  217. window.mainHeroLinks = JSON.parse(
  218. '${JSON.stringify(mainhero.map((hero) => {
  219. const heroData = {
  220. root: hero.root,
  221. link: hero.link
  222. }
  223. return heroData;
  224. }))}'
  225. );
  226. window.trendingPosterLinks = JSON.parse(
  227. '${JSON.stringify(trendingPosters.map((poster) => {
  228. const posterData = {
  229. root: poster.root,
  230. link: poster.link
  231. }
  232. return posterData;
  233. }))}'
  234. );
  235. window.homeCategories = JSON.parse(
  236. '${JSON.stringify(categories.map((category) => {
  237. const categoryData = {
  238. root: category.root,
  239. link: category.link
  240. }
  241. return categoryData;
  242. }))}'
  243. );
  244. window.themeOptions = JSON.parse('${JSON.stringify(themeOptions)}');
  245. window.gds = JSON.parse('${JSON.stringify(
  246. authConfig.roots.map((it) => it.name)
  247. )}');
  248. window.MODEL = JSON.parse('${JSON.stringify(model)}');
  249. window.current_drive_order = ${current_drive_order};
  250. </script>
  251. </head>
  252. <body>
  253. <div id="app"></div>
  254. <script src="https://cdn.jsdelivr.net/gh/${authConfig.github_name}/${authConfig.github_repo}@${authConfig.version}/vuejs/dist/app.js"></script>
  255. </body>
  256. </html>
  257. `;
  258. }
  259.  
  260. addEventListener("fetch", (event) => {
  261. event.respondWith(handleRequest(event.request));
  262. });
  263.  
  264. /**
  265. * Fetch and log a request
  266. * @param {Request} request
  267. */
  268. async function handleRequest(request) {
  269. if (gds.length === 0) {
  270. for (let i = 0; i < authConfig.roots.length; i++) {
  271. const gd = new googleDrive(authConfig, i);
  272. await gd.init();
  273. gds.push(gd);
  274. }
  275. let tasks = [];
  276. gds.forEach((gd) => {
  277. tasks.push(gd.initRootType());
  278. });
  279. for (let task of tasks) {
  280. await task;
  281. }
  282. }
  283.  
  284. let gd;
  285. let url = new URL(request.url);
  286. let path = url.pathname;
  287.  
  288. /**
  289. * Redirect to start page
  290. * @returns {Response}
  291. */
  292. function redirectToIndexPage() {
  293. return new Response("", {
  294. status: 301,
  295. headers: { Location: `/${authConfig.default_gd}:home/` },
  296. });
  297. }
  298.  
  299. if (path == "/") return redirectToIndexPage();
  300. if (path.toLowerCase() == "/favicon.ico") {
  301. // You can find a favicon later
  302. return new Response("", { status: 404 });
  303. }
  304.  
  305. // Special command format
  306. const command_reg = /^\/(?<num>[\S]+):(?<command>[a-zA-Z0-9]+)(\/.*)?$/g;
  307. const match = command_reg.exec(path);
  308. let command;
  309. if (match) {
  310. const num = match.groups.num;
  311. const order = Number(num);
  312. if (order >= 0 && order < gds.length) {
  313. gd = gds[order];
  314. } else {
  315. return redirectToIndexPage();
  316. }
  317. // basic auth
  318. for (const r = gd.basicAuthResponse(request); r; ) return r;
  319. command = match.groups.command;
  320.  
  321. // search for
  322. if (command === "search") {
  323. if (request.method === "POST") {
  324. // search results
  325. return handleSearch(request, gd);
  326. } else {
  327. const params = url.searchParams;
  328. // Search page
  329. return new Response(
  330. html(gd.order, {
  331. q: params.get("q") || "",
  332. is_search_page: true,
  333. root_type: gd.root_type,
  334. }),
  335. {
  336. status: 200,
  337. headers: { "Content-Type": "text/html; charset=utf-8" },
  338. }
  339. );
  340. }
  341. } else if (command === "id2path" && request.method === "POST") {
  342. return handleId2Path(request, gd);
  343. } else if (command === "view") {
  344. const params = url.searchParams;
  345. return gd.view(params.get("url"), request.headers.get("Range"));
  346. } else if (command !== "down" && request.method === "GET") {
  347. return new Response(html(gd.order, { root_type: gd.root_type }), {
  348. status: 200,
  349. headers: { "Content-Type": "text/html; charset=utf-8" },
  350. });
  351. }
  352. }
  353. const reg = new RegExp(`^(/\\d+:)${command}/`, "g");
  354. path = path.replace(reg, (p1, p2) => {
  355. return p2 + "/";
  356. });
  357. // Desired path format
  358. const common_reg = /^\/\d+:\/.*$/g;
  359. try {
  360. if (!path.match(common_reg)) {
  361. return redirectToIndexPage();
  362. }
  363. let split = path.split("/");
  364. let order = Number(split[1].slice(0, -1));
  365. if (order >= 0 && order < gds.length) {
  366. gd = gds[order];
  367. } else {
  368. return redirectToIndexPage();
  369. }
  370. } catch (e) {
  371. return redirectToIndexPage();
  372. }
  373.  
  374. // basic auth
  375. // for (const r = gd.basicAuthResponse(request); r;) return r;
  376. const basic_auth_res = gd.basicAuthResponse(request);
  377. path = path.replace(gd.url_path_prefix, "") || "/";
  378. if (request.method == "POST") {
  379. return basic_auth_res || apiRequest(request, gd);
  380. }
  381.  
  382. let action = url.searchParams.get("a");
  383.  
  384. if (path.substr(-1) == "/" || action != null) {
  385. return (
  386. basic_auth_res ||
  387. new Response(html(gd.order, { root_type: gd.root_type }), {
  388. status: 200,
  389. headers: { "Content-Type": "text/html; charset=utf-8" },
  390. })
  391. );
  392. } else {
  393. if (
  394. path
  395. .split("/")
  396. .pop()
  397. .toLowerCase() == ".password"
  398. ) {
  399. return basic_auth_res || new Response("", { status: 404 });
  400. }
  401. let file = await gd.file(path);
  402. let range = request.headers.get("Range");
  403. if (gd.root.protect_file_link && basic_auth_res) return basic_auth_res;
  404. const is_down = !(command && command == "down");
  405. return gd.down(file.id, range, is_down);
  406. }
  407. }
  408.  
  409. async function apiRequest(request, gd) {
  410. let url = new URL(request.url);
  411. let path = url.pathname;
  412. path = path.replace(gd.url_path_prefix, "") || "/";
  413.  
  414. let option = { status: 200, headers: { "Access-Control-Allow-Origin": "*" } };
  415.  
  416. if (path.substr(-1) == "/") {
  417. let deferred_pass = gd.password(path);
  418. let body = await request.text();
  419. body = JSON.parse(body);
  420. // This can increase the speed when listing directories for the first time. The disadvantage is that if the password verification fails, the overhead of listing directories will still be incurred
  421. let deferred_list_result = gd.list(
  422. path,
  423. body.page_token,
  424. Number(body.page_index)
  425. );
  426.  
  427. // check .password file, if `enable_password_file_verify` is true
  428. if (authConfig["enable_password_file_verify"]) {
  429. let password = await gd.password(path);
  430. // console.log("dir password", password);
  431. if (password && password.replace("\n", "") !== body.password) {
  432. let html = `{"error": {"code": 401,"message": "password error."}}`;
  433. return new Response(html, option);
  434. }
  435. }
  436.  
  437. let list_result = await deferred_list_result;
  438. return new Response(JSON.stringify(list_result), option);
  439. } else {
  440. let file = await gd.file(path);
  441. let range = request.headers.get("Range");
  442. return new Response(JSON.stringify(file));
  443. }
  444. }
  445.  
  446. // Processing search
  447. async function handleSearch(request, gd) {
  448. const option = {
  449. status: 200,
  450. headers: { "Access-Control-Allow-Origin": "*" },
  451. };
  452. let body = await request.text();
  453. body = JSON.parse(body);
  454. let search_result = await gd.search(
  455. body.q || "",
  456. body.page_token,
  457. Number(body.page_index)
  458. );
  459. return new Response(JSON.stringify(search_result), option);
  460. }
  461.  
  462. /**
  463. * deal with id2path
  464. * @param request 需要 id 参数
  465. * @param gd
  466. * @returns {Promise<Response>} [Note] If the item represented by the id received from the front desk is not under the target gd disk, then the response will be returned to the front desk with an empty string ""
  467. */
  468. async function handleId2Path(request, gd) {
  469. const option = {
  470. status: 200,
  471. headers: { "Access-Control-Allow-Origin": "*" },
  472. };
  473. let body = await request.text();
  474. body = JSON.parse(body);
  475. let path = await gd.findPathById(body.id);
  476. return new Response(path || "", option);
  477. }
  478.  
  479. class googleDrive {
  480. constructor(authConfig, order) {
  481. // Each disk corresponds to an order, corresponding to a gd instance
  482. this.order = order;
  483. this.root = authConfig.roots[order];
  484. this.root.protect_file_link = this.root.protect_file_link || false;
  485. this.url_path_prefix = `/${order}:`;
  486. this.authConfig = authConfig;
  487. // TODO: The invalid refresh strategy of these caches can be formulated later
  488. // path id
  489. this.paths = [];
  490. // path file
  491. this.files = [];
  492. // path pass
  493. this.passwords = [];
  494. // id <-> path
  495. this.id_path_cache = {};
  496. this.id_path_cache[this.root["id"]] = "/";
  497. this.paths["/"] = this.root["id"];
  498. /*if (this.root['pass'] != "") {
  499. this.passwords['/'] = this.root['pass'];
  500. }*/
  501. // this.init();
  502. }
  503.  
  504. /**
  505. * Initial authorization; then obtain user_drive_real_root_id
  506. * @returns {Promise<void>}
  507. */
  508. async init() {
  509. await this.accessToken();
  510. /*await (async () => {
  511. // Get only 1 time
  512. if (authConfig.user_drive_real_root_id) return;
  513. const root_obj = await (gds[0] || this).findItemById('root');
  514. if (root_obj && root_obj.id) {
  515. authConfig.user_drive_real_root_id = root_obj.id
  516. }
  517. })();*/
  518. // Wait for user_drive_real_root_id and only get it once
  519. if (authConfig.user_drive_real_root_id) return;
  520. const root_obj = await (gds[0] || this).findItemById("root");
  521. if (root_obj && root_obj.id) {
  522. authConfig.user_drive_real_root_id = root_obj.id;
  523. }
  524. }
  525.  
  526. /**
  527. * Get the root directory type, set to root_type
  528. * @returns {Promise<void>}
  529. */
  530. async initRootType() {
  531. const root_id = this.root["id"];
  532. const types = CONSTS.gd_root_type;
  533. if (root_id === "root" || root_id === authConfig.user_drive_real_root_id) {
  534. this.root_type = types.user_drive;
  535. } else {
  536. const obj = await this.getShareDriveObjById(root_id);
  537. this.root_type = obj ? types.share_drive : types.sub_folder;
  538. }
  539. }
  540.  
  541. /**
  542. * Returns a response that requires authorization, or null
  543. * @param request
  544. * @returns {Response|null}
  545. */
  546. basicAuthResponse(request) {
  547. const user = this.root.user || "",
  548. pass = this.root.pass || "",
  549. _401 = new Response("Unauthorized", {
  550. headers: {
  551. "WWW-Authenticate": `Basic realm="goindex:drive:${this.order}"`,
  552. },
  553. status: 401,
  554. });
  555. if (user || pass) {
  556. const auth = request.headers.get("Authorization");
  557. if (auth) {
  558. try {
  559. const [received_user, received_pass] = atob(
  560. auth.split(" ").pop()
  561. ).split(":");
  562. return received_user === user && received_pass === pass ? null : _401;
  563. } catch (e) {}
  564. }
  565. } else return null;
  566. return _401;
  567. }
  568.  
  569. async view(url, range = "", inline = true) {
  570. let requestOption = await this.requestOption();
  571. requestOption.headers["Range"] = range;
  572. let res = await fetch(url, requestOption);
  573. const { headers } = (res = new Response(res.body, res));
  574. this.authConfig.enable_cors_file_down &&
  575. headers.append("Access-Control-Allow-Origin", "*");
  576. inline === true && headers.set("Content-Disposition", "inline");
  577. return res;
  578. }
  579.  
  580. async down(id, range = "", inline = false) {
  581. let url = `https://www.googleapis.com/drive/v3/files/${id}?alt=media`;
  582. let requestOption = await this.requestOption();
  583. requestOption.headers["Range"] = range;
  584. let res = await fetch(url, requestOption);
  585. const { headers } = (res = new Response(res.body, res));
  586. this.authConfig.enable_cors_file_down &&
  587. headers.append("Access-Control-Allow-Origin", "*");
  588. inline === true && headers.set("Content-Disposition", "inline");
  589. return res;
  590. }
  591.  
  592. async file(path) {
  593. if (typeof this.files[path] == "undefined") {
  594. this.files[path] = await this._file(path);
  595. }
  596. return this.files[path];
  597. }
  598.  
  599. async _file(path) {
  600. let arr = path.split("/");
  601. let name = arr.pop();
  602. name = decodeURIComponent(name).replace(/\'/g, "\\'");
  603. let dir = arr.join("/") + "/";
  604. // console.log(name, dir);
  605. let parent = await this.findPathId(dir);
  606. // console.log(parent);
  607. let url = "https://www.googleapis.com/drive/v3/files";
  608. let params = { includeItemsFromAllDrives: true, supportsAllDrives: true };
  609. params.q = `'${parent}' in parents and name = '${name}' and trashed = false`;
  610. params.fields =
  611. "files(id, name, mimeType, size ,createdTime, modifiedTime, iconLink, thumbnailLink)";
  612. url += "?" + this.enQuery(params);
  613. let requestOption = await this.requestOption();
  614. let response = await fetch(url, requestOption);
  615. let obj = await response.json();
  616. // console.log(obj);
  617. return obj.files[0];
  618. }
  619.  
  620. // Cache through reqeust cache
  621. async list(path, page_token = null, page_index = 0) {
  622. if (this.path_children_cache == undefined) {
  623. // { <path> :[ {nextPageToken:'',data:{}}, {nextPageToken:'',data:{}} ...], ...}
  624. this.path_children_cache = {};
  625. }
  626.  
  627. if (
  628. this.path_children_cache[path] &&
  629. this.path_children_cache[path][page_index] &&
  630. this.path_children_cache[path][page_index].data
  631. ) {
  632. let child_obj = this.path_children_cache[path][page_index];
  633. return {
  634. nextPageToken: child_obj.nextPageToken || null,
  635. curPageIndex: page_index,
  636. data: child_obj.data,
  637. };
  638. }
  639.  
  640. let id = await this.findPathId(path);
  641. let result = await this._ls(id, page_token, page_index);
  642. let data = result.data;
  643. // Cache multiple pages
  644. if (result.nextPageToken && data.files) {
  645. if (!Array.isArray(this.path_children_cache[path])) {
  646. this.path_children_cache[path] = [];
  647. }
  648. this.path_children_cache[path][Number(result.curPageIndex)] = {
  649. nextPageToken: result.nextPageToken,
  650. data: data,
  651. };
  652. }
  653.  
  654. return result;
  655. }
  656.  
  657. async _ls(parent, page_token = null, page_index = 0) {
  658. // console.log("_ls", parent);
  659.  
  660. if (parent == undefined) {
  661. return null;
  662. }
  663. let obj;
  664. let params = { includeItemsFromAllDrives: true, supportsAllDrives: true };
  665. params.q = `'${parent}' in parents and trashed = false AND name !='.password'`;
  666. params.orderBy = "folder,name,modifiedTime desc";
  667. params.fields =
  668. "nextPageToken, files(id, name, mimeType, size , modifiedTime, thumbnailLink, description)";
  669. params.pageSize = this.authConfig.files_list_page_size;
  670.  
  671. if (page_token) {
  672. params.pageToken = page_token;
  673. }
  674. let url = "https://www.googleapis.com/drive/v3/files";
  675. url += "?" + this.enQuery(params);
  676. let requestOption = await this.requestOption();
  677. let response = await fetch(url, requestOption);
  678. obj = await response.json();
  679.  
  680. return {
  681. nextPageToken: obj.nextPageToken || null,
  682. curPageIndex: page_index,
  683. data: obj,
  684. };
  685.  
  686. /*do {
  687. if (pageToken) {
  688. params.pageToken = pageToken;
  689. }
  690. let url = 'https://www.googleapis.com/drive/v3/files';
  691. url += '?' + this.enQuery(params);
  692. let requestOption = await this.requestOption();
  693. let response = await fetch(url, requestOption);
  694. obj = await response.json();
  695. files.push(...obj.files);
  696. pageToken = obj.nextPageToken;
  697. } while (pageToken);*/
  698. }
  699.  
  700. async password(path) {
  701. if (this.passwords[path] !== undefined) {
  702. return this.passwords[path];
  703. }
  704.  
  705. // console.log("load", path, ".password", this.passwords[path]);
  706.  
  707. let file = await this.file(path + ".password");
  708. if (file == undefined) {
  709. this.passwords[path] = null;
  710. } else {
  711. let url = `https://www.googleapis.com/drive/v3/files/${file.id}?alt=media`;
  712. let requestOption = await this.requestOption();
  713. let response = await this.fetch200(url, requestOption);
  714. this.passwords[path] = await response.text();
  715. }
  716.  
  717. return this.passwords[path];
  718. }
  719.  
  720. /**
  721. * Get share drive information by id
  722. * @param any_id
  723. * @returns {Promise<null|{id}|any>} Any abnormal situation returns null
  724. */
  725. async getShareDriveObjById(any_id) {
  726. if (!any_id) return null;
  727. if ("string" !== typeof any_id) return null;
  728.  
  729. let url = `https://www.googleapis.com/drive/v3/drives/${any_id}`;
  730. let requestOption = await this.requestOption();
  731. let res = await fetch(url, requestOption);
  732. let obj = await res.json();
  733. if (obj && obj.id) return obj;
  734.  
  735. return null;
  736. }
  737.  
  738. /**
  739. * search for
  740. * @returns {Promise<{data: null, nextPageToken: null, curPageIndex: number}>}
  741. */
  742. async search(origin_keyword, page_token = null, page_index = 0) {
  743. const types = CONSTS.gd_root_type;
  744. const is_user_drive = this.root_type === types.user_drive;
  745. const is_share_drive = this.root_type === types.share_drive;
  746.  
  747. const empty_result = {
  748. nextPageToken: null,
  749. curPageIndex: page_index,
  750. data: null,
  751. };
  752.  
  753. if (!is_user_drive && !is_share_drive) {
  754. return empty_result;
  755. }
  756. let keyword = FUNCS.formatSearchKeyword(origin_keyword);
  757. if (!keyword) {
  758. // The keyword is empty, return
  759. return empty_result;
  760. }
  761. let words = keyword.split(/\s+/);
  762. let name_search_str = `name contains '${words.join(
  763. "' AND name contains '"
  764. )}'`;
  765.  
  766. // corpora is a personal drive for user and a team drive for drive. With driveId
  767. let params = {};
  768. if (is_user_drive) {
  769. params.corpora = "user";
  770. }
  771. if (is_share_drive) {
  772. params.corpora = "drive";
  773. params.driveId = this.root.id;
  774. // This parameter will only be effective until June 1, 2020. Afterwards shared drive items will be included in the results.
  775. params.includeItemsFromAllDrives = true;
  776. params.supportsAllDrives = true;
  777. }
  778. if (page_token) {
  779. params.pageToken = page_token;
  780. }
  781. params.q = `trashed = false AND name !='.password' AND (${name_search_str})`;
  782. params.fields =
  783. "nextPageToken, files(id, name, mimeType, size , modifiedTime, thumbnailLink, description)";
  784. params.pageSize = this.authConfig.search_result_list_page_size;
  785. // params.orderBy = 'folder,name,modifiedTime desc';
  786.  
  787. let url = "https://www.googleapis.com/drive/v3/files";
  788. url += "?" + this.enQuery(params);
  789. // console.log(params)
  790. let requestOption = await this.requestOption();
  791. let response = await fetch(url, requestOption);
  792. let res_obj = await response.json();
  793.  
  794. return {
  795. nextPageToken: res_obj.nextPageToken || null,
  796. curPageIndex: page_index,
  797. data: res_obj,
  798. };
  799. }
  800.  
  801. /**
  802. * Get the file object of the upper folder of this file or folder up layer by layer. Note: It will be very slow! ! !
  803. * Up to find the root directory (root id) of the current gd object
  804. * Only consider a single upward chain.
  805. * [Note] If the item represented by this id is not in the target gd disk, then this function will return null
  806. *
  807. * @param child_id
  808. * @param contain_myself
  809. * @returns {Promise<[]>}
  810. */
  811. async findParentFilesRecursion(child_id, contain_myself = true) {
  812. const gd = this;
  813. const gd_root_id = gd.root.id;
  814. const user_drive_real_root_id = authConfig.user_drive_real_root_id;
  815. const is_user_drive = gd.root_type === CONSTS.gd_root_type.user_drive;
  816.  
  817. // End point query id from bottom to top
  818. const target_top_id = is_user_drive ? user_drive_real_root_id : gd_root_id;
  819. const fields = CONSTS.default_file_fields;
  820.  
  821. // [{},{},...]
  822. const parent_files = [];
  823. let meet_top = false;
  824.  
  825. async function addItsFirstParent(file_obj) {
  826. if (!file_obj) return;
  827. if (!file_obj.parents) return;
  828. if (file_obj.parents.length < 1) return;
  829.  
  830. // ['','',...]
  831. let p_ids = file_obj.parents;
  832. if (p_ids && p_ids.length > 0) {
  833. // its first parent
  834. const first_p_id = p_ids[0];
  835. if (first_p_id === target_top_id) {
  836. meet_top = true;
  837. return;
  838. }
  839. const p_file_obj = await gd.findItemById(first_p_id);
  840. if (p_file_obj && p_file_obj.id) {
  841. parent_files.push(p_file_obj);
  842. await addItsFirstParent(p_file_obj);
  843. }
  844. }
  845. }
  846.  
  847. const child_obj = await gd.findItemById(child_id);
  848. if (contain_myself) {
  849. parent_files.push(child_obj);
  850. }
  851. await addItsFirstParent(child_obj);
  852.  
  853. return meet_top ? parent_files : null;
  854. }
  855.  
  856. /**
  857. * Get the path relative to the root directory of this disk
  858. * @param child_id
  859. * @returns {Promise<string>} [Note] If the item represented by this id is not in the target gd disk, then this method will return an empty string ""
  860. */
  861. async findPathById(child_id) {
  862. if (this.id_path_cache[child_id]) {
  863. return this.id_path_cache[child_id];
  864. }
  865.  
  866. const p_files = await this.findParentFilesRecursion(child_id);
  867. if (!p_files || p_files.length < 1) return "";
  868.  
  869. let cache = [];
  870. // Cache the path and id of each level found
  871. p_files.forEach((value, idx) => {
  872. const is_folder =
  873. idx === 0 ? p_files[idx].mimeType === CONSTS.folder_mime_type : true;
  874. let path =
  875. "/" +
  876. p_files
  877. .slice(idx)
  878. .map((it) => it.name)
  879. .reverse()
  880. .join("/");
  881. if (is_folder) path += "/";
  882. cache.push({ id: p_files[idx].id, path: path });
  883. });
  884.  
  885. cache.forEach((obj) => {
  886. this.id_path_cache[obj.id] = obj.path;
  887. this.paths[obj.path] = obj.id;
  888. });
  889.  
  890. /*const is_folder = p_files[0].mimeType === CONSTS.folder_mime_type;
  891. let path = '/' + p_files.map(it => it.name).reverse().join('/');
  892. if (is_folder) path += '/';*/
  893.  
  894. return cache[0].path;
  895. }
  896.  
  897. // Get file item based on id
  898. async findItemById(id) {
  899. const is_user_drive = this.root_type === CONSTS.gd_root_type.user_drive;
  900. let url = `https://www.googleapis.com/drive/v3/files/${id}?fields=${
  901. CONSTS.default_file_fields
  902. }${is_user_drive ? "" : "&supportsAllDrives=true"}`;
  903. let requestOption = await this.requestOption();
  904. let res = await fetch(url, requestOption);
  905. return await res.json();
  906. }
  907.  
  908. async findPathId(path) {
  909. let c_path = "/";
  910. let c_id = this.paths[c_path];
  911.  
  912. let arr = path.trim("/").split("/");
  913. for (let name of arr) {
  914. c_path += name + "/";
  915.  
  916. if (typeof this.paths[c_path] == "undefined") {
  917. let id = await this._findDirId(c_id, name);
  918. this.paths[c_path] = id;
  919. }
  920.  
  921. c_id = this.paths[c_path];
  922. if (c_id == undefined || c_id == null) {
  923. break;
  924. }
  925. }
  926. // console.log(this.paths);
  927. return this.paths[path];
  928. }
  929.  
  930. async _findDirId(parent, name) {
  931. name = decodeURIComponent(name).replace(/\'/g, "\\'");
  932.  
  933. // console.log("_findDirId", parent, name);
  934.  
  935. if (parent == undefined) {
  936. return null;
  937. }
  938.  
  939. let url = "https://www.googleapis.com/drive/v3/files";
  940. let params = { includeItemsFromAllDrives: true, supportsAllDrives: true };
  941. params.q = `'${parent}' in parents and mimeType = 'application/vnd.google-apps.folder' and name = '${name}' and trashed = false`;
  942. params.fields = "nextPageToken, files(id, name, mimeType)";
  943. url += "?" + this.enQuery(params);
  944. let requestOption = await this.requestOption();
  945. let response = await fetch(url, requestOption);
  946. let obj = await response.json();
  947. if (obj.files[0] == undefined) {
  948. return null;
  949. }
  950. return obj.files[0].id;
  951. }
  952.  
  953. async accessToken() {
  954. console.log("accessToken");
  955. if (
  956. this.authConfig.expires == undefined ||
  957. this.authConfig.expires < Date.now()
  958. ) {
  959. const obj = await this.fetchAccessToken();
  960. if (obj.access_token != undefined) {
  961. this.authConfig.accessToken = obj.access_token;
  962. this.authConfig.expires = Date.now() + 3500 * 1000;
  963. }
  964. }
  965. return this.authConfig.accessToken;
  966. }
  967.  
  968. async fetchAccessToken() {
  969. console.log("fetchAccessToken");
  970. const url = "https://www.googleapis.com/oauth2/v4/token";
  971. const headers = {
  972. "Content-Type": "application/x-www-form-urlencoded",
  973. };
  974. const post_data = {
  975. client_id: this.authConfig.client_id,
  976. client_secret: this.authConfig.client_secret,
  977. refresh_token: this.authConfig.refresh_token,
  978. grant_type: "refresh_token",
  979. };
  980.  
  981. let requestOption = {
  982. method: "POST",
  983. headers: headers,
  984. body: this.enQuery(post_data),
  985. };
  986.  
  987. const response = await fetch(url, requestOption);
  988. return await response.json();
  989. }
  990.  
  991. async fetch200(url, requestOption) {
  992. let response;
  993. for (let i = 0; i < 3; i++) {
  994. response = await fetch(url, requestOption);
  995. console.log(response.status);
  996. if (response.status != 403) {
  997. break;
  998. }
  999. await this.sleep(800 * (i + 1));
  1000. }
  1001. return response;
  1002. }
  1003.  
  1004. async requestOption(headers = {}, method = "GET") {
  1005. const accessToken = await this.accessToken();
  1006. headers["authorization"] = "Bearer " + accessToken;
  1007. return { method: method, headers: headers };
  1008. }
  1009.  
  1010. enQuery(data) {
  1011. const ret = [];
  1012. for (let d in data) {
  1013. ret.push(encodeURIComponent(d) + "=" + encodeURIComponent(data[d]));
  1014. }
  1015. return ret.join("&");
  1016. }
  1017.  
  1018. sleep(ms) {
  1019. return new Promise(function(resolve, reject) {
  1020. let i = 0;
  1021. setTimeout(function() {
  1022. console.log("sleep" + ms);
  1023. i++;
  1024. if (i >= 2) reject(new Error("i>=2"));
  1025. else resolve(i);
  1026. }, ms);
  1027. });
  1028. }
  1029. }
  1030.  
  1031. String.prototype.trim = function(char) {
  1032. if (char) {
  1033. return this.replace(
  1034. new RegExp("^\\" + char + "+|\\" + char + "+$", "g"),
  1035. ""
  1036. );
  1037. }
  1038. return this.replace(/^\s+|\s+$/g, "");
  1039. };
Add Comment
Please, Sign In to add comment