View difference between Paste ID: cWTPR8pA and YyP84Mpc
SHOW: | | - or go back to the newest paste.
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",
13+
      id: "0ACde3f7toF5xUk9PVA",
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
};