2020-05-22 01:53:30 +08:00
|
|
|
|
// =======Options START=======
|
2020-05-19 19:01:24 +08:00
|
|
|
|
var authConfig = {
|
2024-08-04 12:45:11 +08:00
|
|
|
|
siteName: "GoIndex-theme-acrou", // 网站名称
|
2020-08-29 19:00:27 +08:00
|
|
|
|
version: "1.1.2", // 程序版本
|
2020-05-20 02:11:53 +08:00
|
|
|
|
theme: "acrou",
|
|
|
|
|
// 强烈推荐使用自己的 client_id 和 client_secret
|
2020-05-22 01:53:30 +08:00
|
|
|
|
client_id: "202264815644.apps.googleusercontent.com",
|
|
|
|
|
client_secret: "X4Z3ca8xfWDb1Voo-F9a7ZxJ",
|
2020-05-20 02:11:53 +08:00
|
|
|
|
refresh_token: "", // 授权 token
|
|
|
|
|
/**
|
|
|
|
|
* 设置要显示的多个云端硬盘;按格式添加多个
|
|
|
|
|
* [id]: 可以是 团队盘id、子文件夹id、或者"root"(代表个人盘根目录);
|
|
|
|
|
* [name]: 显示的名称
|
|
|
|
|
* [user]: Basic Auth 的用户名
|
|
|
|
|
* [pass]: Basic Auth 的密码
|
|
|
|
|
* [protect_file_link]: Basic Auth 是否用于保护文件链接,默认值(不设置时)为 false,即不保护文件链接(方便 直链下载/外部播放 等)
|
|
|
|
|
* 每个盘的 Basic Auth 都可以单独设置。Basic Auth 默认保护该盘下所有文件夹/子文件夹路径
|
|
|
|
|
* 【注意】默认不保护文件链接,这样可以方便 直链下载/外部播放;
|
|
|
|
|
* 如果要保护文件链接,需要将 protect_file_link 设置为 true,此时如果要进行外部播放等操作,需要将 host 替换为 user:pass@host 的 形式
|
|
|
|
|
* 不需要 Basic Auth 的盘,保持 user 和 pass 同时为空即可。(直接不设置也可以)
|
|
|
|
|
* 【注意】对于id设置为为子文件夹id的盘将不支持搜索功能(不影响其他盘)。
|
|
|
|
|
*/
|
|
|
|
|
roots: [
|
|
|
|
|
{
|
|
|
|
|
id: "",
|
|
|
|
|
name: "TeamDrive",
|
|
|
|
|
pass: "",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "root",
|
|
|
|
|
name: "PrivateDrive",
|
|
|
|
|
user: "",
|
|
|
|
|
pass: "",
|
|
|
|
|
protect_file_link: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: "",
|
|
|
|
|
name: "folder1",
|
|
|
|
|
pass: "",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
default_gd: 0,
|
|
|
|
|
/**
|
|
|
|
|
* 文件列表页面每页显示的数量。【推荐设置值为 100 到 1000 之间】;
|
|
|
|
|
* 如果设置大于1000,会导致请求 drive api 时出错;
|
|
|
|
|
* 如果设置的值过小,会导致文件列表页面滚动条增量加载(分页加载)失效;
|
|
|
|
|
* 此值的另一个作用是,如果目录内文件数大于此设置值(即需要多页展示的),将会对首次列目录结果进行缓存。
|
|
|
|
|
*/
|
|
|
|
|
files_list_page_size: 50,
|
|
|
|
|
/**
|
|
|
|
|
* 搜索结果页面每页显示的数量。【推荐设置值为 50 到 1000 之间】;
|
|
|
|
|
* 如果设置大于1000,会导致请求 drive api 时出错;
|
|
|
|
|
* 如果设置的值过小,会导致搜索结果页面滚动条增量加载(分页加载)失效;
|
|
|
|
|
* 此值的大小影响搜索操作的响应速度。
|
|
|
|
|
*/
|
|
|
|
|
search_result_list_page_size: 50,
|
|
|
|
|
// 确认有 cors 用途的可以开启
|
2024-08-04 12:45:11 +08:00
|
|
|
|
enable_cors_file_down: false,
|
2020-05-20 02:11:53 +08:00
|
|
|
|
/**
|
|
|
|
|
* 上面的 basic auth 已经包含了盘内全局保护的功能。所以默认不再去认证 .password 文件内的密码;
|
|
|
|
|
* 如果在全局认证的基础上,仍需要给某些目录单独进行 .password 文件内的密码验证的话,将此选项设置为 true;
|
|
|
|
|
* 【注意】如果开启了 .password 文件密码验证,每次列目录都会额外增加查询目录内 .password 文件是否存在的开销。
|
|
|
|
|
*/
|
2020-05-22 01:53:30 +08:00
|
|
|
|
enable_password_file_verify: false,
|
2020-05-19 19:01:24 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var themeOptions = {
|
2024-08-04 12:45:11 +08:00
|
|
|
|
cdn: "https://cdn.jsdelivr.net/gh/RandallAnjie/goindex-theme-r",
|
|
|
|
|
// 主题版本号
|
|
|
|
|
version: "v2.0.9",
|
2020-05-20 02:11:53 +08:00
|
|
|
|
//可选默认系统语言:en/zh-chs/zh-cht
|
2024-08-04 12:23:32 +08:00
|
|
|
|
languages: "zh-chs",
|
2020-05-20 02:11:53 +08:00
|
|
|
|
render: {
|
2020-05-22 01:53:30 +08:00
|
|
|
|
/**
|
|
|
|
|
* 是否渲染HEAD.md文件
|
|
|
|
|
* Render HEAD.md file
|
|
|
|
|
*/
|
2024-08-04 12:23:32 +08:00
|
|
|
|
head_md: true,
|
2020-05-22 01:53:30 +08:00
|
|
|
|
/**
|
|
|
|
|
* 是否渲染README.md文件
|
|
|
|
|
* Render README.md file
|
|
|
|
|
*/
|
2024-08-04 12:23:32 +08:00
|
|
|
|
readme_md: true,
|
2020-05-22 01:53:30 +08:00
|
|
|
|
/**
|
|
|
|
|
* 是否渲染文件/文件夹描述
|
|
|
|
|
* Render file/folder description or not
|
|
|
|
|
*/
|
2024-08-04 12:23:32 +08:00
|
|
|
|
desc: true,
|
2020-05-31 02:03:23 +08:00
|
|
|
|
},
|
|
|
|
|
/**
|
2020-08-29 19:00:27 +08:00
|
|
|
|
* 视频播放器选项
|
|
|
|
|
* Video player options
|
2020-05-31 02:03:23 +08:00
|
|
|
|
*/
|
2020-08-29 19:00:27 +08:00
|
|
|
|
video: {
|
2020-05-31 02:03:23 +08:00
|
|
|
|
/**
|
2020-08-29 19:00:27 +08:00
|
|
|
|
* 播放器api(不指定则使用默认播放器)
|
|
|
|
|
* Player api(Use default player if not specified)
|
2020-05-31 02:03:23 +08:00
|
|
|
|
*/
|
2020-08-29 19:00:27 +08:00
|
|
|
|
api: "",
|
|
|
|
|
autoplay: true,
|
2020-05-20 02:11:53 +08:00
|
|
|
|
},
|
2020-08-29 19:00:27 +08:00
|
|
|
|
/**
|
|
|
|
|
* 音频播放器选项
|
|
|
|
|
* Audio player options
|
|
|
|
|
*/
|
|
|
|
|
audio: {},
|
2020-05-20 02:11:53 +08:00
|
|
|
|
};
|
2020-05-22 01:53:30 +08:00
|
|
|
|
// =======Options END=======
|
2020-05-19 19:01:24 +08:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* global functions
|
|
|
|
|
*/
|
|
|
|
|
const FUNCS = {
|
2020-05-20 02:11:53 +08:00
|
|
|
|
/**
|
|
|
|
|
* 转换成针对谷歌搜索词法相对安全的搜索关键词
|
|
|
|
|
*/
|
2020-08-29 19:00:27 +08:00
|
|
|
|
formatSearchKeyword: function(keyword) {
|
2020-05-20 02:11:53 +08:00
|
|
|
|
let nothing = "";
|
|
|
|
|
let space = " ";
|
|
|
|
|
if (!keyword) return nothing;
|
|
|
|
|
return keyword
|
|
|
|
|
.replace(/(!=)|['"=<>/\\:]/g, nothing)
|
|
|
|
|
.replace(/[,,|(){}]/g, space)
|
|
|
|
|
.trim();
|
|
|
|
|
},
|
2020-05-19 19:01:24 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* global consts
|
|
|
|
|
* @type {{folder_mime_type: string, default_file_fields: string, gd_root_type: {share_drive: number, user_drive: number, sub_folder: number}}}
|
|
|
|
|
*/
|
|
|
|
|
const CONSTS = new (class {
|
2020-05-20 02:11:53 +08:00
|
|
|
|
default_file_fields =
|
|
|
|
|
"parents,id,name,mimeType,modifiedTime,createdTime,fileExtension,size";
|
|
|
|
|
gd_root_type = {
|
|
|
|
|
user_drive: 0,
|
|
|
|
|
share_drive: 1,
|
|
|
|
|
sub_folder: 2,
|
|
|
|
|
};
|
|
|
|
|
folder_mime_type = "application/vnd.google-apps.folder";
|
2020-05-19 19:01:24 +08:00
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
// gd instances
|
|
|
|
|
var gds = [];
|
|
|
|
|
|
|
|
|
|
function html(current_drive_order = 0, model = {}) {
|
2024-08-04 12:45:11 +08:00
|
|
|
|
if (themeOptions.version && themeOptions.version !== '') {
|
|
|
|
|
themeOptions.version = '@' + themeOptions.version;
|
|
|
|
|
}
|
2020-05-20 02:11:53 +08:00
|
|
|
|
return `
|
2020-05-19 19:01:24 +08:00
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html>
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="utf-8">
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0, user-scalable=no"/>
|
|
|
|
|
<title>${authConfig.siteName}</title>
|
|
|
|
|
<style>
|
2024-08-04 12:45:11 +08:00
|
|
|
|
@import url(${themeOptions.cdn}${themeOptions.version}/dist/style.css);
|
2020-05-19 19:01:24 +08:00
|
|
|
|
</style>
|
|
|
|
|
<script>
|
2020-05-20 02:11:53 +08:00
|
|
|
|
window.gdconfig = JSON.parse('${JSON.stringify({
|
|
|
|
|
version: authConfig.version,
|
|
|
|
|
themeOptions: themeOptions,
|
|
|
|
|
})}');
|
|
|
|
|
window.themeOptions = JSON.parse('${JSON.stringify(themeOptions)}');
|
|
|
|
|
window.gds = JSON.parse('${JSON.stringify(
|
|
|
|
|
authConfig.roots.map((it) => it.name)
|
|
|
|
|
)}');
|
2020-05-19 19:01:24 +08:00
|
|
|
|
window.MODEL = JSON.parse('${JSON.stringify(model)}');
|
|
|
|
|
window.current_drive_order = ${current_drive_order};
|
|
|
|
|
</script>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
<div id="app"></div>
|
2024-08-04 12:45:11 +08:00
|
|
|
|
<script src="${themeOptions.cdn}${
|
|
|
|
|
themeOptions.version
|
|
|
|
|
}/dist/app.js"></script>
|
2020-05-19 19:01:24 +08:00
|
|
|
|
</body>
|
|
|
|
|
</html>
|
|
|
|
|
`;
|
2020-05-20 02:11:53 +08:00
|
|
|
|
}
|
2020-05-19 19:01:24 +08:00
|
|
|
|
|
2020-05-20 02:11:53 +08:00
|
|
|
|
addEventListener("fetch", (event) => {
|
|
|
|
|
event.respondWith(handleRequest(event.request));
|
2020-05-19 19:01:24 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Fetch and log a request
|
|
|
|
|
* @param {Request} request
|
|
|
|
|
*/
|
|
|
|
|
async function handleRequest(request) {
|
2020-05-20 02:11:53 +08:00
|
|
|
|
if (gds.length === 0) {
|
|
|
|
|
for (let i = 0; i < authConfig.roots.length; i++) {
|
|
|
|
|
const gd = new googleDrive(authConfig, i);
|
|
|
|
|
await gd.init();
|
|
|
|
|
gds.push(gd);
|
|
|
|
|
}
|
|
|
|
|
// 这个操作并行,提高效率
|
|
|
|
|
let tasks = [];
|
|
|
|
|
gds.forEach((gd) => {
|
|
|
|
|
tasks.push(gd.initRootType());
|
|
|
|
|
});
|
|
|
|
|
for (let task of tasks) {
|
|
|
|
|
await task;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 从 path 中提取 drive order
|
|
|
|
|
// 并根据 drive order 获取对应的 gd instance
|
|
|
|
|
let gd;
|
|
|
|
|
let url = new URL(request.url);
|
2020-05-31 02:03:23 +08:00
|
|
|
|
let path = decodeURI(url.pathname);
|
2020-05-20 02:11:53 +08:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 重定向至起始页
|
|
|
|
|
* @returns {Response}
|
|
|
|
|
*/
|
|
|
|
|
function redirectToIndexPage() {
|
|
|
|
|
return new Response("", {
|
|
|
|
|
status: 301,
|
|
|
|
|
headers: { Location: `/${authConfig.default_gd}:/` },
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (path == "/") return redirectToIndexPage();
|
|
|
|
|
if (path.toLowerCase() == "/favicon.ico") {
|
|
|
|
|
// 后面可以找一个 favicon
|
|
|
|
|
return new Response("", { status: 404 });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 特殊命令格式
|
|
|
|
|
const command_reg = /^\/(?<num>\d+):(?<command>[a-zA-Z0-9]+)(\/.*)?$/g;
|
|
|
|
|
const match = command_reg.exec(path);
|
|
|
|
|
let command;
|
|
|
|
|
if (match) {
|
|
|
|
|
const num = match.groups.num;
|
|
|
|
|
const order = Number(num);
|
|
|
|
|
if (order >= 0 && order < gds.length) {
|
|
|
|
|
gd = gds[order];
|
|
|
|
|
} else {
|
|
|
|
|
return redirectToIndexPage();
|
2020-05-19 19:01:24 +08:00
|
|
|
|
}
|
|
|
|
|
// basic auth
|
2020-05-20 02:11:53 +08:00
|
|
|
|
for (const r = gd.basicAuthResponse(request); r; ) return r;
|
|
|
|
|
command = match.groups.command;
|
|
|
|
|
|
|
|
|
|
// 搜索
|
|
|
|
|
if (command === "search") {
|
|
|
|
|
if (request.method === "POST") {
|
|
|
|
|
// 搜索结果
|
|
|
|
|
return handleSearch(request, gd);
|
|
|
|
|
} else {
|
|
|
|
|
const params = url.searchParams;
|
|
|
|
|
// 搜索页面
|
|
|
|
|
return new Response(
|
|
|
|
|
html(gd.order, {
|
|
|
|
|
q: params.get("q") || "",
|
|
|
|
|
is_search_page: true,
|
|
|
|
|
root_type: gd.root_type,
|
|
|
|
|
}),
|
|
|
|
|
{
|
2020-05-19 19:01:24 +08:00
|
|
|
|
status: 200,
|
2020-05-20 02:11:53 +08:00
|
|
|
|
headers: { "Content-Type": "text/html; charset=utf-8" },
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} else if (command === "id2path" && request.method === "POST") {
|
|
|
|
|
return handleId2Path(request, gd);
|
|
|
|
|
} else if (command === "view") {
|
|
|
|
|
const params = url.searchParams;
|
|
|
|
|
return gd.view(params.get("url"), request.headers.get("Range"));
|
|
|
|
|
} else if (command !== "down" && request.method === "GET") {
|
|
|
|
|
return new Response(html(gd.order, { root_type: gd.root_type }), {
|
|
|
|
|
status: 200,
|
|
|
|
|
headers: { "Content-Type": "text/html; charset=utf-8" },
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const reg = new RegExp(`^(/\\d+:)${command}/`, "g");
|
|
|
|
|
path = path.replace(reg, (p1, p2) => {
|
|
|
|
|
return p2 + "/";
|
|
|
|
|
});
|
|
|
|
|
// 期望的 path 格式
|
|
|
|
|
const common_reg = /^\/\d+:\/.*$/g;
|
|
|
|
|
try {
|
|
|
|
|
if (!path.match(common_reg)) {
|
|
|
|
|
return redirectToIndexPage();
|
|
|
|
|
}
|
|
|
|
|
let split = path.split("/");
|
|
|
|
|
let order = Number(split[1].slice(0, -1));
|
|
|
|
|
if (order >= 0 && order < gds.length) {
|
|
|
|
|
gd = gds[order];
|
2020-05-19 19:01:24 +08:00
|
|
|
|
} else {
|
2020-05-20 02:11:53 +08:00
|
|
|
|
return redirectToIndexPage();
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
return redirectToIndexPage();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// basic auth
|
|
|
|
|
// for (const r = gd.basicAuthResponse(request); r;) return r;
|
|
|
|
|
const basic_auth_res = gd.basicAuthResponse(request);
|
|
|
|
|
path = path.replace(gd.url_path_prefix, "") || "/";
|
|
|
|
|
if (request.method == "POST") {
|
|
|
|
|
return basic_auth_res || apiRequest(request, gd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let action = url.searchParams.get("a");
|
|
|
|
|
|
|
|
|
|
if (path.substr(-1) == "/" || action != null) {
|
|
|
|
|
return (
|
|
|
|
|
basic_auth_res ||
|
|
|
|
|
new Response(html(gd.order, { root_type: gd.root_type }), {
|
|
|
|
|
status: 200,
|
|
|
|
|
headers: { "Content-Type": "text/html; charset=utf-8" },
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
} else {
|
2020-08-29 19:00:27 +08:00
|
|
|
|
if (
|
|
|
|
|
path
|
|
|
|
|
.split("/")
|
|
|
|
|
.pop()
|
|
|
|
|
.toLowerCase() == ".password"
|
|
|
|
|
) {
|
2020-05-20 02:11:53 +08:00
|
|
|
|
return basic_auth_res || new Response("", { status: 404 });
|
|
|
|
|
}
|
|
|
|
|
let file = await gd.file(path);
|
|
|
|
|
let range = request.headers.get("Range");
|
|
|
|
|
if (gd.root.protect_file_link && basic_auth_res) return basic_auth_res;
|
|
|
|
|
const is_down = !(command && command == "down");
|
|
|
|
|
return gd.down(file.id, range, is_down);
|
|
|
|
|
}
|
2020-05-19 19:01:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function apiRequest(request, gd) {
|
2020-05-20 02:11:53 +08:00
|
|
|
|
let url = new URL(request.url);
|
|
|
|
|
let path = url.pathname;
|
|
|
|
|
path = path.replace(gd.url_path_prefix, "") || "/";
|
2020-05-19 19:01:24 +08:00
|
|
|
|
|
2020-05-20 02:11:53 +08:00
|
|
|
|
let option = { status: 200, headers: { "Access-Control-Allow-Origin": "*" } };
|
|
|
|
|
|
|
|
|
|
if (path.substr(-1) == "/") {
|
|
|
|
|
let deferred_pass = gd.password(path);
|
|
|
|
|
let body = await request.text();
|
|
|
|
|
body = JSON.parse(body);
|
|
|
|
|
// 这样可以提升首次列目录时的速度。缺点是,如果password验证失败,也依然会产生列目录的开销
|
|
|
|
|
let deferred_list_result = gd.list(
|
|
|
|
|
path,
|
|
|
|
|
body.page_token,
|
|
|
|
|
Number(body.page_index)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// check .password file, if `enable_password_file_verify` is true
|
|
|
|
|
if (authConfig["enable_password_file_verify"]) {
|
|
|
|
|
let password = await gd.password(path);
|
|
|
|
|
// console.log("dir password", password);
|
|
|
|
|
if (password && password.replace("\n", "") !== body.password) {
|
|
|
|
|
let html = `{"error": {"code": 401,"message": "password error."}}`;
|
|
|
|
|
return new Response(html, option);
|
|
|
|
|
}
|
2020-05-19 19:01:24 +08:00
|
|
|
|
}
|
2020-05-20 02:11:53 +08:00
|
|
|
|
|
|
|
|
|
let list_result = await deferred_list_result;
|
|
|
|
|
return new Response(JSON.stringify(list_result), option);
|
|
|
|
|
} else {
|
|
|
|
|
let file = await gd.file(path);
|
|
|
|
|
let range = request.headers.get("Range");
|
|
|
|
|
return new Response(JSON.stringify(file));
|
|
|
|
|
}
|
2020-05-19 19:01:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理 search
|
|
|
|
|
async function handleSearch(request, gd) {
|
2020-05-20 02:11:53 +08:00
|
|
|
|
const option = {
|
|
|
|
|
status: 200,
|
|
|
|
|
headers: { "Access-Control-Allow-Origin": "*" },
|
|
|
|
|
};
|
|
|
|
|
let body = await request.text();
|
|
|
|
|
body = JSON.parse(body);
|
|
|
|
|
let search_result = await gd.search(
|
|
|
|
|
body.q || "",
|
|
|
|
|
body.page_token,
|
|
|
|
|
Number(body.page_index)
|
|
|
|
|
);
|
|
|
|
|
return new Response(JSON.stringify(search_result), option);
|
2020-05-19 19:01:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理 id2path
|
|
|
|
|
* @param request 需要 id 参数
|
|
|
|
|
* @param gd
|
|
|
|
|
* @returns {Promise<Response>} 【注意】如果从前台接收的id代表的项目不在目标gd盘下,那么response会返回给前台一个空字符串""
|
|
|
|
|
*/
|
|
|
|
|
async function handleId2Path(request, gd) {
|
2020-05-20 02:11:53 +08:00
|
|
|
|
const option = {
|
|
|
|
|
status: 200,
|
|
|
|
|
headers: { "Access-Control-Allow-Origin": "*" },
|
|
|
|
|
};
|
|
|
|
|
let body = await request.text();
|
|
|
|
|
body = JSON.parse(body);
|
|
|
|
|
let path = await gd.findPathById(body.id);
|
|
|
|
|
return new Response(path || "", option);
|
2020-05-19 19:01:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class googleDrive {
|
2020-05-20 02:11:53 +08:00
|
|
|
|
constructor(authConfig, order) {
|
|
|
|
|
// 每个盘对应一个order,对应一个gd实例
|
|
|
|
|
this.order = order;
|
|
|
|
|
this.root = authConfig.roots[order];
|
|
|
|
|
this.root.protect_file_link = this.root.protect_file_link || false;
|
|
|
|
|
this.url_path_prefix = `/${order}:`;
|
|
|
|
|
this.authConfig = authConfig;
|
|
|
|
|
// TODO: 这些缓存的失效刷新策略,后期可以制定一下
|
|
|
|
|
// path id
|
|
|
|
|
this.paths = [];
|
|
|
|
|
// path file
|
|
|
|
|
this.files = [];
|
|
|
|
|
// path pass
|
|
|
|
|
this.passwords = [];
|
|
|
|
|
// id <-> path
|
|
|
|
|
this.id_path_cache = {};
|
|
|
|
|
this.id_path_cache[this.root["id"]] = "/";
|
|
|
|
|
this.paths["/"] = this.root["id"];
|
|
|
|
|
/*if (this.root['pass'] != "") {
|
2020-05-19 19:01:24 +08:00
|
|
|
|
this.passwords['/'] = this.root['pass'];
|
|
|
|
|
}*/
|
2020-05-20 02:11:53 +08:00
|
|
|
|
// this.init();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 初次授权;然后获取 user_drive_real_root_id
|
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
*/
|
|
|
|
|
async init() {
|
|
|
|
|
await this.accessToken();
|
|
|
|
|
/*await (async () => {
|
2020-05-19 19:01:24 +08:00
|
|
|
|
// 只获取1次
|
|
|
|
|
if (authConfig.user_drive_real_root_id) return;
|
|
|
|
|
const root_obj = await (gds[0] || this).findItemById('root');
|
|
|
|
|
if (root_obj && root_obj.id) {
|
|
|
|
|
authConfig.user_drive_real_root_id = root_obj.id
|
|
|
|
|
}
|
|
|
|
|
})();*/
|
2020-05-20 02:11:53 +08:00
|
|
|
|
// 等待 user_drive_real_root_id ,只获取1次
|
|
|
|
|
if (authConfig.user_drive_real_root_id) return;
|
|
|
|
|
const root_obj = await (gds[0] || this).findItemById("root");
|
|
|
|
|
if (root_obj && root_obj.id) {
|
|
|
|
|
authConfig.user_drive_real_root_id = root_obj.id;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取根目录类型,设置到 root_type
|
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
*/
|
|
|
|
|
async initRootType() {
|
|
|
|
|
const root_id = this.root["id"];
|
|
|
|
|
const types = CONSTS.gd_root_type;
|
|
|
|
|
if (root_id === "root" || root_id === authConfig.user_drive_real_root_id) {
|
|
|
|
|
this.root_type = types.user_drive;
|
|
|
|
|
} else {
|
|
|
|
|
const obj = await this.getShareDriveObjById(root_id);
|
|
|
|
|
this.root_type = obj ? types.share_drive : types.sub_folder;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a response that requires authorization, or null
|
|
|
|
|
* @param request
|
|
|
|
|
* @returns {Response|null}
|
|
|
|
|
*/
|
|
|
|
|
basicAuthResponse(request) {
|
|
|
|
|
const user = this.root.user || "",
|
|
|
|
|
pass = this.root.pass || "",
|
|
|
|
|
_401 = new Response("Unauthorized", {
|
|
|
|
|
headers: {
|
|
|
|
|
"WWW-Authenticate": `Basic realm="goindex:drive:${this.order}"`,
|
|
|
|
|
},
|
|
|
|
|
status: 401,
|
|
|
|
|
});
|
|
|
|
|
if (user || pass) {
|
|
|
|
|
const auth = request.headers.get("Authorization");
|
|
|
|
|
if (auth) {
|
|
|
|
|
try {
|
|
|
|
|
const [received_user, received_pass] = atob(
|
|
|
|
|
auth.split(" ").pop()
|
|
|
|
|
).split(":");
|
|
|
|
|
return received_user === user && received_pass === pass ? null : _401;
|
|
|
|
|
} catch (e) {}
|
2020-05-19 19:01:24 +08:00
|
|
|
|
}
|
2020-05-20 02:11:53 +08:00
|
|
|
|
} else return null;
|
|
|
|
|
return _401;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async view(url, range = "", inline = true) {
|
|
|
|
|
let requestOption = await this.requestOption();
|
|
|
|
|
requestOption.headers["Range"] = range;
|
|
|
|
|
let res = await fetch(url, requestOption);
|
|
|
|
|
const { headers } = (res = new Response(res.body, res));
|
|
|
|
|
this.authConfig.enable_cors_file_down &&
|
|
|
|
|
headers.append("Access-Control-Allow-Origin", "*");
|
|
|
|
|
inline === true && headers.set("Content-Disposition", "inline");
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async down(id, range = "", inline = false) {
|
|
|
|
|
let url = `https://www.googleapis.com/drive/v3/files/${id}?alt=media`;
|
|
|
|
|
let requestOption = await this.requestOption();
|
|
|
|
|
requestOption.headers["Range"] = range;
|
|
|
|
|
let res = await fetch(url, requestOption);
|
|
|
|
|
const { headers } = (res = new Response(res.body, res));
|
|
|
|
|
this.authConfig.enable_cors_file_down &&
|
|
|
|
|
headers.append("Access-Control-Allow-Origin", "*");
|
|
|
|
|
inline === true && headers.set("Content-Disposition", "inline");
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async file(path) {
|
|
|
|
|
if (typeof this.files[path] == "undefined") {
|
|
|
|
|
this.files[path] = await this._file(path);
|
|
|
|
|
}
|
|
|
|
|
return this.files[path];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async _file(path) {
|
|
|
|
|
let arr = path.split("/");
|
|
|
|
|
let name = arr.pop();
|
|
|
|
|
name = decodeURIComponent(name).replace(/\'/g, "\\'");
|
|
|
|
|
let dir = arr.join("/") + "/";
|
|
|
|
|
// console.log(name, dir);
|
|
|
|
|
let parent = await this.findPathId(dir);
|
|
|
|
|
// console.log(parent);
|
|
|
|
|
let url = "https://www.googleapis.com/drive/v3/files";
|
|
|
|
|
let params = { includeItemsFromAllDrives: true, supportsAllDrives: true };
|
|
|
|
|
params.q = `'${parent}' in parents and name = '${name}' and trashed = false`;
|
|
|
|
|
params.fields =
|
|
|
|
|
"files(id, name, mimeType, size ,createdTime, modifiedTime, iconLink, thumbnailLink)";
|
|
|
|
|
url += "?" + this.enQuery(params);
|
|
|
|
|
let requestOption = await this.requestOption();
|
|
|
|
|
let response = await fetch(url, requestOption);
|
|
|
|
|
let obj = await response.json();
|
|
|
|
|
// console.log(obj);
|
|
|
|
|
return obj.files[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 通过reqeust cache 来缓存
|
|
|
|
|
async list(path, page_token = null, page_index = 0) {
|
|
|
|
|
if (this.path_children_cache == undefined) {
|
|
|
|
|
// { <path> :[ {nextPageToken:'',data:{}}, {nextPageToken:'',data:{}} ...], ...}
|
|
|
|
|
this.path_children_cache = {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
this.path_children_cache[path] &&
|
|
|
|
|
this.path_children_cache[path][page_index] &&
|
|
|
|
|
this.path_children_cache[path][page_index].data
|
|
|
|
|
) {
|
|
|
|
|
let child_obj = this.path_children_cache[path][page_index];
|
|
|
|
|
return {
|
|
|
|
|
nextPageToken: child_obj.nextPageToken || null,
|
|
|
|
|
curPageIndex: page_index,
|
|
|
|
|
data: child_obj.data,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let id = await this.findPathId(path);
|
|
|
|
|
let result = await this._ls(id, page_token, page_index);
|
|
|
|
|
let data = result.data;
|
|
|
|
|
// 对有多页的,进行缓存
|
|
|
|
|
if (result.nextPageToken && data.files) {
|
|
|
|
|
if (!Array.isArray(this.path_children_cache[path])) {
|
|
|
|
|
this.path_children_cache[path] = [];
|
|
|
|
|
}
|
|
|
|
|
this.path_children_cache[path][Number(result.curPageIndex)] = {
|
|
|
|
|
nextPageToken: result.nextPageToken,
|
|
|
|
|
data: data,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async _ls(parent, page_token = null, page_index = 0) {
|
|
|
|
|
// console.log("_ls", parent);
|
|
|
|
|
|
|
|
|
|
if (parent == undefined) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
let obj;
|
|
|
|
|
let params = { includeItemsFromAllDrives: true, supportsAllDrives: true };
|
|
|
|
|
params.q = `'${parent}' in parents and trashed = false AND name !='.password'`;
|
|
|
|
|
params.orderBy = "folder,name,modifiedTime desc";
|
|
|
|
|
params.fields =
|
|
|
|
|
"nextPageToken, files(id, name, mimeType, size , modifiedTime, thumbnailLink, description)";
|
|
|
|
|
params.pageSize = this.authConfig.files_list_page_size;
|
|
|
|
|
|
|
|
|
|
if (page_token) {
|
|
|
|
|
params.pageToken = page_token;
|
|
|
|
|
}
|
|
|
|
|
let url = "https://www.googleapis.com/drive/v3/files";
|
|
|
|
|
url += "?" + this.enQuery(params);
|
|
|
|
|
let requestOption = await this.requestOption();
|
|
|
|
|
let response = await fetch(url, requestOption);
|
|
|
|
|
obj = await response.json();
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
nextPageToken: obj.nextPageToken || null,
|
|
|
|
|
curPageIndex: page_index,
|
|
|
|
|
data: obj,
|
|
|
|
|
};
|
2020-05-19 19:01:24 +08:00
|
|
|
|
|
2020-05-20 02:11:53 +08:00
|
|
|
|
/*do {
|
2020-05-19 19:01:24 +08:00
|
|
|
|
if (pageToken) {
|
|
|
|
|
params.pageToken = pageToken;
|
|
|
|
|
}
|
|
|
|
|
let url = 'https://www.googleapis.com/drive/v3/files';
|
|
|
|
|
url += '?' + this.enQuery(params);
|
|
|
|
|
let requestOption = await this.requestOption();
|
|
|
|
|
let response = await fetch(url, requestOption);
|
|
|
|
|
obj = await response.json();
|
|
|
|
|
files.push(...obj.files);
|
|
|
|
|
pageToken = obj.nextPageToken;
|
|
|
|
|
} while (pageToken);*/
|
2020-05-20 02:11:53 +08:00
|
|
|
|
}
|
2020-05-19 19:01:24 +08:00
|
|
|
|
|
2020-05-20 02:11:53 +08:00
|
|
|
|
async password(path) {
|
|
|
|
|
if (this.passwords[path] !== undefined) {
|
|
|
|
|
return this.passwords[path];
|
2020-05-19 19:01:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-05-20 02:11:53 +08:00
|
|
|
|
// console.log("load", path, ".password", this.passwords[path]);
|
2020-05-19 19:01:24 +08:00
|
|
|
|
|
2020-05-20 02:11:53 +08:00
|
|
|
|
let file = await this.file(path + ".password");
|
|
|
|
|
if (file == undefined) {
|
|
|
|
|
this.passwords[path] = null;
|
|
|
|
|
} else {
|
|
|
|
|
let url = `https://www.googleapis.com/drive/v3/files/${file.id}?alt=media`;
|
|
|
|
|
let requestOption = await this.requestOption();
|
|
|
|
|
let response = await this.fetch200(url, requestOption);
|
|
|
|
|
this.passwords[path] = await response.text();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this.passwords[path];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 通过 id 获取 share drive 信息
|
|
|
|
|
* @param any_id
|
|
|
|
|
* @returns {Promise<null|{id}|any>} 任何非正常情况都返回 null
|
|
|
|
|
*/
|
|
|
|
|
async getShareDriveObjById(any_id) {
|
|
|
|
|
if (!any_id) return null;
|
|
|
|
|
if ("string" !== typeof any_id) return null;
|
|
|
|
|
|
|
|
|
|
let url = `https://www.googleapis.com/drive/v3/drives/${any_id}`;
|
|
|
|
|
let requestOption = await this.requestOption();
|
|
|
|
|
let res = await fetch(url, requestOption);
|
|
|
|
|
let obj = await res.json();
|
|
|
|
|
if (obj && obj.id) return obj;
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 搜索
|
|
|
|
|
* @returns {Promise<{data: null, nextPageToken: null, curPageIndex: number}>}
|
|
|
|
|
*/
|
|
|
|
|
async search(origin_keyword, page_token = null, page_index = 0) {
|
|
|
|
|
const types = CONSTS.gd_root_type;
|
|
|
|
|
const is_user_drive = this.root_type === types.user_drive;
|
|
|
|
|
const is_share_drive = this.root_type === types.share_drive;
|
|
|
|
|
|
|
|
|
|
const empty_result = {
|
|
|
|
|
nextPageToken: null,
|
|
|
|
|
curPageIndex: page_index,
|
|
|
|
|
data: null,
|
|
|
|
|
};
|
2020-05-19 19:01:24 +08:00
|
|
|
|
|
2020-05-20 02:11:53 +08:00
|
|
|
|
if (!is_user_drive && !is_share_drive) {
|
|
|
|
|
return empty_result;
|
|
|
|
|
}
|
|
|
|
|
let keyword = FUNCS.formatSearchKeyword(origin_keyword);
|
|
|
|
|
if (!keyword) {
|
|
|
|
|
// 关键词为空,返回
|
|
|
|
|
return empty_result;
|
|
|
|
|
}
|
|
|
|
|
let words = keyword.split(/\s+/);
|
|
|
|
|
let name_search_str = `name contains '${words.join(
|
|
|
|
|
"' AND name contains '"
|
|
|
|
|
)}'`;
|
|
|
|
|
|
|
|
|
|
// corpora 为 user 是个人盘 ,为 drive 是团队盘。配合 driveId
|
|
|
|
|
let params = {};
|
|
|
|
|
if (is_user_drive) {
|
|
|
|
|
params.corpora = "user";
|
|
|
|
|
}
|
|
|
|
|
if (is_share_drive) {
|
|
|
|
|
params.corpora = "drive";
|
|
|
|
|
params.driveId = this.root.id;
|
|
|
|
|
// This parameter will only be effective until June 1, 2020. Afterwards shared drive items will be included in the results.
|
|
|
|
|
params.includeItemsFromAllDrives = true;
|
|
|
|
|
params.supportsAllDrives = true;
|
|
|
|
|
}
|
|
|
|
|
if (page_token) {
|
|
|
|
|
params.pageToken = page_token;
|
|
|
|
|
}
|
|
|
|
|
params.q = `trashed = false AND name !='.password' AND (${name_search_str})`;
|
|
|
|
|
params.fields =
|
|
|
|
|
"nextPageToken, files(id, name, mimeType, size , modifiedTime, thumbnailLink, description)";
|
|
|
|
|
params.pageSize = this.authConfig.search_result_list_page_size;
|
|
|
|
|
// params.orderBy = 'folder,name,modifiedTime desc';
|
|
|
|
|
|
|
|
|
|
let url = "https://www.googleapis.com/drive/v3/files";
|
|
|
|
|
url += "?" + this.enQuery(params);
|
|
|
|
|
// console.log(params)
|
|
|
|
|
let requestOption = await this.requestOption();
|
|
|
|
|
let response = await fetch(url, requestOption);
|
|
|
|
|
let res_obj = await response.json();
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
nextPageToken: res_obj.nextPageToken || null,
|
|
|
|
|
curPageIndex: page_index,
|
|
|
|
|
data: res_obj,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 一层一层的向上获取这个文件或文件夹的上级文件夹的 file 对象。注意:会很慢!!!
|
|
|
|
|
* 最多向上寻找到当前 gd 对象的根目录 (root id)
|
|
|
|
|
* 只考虑一条单独的向上链。
|
|
|
|
|
* 【注意】如果此id代表的项目不在目标gd盘下,那么此函数会返回null
|
|
|
|
|
*
|
|
|
|
|
* @param child_id
|
|
|
|
|
* @param contain_myself
|
|
|
|
|
* @returns {Promise<[]>}
|
|
|
|
|
*/
|
|
|
|
|
async findParentFilesRecursion(child_id, contain_myself = true) {
|
|
|
|
|
const gd = this;
|
|
|
|
|
const gd_root_id = gd.root.id;
|
|
|
|
|
const user_drive_real_root_id = authConfig.user_drive_real_root_id;
|
|
|
|
|
const is_user_drive = gd.root_type === CONSTS.gd_root_type.user_drive;
|
|
|
|
|
|
|
|
|
|
// 自下向上查询的终点目标id
|
|
|
|
|
const target_top_id = is_user_drive ? user_drive_real_root_id : gd_root_id;
|
|
|
|
|
const fields = CONSTS.default_file_fields;
|
|
|
|
|
|
|
|
|
|
// [{},{},...]
|
|
|
|
|
const parent_files = [];
|
|
|
|
|
let meet_top = false;
|
|
|
|
|
|
|
|
|
|
async function addItsFirstParent(file_obj) {
|
|
|
|
|
if (!file_obj) return;
|
|
|
|
|
if (!file_obj.parents) return;
|
|
|
|
|
if (file_obj.parents.length < 1) return;
|
|
|
|
|
|
|
|
|
|
// ['','',...]
|
|
|
|
|
let p_ids = file_obj.parents;
|
|
|
|
|
if (p_ids && p_ids.length > 0) {
|
|
|
|
|
// its first parent
|
|
|
|
|
const first_p_id = p_ids[0];
|
|
|
|
|
if (first_p_id === target_top_id) {
|
|
|
|
|
meet_top = true;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const p_file_obj = await gd.findItemById(first_p_id);
|
|
|
|
|
if (p_file_obj && p_file_obj.id) {
|
|
|
|
|
parent_files.push(p_file_obj);
|
|
|
|
|
await addItsFirstParent(p_file_obj);
|
2020-05-19 19:01:24 +08:00
|
|
|
|
}
|
2020-05-20 02:11:53 +08:00
|
|
|
|
}
|
2020-05-19 19:01:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-05-20 02:11:53 +08:00
|
|
|
|
const child_obj = await gd.findItemById(child_id);
|
|
|
|
|
if (contain_myself) {
|
|
|
|
|
parent_files.push(child_obj);
|
|
|
|
|
}
|
|
|
|
|
await addItsFirstParent(child_obj);
|
|
|
|
|
|
|
|
|
|
return meet_top ? parent_files : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取相对于本盘根目录的path
|
|
|
|
|
* @param child_id
|
|
|
|
|
* @returns {Promise<string>} 【注意】如果此id代表的项目不在目标gd盘下,那么此方法会返回空字符串""
|
|
|
|
|
*/
|
|
|
|
|
async findPathById(child_id) {
|
|
|
|
|
if (this.id_path_cache[child_id]) {
|
|
|
|
|
return this.id_path_cache[child_id];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const p_files = await this.findParentFilesRecursion(child_id);
|
|
|
|
|
if (!p_files || p_files.length < 1) return "";
|
|
|
|
|
|
|
|
|
|
let cache = [];
|
|
|
|
|
// 把查出来的每一级的path和id都缓存一下
|
|
|
|
|
p_files.forEach((value, idx) => {
|
|
|
|
|
const is_folder =
|
|
|
|
|
idx === 0 ? p_files[idx].mimeType === CONSTS.folder_mime_type : true;
|
|
|
|
|
let path =
|
|
|
|
|
"/" +
|
|
|
|
|
p_files
|
|
|
|
|
.slice(idx)
|
|
|
|
|
.map((it) => it.name)
|
|
|
|
|
.reverse()
|
|
|
|
|
.join("/");
|
|
|
|
|
if (is_folder) path += "/";
|
|
|
|
|
cache.push({ id: p_files[idx].id, path: path });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
cache.forEach((obj) => {
|
|
|
|
|
this.id_path_cache[obj.id] = obj.path;
|
|
|
|
|
this.paths[obj.path] = obj.id;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/*const is_folder = p_files[0].mimeType === CONSTS.folder_mime_type;
|
2020-05-19 19:01:24 +08:00
|
|
|
|
let path = '/' + p_files.map(it => it.name).reverse().join('/');
|
|
|
|
|
if (is_folder) path += '/';*/
|
|
|
|
|
|
2020-05-20 02:11:53 +08:00
|
|
|
|
return cache[0].path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 根据id获取file item
|
|
|
|
|
async findItemById(id) {
|
|
|
|
|
const is_user_drive = this.root_type === CONSTS.gd_root_type.user_drive;
|
|
|
|
|
let url = `https://www.googleapis.com/drive/v3/files/${id}?fields=${
|
|
|
|
|
CONSTS.default_file_fields
|
|
|
|
|
}${is_user_drive ? "" : "&supportsAllDrives=true"}`;
|
|
|
|
|
let requestOption = await this.requestOption();
|
|
|
|
|
let res = await fetch(url, requestOption);
|
|
|
|
|
return await res.json();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async findPathId(path) {
|
|
|
|
|
let c_path = "/";
|
|
|
|
|
let c_id = this.paths[c_path];
|
|
|
|
|
|
|
|
|
|
let arr = path.trim("/").split("/");
|
|
|
|
|
for (let name of arr) {
|
|
|
|
|
c_path += name + "/";
|
|
|
|
|
|
|
|
|
|
if (typeof this.paths[c_path] == "undefined") {
|
|
|
|
|
let id = await this._findDirId(c_id, name);
|
|
|
|
|
this.paths[c_path] = id;
|
|
|
|
|
}
|
2020-05-19 19:01:24 +08:00
|
|
|
|
|
2020-05-20 02:11:53 +08:00
|
|
|
|
c_id = this.paths[c_path];
|
|
|
|
|
if (c_id == undefined || c_id == null) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2020-05-19 19:01:24 +08:00
|
|
|
|
}
|
2020-05-20 02:11:53 +08:00
|
|
|
|
// console.log(this.paths);
|
|
|
|
|
return this.paths[path];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async _findDirId(parent, name) {
|
|
|
|
|
name = decodeURIComponent(name).replace(/\'/g, "\\'");
|
|
|
|
|
|
|
|
|
|
// console.log("_findDirId", parent, name);
|
|
|
|
|
|
|
|
|
|
if (parent == undefined) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let url = "https://www.googleapis.com/drive/v3/files";
|
|
|
|
|
let params = { includeItemsFromAllDrives: true, supportsAllDrives: true };
|
|
|
|
|
params.q = `'${parent}' in parents and mimeType = 'application/vnd.google-apps.folder' and name = '${name}' and trashed = false`;
|
|
|
|
|
params.fields = "nextPageToken, files(id, name, mimeType)";
|
|
|
|
|
url += "?" + this.enQuery(params);
|
|
|
|
|
let requestOption = await this.requestOption();
|
|
|
|
|
let response = await fetch(url, requestOption);
|
|
|
|
|
let obj = await response.json();
|
|
|
|
|
if (obj.files[0] == undefined) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return obj.files[0].id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async accessToken() {
|
|
|
|
|
console.log("accessToken");
|
|
|
|
|
if (
|
|
|
|
|
this.authConfig.expires == undefined ||
|
|
|
|
|
this.authConfig.expires < Date.now()
|
|
|
|
|
) {
|
|
|
|
|
const obj = await this.fetchAccessToken();
|
|
|
|
|
if (obj.access_token != undefined) {
|
|
|
|
|
this.authConfig.accessToken = obj.access_token;
|
|
|
|
|
this.authConfig.expires = Date.now() + 3500 * 1000;
|
|
|
|
|
}
|
2020-05-19 19:01:24 +08:00
|
|
|
|
}
|
2020-05-20 02:11:53 +08:00
|
|
|
|
return this.authConfig.accessToken;
|
|
|
|
|
}
|
2020-05-19 19:01:24 +08:00
|
|
|
|
|
2020-05-20 02:11:53 +08:00
|
|
|
|
async fetchAccessToken() {
|
|
|
|
|
console.log("fetchAccessToken");
|
|
|
|
|
const url = "https://www.googleapis.com/oauth2/v4/token";
|
|
|
|
|
const headers = {
|
|
|
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
|
|
|
};
|
|
|
|
|
const post_data = {
|
|
|
|
|
client_id: this.authConfig.client_id,
|
|
|
|
|
client_secret: this.authConfig.client_secret,
|
|
|
|
|
refresh_token: this.authConfig.refresh_token,
|
|
|
|
|
grant_type: "refresh_token",
|
|
|
|
|
};
|
2020-05-19 19:01:24 +08:00
|
|
|
|
|
2020-05-20 02:11:53 +08:00
|
|
|
|
let requestOption = {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: headers,
|
|
|
|
|
body: this.enQuery(post_data),
|
|
|
|
|
};
|
2020-05-19 19:01:24 +08:00
|
|
|
|
|
2020-05-20 02:11:53 +08:00
|
|
|
|
const response = await fetch(url, requestOption);
|
|
|
|
|
return await response.json();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fetch200(url, requestOption) {
|
|
|
|
|
let response;
|
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
|
|
|
response = await fetch(url, requestOption);
|
|
|
|
|
console.log(response.status);
|
|
|
|
|
if (response.status != 403) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
await this.sleep(800 * (i + 1));
|
|
|
|
|
}
|
|
|
|
|
return response;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async requestOption(headers = {}, method = "GET") {
|
|
|
|
|
const accessToken = await this.accessToken();
|
|
|
|
|
headers["authorization"] = "Bearer " + accessToken;
|
|
|
|
|
return { method: method, headers: headers };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enQuery(data) {
|
|
|
|
|
const ret = [];
|
|
|
|
|
for (let d in data) {
|
|
|
|
|
ret.push(encodeURIComponent(d) + "=" + encodeURIComponent(data[d]));
|
|
|
|
|
}
|
|
|
|
|
return ret.join("&");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sleep(ms) {
|
2020-08-29 19:00:27 +08:00
|
|
|
|
return new Promise(function(resolve, reject) {
|
2020-05-20 02:11:53 +08:00
|
|
|
|
let i = 0;
|
2020-08-29 19:00:27 +08:00
|
|
|
|
setTimeout(function() {
|
2020-05-20 02:11:53 +08:00
|
|
|
|
console.log("sleep" + ms);
|
|
|
|
|
i++;
|
|
|
|
|
if (i >= 2) reject(new Error("i>=2"));
|
|
|
|
|
else resolve(i);
|
|
|
|
|
}, ms);
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-05-19 19:01:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-29 19:00:27 +08:00
|
|
|
|
String.prototype.trim = function(char) {
|
2020-05-20 02:11:53 +08:00
|
|
|
|
if (char) {
|
|
|
|
|
return this.replace(
|
|
|
|
|
new RegExp("^\\" + char + "+|\\" + char + "+$", "g"),
|
|
|
|
|
""
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return this.replace(/^\s+|\s+$/g, "");
|
2020-05-19 19:01:24 +08:00
|
|
|
|
};
|