bunnyrockz

Untitled

Jun 23rd, 2020
19
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 27.80 KB | None | 0 0
  1. var authConfig = {
  2. siteName: 'GDrive Server', // WebSite Name
  3. version: '1.0', // VersionControl, do not modify manually
  4. // Only material!
  5. theme: 'material', // material classic
  6. //add themes color, darkmode
  7. main_color: 'purple', // red | pink | purple | deep-purple | indigo | blue | light-blue | cyan | teal | green | light-green | lime yellow | amber orange | deep-orange | brown | greyblue-grey
  8. accent_color: 'light-blue', // red | pink | purple | deep-purple | indigo | blue | light-blue | cyan | teal | green | light-green | lime | yellow | amber | orange | deep-orange
  9. dark_theme: true, // true for dark theme
  10. // client_id & client_secret - PLEASE USE YOUR OWN!
  11. client_id: '',
  12. client_secret: '',
  13. refresh_token: '', // Refresh token
  14.  
  15. /**
  16. * Set up multiple Drives to display; add multiples by format
  17. * [id] can be team disk id, subfolder id, or "root" (representing the root directory of personal disk);
  18. * [name] The displayed name
  19. * [user] Basic Auth username
  20. * [pass] Basic Auth password
  21. * Basic Auth of each disk can be set separately. Basic Auth takes effect on all paths under the disk, including subfolders, file chains on the disk, etc.
  22. * No need for Basic Auth disk, just keep user and pass empty at the same time. (No need to set it directly)
  23. * [Note] For the disk whose id is set to the subfolder id, the search function will not be supported (it does not affect other disks)
  24. */
  25. // It is possible to set only the password, only the user name, and the user name and password at the same time
  26. roots: [
  27. {
  28. id: '0APo5FwGx5mYeUk9PVA',
  29. name: 'Movies/TV Shows',
  30. user: 'dont',
  31. pass: 'misuse'
  32. }
  33. ],
  34. /**
  35. * The number displayed on each page of the file list page. [Recommended setting value is between 100 and 1000];
  36. * If the setting is greater than 1000, it will cause an error when requesting drive api;
  37. * If the set value is too small, the incremental loading (page loading) of the scroll bar of the file list page will be invalid
  38. * Another effect of this value is that if the number of files in the directory is greater than this setting (that is, multiple pages need to be displayed), the results of the first listing directory will be cached.
  39. */
  40. files_list_page_size: 500,
  41. /**
  42. * The number displayed on each page of the search results page. [Recommended setting value is between 50 and 1000];
  43. * If the setting is greater than 1000, it will cause an error when requesting drive api;
  44. * If the set value is too small, it will cause the incremental loading (page loading) of the scroll bar of the search results page to fail;
  45. * The size of this value affects the response speed of the search operation
  46. */
  47. search_result_list_page_size: 50,
  48. // Confirm that cors can be opened
  49. enable_cors_file_down: false,
  50. // user_drive_real_root_id
  51. /**
  52. * The above basic auth already contains the function of global protection in the disk. So by default, the password in the .password file is no longer authenticated;
  53. * If you still need to verify the password in the .password file for certain directories based on global authentication, set this option to true;
  54. * [Note] If the password verification of the .password file is enabled, the overhead of querying whether the .password file in the directory will be added each time the directory is listed.
  55. */
  56.  
  57. "enable_password_file_verify": false
  58.  
  59. };
  60.  
  61.  
  62. /**
  63. * global functions
  64. */
  65. const FUNCS = {
  66. /**
  67. * Transform into relatively safe search keywords for Google search morphology
  68. */
  69. formatSearchKeyword: function (keyword) {
  70. let nothing = "";
  71. let space = " ";
  72. if (!keyword) return nothing;
  73. return keyword.replace(/(!=)|['"=<>/\\:]/g, nothing)
  74. .replace(/[,,|(){}]/g, space)
  75. .trim()
  76. }
  77.  
  78. };
  79.  
  80. /**
  81. * global consts
  82. * @type {{folder_mime_type: string, default_file_fields: string, gd_root_type: {share_drive: number, user_drive: number, sub_folder: number}}}
  83. */
  84. const CONSTS = new (class {
  85. default_file_fields = 'parents,id,name,mimeType,modifiedTime,createdTime,fileExtension,size';
  86. gd_root_type = {
  87. user_drive: 0,
  88. share_drive: 1,
  89. sub_folder: 2
  90. };
  91. folder_mime_type = 'application/vnd.google-apps.folder';
  92. })();
  93.  
  94.  
  95. // gd instances
  96. var gds = [];
  97.  
  98. function html(current_drive_order = 0, model = {}) {
  99. return `
  100. <!DOCTYPE html>
  101. <html>
  102. <head>
  103. <meta charset="utf-8">
  104. <meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0, user-scalable=no"/>
  105. <title>${authConfig.siteName}</title>
  106. <script>
  107. window.drive_names = JSON.parse('${JSON.stringify(authConfig.roots.map(it => it.name))}');
  108. window.MODEL = JSON.parse('${JSON.stringify(model)}');
  109. window.current_drive_order = ${current_drive_order};
  110. </script>
  111. <script>var main_color = "${authConfig.main_color}";var accent_color = "${authConfig.accent_color}";var dark = ${authConfig.dark_theme};</script>
  112. <script src="//cdn.jsdelivr.net/combine/gh/jquery/jquery@3.2/dist/jquery.min.js,gh/LeeluPradhan/G-Index@${authConfig.version}/Search/themes/${authConfig.theme}/app.js"></script>
  113. <script src="//cdnjs.cloudflare.com/ajax/libs/mdui/0.4.3/js/mdui.min.js"></script>
  114. </head>
  115. <body>
  116. </body>
  117. </html>
  118. `;
  119. };
  120.  
  121. addEventListener('fetch', event => {
  122. event.respondWith(handleRequest(event.request));
  123. });
  124.  
  125. /**
  126. * Fetch and log a request
  127. * @param {Request} request
  128. */
  129. async function handleRequest(request) {
  130. if (gds.length === 0) {
  131. for (let i = 0; i < authConfig.roots.length; i++) {
  132. const gd = new googleDrive(authConfig, i);
  133. await gd.init();
  134. gds.push(gd)
  135. }
  136. // This operation is parallel to improve efficiency
  137. let tasks = [];
  138. gds.forEach(gd => {
  139. tasks.push(gd.initRootType());
  140. });
  141. for (let task of tasks) {
  142. await task;
  143. }
  144. }
  145.  
  146. // Extract drive order from path
  147. // and get the corresponding gd instance according to the drive order
  148. let gd;
  149. let url = new URL(request.url);
  150. let path = url.pathname;
  151.  
  152. /**
  153. * Redirect to start page
  154. * @returns {Response}
  155. */
  156. function redirectToIndexPage() {
  157. return new Response('', {status: 301, headers: {'Location': `${url.origin}/0:/`}});
  158. }
  159.  
  160. if (path == '/') return redirectToIndexPage();
  161. if (path.toLowerCase() == '/favicon.ico') {
  162. // You can find one later favicon
  163. return new Response('', {status: 404})
  164. }
  165.  
  166. // Special command format
  167. const command_reg = /^\/(?<num>\d+):(?<command>[a-zA-Z0-9]+)$/g;
  168. const match = command_reg.exec(path);
  169. if (match) {
  170. const num = match.groups.num;
  171. const order = Number(num);
  172. if (order >= 0 && order < gds.length) {
  173. gd = gds[order];
  174. } else {
  175. return redirectToIndexPage()
  176. }
  177. const command = match.groups.command;
  178. // Search
  179. if (command === 'search') {
  180. if (request.method === 'POST') {
  181. // search results
  182. return handleSearch(request, gd);
  183. } else {
  184. const params = url.searchParams;
  185. // Search Page
  186. return new Response(html(gd.order, {
  187. q: params.get("q") || '',
  188. is_search_page: true,
  189. root_type: gd.root_type
  190. }),
  191. {
  192. status: 200,
  193. headers: {'Content-Type': 'text/html; charset=utf-8'}
  194. });
  195. }
  196. } else if (command === 'id2path' && request.method === 'POST') {
  197. return handleId2Path(request, gd)
  198. }
  199. }
  200.  
  201. // Desired path format
  202. const common_reg = /^\/\d+:\/.*$/g;
  203. try {
  204. if (!path.match(common_reg)) {
  205. return redirectToIndexPage();
  206. }
  207. let split = path.split("/");
  208. let order = Number(split[1].slice(0, -1));
  209. if (order >= 0 && order < gds.length) {
  210. gd = gds[order];
  211. } else {
  212. return redirectToIndexPage()
  213. }
  214. } catch (e) {
  215. return redirectToIndexPage()
  216. }
  217.  
  218. // basic auth
  219. for (const r = gd.basicAuthResponse(request); r;) return r;
  220.  
  221. path = path.replace(gd.url_path_prefix, '') || '/';
  222. if (request.method == 'POST') {
  223. return apiRequest(request, gd);
  224. }
  225.  
  226. let action = url.searchParams.get('a');
  227.  
  228. if (path.substr(-1) == '/' || action != null) {
  229. return new Response(html(gd.order, {root_type: gd.root_type}), {
  230. status: 200,
  231. headers: {'Content-Type': 'text/html; charset=utf-8'}
  232. });
  233. } else {
  234. if (path.split('/').pop().toLowerCase() == ".password") {
  235. return new Response("", {status: 404});
  236. }
  237. let file = await gd.file(path);
  238. let range = request.headers.get('Range');
  239. const inline_down = 'true' === url.searchParams.get('inline');
  240. return gd.down(file.id, range, inline_down);
  241. }
  242. }
  243.  
  244.  
  245. async function apiRequest(request, gd) {
  246. let url = new URL(request.url);
  247. let path = url.pathname;
  248. path = path.replace(gd.url_path_prefix, '') || '/';
  249.  
  250. let option = {status: 200, headers: {'Access-Control-Allow-Origin': '*'}}
  251.  
  252. if (path.substr(-1) == '/') {
  253. let form = await request.formData();
  254. // This can increase the speed of the first listing. The disadvantage is that if the password verification fails, the overhead of listing directories will still be incurred
  255. let deferred_list_result = gd.list(path, form.get('page_token'), Number(form.get('page_index')));
  256.  
  257. // check .password file, if `enable_password_file_verify` is true
  258. if (authConfig['enable_password_file_verify']) {
  259. let password = await gd.password(path);
  260. // console.log("dir password", password);
  261. if (password && password.replace("\n", "") !== form.get('password')) {
  262. let html = `{"error": {"code": 401,"message": "password error."}}`;
  263. return new Response(html, option);
  264. }
  265. }
  266.  
  267. let list_result = await deferred_list_result;
  268. return new Response(JSON.stringify(list_result), option);
  269. } else {
  270. let file = await gd.file(path);
  271. let range = request.headers.get('Range');
  272. return new Response(JSON.stringify(file));
  273. }
  274. }
  275.  
  276. // Deal With search
  277. async function handleSearch(request, gd) {
  278. const option = {status: 200, headers: {'Access-Control-Allow-Origin': '*'}};
  279. let form = await request.formData();
  280. let search_result = await
  281. gd.search(form.get('q') || '', form.get('page_token'), Number(form.get('page_index')));
  282. return new Response(JSON.stringify(search_result), option);
  283. }
  284.  
  285. /**
  286. * Deal With id2path
  287. * @param request Id parameter required
  288. * @param gd
  289. * @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 ""
  290. */
  291. async function handleId2Path(request, gd) {
  292. const option = {status: 200, headers: {'Access-Control-Allow-Origin': '*'}};
  293. let form = await request.formData();
  294. let path = await gd.findPathById(form.get('id'));
  295. return new Response(path || '', option);
  296. }
  297.  
  298. class googleDrive {
  299. constructor(authConfig, order) {
  300. // Each disk corresponds to an order, corresponding to a gd instance
  301. this.order = order;
  302. this.root = authConfig.roots[order];
  303. this.url_path_prefix = `/${order}:`;
  304. this.authConfig = authConfig;
  305. // TODO: The invalid refresh strategy of these caches can be formulated later
  306. // path id
  307. this.paths = [];
  308. // path file
  309. this.files = [];
  310. // path pass
  311. this.passwords = [];
  312. // id <-> path
  313. this.id_path_cache = {};
  314. this.id_path_cache[this.root['id']] = '/';
  315. this.paths["/"] = this.root['id'];
  316. /*if (this.root['pass'] != "") {
  317. this.passwords['/'] = this.root['pass'];
  318. }*/
  319. // this.init();
  320. }
  321.  
  322. /**
  323. * Initial authorization; then obtain user_drive_real_root_id
  324. * @returns {Promise<void>}
  325. */
  326. async init() {
  327. await this.accessToken();
  328. /*await (async () => {
  329. // Get only 1 time
  330. if (authConfig.user_drive_real_root_id) return;
  331. const root_obj = await (gds[0] || this).findItemById('root');
  332. if (root_obj && root_obj.id) {
  333. authConfig.user_drive_real_root_id = root_obj.id
  334. }
  335. })();*/
  336. // Wait for user_drive_real_root_id and only get it once
  337. if (authConfig.user_drive_real_root_id) return;
  338. const root_obj = await (gds[0] || this).findItemById('root');
  339. if (root_obj && root_obj.id) {
  340. authConfig.user_drive_real_root_id = root_obj.id
  341. }
  342. }
  343.  
  344. /**
  345. * Get the root directory type, set to root_type
  346. * @returns {Promise<void>}
  347. */
  348. async initRootType() {
  349. const root_id = this.root['id'];
  350. const types = CONSTS.gd_root_type;
  351. if (root_id === 'root' || root_id === authConfig.user_drive_real_root_id) {
  352. this.root_type = types.user_drive;
  353. } else {
  354. const obj = await this.getShareDriveObjById(root_id);
  355. this.root_type = obj ? types.share_drive : types.sub_folder;
  356. }
  357. }
  358. /**
  359. * Returns a response that requires authorization, or null
  360. * @param request
  361. * @returns {Response|null}
  362. */
  363. basicAuthResponse(request) {
  364. const user = this.root.user || '',
  365. pass = this.root.pass || '',
  366. _401 = new Response('Unauthorized', {
  367. headers: {'WWW-Authenticate': `Basic realm="goindex:drive:${this.order}"`},
  368. status: 401
  369. });
  370. if (user || pass) {
  371. const auth = request.headers.get('Authorization')
  372. if (auth) {
  373. try {
  374. const [received_user, received_pass] = atob(auth.split(' ').pop()).split(':');
  375. return (received_user === user && received_pass === pass) ? null : _401;
  376. } catch (e) {
  377. }
  378. }
  379. } else return null;
  380. return _401;
  381. }
  382.  
  383. async down(id, range = '', inline = false) {
  384. let url = `https://www.googleapis.com/drive/v3/files/${id}?alt=media`;
  385. let requestOption = await this.requestOption();
  386. requestOption.headers['Range'] = range;
  387. let res = await fetch(url, requestOption);
  388. const {headers} = res = new Response(res.body, res)
  389. this.authConfig.enable_cors_file_down && headers.append('Access-Control-Allow-Origin', '*');
  390. inline === true && headers.set('Content-Disposition', 'inline');
  391. return res;
  392. }
  393.  
  394. async file(path) {
  395. if (typeof this.files[path] == 'undefined') {
  396. this.files[path] = await this._file(path);
  397. }
  398. return this.files[path];
  399. }
  400.  
  401. async _file(path) {
  402. let arr = path.split('/');
  403. let name = arr.pop();
  404. name = decodeURIComponent(name).replace(/\'/g, "\\'");
  405. let dir = arr.join('/') + '/';
  406. // console.log(name, dir);
  407. let parent = await this.findPathId(dir);
  408. // console.log(parent);
  409. let url = 'https://www.googleapis.com/drive/v3/files';
  410. let params = {'includeItemsFromAllDrives': true, 'supportsAllDrives': true};
  411. params.q = `'${parent}' in parents and name = '${name}' and trashed = false`;
  412. params.fields = "files(id, name, mimeType, size ,createdTime, modifiedTime, iconLink, thumbnailLink)";
  413. url += '?' + this.enQuery(params);
  414. let requestOption = await this.requestOption();
  415. let response = await fetch(url, requestOption);
  416. let obj = await response.json();
  417. // console.log(obj);
  418. return obj.files[0];
  419. }
  420.  
  421. // Cache through reqeust cache
  422. async list(path, page_token = null, page_index = 0) {
  423. if (this.path_children_cache == undefined) {
  424. // { <path> :[ {nextPageToken:'',data:{}}, {nextPageToken:'',data:{}} ...], ...}
  425. this.path_children_cache = {};
  426. }
  427.  
  428. if (this.path_children_cache[path]
  429. && this.path_children_cache[path][page_index]
  430. && this.path_children_cache[path][page_index].data
  431. ) {
  432. let child_obj = this.path_children_cache[path][page_index];
  433. return {
  434. nextPageToken: child_obj.nextPageToken || null,
  435. curPageIndex: page_index,
  436. data: child_obj.data
  437. };
  438. }
  439.  
  440. let id = await this.findPathId(path);
  441. let result = await this._ls(id, page_token, page_index);
  442. let data = result.data;
  443. // Cache multiple pages
  444. if (result.nextPageToken && data.files) {
  445. if (!Array.isArray(this.path_children_cache[path])) {
  446. this.path_children_cache[path] = []
  447. }
  448. this.path_children_cache[path][Number(result.curPageIndex)] = {
  449. nextPageToken: result.nextPageToken,
  450. data: data
  451. };
  452. }
  453.  
  454. return result
  455. }
  456.  
  457.  
  458. async _ls(parent, page_token = null, page_index = 0) {
  459. // console.log("_ls", parent);
  460.  
  461. if (parent == undefined) {
  462. return null;
  463. }
  464. let obj;
  465. let params = {'includeItemsFromAllDrives': true, 'supportsAllDrives': true};
  466. params.q = `'${parent}' in parents and trashed = false AND name !='.password'`;
  467. params.orderBy = 'folder,name,modifiedTime desc';
  468. params.fields = "nextPageToken, files(id, name, mimeType, size , modifiedTime)";
  469. params.pageSize = this.authConfig.files_list_page_size;
  470.  
  471. if (page_token) {
  472. params.pageToken = page_token;
  473. }
  474. let url = 'https://www.googleapis.com/drive/v3/files';
  475. url += '?' + this.enQuery(params);
  476. let requestOption = await this.requestOption();
  477. let response = await fetch(url, requestOption);
  478. obj = await response.json();
  479.  
  480. return {
  481. nextPageToken: obj.nextPageToken || null,
  482. curPageIndex: page_index,
  483. data: obj
  484. };
  485.  
  486. /*do {
  487. if (pageToken) {
  488. params.pageToken = pageToken;
  489. }
  490. let url = 'https://www.googleapis.com/drive/v3/files';
  491. url += '?' + this.enQuery(params);
  492. let requestOption = await this.requestOption();
  493. let response = await fetch(url, requestOption);
  494. obj = await response.json();
  495. files.push(...obj.files);
  496. pageToken = obj.nextPageToken;
  497. } while (pageToken);*/
  498.  
  499. }
  500.  
  501. async password(path) {
  502. if (this.passwords[path] !== undefined) {
  503. return this.passwords[path];
  504. }
  505.  
  506. // console.log("load", path, ".password", this.passwords[path]);
  507.  
  508. let file = await this.file(path + '.password');
  509. if (file == undefined) {
  510. this.passwords[path] = null;
  511. } else {
  512. let url = `https://www.googleapis.com/drive/v3/files/${file.id}?alt=media`;
  513. let requestOption = await this.requestOption();
  514. let response = await this.fetch200(url, requestOption);
  515. this.passwords[path] = await response.text();
  516. }
  517.  
  518. return this.passwords[path];
  519. }
  520.  
  521.  
  522. /**
  523. * Get share drive information by id
  524. * @param any_id
  525. * @returns {Promise<null|{id}|any>} Any abnormal situation returns null
  526. */
  527. async getShareDriveObjById(any_id) {
  528. if (!any_id) return null;
  529. if ('string' !== typeof any_id) return null;
  530.  
  531. let url = `https://www.googleapis.com/drive/v3/drives/${any_id}`;
  532. let requestOption = await this.requestOption();
  533. let res = await fetch(url, requestOption);
  534. let obj = await res.json();
  535. if (obj && obj.id) return obj;
  536.  
  537. return null
  538. }
  539.  
  540.  
  541. /**
  542. * search for
  543. * @returns {Promise<{data: null, nextPageToken: null, curPageIndex: number}>}
  544. */
  545. async search(origin_keyword, page_token = null, page_index = 0) {
  546. const types = CONSTS.gd_root_type;
  547. const is_user_drive = this.root_type === types.user_drive;
  548. const is_share_drive = this.root_type === types.share_drive;
  549.  
  550. const empty_result = {
  551. nextPageToken: null,
  552. curPageIndex: page_index,
  553. data: null
  554. };
  555.  
  556. if (!is_user_drive && !is_share_drive) {
  557. return empty_result;
  558. }
  559. let keyword = FUNCS.formatSearchKeyword(origin_keyword);
  560. if (!keyword) {
  561. // The keyword is empty, return
  562. return empty_result;
  563. }
  564. let words = keyword.split(/\s+/);
  565. let name_search_str = `name contains '${words.join("' AND name contains '")}'`;
  566.  
  567. // corpora is a personal drive for user and a team drive for drive. With driveId
  568. let params = {};
  569. if (is_user_drive) {
  570. params.corpora = 'user'
  571. }
  572. if (is_share_drive) {
  573. params.corpora = 'drive';
  574. params.driveId = this.root.id;
  575. // This parameter will only be effective until June 1, 2020. Afterwards shared drive items will be included in the results.
  576. params.includeItemsFromAllDrives = true;
  577. params.supportsAllDrives = true;
  578. }
  579. if (page_token) {
  580. params.pageToken = page_token;
  581. }
  582. params.q = `trashed = false AND name !='.password' AND (${name_search_str})`;
  583. params.fields = "nextPageToken, files(id, name, mimeType, size , modifiedTime)";
  584. params.pageSize = this.authConfig.search_result_list_page_size;
  585. // params.orderBy = 'folder,name,modifiedTime desc';
  586.  
  587. let url = 'https://www.googleapis.com/drive/v3/files';
  588. url += '?' + this.enQuery(params);
  589. // console.log(params)
  590. let requestOption = await this.requestOption();
  591. let response = await fetch(url, requestOption);
  592. let res_obj = await response.json();
  593.  
  594. return {
  595. nextPageToken: res_obj.nextPageToken || null,
  596. curPageIndex: page_index,
  597. data: res_obj
  598. };
  599. }
  600.  
  601.  
  602. /**
  603. * Get the file object of the superior folder of this file or folder layer by layer. Note: It will be very slow! ! !
  604. * Up to find the root directory (root id) of the current gd object
  605. * Only consider a single upward chain.
  606. * [Note] If the item represented by this id is not under the target gd disk, then this function will return null
  607. *
  608. * @param child_id
  609. * @param contain_myself
  610. * @returns {Promise<[]>}
  611. */
  612. async findParentFilesRecursion(child_id, contain_myself = true) {
  613. const gd = this;
  614. const gd_root_id = gd.root.id;
  615. const user_drive_real_root_id = authConfig.user_drive_real_root_id;
  616. const is_user_drive = gd.root_type === CONSTS.gd_root_type.user_drive;
  617.  
  618. // End point query id from bottom to top
  619. const target_top_id = is_user_drive ? user_drive_real_root_id : gd_root_id;
  620. const fields = CONSTS.default_file_fields;
  621.  
  622. // [{},{},...]
  623. const parent_files = [];
  624. let meet_top = false;
  625.  
  626. async function addItsFirstParent(file_obj) {
  627. if (!file_obj) return;
  628. if (!file_obj.parents) return;
  629. if (file_obj.parents.length < 1) return;
  630.  
  631. // ['','',...]
  632. let p_ids = file_obj.parents;
  633. if (p_ids && p_ids.length > 0) {
  634. // its first parent
  635. const first_p_id = p_ids[0];
  636. if (first_p_id === target_top_id) {
  637. meet_top = true;
  638. return;
  639. }
  640. const p_file_obj = await gd.findItemById(first_p_id);
  641. if (p_file_obj && p_file_obj.id) {
  642. parent_files.push(p_file_obj);
  643. await addItsFirstParent(p_file_obj);
  644. }
  645. }
  646. }
  647.  
  648. const child_obj = await gd.findItemById(child_id);
  649. if (contain_myself) {
  650. parent_files.push(child_obj);
  651. }
  652. await addItsFirstParent(child_obj);
  653.  
  654. return meet_top ? parent_files : null
  655. }
  656.  
  657. /**
  658. * Get the path relative to the root directory of this disk
  659. * @param child_id
  660. * @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 ""
  661. */
  662. async findPathById(child_id) {
  663. if (this.id_path_cache[child_id]) {
  664. return this.id_path_cache[child_id];
  665. }
  666.  
  667. const p_files = await this.findParentFilesRecursion(child_id);
  668. if (!p_files || p_files.length < 1) return '';
  669.  
  670. let cache = [];
  671. // Cache the path and id of each level found
  672. p_files.forEach((value, idx) => {
  673. const is_folder = idx === 0 ? (p_files[idx].mimeType === CONSTS.folder_mime_type) : true;
  674. let path = '/' + p_files.slice(idx).map(it => it.name).reverse().join('/');
  675. if (is_folder) path += '/';
  676. cache.push({id: p_files[idx].id, path: path})
  677. });
  678.  
  679. cache.forEach((obj) => {
  680. this.id_path_cache[obj.id] = obj.path;
  681. this.paths[obj.path] = obj.id
  682. });
  683.  
  684. /*const is_folder = p_files[0].mimeType === CONSTS.folder_mime_type;
  685. let path = '/' + p_files.map(it => it.name).reverse().join('/');
  686. if (is_folder) path += '/';*/
  687.  
  688. return cache[0].path;
  689. }
  690.  
  691.  
  692. // Get file item based on id
  693. async findItemById(id) {
  694. const is_user_drive = this.root_type === CONSTS.gd_root_type.user_drive;
  695. let url = `https://www.googleapis.com/drive/v3/files/${id}?fields=${CONSTS.default_file_fields}${is_user_drive ? '' : '&supportsAllDrives=true'}`;
  696. let requestOption = await this.requestOption();
  697. let res = await fetch(url, requestOption);
  698. return await res.json()
  699. }
  700.  
  701. async findPathId(path) {
  702. let c_path = '/';
  703. let c_id = this.paths[c_path];
  704.  
  705. let arr = path.trim('/').split('/');
  706. for (let name of arr) {
  707. c_path += name + '/';
  708.  
  709. if (typeof this.paths[c_path] == 'undefined') {
  710. let id = await this._findDirId(c_id, name);
  711. this.paths[c_path] = id;
  712. }
  713.  
  714. c_id = this.paths[c_path];
  715. if (c_id == undefined || c_id == null) {
  716. break;
  717. }
  718. }
  719. // console.log(this.paths);
  720. return this.paths[path];
  721. }
  722.  
  723. async _findDirId(parent, name) {
  724. name = decodeURIComponent(name).replace(/\'/g, "\\'");
  725.  
  726. // console.log("_findDirId", parent, name);
  727.  
  728. if (parent == undefined) {
  729. return null;
  730. }
  731.  
  732. let url = 'https://www.googleapis.com/drive/v3/files';
  733. let params = {'includeItemsFromAllDrives': true, 'supportsAllDrives': true};
  734. params.q = `'${parent}' in parents and mimeType = 'application/vnd.google-apps.folder' and name = '${name}' and trashed = false`;
  735. params.fields = "nextPageToken, files(id, name, mimeType)";
  736. url += '?' + this.enQuery(params);
  737. let requestOption = await this.requestOption();
  738. let response = await fetch(url, requestOption);
  739. let obj = await response.json();
  740. if (obj.files[0] == undefined) {
  741. return null;
  742. }
  743. return obj.files[0].id;
  744. }
  745.  
  746. async accessToken() {
  747. console.log("accessToken");
  748. if (this.authConfig.expires == undefined || this.authConfig.expires < Date.now()) {
  749. const obj = await this.fetchAccessToken();
  750. if (obj.access_token != undefined) {
  751. this.authConfig.accessToken = obj.access_token;
  752. this.authConfig.expires = Date.now() + 3500 * 1000;
  753. }
  754. }
  755. return this.authConfig.accessToken;
  756. }
  757.  
  758. async fetchAccessToken() {
  759. console.log("fetchAccessToken");
  760. const url = "https://www.googleapis.com/oauth2/v4/token";
  761. const headers = {
  762. 'Content-Type': 'application/x-www-form-urlencoded'
  763. };
  764. const post_data = {
  765. 'client_id': this.authConfig.client_id,
  766. 'client_secret': this.authConfig.client_secret,
  767. 'refresh_token': this.authConfig.refresh_token,
  768. 'grant_type': 'refresh_token'
  769. }
  770.  
  771. let requestOption = {
  772. 'method': 'POST',
  773. 'headers': headers,
  774. 'body': this.enQuery(post_data)
  775. };
  776.  
  777. const response = await fetch(url, requestOption);
  778. return await response.json();
  779. }
  780.  
  781. async fetch200(url, requestOption) {
  782. let response;
  783. for (let i = 0; i < 3; i++) {
  784. response = await fetch(url, requestOption);
  785. console.log(response.status);
  786. if (response.status != 403) {
  787. break;
  788. }
  789. await this.sleep(800 * (i + 1));
  790. }
  791. return response;
  792. }
  793.  
  794. async requestOption(headers = {}, method = 'GET') {
  795. const accessToken = await this.accessToken();
  796. headers['authorization'] = 'Bearer ' + accessToken;
  797. return {'method': method, 'headers': headers};
  798. }
  799.  
  800. enQuery(data) {
  801. const ret = [];
  802. for (let d in data) {
  803. ret.push(encodeURIComponent(d) + '=' + encodeURIComponent(data[d]));
  804. }
  805. return ret.join('&');
  806. }
  807.  
  808. sleep(ms) {
  809. return new Promise(function (resolve, reject) {
  810. let i = 0;
  811. setTimeout(function () {
  812. console.log('sleep' + ms);
  813. i++;
  814. if (i >= 2) reject(new Error('i>=2'));
  815. else resolve(i);
  816. }, ms);
  817. })
  818. }
  819. }
  820.  
  821. String.prototype.trim = function (char) {
  822. if (char) {
  823. return this.replace(new RegExp('^\\' + char + '+|\\' + char + '+$', 'g'), '');
  824. }
  825. return this.replace(/^\s+|\s+$/g, '');
  826. };
Add Comment
Please, Sign In to add comment