├── web
├── .browserslistrc
├── src
│ ├── style.css
│ ├── assets
│ │ ├── logo.png
│ │ ├── README.md
│ │ └── logo.svg
│ ├── api.js
│ ├── plugins
│ │ └── vuetify.js
│ ├── i18n.js
│ ├── locales
│ │ ├── zh-CN.js
│ │ ├── zh-TW.js
│ │ └── en.js
│ ├── main.js
│ ├── components
│ │ ├── PdfViewer.vue
│ │ ├── EpubViewer.vue
│ │ ├── LoginDialog.vue
│ │ ├── VideoViewer.vue
│ │ ├── FileUploadDialog.vue
│ │ └── FileViewer.vue
│ ├── router.js
│ ├── App.vue
│ └── xfetch.js
├── babel.config.js
├── postcss.config.js
├── .prettierrc
├── dist
│ ├── fonts
│ │ ├── materialdesignicons-webfont.eot
│ │ ├── materialdesignicons-webfont.ttf
│ │ ├── materialdesignicons-webfont.woff
│ │ └── materialdesignicons-webfont.woff2
│ └── index.html
├── vue.config.js
├── .eslintrc.js
├── README.md
└── package.json
├── worker
├── .babelrc
├── package.json
├── bili.config.js
├── router.js
├── xfetch.js
├── googleDrive.js
├── index.js
└── dist
│ └── worker.js
├── .editorconfig
├── .github
└── ISSUE_TEMPLATE
│ ├── custom.md
│ └── bug-report-------.md
├── README.zh.md
├── README.zhtw.md
├── LICENSE
├── README.md
└── .gitignore
/web/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 |
--------------------------------------------------------------------------------
/web/src/style.css:
--------------------------------------------------------------------------------
1 | .pointer {
2 | cursor: pointer;
3 | }
4 |
--------------------------------------------------------------------------------
/worker/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [],
3 | "plugins": []
4 | }
5 |
--------------------------------------------------------------------------------
/web/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@vue/app']
3 | }
4 |
--------------------------------------------------------------------------------
/web/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | autoprefixer: {}
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/web/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coko8023/GDIndex/master/web/src/assets/logo.png
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | tab_width = 4
6 | indent_style = tab
7 | insert_final_newline = true
8 |
--------------------------------------------------------------------------------
/web/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": true,
3 | "tabWidth": 4,
4 | "endOfLine": "lf",
5 | "singleQuote": true,
6 | "semi": false
7 | }
8 |
--------------------------------------------------------------------------------
/web/dist/fonts/materialdesignicons-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coko8023/GDIndex/master/web/dist/fonts/materialdesignicons-webfont.eot
--------------------------------------------------------------------------------
/web/dist/fonts/materialdesignicons-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coko8023/GDIndex/master/web/dist/fonts/materialdesignicons-webfont.ttf
--------------------------------------------------------------------------------
/web/dist/fonts/materialdesignicons-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coko8023/GDIndex/master/web/dist/fonts/materialdesignicons-webfont.woff
--------------------------------------------------------------------------------
/web/dist/fonts/materialdesignicons-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coko8023/GDIndex/master/web/dist/fonts/materialdesignicons-webfont.woff2
--------------------------------------------------------------------------------
/web/src/assets/README.md:
--------------------------------------------------------------------------------
1 | `epub-reader.html` is the inlined version of https://github.com/maple3142/epubjs-reader, and the tool I used https://github.com/remy/inliner
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/custom.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Custom issue template
3 | about: Describe this issue template's purpose here.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/web/src/api.js:
--------------------------------------------------------------------------------
1 | import xf from './xfetch'
2 |
3 | const headers = {}
4 | if (localStorage.token) {
5 | headers.Authorization = 'Basic ' + localStorage.token
6 | }
7 | export default xf.extend({
8 | baseURI: window.props.api,
9 | headers
10 | })
11 |
--------------------------------------------------------------------------------
/web/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | pluginOptions: {
3 | i18n: {
4 | locale: 'en',
5 | fallbackLocale: 'en',
6 | localeDir: 'locales',
7 | enableInSFC: false
8 | }
9 | },
10 | filenameHashing: false,
11 | configureWebpack: {
12 | optimization: {
13 | splitChunks: false
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/worker/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gdindex-worker",
3 | "version": "0.1.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "dependencies": {
7 | "mime": "^2.4.4",
8 | "path-to-regexp": "^3.1.0"
9 | },
10 | "devDependencies": {
11 | "bili": "^4.8.1"
12 | },
13 | "scripts": {
14 | "build": "bili"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/web/src/plugins/vuetify.js:
--------------------------------------------------------------------------------
1 | import '@mdi/font/css/materialdesignicons.min.css'
2 | import Vue from 'vue'
3 | import Vuetify from 'vuetify/lib'
4 | import i18n from '../i18n'
5 |
6 | Vue.use(Vuetify)
7 |
8 | export default new Vuetify({
9 | icons: {
10 | iconfont: 'mdi'
11 | },
12 | lang: {
13 | t: (key, ...params) => i18n.t(key, params)
14 | }
15 | })
16 |
--------------------------------------------------------------------------------
/web/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | extends: ['plugin:vue/essential', '@vue/prettier'],
7 | rules: {
8 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
9 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
10 | },
11 | parserOptions: {
12 | parser: 'babel-eslint'
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/web/src/i18n.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueI18n from 'vue-i18n'
3 | import en from './locales/en'
4 | import zhTW from './locales/zh-TW'
5 | import zhCN from './locales/zh-CN'
6 |
7 | Vue.use(VueI18n)
8 |
9 | export default new VueI18n({
10 | locale: navigator.language,
11 | fallbackLocale: 'en',
12 | messages: {
13 | en,
14 | 'zh-TW': zhTW,
15 | 'zh-HK': zhTW,
16 | 'zh-CN': zhCN,
17 | zh: zhCN
18 | }
19 | })
20 |
--------------------------------------------------------------------------------
/web/src/locales/zh-CN.js:
--------------------------------------------------------------------------------
1 | import $vuetify from 'vuetify/es5/locale/zh-Hans'
2 |
3 | export default {
4 | fileName: '文件名称',
5 | modifiedTime: '修改时间',
6 | fileSize: '文件大小',
7 | mainDrive: '主硬盘',
8 | search: '搜索',
9 | fileUpload: '上传文件',
10 | urlUpload: '从网址上传',
11 | upload: '上传',
12 | fileToUpload: '要上传的文件',
13 | uploading: '正在上传...',
14 | serverProcessing: '服务器正在处理文件',
15 | bigFileUploadWarning:
16 | '由于 CloudFlare Workers 的限制,上传大档案可能会随机失败',
17 | $vuetify
18 | }
19 |
--------------------------------------------------------------------------------
/web/src/locales/zh-TW.js:
--------------------------------------------------------------------------------
1 | import $vuetify from 'vuetify/es5/locale/zh-Hant'
2 |
3 | export default {
4 | fileName: '檔案名稱',
5 | modifiedTime: '修改時間',
6 | fileSize: '檔案大小',
7 | mainDrive: '主要硬碟',
8 | search: '搜尋',
9 | fileUpload: '檔案上傳',
10 | urlUpload: '從網址上傳',
11 | upload: '上傳',
12 | fileToUpload: '要上傳的檔案',
13 | uploading: '上傳中...',
14 | serverProcessing: '伺服器正在處理檔案',
15 | bigFileUploadWarning:
16 | '由於 CloudFlare Workers 的限制,上傳大檔案可能會隨機失敗',
17 | $vuetify
18 | }
19 |
--------------------------------------------------------------------------------
/web/README.md:
--------------------------------------------------------------------------------
1 | # gdindex
2 |
3 | ## Project setup
4 | ```
5 | yarn install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | yarn run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | yarn run build
16 | ```
17 |
18 | ### Run your tests
19 | ```
20 | yarn run test
21 | ```
22 |
23 | ### Lints and fixes files
24 | ```
25 | yarn run lint
26 | ```
27 |
28 | ### Customize configuration
29 | See [Configuration Reference](https://cli.vuejs.org/config/).
30 |
--------------------------------------------------------------------------------
/web/src/locales/en.js:
--------------------------------------------------------------------------------
1 | export default {
2 | fileName: 'File Name',
3 | modifiedTime: 'Modified Time',
4 | fileSize: 'File Size',
5 | mainDrive: 'Main Drive',
6 | search: 'Search',
7 | fileUpload: 'File Upload',
8 | urlUpload: 'Upload from url',
9 | upload: 'Upload',
10 | fileToUpload: 'File to upload',
11 | uploading: 'Uploading...',
12 | serverProcessing: 'Server is processing the file now',
13 | bigFileUploadWarning:
14 | "Due to CloudFlare Workers' limitation, uploading bigfiles may randomly failed."
15 | }
16 |
--------------------------------------------------------------------------------
/worker/bili.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | input: 'index.js',
3 | output: {
4 | dir: 'dist',
5 | fileName: 'worker.js',
6 | format: 'iife'
7 | },
8 | minify: false,
9 | target: 'browser',
10 | banner: `
11 | self.props = {
12 | title: 'GDIndex',
13 | default_root_id: 'root',
14 | client_id: '202264815644.apps.googleusercontent.com',
15 | client_secret: 'X4Z3ca8xfWDb1Voo-F9a7ZxJ',
16 | refresh_token: '',
17 | auth: false,
18 | user: '',
19 | pass: '',
20 | upload: false,
21 | lite: false
22 | };`.slice(1)
23 | }
24 |
--------------------------------------------------------------------------------
/web/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report-------.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report / 錯誤回報
3 | about: You must follow this template or I will close this directly. / 請務必填寫此模板,否則我會直接關閉。
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
12 |
13 | # Bug Description / 問題描述
14 |
15 |
16 |
17 |
18 | # Environment / 環境
19 |
20 | * Browser version / 瀏覽器版本:
21 | * OS version / 作業系統版本:
22 |
--------------------------------------------------------------------------------
/web/src/main.js:
--------------------------------------------------------------------------------
1 | import './style.css'
2 | import Vue from 'vue'
3 | import App from './App.vue'
4 | import router from './router'
5 | import vuetify from './plugins/vuetify'
6 | import i18n from './i18n'
7 | import PortalVue from 'portal-vue'
8 |
9 | if (window.props.defaultRootId) {
10 | // backward compability
11 | window.props.default_root_id = window.props.defaultRootId
12 | }
13 |
14 | Vue.use(PortalVue)
15 |
16 | Vue.config.productionTip = false
17 |
18 | window.app = new Vue({
19 | router,
20 | vuetify,
21 | i18n,
22 | render: h => h(App, { props: window.props })
23 | }).$mount('#app')
24 |
--------------------------------------------------------------------------------
/web/src/components/PdfViewer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
21 |
27 |
--------------------------------------------------------------------------------
/web/dist/index.html:
--------------------------------------------------------------------------------
1 |
GDIndex
--------------------------------------------------------------------------------
/web/src/router.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 | import FileViewer from './components/FileViewer.vue'
4 | import EpubViewer from './components/EpubViewer.vue'
5 | import VideoViewer from './components/VideoViewer.vue'
6 | import PdfViewer from './components/PdfViewer.vue'
7 |
8 | Vue.use(VueRouter)
9 | const router = new VueRouter({
10 | routes: [
11 | { path: '/~viewer/epub', component: EpubViewer },
12 | { path: '/~viewer/video', component: VideoViewer },
13 | { path: '/~viewer/pdf', component: PdfViewer },
14 | { path: '/:path(.*)', component: FileViewer }
15 | ],
16 | mode: 'history'
17 | })
18 |
19 | export default router
20 |
--------------------------------------------------------------------------------
/README.zh.md:
--------------------------------------------------------------------------------
1 | # GDIndex
2 |
3 | 
4 |
5 | > GDIndex 是一个类似 [GOIndex](https://github.com/donwa/goindex) 的东西,可以在 CloudFlare Workers 上架设 Google Drive 的目录,并提供许多功能
6 | >
7 | > 另外,这个并不是从 GOIndex 修改来了,而是直接重写
8 |
9 | [Demo](https://gdindex-demo.maple3142.workers.dev/)
10 |
11 | ## 和 GOIndex 不同之处
12 |
13 | - 前端使用 Vue 完成
14 | - 查看图片不用另开新窗口
15 | - 视频播放器支持字幕(目前只支持 srt)
16 | - 支持在线阅读 PDF, EPUB
17 | - 不支持目录加密(.password)
18 | - 支持 Http Basic Auth
19 | - 无需修改程序,即可接入多个云端硬盘(个人、团队)
20 |
21 | ## 使用教学
22 |
23 | ### 简单、自动的方法
24 |
25 | 前往 [https://gdindex-code-builder.glitch.me/](https://gdindex-code-builder.glitch.me/)(英文) 并遵照它的指示。
26 |
27 | ### 手动的方法
28 |
29 | 1. 安装 [rclone](https://rclone.org/)
30 | 2. 设定 Google Drive: https://rclone.org/drive/
31 | 3. 执行 `rclone config file` 以找到你的 `rclone.conf`
32 | 4. 在 `rclone.conf` 中寻找 `refresh_token` 以及 `root_folder_id` (可选)
33 | 5. 复制 [worker/dist/worker.js](worker/dist/worker.js) 的内容到 CloudFlare Workers
34 | 6. 在脚本顶端填上 `refresh_token`, `root_folder_id` 以及其他的选项
35 | 7. 部署!
36 |
--------------------------------------------------------------------------------
/README.zhtw.md:
--------------------------------------------------------------------------------
1 | # GDIndex
2 |
3 | 
4 |
5 | > GDIndex 是一個類似 [GOIndex](https://github.com/donwa/goindex) 的東西,可以在 CloudFlare Workers 上架設 Google Drive 的目錄,並提供許多功能
6 | >
7 | > 另外,這個並不是從 GOIndex 修改來了,而是直接重寫
8 |
9 | [Demo](https://gdindex-demo.maple3142.workers.dev/)
10 |
11 | ## 和 GOIndex 不同之處
12 |
13 | - 前端使用 Vue 完成
14 | - 圖片檢視不用另開新頁面
15 | - 影片播放器支援字幕(目前只有 srt)
16 | - 線上 PDF, EPUB 閱讀器
17 | - 不支援目錄加密(.password)
18 | - 支援 Http Basic Auth
19 | - 支援多雲端硬碟(個人、團隊),不需要額外改程式設定
20 |
21 | ## 使用教學
22 |
23 | ### 簡單、自動的方法
24 |
25 | 前往 [https://gdindex-code-builder.glitch.me/](https://gdindex-code-builder.glitch.me/)(英文) 並遵照它的指示。
26 |
27 | ### 手動的方法
28 |
29 | 1. 安裝 [rclone](https://rclone.org/)
30 | 2. 設定 Google Drive: https://rclone.org/drive/
31 | 3. 執行 `rclone config file` 以找到你的 `rclone.conf`
32 | 4. 在 `rclone.conf` 中尋找 `refresh_token` 以及 `root_folder_id` (選擇性)
33 | 5. 複製 [worker/dist/worker.js](worker/dist/worker.js) 的內容到 CloudFlare Workers
34 | 6. 在腳本頂端填上 `refresh_token`, `root_folder_id` 以及其他的選項
35 | 7. 部署!
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 maple3142
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/web/src/components/EpubViewer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
38 |
44 |
--------------------------------------------------------------------------------
/web/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gdindex-web",
3 | "version": "0.1.0",
4 | "scripts": {
5 | "serve": "vue-cli-service serve",
6 | "build": "vue-cli-service build",
7 | "lint": "vue-cli-service lint"
8 | },
9 | "dependencies": {
10 | "core-js": "^2.6.5",
11 | "date-fns": "^2.4.1",
12 | "inline-resource": "^0.1.7",
13 | "inliner": "^1.13.1",
14 | "portal-vue": "^2.1.6",
15 | "pretty-bytes": "^5.3.0",
16 | "viewerjs": "^1.3.7",
17 | "vue": "^2.6.10",
18 | "vue-i18n": "^8.0.0",
19 | "vue-router": "^3.0.3",
20 | "vuetify": "^2.0.0",
21 | "xfetch-js": "^0.3.4"
22 | },
23 | "devDependencies": {
24 | "@mdi/font": "^4.4.95",
25 | "@vue/cli-plugin-babel": "^3.11.0",
26 | "@vue/cli-plugin-eslint": "^3.11.0",
27 | "@vue/cli-service": "^3.11.0",
28 | "@vue/eslint-config-prettier": "^5.0.0",
29 | "babel-eslint": "^10.0.1",
30 | "eslint": "^5.16.0",
31 | "eslint-plugin-prettier": "^3.1.0",
32 | "eslint-plugin-vue": "^5.0.0",
33 | "material-design-icons-iconfont": "^5.0.1",
34 | "prettier": "^1.18.2",
35 | "raw-loader": "^3.1.0",
36 | "sass": "^1.17.4",
37 | "sass-loader": "^7.1.0",
38 | "vue-cli-plugin-i18n": "^0.6.0",
39 | "vue-cli-plugin-vuetify": "^0.6.3",
40 | "vue-template-compiler": "^2.6.10",
41 | "vuetify-loader": "^1.2.2"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/web/src/components/LoginDialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Require Authentiaction
5 |
6 |
7 |
8 |
9 |
15 |
16 |
17 |
18 |
19 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | Login
34 |
35 |
36 |
37 |
38 |
39 |
76 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GDIndex
2 |
3 | 
4 |
5 | [繁體中文](README.zhtw.md)
6 | [简体中文](README.zh.md)
7 |
8 | > GDIndex is similar to [GOIndex](https://github.com/donwa/goindex).
9 | > It allows you to deploy a "Google Drive Index" on CloudFlare Workers along with many extra features
10 | >
11 | > By the way, instead of modify from GOIndex, this is a total rewrite
12 |
13 | [Demo](https://gdindex-demo.maple3142.workers.dev/)
14 |
15 | ## Difference between GOIndex and GDIndex
16 |
17 | - Frontend is based on Vue.js
18 | - Image viewer doesn't require opening new page
19 | - Video player support subtitles(Currently only srt is supported)
20 | - Online PDF, EPUB reader
21 | - No directory-level password protection(.password)
22 | - Support Http Basic Auth
23 | - Support multiple drives(personal, team) without changing server's code
24 |
25 | ## Usage
26 |
27 | ### Simple and automatic way
28 |
29 | Go [https://gdindex-code-builder.glitch.me/](https://gdindex-code-builder.glitch.me/), and follow its instructions.
30 |
31 | ### Manual way
32 |
33 | 1. Install [rclone](https://rclone.org/)
34 | 2. Setup your Google Drive: https://rclone.org/drive/
35 | 3. Run `rclone config file` to find your `rclone.conf` location
36 | 4. Find `refresh_token` in your `rclone.conf`, and `root_folder_id` too(optionally).
37 | 5. Copy the content of [worker/dist/worker.js](worker/dist/worker.js) to CloudFlare Workers.
38 | 6. Fill `refresh_token`, `root_folder_id` and other options on the top of the script.
39 | 7. Deploy!
40 |
41 | ## Lite mode
42 |
43 | This mode will serve a simple nginx-like directory listing, and it only work with one drive. `upload` will be ignored in this mode.
44 |
45 | On the top of the script, change `lite: false` into `lite: true`, than thats all.
46 |
47 | [Lite mode demo](https://gdindex-demo-lite.maple3142.workers.dev/)
48 |
--------------------------------------------------------------------------------
/web/src/components/VideoViewer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
15 |
16 |
17 |
18 |
19 |
74 |
81 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # next.js build output
79 | .next
80 |
81 | # nuxt.js build output
82 | .nuxt
83 |
84 | # gatsby files
85 | .cache/
86 | public
87 |
88 | # vuepress build output
89 | .vuepress/dist
90 |
91 | # Serverless directories
92 | .serverless/
93 |
94 | # FuseBox cache
95 | .fusebox/
96 |
97 | # DynamoDB Local files
98 | .dynamodb/
99 |
100 | # TernJS port file
101 | .tern-port
102 |
103 | worker/script.js
104 | worker/wrangler.toml
105 | **/node_modules/
106 |
--------------------------------------------------------------------------------
/worker/router.js:
--------------------------------------------------------------------------------
1 | const pathToRegexp = require('path-to-regexp')
2 |
3 | class Router {
4 | constructor() {
5 | this.handlers = []
6 | }
7 | use(handler) {
8 | this.handlers.push(handler)
9 | return this
10 | }
11 | useRoute(path, handler) {
12 | const keys = []
13 | const re = pathToRegexp(path, keys)
14 | this.use(async (req, res, next) => {
15 | if (re.test(req.pathname)) {
16 | const [_, ...result] = re.exec(req.pathname)
17 | const params = {}
18 | for (let i = 0; i < result.length; i++) {
19 | params[keys[i].name] = result[i]
20 | }
21 | req.params = params
22 | await handler(req, res, next)
23 | }
24 | })
25 | return this
26 | }
27 | useRouteWithVerb(verb, path, handler) {
28 | verb = verb.toUpperCase()
29 | this.useRoute(path, async (req, res, next) => {
30 | if (req.method === verb) {
31 | await handler(req, res, next)
32 | }
33 | })
34 | return this
35 | }
36 | useWithVerb(verb, handler) {
37 | verb = verb.toUpperCase()
38 | this.use(async (req, res, next) => {
39 | if (req.method === verb) {
40 | await handler(req, res, next)
41 | }
42 | })
43 | return this
44 | }
45 | async handle(request) {
46 | const responseCtx = {
47 | body: '',
48 | headers: {}
49 | }
50 | const requestCtx = Object.assign({}, request, new URL(request.url))
51 | const createNext = n => async () => {
52 | const fn = this.handlers[n]
53 | if (!fn) return
54 | let gotCalled = false
55 | const next = createNext(n + 1)
56 | await fn(requestCtx, responseCtx, () => {
57 | gotCalled = true
58 | return next()
59 | })
60 | if (!gotCalled) {
61 | return next()
62 | }
63 | }
64 | await createNext(0)()
65 | return responseCtx.response ? responseCtx.response : new Response(responseCtx.body, responseCtx)
66 | }
67 | }
68 | for (const verb of ['get', 'post', 'delete', 'put', 'options', 'head']) {
69 | Router.prototype[verb] = function(path, handler) {
70 | if (handler) this.useRouteWithVerb(verb, path, handler)
71 | else this.useWithVerb(verb, path) // when there is only 1 argument, path is handler
72 | return this
73 | }
74 | }
75 | module.exports = Router
76 |
--------------------------------------------------------------------------------
/web/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ title }}
10 |
11 |
12 |
13 |
14 |
15 | mdi-cloud {{
16 | currentDrive.text
17 | }}mdi-menu-down
18 |
19 |
20 |
21 |
26 | {{
27 | item.text
28 | }}
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
43 | mdi-github-circle GitHub
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
112 |
--------------------------------------------------------------------------------
/worker/xfetch.js:
--------------------------------------------------------------------------------
1 | /*
2 | * XFetch.js modified
3 | * A extremely simple fetch extension inspired by sindresorhus/ky.
4 | */
5 | const xf = (() => {
6 | const METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head']
7 | class HTTPError extends Error {
8 | constructor(res) {
9 | super(res.statusText)
10 | this.name = 'HTTPError'
11 | this.response = res
12 | }
13 | }
14 | class XResponsePromise extends Promise {}
15 | for (const alias of ['arrayBuffer', 'blob', 'formData', 'json', 'text']) {
16 | // alias for .json() .text() etc...
17 | XResponsePromise.prototype[alias] = function(fn) {
18 | return this.then(res => res[alias]()).then(fn || (x => x))
19 | }
20 | }
21 | const { assign } = Object
22 | function mergeDeep(target, source) {
23 | const isObject = obj => obj && typeof obj === 'object'
24 |
25 | if (!isObject(target) || !isObject(source)) {
26 | return source
27 | }
28 |
29 | Object.keys(source).forEach(key => {
30 | const targetValue = target[key]
31 | const sourceValue = source[key]
32 |
33 | if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
34 | target[key] = targetValue.concat(sourceValue)
35 | } else if (isObject(targetValue) && isObject(sourceValue)) {
36 | target[key] = mergeDeep(Object.assign({}, targetValue), sourceValue)
37 | } else {
38 | target[key] = sourceValue
39 | }
40 | })
41 |
42 | return target
43 | }
44 | const fromEntries = ent => ent.reduce((acc, [k, v]) => ((acc[k] = v), acc), {})
45 | const typeis = (...types) => val =>
46 | types.some(type => (typeof type === 'string' ? typeof val === type : val instanceof type))
47 | const isstr = typeis('string')
48 | const isobj = typeis('object')
49 | const isstrorobj = v => isstr(v) || isobj(v)
50 | const responseErrorThrower = res => {
51 | if (!res.ok) throw new HTTPError(res)
52 | return res
53 | }
54 | const extend = (defaultInit = {}) => {
55 | const xfetch = (input, init = {}) => {
56 | mergeDeep(init, defaultInit)
57 | const createQueryString = o => new init.URLSearchParams(o).toString()
58 | const parseQueryString = s => fromEntries([...new init.URLSearchParams(s).entries()])
59 | const url = new init.URL(input, init.baseURI || undefined)
60 | if (!init.headers) {
61 | init.headers = {}
62 | } else if (typeis(init.Headers)(init.headers)) {
63 | // Transform into object if it is `Headers`
64 | init.headers = fromEntries([...init.headers.entries()])
65 | }
66 | // Add json or form on body
67 | if (init.json) {
68 | init.body = JSON.stringify(init.json)
69 | init.headers['Content-Type'] = 'application/json'
70 | } else if (isstrorobj(init.urlencoded)) {
71 | init.body = isstr(init.urlencoded) ? init.urlencoded : createQueryString(init.urlencoded)
72 | init.headers['Content-Type'] = 'application/x-www-form-urlencoded'
73 | } else if (typeis(init.FormData, 'object')(init.formData)) {
74 | // init.formData is data passed by user, init.FormData is FormData constructor
75 | if (!typeis(init.FormData)(init.formData)) {
76 | const fd = new init.FormData()
77 | for (const [k, v] of Object.entries(init.formData)) {
78 | fd.append(k, v)
79 | }
80 | init.formData = fd
81 | }
82 | init.body = init.formData
83 | }
84 | // Querystring
85 | if (init.qs) {
86 | if (isstr(init.qs)) init.qs = parseQueryString(init.qs)
87 | url.search = createQueryString(assign(fromEntries([...url.searchParams.entries()]), init.qs))
88 | }
89 | return XResponsePromise.resolve(init.fetch(url, init).then(responseErrorThrower))
90 | }
91 | for (const method of METHODS) {
92 | xfetch[method] = (input, init = {}) => {
93 | init.method = method.toUpperCase()
94 | return xfetch(input, init)
95 | }
96 | }
97 | // Extra methods and classes
98 | xfetch.extend = newDefaultInit => extend(assign({}, defaultInit, newDefaultInit))
99 | xfetch.HTTPError = HTTPError
100 | return xfetch
101 | }
102 | const isWindow = typeof document !== 'undefined'
103 | const isBrowser = typeof self !== 'undefined' // works in both window & worker scope
104 | return isBrowser
105 | ? extend({
106 | fetch: fetch.bind(self),
107 | URL,
108 | Response,
109 | URLSearchParams,
110 | Headers,
111 | FormData,
112 | baseURI: isWindow ? document.baseURI : '' // since there is no document in webworkers
113 | })
114 | : extend()
115 | })()
116 | export default xf
117 |
--------------------------------------------------------------------------------
/web/src/components/FileUploadDialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ $t('fileUpload') }}
6 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
23 |
24 |
25 |
32 |
33 |
34 |
35 |
36 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
52 |
53 |
54 |
55 |
59 |
60 |
61 |
62 |
63 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
179 |
187 |
--------------------------------------------------------------------------------
/web/src/xfetch.js:
--------------------------------------------------------------------------------
1 | /*
2 | * XFetch.js
3 | * A extremely simple fetch extension inspired by sindresorhus/ky.
4 | */
5 | export default (() => {
6 | const METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head']
7 | class HTTPError extends Error {
8 | constructor(res) {
9 | super(res.statusText)
10 | this.name = 'HTTPError'
11 | this.response = res
12 | }
13 | }
14 | class XResponsePromise extends Promise {}
15 | for (const alias of ['arrayBuffer', 'blob', 'formData', 'json', 'text']) {
16 | // alias for .json() .text() etc...
17 | XResponsePromise.prototype[alias] = function(fn) {
18 | return this.then(res => res[alias]()).then(fn || (x => x))
19 | }
20 | }
21 | function mergeDeep(target, source) {
22 | const isObject = obj => obj && typeof obj === 'object'
23 |
24 | if (!isObject(target) || !isObject(source)) {
25 | return source
26 | }
27 |
28 | Object.keys(source).forEach(key => {
29 | const targetValue = target[key]
30 | const sourceValue = source[key]
31 |
32 | if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
33 | target[key] = targetValue.concat(sourceValue)
34 | } else if (isObject(targetValue) && isObject(sourceValue)) {
35 | target[key] = mergeDeep(
36 | Object.assign({}, targetValue),
37 | sourceValue
38 | )
39 | } else {
40 | target[key] = sourceValue
41 | }
42 | })
43 |
44 | return target
45 | }
46 | const { assign } = Object
47 | const fromEntries = ent =>
48 | ent.reduce((acc, [k, v]) => ((acc[k] = v), acc), {})
49 | const typeis = (...types) => val =>
50 | types.some(type =>
51 | typeof type === 'string' ? typeof val === type : val instanceof type
52 | )
53 | const isstr = typeis('string')
54 | const isobj = typeis('object')
55 | const isstrorobj = v => isstr(v) || isobj(v)
56 | const responseErrorThrower = res => {
57 | if (!res.ok) throw new HTTPError(res)
58 | return res
59 | }
60 | const extend = (defaultInit = {}) => {
61 | const xfetch = (input, init = {}) => {
62 | mergeDeep(init, defaultInit)
63 | const createQueryString = o =>
64 | new init.URLSearchParams(o).toString()
65 | const parseQueryString = s =>
66 | fromEntries([...new init.URLSearchParams(s).entries()])
67 | const url = new init.URL(input, init.baseURI || undefined)
68 | if (!init.headers) {
69 | init.headers = {}
70 | } else if (typeis(init.Headers)(init.headers)) {
71 | // Transform into object if it is `Headers`
72 | init.headers = fromEntries([...init.headers.entries()])
73 | }
74 | // Add json or form on body
75 | if (init.json) {
76 | init.body = JSON.stringify(init.json)
77 | init.headers['Content-Type'] = 'application/json'
78 | } else if (isstrorobj(init.urlencoded)) {
79 | init.body = isstr(init.urlencoded)
80 | ? init.urlencoded
81 | : createQueryString(init.urlencoded)
82 | init.headers['Content-Type'] =
83 | 'application/x-www-form-urlencoded'
84 | } else if (typeis(init.FormData, 'object')(init.formData)) {
85 | // init.formData is data passed by user, init.FormData is FormData constructor
86 | if (!typeis(init.FormData)(init.formData)) {
87 | const fd = new init.FormData()
88 | for (const [k, v] of Object.entries(init.formData)) {
89 | fd.append(k, v)
90 | }
91 | init.formData = fd
92 | }
93 | init.body = init.formData
94 | }
95 | // Querystring
96 | if (init.qs) {
97 | if (isstr(init.qs)) init.qs = parseQueryString(init.qs)
98 | url.search = createQueryString(
99 | assign(
100 | fromEntries([...url.searchParams.entries()]),
101 | init.qs
102 | )
103 | )
104 | }
105 | // same-origin by default
106 | if (!init.credentials) {
107 | init.credentials = 'same-origin'
108 | }
109 | return XResponsePromise.resolve(
110 | init.fetch(url, init).then(responseErrorThrower)
111 | )
112 | }
113 | for (const method of METHODS) {
114 | xfetch[method] = (input, init = {}) => {
115 | init.method = method.toUpperCase()
116 | return xfetch(input, init)
117 | }
118 | }
119 | // Extra methods and classes
120 | xfetch.extend = newDefaultInit =>
121 | extend(assign({}, defaultInit, newDefaultInit))
122 | xfetch.HTTPError = HTTPError
123 | return xfetch
124 | }
125 | const isWindow = typeof document !== 'undefined'
126 | const isBrowser = typeof self !== 'undefined' // works in both window & worker scope
127 | return isBrowser
128 | ? extend({
129 | fetch: fetch.bind(self),
130 | URL,
131 | Response,
132 | URLSearchParams,
133 | Headers,
134 | FormData,
135 | baseURI: isWindow ? document.baseURI : '' // since there is no document in webworkers
136 | })
137 | : extend()
138 | })()
139 |
--------------------------------------------------------------------------------
/worker/googleDrive.js:
--------------------------------------------------------------------------------
1 | import xf from './xfetch'
2 |
3 | class GoogleDrive {
4 | constructor(auth) {
5 | this.auth = auth
6 | this.expires = 0
7 | this._getIdCache = new Map()
8 | }
9 | async initializeClient() {
10 | // any method that do api call must call this beforehand
11 | if (Date.now() < this.expires) return
12 | const resp = await xf
13 | .post('https://www.googleapis.com/oauth2/v4/token', {
14 | urlencoded: {
15 | client_id: this.auth.client_id,
16 | client_secret: this.auth.client_secret,
17 | refresh_token: this.auth.refresh_token,
18 | grant_type: 'refresh_token'
19 | }
20 | })
21 | .json()
22 | this.client = xf.extend({
23 | baseURI: 'https://www.googleapis.com/drive/v3/',
24 | headers: {
25 | Authorization: `Bearer ${resp.access_token}`
26 | }
27 | })
28 | this.expires = Date.now() + 3500 * 1000 // normally, it should expiers after 3600 seconds
29 | }
30 | async listDrive() {
31 | await this.initializeClient()
32 | return this.client.get('drives').json()
33 | }
34 | async download(id, range = '') {
35 | await this.initializeClient()
36 | return this.client.get(`files/${id}`, {
37 | qs: {
38 | includeItemsFromAllDrives: true,
39 | supportsAllDrives: true,
40 | alt: 'media'
41 | },
42 | headers: {
43 | Range: range
44 | }
45 | })
46 | }
47 | async downloadByPath(path, rootId = 'root', range = '') {
48 | const id = await this.getId(path, rootId)
49 | if (!id) return null
50 | return this.download(id, range)
51 | }
52 | async getMeta(id) {
53 | await this.initializeClient()
54 | return this.client
55 | .get(`files/${id}`, {
56 | qs: {
57 | includeItemsFromAllDrives: true,
58 | supportsAllDrives: true,
59 | fields: '*'
60 | }
61 | })
62 | .json()
63 | }
64 | async getMetaByPath(path, rootId = 'root') {
65 | const id = await this.getId(path, rootId)
66 | if (!id) return null
67 | return this.getMeta(id)
68 | }
69 | async listFolder(id) {
70 | await this.initializeClient()
71 | const getList = pageToken => {
72 | const qs = {
73 | includeItemsFromAllDrives: true,
74 | supportsAllDrives: true,
75 | q: `'${id}' in parents and trashed = false`,
76 | orderBy: 'folder,name,modifiedTime desc',
77 | fields:
78 | 'files(id,name,mimeType,size,modifiedTime),nextPageToken',
79 | pageSize: 1000
80 | }
81 | if (pageToken) {
82 | qs.pageToken = pageToken
83 | }
84 | return this.client
85 | .get('files', {
86 | qs
87 | })
88 | .json()
89 | }
90 | const files = []
91 | let pageToken
92 | do {
93 | const resp = await getList(pageToken)
94 | files.push(...resp.files)
95 | pageToken = resp.nextPageToken
96 | } while (pageToken)
97 | return { files }
98 | }
99 | async listFolderByPath(path, rootId = 'root') {
100 | const id = await this.getId(path, rootId)
101 | if (!id) return null
102 | return this.listFolder(id)
103 | }
104 | async getId(path, rootId = 'root') {
105 | const toks = path.split('/').filter(Boolean)
106 | let id = rootId
107 | for (const tok of toks) {
108 | id = await this._getId(id, tok)
109 | }
110 | return id
111 | }
112 | async _getId(parentId, childName) {
113 | if (this._getIdCache.has(parentId + childName)) {
114 | return this._getIdCache.get(parentId + childName)
115 | }
116 | await this.initializeClient()
117 | childName = childName.replace(/\'/g, `\\'`) // escape single quote
118 | const resp = await this.client
119 | .get('files', {
120 | qs: {
121 | includeItemsFromAllDrives: true,
122 | supportsAllDrives: true,
123 | q: `'${parentId}' in parents and name = '${childName}' and trashed = false`,
124 | fields: 'files(id)'
125 | }
126 | })
127 | .json()
128 | .catch(e => ({ files: [] })) // if error, make it empty
129 | if (resp.files.length === 0) {
130 | return null
131 | }
132 | this._getIdCache.has(parentId + childName)
133 | return resp.files[0].id // when there are more than 1 items, simply return the first one
134 | }
135 | async upload(parentId, name, file) {
136 | await this.initializeClient()
137 | const createResp = await this.client.post(
138 | 'https://www.googleapis.com/upload/drive/v3/files',
139 | {
140 | qs: {
141 | uploadType: 'resumable',
142 | supportsAllDrives: true
143 | },
144 | json: {
145 | name,
146 | parents: [parentId]
147 | }
148 | }
149 | )
150 | const putUrl = createResp.headers.get('Location')
151 | return this.client
152 | .put(putUrl, {
153 | body: file
154 | })
155 | .json()
156 | }
157 | async uploadByPath(path, name, file, rootId = 'root') {
158 | const id = await this.getId(path, rootId)
159 | if (!id) return null
160 | return this.upload(id, name, file)
161 | }
162 | async delete(fileId) {
163 | return this.client.delete(`files/${fileId}`)
164 | }
165 | async deleteByPath(path, rootId = 'root') {
166 | const id = await this.getId(path, rootId)
167 | if (!id) return null
168 | return this.delete(id)
169 | }
170 | }
171 | export default GoogleDrive
172 |
--------------------------------------------------------------------------------
/worker/index.js:
--------------------------------------------------------------------------------
1 | import mime from 'mime'
2 | import GoogleDrive from './googleDrive'
3 |
4 | const gd = new GoogleDrive(self.props)
5 |
6 | const HTML = `${self.props.title}
327 |
361 |
--------------------------------------------------------------------------------
/worker/dist/worker.js:
--------------------------------------------------------------------------------
1 | self.props = {
2 | title: 'GDIndex',
3 | default_root_id: 'root',
4 | client_id: '202264815644.apps.googleusercontent.com',
5 | client_secret: 'X4Z3ca8xfWDb1Voo-F9a7ZxJ',
6 | refresh_token: '',
7 | auth: false,
8 | user: '',
9 | pass: '',
10 | upload: false,
11 | lite: false
12 | };
13 | (function () {
14 | 'use strict';
15 |
16 | /**
17 | * @param typeMap [Object] Map of MIME type -> Array[extensions]
18 | * @param ...
19 | */
20 | function Mime() {
21 | this._types = Object.create(null);
22 | this._extensions = Object.create(null);
23 |
24 | for (var i = 0; i < arguments.length; i++) {
25 | this.define(arguments[i]);
26 | }
27 |
28 | this.define = this.define.bind(this);
29 | this.getType = this.getType.bind(this);
30 | this.getExtension = this.getExtension.bind(this);
31 | }
32 |
33 | /**
34 | * Define mimetype -> extension mappings. Each key is a mime-type that maps
35 | * to an array of extensions associated with the type. The first extension is
36 | * used as the default extension for the type.
37 | *
38 | * e.g. mime.define({'audio/ogg', ['oga', 'ogg', 'spx']});
39 | *
40 | * If a type declares an extension that has already been defined, an error will
41 | * be thrown. To suppress this error and force the extension to be associated
42 | * with the new type, pass `force`=true. Alternatively, you may prefix the
43 | * extension with "*" to map the type to extension, without mapping the
44 | * extension to the type.
45 | *
46 | * e.g. mime.define({'audio/wav', ['wav']}, {'audio/x-wav', ['*wav']});
47 | *
48 | *
49 | * @param map (Object) type definitions
50 | * @param force (Boolean) if true, force overriding of existing definitions
51 | */
52 | Mime.prototype.define = function(typeMap, force) {
53 | for (var type in typeMap) {
54 | var extensions = typeMap[type].map(function(t) {return t.toLowerCase()});
55 | type = type.toLowerCase();
56 |
57 | for (var i = 0; i < extensions.length; i++) {
58 | var ext = extensions[i];
59 |
60 | // '*' prefix = not the preferred type for this extension. So fixup the
61 | // extension, and skip it.
62 | if (ext[0] == '*') {
63 | continue;
64 | }
65 |
66 | if (!force && (ext in this._types)) {
67 | throw new Error(
68 | 'Attempt to change mapping for "' + ext +
69 | '" extension from "' + this._types[ext] + '" to "' + type +
70 | '". Pass `force=true` to allow this, otherwise remove "' + ext +
71 | '" from the list of extensions for "' + type + '".'
72 | );
73 | }
74 |
75 | this._types[ext] = type;
76 | }
77 |
78 | // Use first extension as default
79 | if (force || !this._extensions[type]) {
80 | var ext = extensions[0];
81 | this._extensions[type] = (ext[0] != '*') ? ext : ext.substr(1);
82 | }
83 | }
84 | };
85 |
86 | /**
87 | * Lookup a mime type based on extension
88 | */
89 | Mime.prototype.getType = function(path) {
90 | path = String(path);
91 | var last = path.replace(/^.*[/\\]/, '').toLowerCase();
92 | var ext = last.replace(/^.*\./, '').toLowerCase();
93 |
94 | var hasPath = last.length < path.length;
95 | var hasDot = ext.length < last.length - 1;
96 |
97 | return (hasDot || !hasPath) && this._types[ext] || null;
98 | };
99 |
100 | /**
101 | * Return file extension associated with a mime type
102 | */
103 | Mime.prototype.getExtension = function(type) {
104 | type = /^\s*([^;\s]*)/.test(type) && RegExp.$1;
105 | return type && this._extensions[type.toLowerCase()] || null;
106 | };
107 |
108 | var Mime_1 = Mime;
109 |
110 | var standard = {"application/andrew-inset":["ez"],"application/applixware":["aw"],"application/atom+xml":["atom"],"application/atomcat+xml":["atomcat"],"application/atomsvc+xml":["atomsvc"],"application/bdoc":["bdoc"],"application/ccxml+xml":["ccxml"],"application/cdmi-capability":["cdmia"],"application/cdmi-container":["cdmic"],"application/cdmi-domain":["cdmid"],"application/cdmi-object":["cdmio"],"application/cdmi-queue":["cdmiq"],"application/cu-seeme":["cu"],"application/dash+xml":["mpd"],"application/davmount+xml":["davmount"],"application/docbook+xml":["dbk"],"application/dssc+der":["dssc"],"application/dssc+xml":["xdssc"],"application/ecmascript":["ecma","es"],"application/emma+xml":["emma"],"application/epub+zip":["epub"],"application/exi":["exi"],"application/font-tdpfr":["pfr"],"application/geo+json":["geojson"],"application/gml+xml":["gml"],"application/gpx+xml":["gpx"],"application/gxf":["gxf"],"application/gzip":["gz"],"application/hjson":["hjson"],"application/hyperstudio":["stk"],"application/inkml+xml":["ink","inkml"],"application/ipfix":["ipfix"],"application/java-archive":["jar","war","ear"],"application/java-serialized-object":["ser"],"application/java-vm":["class"],"application/javascript":["js","mjs"],"application/json":["json","map"],"application/json5":["json5"],"application/jsonml+json":["jsonml"],"application/ld+json":["jsonld"],"application/lost+xml":["lostxml"],"application/mac-binhex40":["hqx"],"application/mac-compactpro":["cpt"],"application/mads+xml":["mads"],"application/manifest+json":["webmanifest"],"application/marc":["mrc"],"application/marcxml+xml":["mrcx"],"application/mathematica":["ma","nb","mb"],"application/mathml+xml":["mathml"],"application/mbox":["mbox"],"application/mediaservercontrol+xml":["mscml"],"application/metalink+xml":["metalink"],"application/metalink4+xml":["meta4"],"application/mets+xml":["mets"],"application/mods+xml":["mods"],"application/mp21":["m21","mp21"],"application/mp4":["mp4s","m4p"],"application/msword":["doc","dot"],"application/mxf":["mxf"],"application/n-quads":["nq"],"application/n-triples":["nt"],"application/octet-stream":["bin","dms","lrf","mar","so","dist","distz","pkg","bpk","dump","elc","deploy","exe","dll","deb","dmg","iso","img","msi","msp","msm","buffer"],"application/oda":["oda"],"application/oebps-package+xml":["opf"],"application/ogg":["ogx"],"application/omdoc+xml":["omdoc"],"application/onenote":["onetoc","onetoc2","onetmp","onepkg"],"application/oxps":["oxps"],"application/patch-ops-error+xml":["xer"],"application/pdf":["pdf"],"application/pgp-encrypted":["pgp"],"application/pgp-signature":["asc","sig"],"application/pics-rules":["prf"],"application/pkcs10":["p10"],"application/pkcs7-mime":["p7m","p7c"],"application/pkcs7-signature":["p7s"],"application/pkcs8":["p8"],"application/pkix-attr-cert":["ac"],"application/pkix-cert":["cer"],"application/pkix-crl":["crl"],"application/pkix-pkipath":["pkipath"],"application/pkixcmp":["pki"],"application/pls+xml":["pls"],"application/postscript":["ai","eps","ps"],"application/pskc+xml":["pskcxml"],"application/raml+yaml":["raml"],"application/rdf+xml":["rdf","owl"],"application/reginfo+xml":["rif"],"application/relax-ng-compact-syntax":["rnc"],"application/resource-lists+xml":["rl"],"application/resource-lists-diff+xml":["rld"],"application/rls-services+xml":["rs"],"application/rpki-ghostbusters":["gbr"],"application/rpki-manifest":["mft"],"application/rpki-roa":["roa"],"application/rsd+xml":["rsd"],"application/rss+xml":["rss"],"application/rtf":["rtf"],"application/sbml+xml":["sbml"],"application/scvp-cv-request":["scq"],"application/scvp-cv-response":["scs"],"application/scvp-vp-request":["spq"],"application/scvp-vp-response":["spp"],"application/sdp":["sdp"],"application/set-payment-initiation":["setpay"],"application/set-registration-initiation":["setreg"],"application/shf+xml":["shf"],"application/sieve":["siv","sieve"],"application/smil+xml":["smi","smil"],"application/sparql-query":["rq"],"application/sparql-results+xml":["srx"],"application/srgs":["gram"],"application/srgs+xml":["grxml"],"application/sru+xml":["sru"],"application/ssdl+xml":["ssdl"],"application/ssml+xml":["ssml"],"application/tei+xml":["tei","teicorpus"],"application/thraud+xml":["tfi"],"application/timestamped-data":["tsd"],"application/voicexml+xml":["vxml"],"application/wasm":["wasm"],"application/widget":["wgt"],"application/winhlp":["hlp"],"application/wsdl+xml":["wsdl"],"application/wspolicy+xml":["wspolicy"],"application/xaml+xml":["xaml"],"application/xcap-diff+xml":["xdf"],"application/xenc+xml":["xenc"],"application/xhtml+xml":["xhtml","xht"],"application/xml":["xml","xsl","xsd","rng"],"application/xml-dtd":["dtd"],"application/xop+xml":["xop"],"application/xproc+xml":["xpl"],"application/xslt+xml":["xslt"],"application/xspf+xml":["xspf"],"application/xv+xml":["mxml","xhvml","xvml","xvm"],"application/yang":["yang"],"application/yin+xml":["yin"],"application/zip":["zip"],"audio/3gpp":["*3gpp"],"audio/adpcm":["adp"],"audio/basic":["au","snd"],"audio/midi":["mid","midi","kar","rmi"],"audio/mp3":["*mp3"],"audio/mp4":["m4a","mp4a"],"audio/mpeg":["mpga","mp2","mp2a","mp3","m2a","m3a"],"audio/ogg":["oga","ogg","spx"],"audio/s3m":["s3m"],"audio/silk":["sil"],"audio/wav":["wav"],"audio/wave":["*wav"],"audio/webm":["weba"],"audio/xm":["xm"],"font/collection":["ttc"],"font/otf":["otf"],"font/ttf":["ttf"],"font/woff":["woff"],"font/woff2":["woff2"],"image/aces":["exr"],"image/apng":["apng"],"image/bmp":["bmp"],"image/cgm":["cgm"],"image/dicom-rle":["drle"],"image/emf":["emf"],"image/fits":["fits"],"image/g3fax":["g3"],"image/gif":["gif"],"image/heic":["heic"],"image/heic-sequence":["heics"],"image/heif":["heif"],"image/heif-sequence":["heifs"],"image/ief":["ief"],"image/jls":["jls"],"image/jp2":["jp2","jpg2"],"image/jpeg":["jpeg","jpg","jpe"],"image/jpm":["jpm"],"image/jpx":["jpx","jpf"],"image/jxr":["jxr"],"image/ktx":["ktx"],"image/png":["png"],"image/sgi":["sgi"],"image/svg+xml":["svg","svgz"],"image/t38":["t38"],"image/tiff":["tif","tiff"],"image/tiff-fx":["tfx"],"image/webp":["webp"],"image/wmf":["wmf"],"message/disposition-notification":["disposition-notification"],"message/global":["u8msg"],"message/global-delivery-status":["u8dsn"],"message/global-disposition-notification":["u8mdn"],"message/global-headers":["u8hdr"],"message/rfc822":["eml","mime"],"model/3mf":["3mf"],"model/gltf+json":["gltf"],"model/gltf-binary":["glb"],"model/iges":["igs","iges"],"model/mesh":["msh","mesh","silo"],"model/stl":["stl"],"model/vrml":["wrl","vrml"],"model/x3d+binary":["*x3db","x3dbz"],"model/x3d+fastinfoset":["x3db"],"model/x3d+vrml":["*x3dv","x3dvz"],"model/x3d+xml":["x3d","x3dz"],"model/x3d-vrml":["x3dv"],"text/cache-manifest":["appcache","manifest"],"text/calendar":["ics","ifb"],"text/coffeescript":["coffee","litcoffee"],"text/css":["css"],"text/csv":["csv"],"text/html":["html","htm","shtml"],"text/jade":["jade"],"text/jsx":["jsx"],"text/less":["less"],"text/markdown":["markdown","md"],"text/mathml":["mml"],"text/mdx":["mdx"],"text/n3":["n3"],"text/plain":["txt","text","conf","def","list","log","in","ini"],"text/richtext":["rtx"],"text/rtf":["*rtf"],"text/sgml":["sgml","sgm"],"text/shex":["shex"],"text/slim":["slim","slm"],"text/stylus":["stylus","styl"],"text/tab-separated-values":["tsv"],"text/troff":["t","tr","roff","man","me","ms"],"text/turtle":["ttl"],"text/uri-list":["uri","uris","urls"],"text/vcard":["vcard"],"text/vtt":["vtt"],"text/xml":["*xml"],"text/yaml":["yaml","yml"],"video/3gpp":["3gp","3gpp"],"video/3gpp2":["3g2"],"video/h261":["h261"],"video/h263":["h263"],"video/h264":["h264"],"video/jpeg":["jpgv"],"video/jpm":["*jpm","jpgm"],"video/mj2":["mj2","mjp2"],"video/mp2t":["ts"],"video/mp4":["mp4","mp4v","mpg4"],"video/mpeg":["mpeg","mpg","mpe","m1v","m2v"],"video/ogg":["ogv"],"video/quicktime":["qt","mov"],"video/webm":["webm"]};
111 |
112 | var other = {"application/prs.cww":["cww"],"application/vnd.3gpp.pic-bw-large":["plb"],"application/vnd.3gpp.pic-bw-small":["psb"],"application/vnd.3gpp.pic-bw-var":["pvb"],"application/vnd.3gpp2.tcap":["tcap"],"application/vnd.3m.post-it-notes":["pwn"],"application/vnd.accpac.simply.aso":["aso"],"application/vnd.accpac.simply.imp":["imp"],"application/vnd.acucobol":["acu"],"application/vnd.acucorp":["atc","acutc"],"application/vnd.adobe.air-application-installer-package+zip":["air"],"application/vnd.adobe.formscentral.fcdt":["fcdt"],"application/vnd.adobe.fxp":["fxp","fxpl"],"application/vnd.adobe.xdp+xml":["xdp"],"application/vnd.adobe.xfdf":["xfdf"],"application/vnd.ahead.space":["ahead"],"application/vnd.airzip.filesecure.azf":["azf"],"application/vnd.airzip.filesecure.azs":["azs"],"application/vnd.amazon.ebook":["azw"],"application/vnd.americandynamics.acc":["acc"],"application/vnd.amiga.ami":["ami"],"application/vnd.android.package-archive":["apk"],"application/vnd.anser-web-certificate-issue-initiation":["cii"],"application/vnd.anser-web-funds-transfer-initiation":["fti"],"application/vnd.antix.game-component":["atx"],"application/vnd.apple.installer+xml":["mpkg"],"application/vnd.apple.keynote":["keynote"],"application/vnd.apple.mpegurl":["m3u8"],"application/vnd.apple.numbers":["numbers"],"application/vnd.apple.pages":["pages"],"application/vnd.apple.pkpass":["pkpass"],"application/vnd.aristanetworks.swi":["swi"],"application/vnd.astraea-software.iota":["iota"],"application/vnd.audiograph":["aep"],"application/vnd.blueice.multipass":["mpm"],"application/vnd.bmi":["bmi"],"application/vnd.businessobjects":["rep"],"application/vnd.chemdraw+xml":["cdxml"],"application/vnd.chipnuts.karaoke-mmd":["mmd"],"application/vnd.cinderella":["cdy"],"application/vnd.citationstyles.style+xml":["csl"],"application/vnd.claymore":["cla"],"application/vnd.cloanto.rp9":["rp9"],"application/vnd.clonk.c4group":["c4g","c4d","c4f","c4p","c4u"],"application/vnd.cluetrust.cartomobile-config":["c11amc"],"application/vnd.cluetrust.cartomobile-config-pkg":["c11amz"],"application/vnd.commonspace":["csp"],"application/vnd.contact.cmsg":["cdbcmsg"],"application/vnd.cosmocaller":["cmc"],"application/vnd.crick.clicker":["clkx"],"application/vnd.crick.clicker.keyboard":["clkk"],"application/vnd.crick.clicker.palette":["clkp"],"application/vnd.crick.clicker.template":["clkt"],"application/vnd.crick.clicker.wordbank":["clkw"],"application/vnd.criticaltools.wbs+xml":["wbs"],"application/vnd.ctc-posml":["pml"],"application/vnd.cups-ppd":["ppd"],"application/vnd.curl.car":["car"],"application/vnd.curl.pcurl":["pcurl"],"application/vnd.dart":["dart"],"application/vnd.data-vision.rdz":["rdz"],"application/vnd.dece.data":["uvf","uvvf","uvd","uvvd"],"application/vnd.dece.ttml+xml":["uvt","uvvt"],"application/vnd.dece.unspecified":["uvx","uvvx"],"application/vnd.dece.zip":["uvz","uvvz"],"application/vnd.denovo.fcselayout-link":["fe_launch"],"application/vnd.dna":["dna"],"application/vnd.dolby.mlp":["mlp"],"application/vnd.dpgraph":["dpg"],"application/vnd.dreamfactory":["dfac"],"application/vnd.ds-keypoint":["kpxx"],"application/vnd.dvb.ait":["ait"],"application/vnd.dvb.service":["svc"],"application/vnd.dynageo":["geo"],"application/vnd.ecowin.chart":["mag"],"application/vnd.enliven":["nml"],"application/vnd.epson.esf":["esf"],"application/vnd.epson.msf":["msf"],"application/vnd.epson.quickanime":["qam"],"application/vnd.epson.salt":["slt"],"application/vnd.epson.ssf":["ssf"],"application/vnd.eszigno3+xml":["es3","et3"],"application/vnd.ezpix-album":["ez2"],"application/vnd.ezpix-package":["ez3"],"application/vnd.fdf":["fdf"],"application/vnd.fdsn.mseed":["mseed"],"application/vnd.fdsn.seed":["seed","dataless"],"application/vnd.flographit":["gph"],"application/vnd.fluxtime.clip":["ftc"],"application/vnd.framemaker":["fm","frame","maker","book"],"application/vnd.frogans.fnc":["fnc"],"application/vnd.frogans.ltf":["ltf"],"application/vnd.fsc.weblaunch":["fsc"],"application/vnd.fujitsu.oasys":["oas"],"application/vnd.fujitsu.oasys2":["oa2"],"application/vnd.fujitsu.oasys3":["oa3"],"application/vnd.fujitsu.oasysgp":["fg5"],"application/vnd.fujitsu.oasysprs":["bh2"],"application/vnd.fujixerox.ddd":["ddd"],"application/vnd.fujixerox.docuworks":["xdw"],"application/vnd.fujixerox.docuworks.binder":["xbd"],"application/vnd.fuzzysheet":["fzs"],"application/vnd.genomatix.tuxedo":["txd"],"application/vnd.geogebra.file":["ggb"],"application/vnd.geogebra.tool":["ggt"],"application/vnd.geometry-explorer":["gex","gre"],"application/vnd.geonext":["gxt"],"application/vnd.geoplan":["g2w"],"application/vnd.geospace":["g3w"],"application/vnd.gmx":["gmx"],"application/vnd.google-apps.document":["gdoc"],"application/vnd.google-apps.presentation":["gslides"],"application/vnd.google-apps.spreadsheet":["gsheet"],"application/vnd.google-earth.kml+xml":["kml"],"application/vnd.google-earth.kmz":["kmz"],"application/vnd.grafeq":["gqf","gqs"],"application/vnd.groove-account":["gac"],"application/vnd.groove-help":["ghf"],"application/vnd.groove-identity-message":["gim"],"application/vnd.groove-injector":["grv"],"application/vnd.groove-tool-message":["gtm"],"application/vnd.groove-tool-template":["tpl"],"application/vnd.groove-vcard":["vcg"],"application/vnd.hal+xml":["hal"],"application/vnd.handheld-entertainment+xml":["zmm"],"application/vnd.hbci":["hbci"],"application/vnd.hhe.lesson-player":["les"],"application/vnd.hp-hpgl":["hpgl"],"application/vnd.hp-hpid":["hpid"],"application/vnd.hp-hps":["hps"],"application/vnd.hp-jlyt":["jlt"],"application/vnd.hp-pcl":["pcl"],"application/vnd.hp-pclxl":["pclxl"],"application/vnd.hydrostatix.sof-data":["sfd-hdstx"],"application/vnd.ibm.minipay":["mpy"],"application/vnd.ibm.modcap":["afp","listafp","list3820"],"application/vnd.ibm.rights-management":["irm"],"application/vnd.ibm.secure-container":["sc"],"application/vnd.iccprofile":["icc","icm"],"application/vnd.igloader":["igl"],"application/vnd.immervision-ivp":["ivp"],"application/vnd.immervision-ivu":["ivu"],"application/vnd.insors.igm":["igm"],"application/vnd.intercon.formnet":["xpw","xpx"],"application/vnd.intergeo":["i2g"],"application/vnd.intu.qbo":["qbo"],"application/vnd.intu.qfx":["qfx"],"application/vnd.ipunplugged.rcprofile":["rcprofile"],"application/vnd.irepository.package+xml":["irp"],"application/vnd.is-xpr":["xpr"],"application/vnd.isac.fcs":["fcs"],"application/vnd.jam":["jam"],"application/vnd.jcp.javame.midlet-rms":["rms"],"application/vnd.jisp":["jisp"],"application/vnd.joost.joda-archive":["joda"],"application/vnd.kahootz":["ktz","ktr"],"application/vnd.kde.karbon":["karbon"],"application/vnd.kde.kchart":["chrt"],"application/vnd.kde.kformula":["kfo"],"application/vnd.kde.kivio":["flw"],"application/vnd.kde.kontour":["kon"],"application/vnd.kde.kpresenter":["kpr","kpt"],"application/vnd.kde.kspread":["ksp"],"application/vnd.kde.kword":["kwd","kwt"],"application/vnd.kenameaapp":["htke"],"application/vnd.kidspiration":["kia"],"application/vnd.kinar":["kne","knp"],"application/vnd.koan":["skp","skd","skt","skm"],"application/vnd.kodak-descriptor":["sse"],"application/vnd.las.las+xml":["lasxml"],"application/vnd.llamagraphics.life-balance.desktop":["lbd"],"application/vnd.llamagraphics.life-balance.exchange+xml":["lbe"],"application/vnd.lotus-1-2-3":["123"],"application/vnd.lotus-approach":["apr"],"application/vnd.lotus-freelance":["pre"],"application/vnd.lotus-notes":["nsf"],"application/vnd.lotus-organizer":["org"],"application/vnd.lotus-screencam":["scm"],"application/vnd.lotus-wordpro":["lwp"],"application/vnd.macports.portpkg":["portpkg"],"application/vnd.mcd":["mcd"],"application/vnd.medcalcdata":["mc1"],"application/vnd.mediastation.cdkey":["cdkey"],"application/vnd.mfer":["mwf"],"application/vnd.mfmp":["mfm"],"application/vnd.micrografx.flo":["flo"],"application/vnd.micrografx.igx":["igx"],"application/vnd.mif":["mif"],"application/vnd.mobius.daf":["daf"],"application/vnd.mobius.dis":["dis"],"application/vnd.mobius.mbk":["mbk"],"application/vnd.mobius.mqy":["mqy"],"application/vnd.mobius.msl":["msl"],"application/vnd.mobius.plc":["plc"],"application/vnd.mobius.txf":["txf"],"application/vnd.mophun.application":["mpn"],"application/vnd.mophun.certificate":["mpc"],"application/vnd.mozilla.xul+xml":["xul"],"application/vnd.ms-artgalry":["cil"],"application/vnd.ms-cab-compressed":["cab"],"application/vnd.ms-excel":["xls","xlm","xla","xlc","xlt","xlw"],"application/vnd.ms-excel.addin.macroenabled.12":["xlam"],"application/vnd.ms-excel.sheet.binary.macroenabled.12":["xlsb"],"application/vnd.ms-excel.sheet.macroenabled.12":["xlsm"],"application/vnd.ms-excel.template.macroenabled.12":["xltm"],"application/vnd.ms-fontobject":["eot"],"application/vnd.ms-htmlhelp":["chm"],"application/vnd.ms-ims":["ims"],"application/vnd.ms-lrm":["lrm"],"application/vnd.ms-officetheme":["thmx"],"application/vnd.ms-outlook":["msg"],"application/vnd.ms-pki.seccat":["cat"],"application/vnd.ms-pki.stl":["*stl"],"application/vnd.ms-powerpoint":["ppt","pps","pot"],"application/vnd.ms-powerpoint.addin.macroenabled.12":["ppam"],"application/vnd.ms-powerpoint.presentation.macroenabled.12":["pptm"],"application/vnd.ms-powerpoint.slide.macroenabled.12":["sldm"],"application/vnd.ms-powerpoint.slideshow.macroenabled.12":["ppsm"],"application/vnd.ms-powerpoint.template.macroenabled.12":["potm"],"application/vnd.ms-project":["mpp","mpt"],"application/vnd.ms-word.document.macroenabled.12":["docm"],"application/vnd.ms-word.template.macroenabled.12":["dotm"],"application/vnd.ms-works":["wps","wks","wcm","wdb"],"application/vnd.ms-wpl":["wpl"],"application/vnd.ms-xpsdocument":["xps"],"application/vnd.mseq":["mseq"],"application/vnd.musician":["mus"],"application/vnd.muvee.style":["msty"],"application/vnd.mynfc":["taglet"],"application/vnd.neurolanguage.nlu":["nlu"],"application/vnd.nitf":["ntf","nitf"],"application/vnd.noblenet-directory":["nnd"],"application/vnd.noblenet-sealer":["nns"],"application/vnd.noblenet-web":["nnw"],"application/vnd.nokia.n-gage.data":["ngdat"],"application/vnd.nokia.n-gage.symbian.install":["n-gage"],"application/vnd.nokia.radio-preset":["rpst"],"application/vnd.nokia.radio-presets":["rpss"],"application/vnd.novadigm.edm":["edm"],"application/vnd.novadigm.edx":["edx"],"application/vnd.novadigm.ext":["ext"],"application/vnd.oasis.opendocument.chart":["odc"],"application/vnd.oasis.opendocument.chart-template":["otc"],"application/vnd.oasis.opendocument.database":["odb"],"application/vnd.oasis.opendocument.formula":["odf"],"application/vnd.oasis.opendocument.formula-template":["odft"],"application/vnd.oasis.opendocument.graphics":["odg"],"application/vnd.oasis.opendocument.graphics-template":["otg"],"application/vnd.oasis.opendocument.image":["odi"],"application/vnd.oasis.opendocument.image-template":["oti"],"application/vnd.oasis.opendocument.presentation":["odp"],"application/vnd.oasis.opendocument.presentation-template":["otp"],"application/vnd.oasis.opendocument.spreadsheet":["ods"],"application/vnd.oasis.opendocument.spreadsheet-template":["ots"],"application/vnd.oasis.opendocument.text":["odt"],"application/vnd.oasis.opendocument.text-master":["odm"],"application/vnd.oasis.opendocument.text-template":["ott"],"application/vnd.oasis.opendocument.text-web":["oth"],"application/vnd.olpc-sugar":["xo"],"application/vnd.oma.dd2+xml":["dd2"],"application/vnd.openofficeorg.extension":["oxt"],"application/vnd.openxmlformats-officedocument.presentationml.presentation":["pptx"],"application/vnd.openxmlformats-officedocument.presentationml.slide":["sldx"],"application/vnd.openxmlformats-officedocument.presentationml.slideshow":["ppsx"],"application/vnd.openxmlformats-officedocument.presentationml.template":["potx"],"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":["xlsx"],"application/vnd.openxmlformats-officedocument.spreadsheetml.template":["xltx"],"application/vnd.openxmlformats-officedocument.wordprocessingml.document":["docx"],"application/vnd.openxmlformats-officedocument.wordprocessingml.template":["dotx"],"application/vnd.osgeo.mapguide.package":["mgp"],"application/vnd.osgi.dp":["dp"],"application/vnd.osgi.subsystem":["esa"],"application/vnd.palm":["pdb","pqa","oprc"],"application/vnd.pawaafile":["paw"],"application/vnd.pg.format":["str"],"application/vnd.pg.osasli":["ei6"],"application/vnd.picsel":["efif"],"application/vnd.pmi.widget":["wg"],"application/vnd.pocketlearn":["plf"],"application/vnd.powerbuilder6":["pbd"],"application/vnd.previewsystems.box":["box"],"application/vnd.proteus.magazine":["mgz"],"application/vnd.publishare-delta-tree":["qps"],"application/vnd.pvi.ptid1":["ptid"],"application/vnd.quark.quarkxpress":["qxd","qxt","qwd","qwt","qxl","qxb"],"application/vnd.realvnc.bed":["bed"],"application/vnd.recordare.musicxml":["mxl"],"application/vnd.recordare.musicxml+xml":["musicxml"],"application/vnd.rig.cryptonote":["cryptonote"],"application/vnd.rim.cod":["cod"],"application/vnd.rn-realmedia":["rm"],"application/vnd.rn-realmedia-vbr":["rmvb"],"application/vnd.route66.link66+xml":["link66"],"application/vnd.sailingtracker.track":["st"],"application/vnd.seemail":["see"],"application/vnd.sema":["sema"],"application/vnd.semd":["semd"],"application/vnd.semf":["semf"],"application/vnd.shana.informed.formdata":["ifm"],"application/vnd.shana.informed.formtemplate":["itp"],"application/vnd.shana.informed.interchange":["iif"],"application/vnd.shana.informed.package":["ipk"],"application/vnd.simtech-mindmapper":["twd","twds"],"application/vnd.smaf":["mmf"],"application/vnd.smart.teacher":["teacher"],"application/vnd.solent.sdkm+xml":["sdkm","sdkd"],"application/vnd.spotfire.dxp":["dxp"],"application/vnd.spotfire.sfs":["sfs"],"application/vnd.stardivision.calc":["sdc"],"application/vnd.stardivision.draw":["sda"],"application/vnd.stardivision.impress":["sdd"],"application/vnd.stardivision.math":["smf"],"application/vnd.stardivision.writer":["sdw","vor"],"application/vnd.stardivision.writer-global":["sgl"],"application/vnd.stepmania.package":["smzip"],"application/vnd.stepmania.stepchart":["sm"],"application/vnd.sun.wadl+xml":["wadl"],"application/vnd.sun.xml.calc":["sxc"],"application/vnd.sun.xml.calc.template":["stc"],"application/vnd.sun.xml.draw":["sxd"],"application/vnd.sun.xml.draw.template":["std"],"application/vnd.sun.xml.impress":["sxi"],"application/vnd.sun.xml.impress.template":["sti"],"application/vnd.sun.xml.math":["sxm"],"application/vnd.sun.xml.writer":["sxw"],"application/vnd.sun.xml.writer.global":["sxg"],"application/vnd.sun.xml.writer.template":["stw"],"application/vnd.sus-calendar":["sus","susp"],"application/vnd.svd":["svd"],"application/vnd.symbian.install":["sis","sisx"],"application/vnd.syncml+xml":["xsm"],"application/vnd.syncml.dm+wbxml":["bdm"],"application/vnd.syncml.dm+xml":["xdm"],"application/vnd.tao.intent-module-archive":["tao"],"application/vnd.tcpdump.pcap":["pcap","cap","dmp"],"application/vnd.tmobile-livetv":["tmo"],"application/vnd.trid.tpt":["tpt"],"application/vnd.triscape.mxs":["mxs"],"application/vnd.trueapp":["tra"],"application/vnd.ufdl":["ufd","ufdl"],"application/vnd.uiq.theme":["utz"],"application/vnd.umajin":["umj"],"application/vnd.unity":["unityweb"],"application/vnd.uoml+xml":["uoml"],"application/vnd.vcx":["vcx"],"application/vnd.visio":["vsd","vst","vss","vsw"],"application/vnd.visionary":["vis"],"application/vnd.vsf":["vsf"],"application/vnd.wap.wbxml":["wbxml"],"application/vnd.wap.wmlc":["wmlc"],"application/vnd.wap.wmlscriptc":["wmlsc"],"application/vnd.webturbo":["wtb"],"application/vnd.wolfram.player":["nbp"],"application/vnd.wordperfect":["wpd"],"application/vnd.wqd":["wqd"],"application/vnd.wt.stf":["stf"],"application/vnd.xara":["xar"],"application/vnd.xfdl":["xfdl"],"application/vnd.yamaha.hv-dic":["hvd"],"application/vnd.yamaha.hv-script":["hvs"],"application/vnd.yamaha.hv-voice":["hvp"],"application/vnd.yamaha.openscoreformat":["osf"],"application/vnd.yamaha.openscoreformat.osfpvg+xml":["osfpvg"],"application/vnd.yamaha.smaf-audio":["saf"],"application/vnd.yamaha.smaf-phrase":["spf"],"application/vnd.yellowriver-custom-menu":["cmp"],"application/vnd.zul":["zir","zirz"],"application/vnd.zzazz.deck+xml":["zaz"],"application/x-7z-compressed":["7z"],"application/x-abiword":["abw"],"application/x-ace-compressed":["ace"],"application/x-apple-diskimage":["*dmg"],"application/x-arj":["arj"],"application/x-authorware-bin":["aab","x32","u32","vox"],"application/x-authorware-map":["aam"],"application/x-authorware-seg":["aas"],"application/x-bcpio":["bcpio"],"application/x-bdoc":["*bdoc"],"application/x-bittorrent":["torrent"],"application/x-blorb":["blb","blorb"],"application/x-bzip":["bz"],"application/x-bzip2":["bz2","boz"],"application/x-cbr":["cbr","cba","cbt","cbz","cb7"],"application/x-cdlink":["vcd"],"application/x-cfs-compressed":["cfs"],"application/x-chat":["chat"],"application/x-chess-pgn":["pgn"],"application/x-chrome-extension":["crx"],"application/x-cocoa":["cco"],"application/x-conference":["nsc"],"application/x-cpio":["cpio"],"application/x-csh":["csh"],"application/x-debian-package":["*deb","udeb"],"application/x-dgc-compressed":["dgc"],"application/x-director":["dir","dcr","dxr","cst","cct","cxt","w3d","fgd","swa"],"application/x-doom":["wad"],"application/x-dtbncx+xml":["ncx"],"application/x-dtbook+xml":["dtb"],"application/x-dtbresource+xml":["res"],"application/x-dvi":["dvi"],"application/x-envoy":["evy"],"application/x-eva":["eva"],"application/x-font-bdf":["bdf"],"application/x-font-ghostscript":["gsf"],"application/x-font-linux-psf":["psf"],"application/x-font-pcf":["pcf"],"application/x-font-snf":["snf"],"application/x-font-type1":["pfa","pfb","pfm","afm"],"application/x-freearc":["arc"],"application/x-futuresplash":["spl"],"application/x-gca-compressed":["gca"],"application/x-glulx":["ulx"],"application/x-gnumeric":["gnumeric"],"application/x-gramps-xml":["gramps"],"application/x-gtar":["gtar"],"application/x-hdf":["hdf"],"application/x-httpd-php":["php"],"application/x-install-instructions":["install"],"application/x-iso9660-image":["*iso"],"application/x-java-archive-diff":["jardiff"],"application/x-java-jnlp-file":["jnlp"],"application/x-latex":["latex"],"application/x-lua-bytecode":["luac"],"application/x-lzh-compressed":["lzh","lha"],"application/x-makeself":["run"],"application/x-mie":["mie"],"application/x-mobipocket-ebook":["prc","mobi"],"application/x-ms-application":["application"],"application/x-ms-shortcut":["lnk"],"application/x-ms-wmd":["wmd"],"application/x-ms-wmz":["wmz"],"application/x-ms-xbap":["xbap"],"application/x-msaccess":["mdb"],"application/x-msbinder":["obd"],"application/x-mscardfile":["crd"],"application/x-msclip":["clp"],"application/x-msdos-program":["*exe"],"application/x-msdownload":["*exe","*dll","com","bat","*msi"],"application/x-msmediaview":["mvb","m13","m14"],"application/x-msmetafile":["*wmf","*wmz","*emf","emz"],"application/x-msmoney":["mny"],"application/x-mspublisher":["pub"],"application/x-msschedule":["scd"],"application/x-msterminal":["trm"],"application/x-mswrite":["wri"],"application/x-netcdf":["nc","cdf"],"application/x-ns-proxy-autoconfig":["pac"],"application/x-nzb":["nzb"],"application/x-perl":["pl","pm"],"application/x-pilot":["*prc","*pdb"],"application/x-pkcs12":["p12","pfx"],"application/x-pkcs7-certificates":["p7b","spc"],"application/x-pkcs7-certreqresp":["p7r"],"application/x-rar-compressed":["rar"],"application/x-redhat-package-manager":["rpm"],"application/x-research-info-systems":["ris"],"application/x-sea":["sea"],"application/x-sh":["sh"],"application/x-shar":["shar"],"application/x-shockwave-flash":["swf"],"application/x-silverlight-app":["xap"],"application/x-sql":["sql"],"application/x-stuffit":["sit"],"application/x-stuffitx":["sitx"],"application/x-subrip":["srt"],"application/x-sv4cpio":["sv4cpio"],"application/x-sv4crc":["sv4crc"],"application/x-t3vm-image":["t3"],"application/x-tads":["gam"],"application/x-tar":["tar"],"application/x-tcl":["tcl","tk"],"application/x-tex":["tex"],"application/x-tex-tfm":["tfm"],"application/x-texinfo":["texinfo","texi"],"application/x-tgif":["obj"],"application/x-ustar":["ustar"],"application/x-virtualbox-hdd":["hdd"],"application/x-virtualbox-ova":["ova"],"application/x-virtualbox-ovf":["ovf"],"application/x-virtualbox-vbox":["vbox"],"application/x-virtualbox-vbox-extpack":["vbox-extpack"],"application/x-virtualbox-vdi":["vdi"],"application/x-virtualbox-vhd":["vhd"],"application/x-virtualbox-vmdk":["vmdk"],"application/x-wais-source":["src"],"application/x-web-app-manifest+json":["webapp"],"application/x-x509-ca-cert":["der","crt","pem"],"application/x-xfig":["fig"],"application/x-xliff+xml":["xlf"],"application/x-xpinstall":["xpi"],"application/x-xz":["xz"],"application/x-zmachine":["z1","z2","z3","z4","z5","z6","z7","z8"],"audio/vnd.dece.audio":["uva","uvva"],"audio/vnd.digital-winds":["eol"],"audio/vnd.dra":["dra"],"audio/vnd.dts":["dts"],"audio/vnd.dts.hd":["dtshd"],"audio/vnd.lucent.voice":["lvp"],"audio/vnd.ms-playready.media.pya":["pya"],"audio/vnd.nuera.ecelp4800":["ecelp4800"],"audio/vnd.nuera.ecelp7470":["ecelp7470"],"audio/vnd.nuera.ecelp9600":["ecelp9600"],"audio/vnd.rip":["rip"],"audio/x-aac":["aac"],"audio/x-aiff":["aif","aiff","aifc"],"audio/x-caf":["caf"],"audio/x-flac":["flac"],"audio/x-m4a":["*m4a"],"audio/x-matroska":["mka"],"audio/x-mpegurl":["m3u"],"audio/x-ms-wax":["wax"],"audio/x-ms-wma":["wma"],"audio/x-pn-realaudio":["ram","ra"],"audio/x-pn-realaudio-plugin":["rmp"],"audio/x-realaudio":["*ra"],"audio/x-wav":["*wav"],"chemical/x-cdx":["cdx"],"chemical/x-cif":["cif"],"chemical/x-cmdf":["cmdf"],"chemical/x-cml":["cml"],"chemical/x-csml":["csml"],"chemical/x-xyz":["xyz"],"image/prs.btif":["btif"],"image/prs.pti":["pti"],"image/vnd.adobe.photoshop":["psd"],"image/vnd.airzip.accelerator.azv":["azv"],"image/vnd.dece.graphic":["uvi","uvvi","uvg","uvvg"],"image/vnd.djvu":["djvu","djv"],"image/vnd.dvb.subtitle":["*sub"],"image/vnd.dwg":["dwg"],"image/vnd.dxf":["dxf"],"image/vnd.fastbidsheet":["fbs"],"image/vnd.fpx":["fpx"],"image/vnd.fst":["fst"],"image/vnd.fujixerox.edmics-mmr":["mmr"],"image/vnd.fujixerox.edmics-rlc":["rlc"],"image/vnd.microsoft.icon":["ico"],"image/vnd.ms-modi":["mdi"],"image/vnd.ms-photo":["wdp"],"image/vnd.net-fpx":["npx"],"image/vnd.tencent.tap":["tap"],"image/vnd.valve.source.texture":["vtf"],"image/vnd.wap.wbmp":["wbmp"],"image/vnd.xiff":["xif"],"image/vnd.zbrush.pcx":["pcx"],"image/x-3ds":["3ds"],"image/x-cmu-raster":["ras"],"image/x-cmx":["cmx"],"image/x-freehand":["fh","fhc","fh4","fh5","fh7"],"image/x-icon":["*ico"],"image/x-jng":["jng"],"image/x-mrsid-image":["sid"],"image/x-ms-bmp":["*bmp"],"image/x-pcx":["*pcx"],"image/x-pict":["pic","pct"],"image/x-portable-anymap":["pnm"],"image/x-portable-bitmap":["pbm"],"image/x-portable-graymap":["pgm"],"image/x-portable-pixmap":["ppm"],"image/x-rgb":["rgb"],"image/x-tga":["tga"],"image/x-xbitmap":["xbm"],"image/x-xpixmap":["xpm"],"image/x-xwindowdump":["xwd"],"message/vnd.wfa.wsc":["wsc"],"model/vnd.collada+xml":["dae"],"model/vnd.dwf":["dwf"],"model/vnd.gdl":["gdl"],"model/vnd.gtw":["gtw"],"model/vnd.mts":["mts"],"model/vnd.opengex":["ogex"],"model/vnd.parasolid.transmit.binary":["x_b"],"model/vnd.parasolid.transmit.text":["x_t"],"model/vnd.usdz+zip":["usdz"],"model/vnd.valve.source.compiled-map":["bsp"],"model/vnd.vtu":["vtu"],"text/prs.lines.tag":["dsc"],"text/vnd.curl":["curl"],"text/vnd.curl.dcurl":["dcurl"],"text/vnd.curl.mcurl":["mcurl"],"text/vnd.curl.scurl":["scurl"],"text/vnd.dvb.subtitle":["sub"],"text/vnd.fly":["fly"],"text/vnd.fmi.flexstor":["flx"],"text/vnd.graphviz":["gv"],"text/vnd.in3d.3dml":["3dml"],"text/vnd.in3d.spot":["spot"],"text/vnd.sun.j2me.app-descriptor":["jad"],"text/vnd.wap.wml":["wml"],"text/vnd.wap.wmlscript":["wmls"],"text/x-asm":["s","asm"],"text/x-c":["c","cc","cxx","cpp","h","hh","dic"],"text/x-component":["htc"],"text/x-fortran":["f","for","f77","f90"],"text/x-handlebars-template":["hbs"],"text/x-java-source":["java"],"text/x-lua":["lua"],"text/x-markdown":["mkd"],"text/x-nfo":["nfo"],"text/x-opml":["opml"],"text/x-org":["*org"],"text/x-pascal":["p","pas"],"text/x-processing":["pde"],"text/x-sass":["sass"],"text/x-scss":["scss"],"text/x-setext":["etx"],"text/x-sfv":["sfv"],"text/x-suse-ymp":["ymp"],"text/x-uuencode":["uu"],"text/x-vcalendar":["vcs"],"text/x-vcard":["vcf"],"video/vnd.dece.hd":["uvh","uvvh"],"video/vnd.dece.mobile":["uvm","uvvm"],"video/vnd.dece.pd":["uvp","uvvp"],"video/vnd.dece.sd":["uvs","uvvs"],"video/vnd.dece.video":["uvv","uvvv"],"video/vnd.dvb.file":["dvb"],"video/vnd.fvt":["fvt"],"video/vnd.mpegurl":["mxu","m4u"],"video/vnd.ms-playready.media.pyv":["pyv"],"video/vnd.uvvu.mp4":["uvu","uvvu"],"video/vnd.vivo":["viv"],"video/x-f4v":["f4v"],"video/x-fli":["fli"],"video/x-flv":["flv"],"video/x-m4v":["m4v"],"video/x-matroska":["mkv","mk3d","mks"],"video/x-mng":["mng"],"video/x-ms-asf":["asf","asx"],"video/x-ms-vob":["vob"],"video/x-ms-wm":["wm"],"video/x-ms-wmv":["wmv"],"video/x-ms-wmx":["wmx"],"video/x-ms-wvx":["wvx"],"video/x-msvideo":["avi"],"video/x-sgi-movie":["movie"],"video/x-smv":["smv"],"x-conference/x-cooltalk":["ice"]};
113 |
114 | var mime = new Mime_1(standard, other);
115 |
116 | /*
117 | * XFetch.js modified
118 | * A extremely simple fetch extension inspired by sindresorhus/ky.
119 | */
120 | const xf = (() => {
121 | const METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head'];
122 |
123 | class HTTPError extends Error {
124 | constructor(res) {
125 | super(res.statusText);
126 | this.name = 'HTTPError';
127 | this.response = res;
128 | }
129 |
130 | }
131 |
132 | class XResponsePromise extends Promise {}
133 |
134 | for (const alias of ['arrayBuffer', 'blob', 'formData', 'json', 'text']) {
135 | // alias for .json() .text() etc...
136 | XResponsePromise.prototype[alias] = function (fn) {
137 | return this.then(res => res[alias]()).then(fn || (x => x));
138 | };
139 | }
140 |
141 | const {
142 | assign
143 | } = Object;
144 |
145 | function mergeDeep(target, source) {
146 | const isObject = obj => obj && typeof obj === 'object';
147 |
148 | if (!isObject(target) || !isObject(source)) {
149 | return source;
150 | }
151 |
152 | Object.keys(source).forEach(key => {
153 | const targetValue = target[key];
154 | const sourceValue = source[key];
155 |
156 | if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
157 | target[key] = targetValue.concat(sourceValue);
158 | } else if (isObject(targetValue) && isObject(sourceValue)) {
159 | target[key] = mergeDeep(Object.assign({}, targetValue), sourceValue);
160 | } else {
161 | target[key] = sourceValue;
162 | }
163 | });
164 | return target;
165 | }
166 |
167 | const fromEntries = ent => ent.reduce((acc, [k, v]) => (acc[k] = v, acc), {});
168 |
169 | const typeis = (...types) => val => types.some(type => typeof type === 'string' ? typeof val === type : val instanceof type);
170 |
171 | const isstr = typeis('string');
172 | const isobj = typeis('object');
173 |
174 | const isstrorobj = v => isstr(v) || isobj(v);
175 |
176 | const responseErrorThrower = res => {
177 | if (!res.ok) throw new HTTPError(res);
178 | return res;
179 | };
180 |
181 | const extend = (defaultInit = {}) => {
182 | const xfetch = (input, init = {}) => {
183 | mergeDeep(init, defaultInit);
184 |
185 | const createQueryString = o => new init.URLSearchParams(o).toString();
186 |
187 | const parseQueryString = s => fromEntries([...new init.URLSearchParams(s).entries()]);
188 |
189 | const url = new init.URL(input, init.baseURI || undefined);
190 |
191 | if (!init.headers) {
192 | init.headers = {};
193 | } else if (typeis(init.Headers)(init.headers)) {
194 | // Transform into object if it is `Headers`
195 | init.headers = fromEntries([...init.headers.entries()]);
196 | } // Add json or form on body
197 |
198 |
199 | if (init.json) {
200 | init.body = JSON.stringify(init.json);
201 | init.headers['Content-Type'] = 'application/json';
202 | } else if (isstrorobj(init.urlencoded)) {
203 | init.body = isstr(init.urlencoded) ? init.urlencoded : createQueryString(init.urlencoded);
204 | init.headers['Content-Type'] = 'application/x-www-form-urlencoded';
205 | } else if (typeis(init.FormData, 'object')(init.formData)) {
206 | // init.formData is data passed by user, init.FormData is FormData constructor
207 | if (!typeis(init.FormData)(init.formData)) {
208 | const fd = new init.FormData();
209 |
210 | for (const [k, v] of Object.entries(init.formData)) {
211 | fd.append(k, v);
212 | }
213 |
214 | init.formData = fd;
215 | }
216 |
217 | init.body = init.formData;
218 | } // Querystring
219 |
220 |
221 | if (init.qs) {
222 | if (isstr(init.qs)) init.qs = parseQueryString(init.qs);
223 | url.search = createQueryString(assign(fromEntries([...url.searchParams.entries()]), init.qs));
224 | }
225 |
226 | return XResponsePromise.resolve(init.fetch(url, init).then(responseErrorThrower));
227 | };
228 |
229 | for (const method of METHODS) {
230 | xfetch[method] = (input, init = {}) => {
231 | init.method = method.toUpperCase();
232 | return xfetch(input, init);
233 | };
234 | } // Extra methods and classes
235 |
236 |
237 | xfetch.extend = newDefaultInit => extend(assign({}, defaultInit, newDefaultInit));
238 |
239 | xfetch.HTTPError = HTTPError;
240 | return xfetch;
241 | };
242 |
243 | const isWindow = typeof document !== 'undefined';
244 | const isBrowser = typeof self !== 'undefined'; // works in both window & worker scope
245 |
246 | return isBrowser ? extend({
247 | fetch: fetch.bind(self),
248 | URL,
249 | Response,
250 | URLSearchParams,
251 | Headers,
252 | FormData,
253 | baseURI: isWindow ? document.baseURI : '' // since there is no document in webworkers
254 |
255 | }) : extend();
256 | })();
257 |
258 | class GoogleDrive {
259 | constructor(auth) {
260 | this.auth = auth;
261 | this.expires = 0;
262 | this._getIdCache = new Map();
263 | }
264 |
265 | async initializeClient() {
266 | // any method that do api call must call this beforehand
267 | if (Date.now() < this.expires) return;
268 | const resp = await xf.post('https://www.googleapis.com/oauth2/v4/token', {
269 | urlencoded: {
270 | client_id: this.auth.client_id,
271 | client_secret: this.auth.client_secret,
272 | refresh_token: this.auth.refresh_token,
273 | grant_type: 'refresh_token'
274 | }
275 | }).json();
276 | this.client = xf.extend({
277 | baseURI: 'https://www.googleapis.com/drive/v3/',
278 | headers: {
279 | Authorization: `Bearer ${resp.access_token}`
280 | }
281 | });
282 | this.expires = Date.now() + 3500 * 1000; // normally, it should expiers after 3600 seconds
283 | }
284 |
285 | async listDrive() {
286 | await this.initializeClient();
287 | return this.client.get('drives').json();
288 | }
289 |
290 | async download(id, range = '') {
291 | await this.initializeClient();
292 | return this.client.get(`files/${id}`, {
293 | qs: {
294 | includeItemsFromAllDrives: true,
295 | supportsAllDrives: true,
296 | alt: 'media'
297 | },
298 | headers: {
299 | Range: range
300 | }
301 | });
302 | }
303 |
304 | async downloadByPath(path, rootId = 'root', range = '') {
305 | const id = await this.getId(path, rootId);
306 | if (!id) return null;
307 | return this.download(id, range);
308 | }
309 |
310 | async getMeta(id) {
311 | await this.initializeClient();
312 | return this.client.get(`files/${id}`, {
313 | qs: {
314 | includeItemsFromAllDrives: true,
315 | supportsAllDrives: true,
316 | fields: '*'
317 | }
318 | }).json();
319 | }
320 |
321 | async getMetaByPath(path, rootId = 'root') {
322 | const id = await this.getId(path, rootId);
323 | if (!id) return null;
324 | return this.getMeta(id);
325 | }
326 |
327 | async listFolder(id) {
328 | await this.initializeClient();
329 |
330 | const getList = pageToken => {
331 | const qs = {
332 | includeItemsFromAllDrives: true,
333 | supportsAllDrives: true,
334 | q: `'${id}' in parents and trashed = false`,
335 | orderBy: 'folder,name,modifiedTime desc',
336 | fields: 'files(id,name,mimeType,size,modifiedTime),nextPageToken',
337 | pageSize: 1000
338 | };
339 |
340 | if (pageToken) {
341 | qs.pageToken = pageToken;
342 | }
343 |
344 | return this.client.get('files', {
345 | qs
346 | }).json();
347 | };
348 |
349 | const files = [];
350 | let pageToken;
351 |
352 | do {
353 | const resp = await getList(pageToken);
354 | files.push(...resp.files);
355 | pageToken = resp.nextPageToken;
356 | } while (pageToken);
357 |
358 | return {
359 | files
360 | };
361 | }
362 |
363 | async listFolderByPath(path, rootId = 'root') {
364 | const id = await this.getId(path, rootId);
365 | if (!id) return null;
366 | return this.listFolder(id);
367 | }
368 |
369 | async getId(path, rootId = 'root') {
370 | const toks = path.split('/').filter(Boolean);
371 | let id = rootId;
372 |
373 | for (const tok of toks) {
374 | id = await this._getId(id, tok);
375 | }
376 |
377 | return id;
378 | }
379 |
380 | async _getId(parentId, childName) {
381 | if (this._getIdCache.has(parentId + childName)) {
382 | return this._getIdCache.get(parentId + childName);
383 | }
384 |
385 | await this.initializeClient();
386 | childName = childName.replace(/\'/g, `\\'`); // escape single quote
387 |
388 | const resp = await this.client.get('files', {
389 | qs: {
390 | includeItemsFromAllDrives: true,
391 | supportsAllDrives: true,
392 | q: `'${parentId}' in parents and name = '${childName}' and trashed = false`,
393 | fields: 'files(id)'
394 | }
395 | }).json().catch(e => ({
396 | files: []
397 | })); // if error, make it empty
398 |
399 | if (resp.files.length === 0) {
400 | return null;
401 | }
402 |
403 | this._getIdCache.has(parentId + childName);
404 |
405 | return resp.files[0].id; // when there are more than 1 items, simply return the first one
406 | }
407 |
408 | async upload(parentId, name, file) {
409 | await this.initializeClient();
410 | const createResp = await this.client.post('https://www.googleapis.com/upload/drive/v3/files', {
411 | qs: {
412 | uploadType: 'resumable',
413 | supportsAllDrives: true
414 | },
415 | json: {
416 | name,
417 | parents: [parentId]
418 | }
419 | });
420 | const putUrl = createResp.headers.get('Location');
421 | return this.client.put(putUrl, {
422 | body: file
423 | }).json();
424 | }
425 |
426 | async uploadByPath(path, name, file, rootId = 'root') {
427 | const id = await this.getId(path, rootId);
428 | if (!id) return null;
429 | return this.upload(id, name, file);
430 | }
431 |
432 | async delete(fileId) {
433 | return this.client.delete(`files/${fileId}`);
434 | }
435 |
436 | async deleteByPath(path, rootId = 'root') {
437 | const id = await this.getId(path, rootId);
438 | if (!id) return null;
439 | return this.delete(id);
440 | }
441 |
442 | }
443 |
444 | const gd = new GoogleDrive(self.props);
445 | const HTML = `${self.props.title}