Merge branch 'main' into feature/alicloud-qr-login-v2

This commit is contained in:
Pikachu Ren 2025-06-15 09:59:12 +08:00 committed by GitHub
commit a4b0c83b69
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 266 additions and 99 deletions

View File

@ -8,5 +8,7 @@
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="bootstrap" level="application" />
<orderEntry type="library" name="sweetalert2" level="application" />
</component> </component>
</module> </module>

17
.idea/workspace.xml generated
View File

@ -30,6 +30,18 @@
<component name="Git.Settings"> <component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" /> <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component> </component>
<component name="GitHubPullRequestSearchHistory"><![CDATA[{
"lastFilter": {
"state": "OPEN",
"assignee": "xiaoman1221"
}
}]]></component>
<component name="GithubPullRequestsUISettings"><![CDATA[{
"selectedUrlAndAccountId": {
"url": "git@github.com:xiaoman1221/cf-worker-api.git",
"accountId": "dc84280e-17eb-471f-bdb9-c542ed6bcde0"
}
}]]></component>
<component name="ProblemsViewState"> <component name="ProblemsViewState">
<option name="selectedTabId" value="CurrentFile" /> <option name="selectedTabId" value="CurrentFile" />
</component> </component>
@ -54,7 +66,7 @@
"node.js.selected.package.tslint": "(autodetect)", "node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm", "nodejs_package_manager_path": "npm",
"settings.editor.selected.configurable": "preferences.language.and.region", "settings.editor.selected.configurable": "preferences.language.and.region",
"ts.external.directory.path": "C:\\Users\\pikachuren\\AppData\\Local\\Programs\\WebStorm\\plugins\\javascript-plugin\\jsLanguageServicesImpl\\external", "ts.external.directory.path": "/Users/xiaoman1221/Applications/WebStorm.app/Contents/plugins/javascript-plugin/jsLanguageServicesImpl/external",
"vue.rearranger.settings.migration": "true" "vue.rearranger.settings.migration": "true"
} }
}]]></component> }]]></component>
@ -84,7 +96,8 @@
<updated>1749696640494</updated> <updated>1749696640494</updated>
<workItem from="1749696641557" duration="119000" /> <workItem from="1749696641557" duration="119000" />
<workItem from="1749696773818" duration="18000" /> <workItem from="1749696773818" duration="18000" />
<workItem from="1749696801474" duration="56160000" /> <workItem from="1749696801474" duration="20247000" />
<workItem from="1749729820615" duration="1140000" />
</task> </task>
<task id="LOCAL-00001" summary="fin onedrive official token"> <task id="LOCAL-00001" summary="fin onedrive official token">
<option name="closed" value="true" /> <option name="closed" value="true" />

View File

@ -1,11 +1,11 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh-CN" data-theme="light"> <html lang="zh-CN" data-theme="light" xmlns="http://www.w3.org/1999/html">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OpenList Token 获取工具</title> <title>OpenList Token 获取工具</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://jsd.tencent.to/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script> <script src="https://jsd.tencent.to/npm/sweetalert2@11.22.0/dist/sweetalert2.all.min.js"></script>
<style> <style>
:root { :root {
--bg-color-light: #ffffff; --bg-color-light: #ffffff;
@ -228,26 +228,27 @@
<option value="115cloud_go">115 网盘 验证登录</option> <option value="115cloud_go">115 网盘 验证登录</option>
<option value="123cloud_go">123 网盘 直接登录</option> <option value="123cloud_go">123 网盘 直接登录</option>
<option value="googleui_go">Google Drive Team</option> <option value="googleui_go">Google Drive Team</option>
<option value="yandex_go">Yandex Drive</option>
</select> </select>
</div> </div>
<div class="mb-3" style="margin-top: 15px"> <div class="mb-3" style="margin-top: 15px">
<input type="checkbox" id="server_use" class="form-check-input"> <input type="checkbox" id="server_use" class="form-check-input">
<label for="server_use" class="form-check-label">使用 OpenList 提供的API</label> <label for="server_use" class="form-check-label">使用 OpenList 提供的参数</label>
</div> </div>
<div class="mb-3"> <div class="mb-3" id="client-id-view">
<label for="client-id" class="form-label">客户端ID</label> <label for="client-id" class="form-label">Client ID客户端ID</label>
<input type="text" id="client-id" class="form-control"> <input type="text" id="client-id" class="form-control">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="app-secret" class="form-label">应用机密</label> <label for="app-secret" class="form-label">AppKey应用秘钥</label>
<input type="text" id="app-secret" class="form-control"> <input type="text" id="app-secret" class="form-control">
</div> </div>
<div class="mb-3" id="secret-key-view"> <div class="mb-3" id="secret-key-view">
<label for="secret-key" class="form-label">访问密钥(SecretKey</label> <label for="secret-key" class="form-label">SecretKey(访问秘钥</label>
<input type="text" id="secret-key" class="form-control"> <input type="text" id="secret-key" class="form-control">
</div> </div>
@ -271,7 +272,17 @@
<label for="refresh-token" class="form-label">刷新秘钥</label> <label for="refresh-token" class="form-label">刷新秘钥</label>
<textarea id="refresh-token" class="form-control" rows="3" readonly onclick="autoCopy(this)"></textarea> <textarea id="refresh-token" class="form-control" rows="3" readonly onclick="autoCopy(this)"></textarea>
</div> </div>
<div class="mb-3">
<label for="sharepoint-url" class="form-label">SharePoint Site URL</label>
<label for="sharepoint-url"></label><input type="text" id="sharepoint-url" class="form-control">
</div>
<div class="d-grid gap-2 mb-3">
<button class="btn btn-primary" onclick="getSiteID()">获取 SharePoint 站点ID</button>
</div>
<div class="mb-3">
<label for="sharepoint-id" class="form-label">SharePoint Site URL</label>
<textarea id="sharepoint-id" class="form-control" rows="3" readonly onclick="autoCopy(this)"></textarea>
</div>
<div class="text-muted text-center"> <div class="text-muted text-center">
<p style="text-align:center"> <p style="text-align:center">
本工具所有信息只以Cookie形式存储于浏览器本地<br> 本工具所有信息只以Cookie形式存储于浏览器本地<br>
@ -282,6 +293,7 @@
</div> </div>
</div> </div>
<!-- 阿里云盘扫码v2模态框 --> <!-- 阿里云盘扫码v2模态框 -->
<div id="qr-modal" class="qr-modal"> <div id="qr-modal" class="qr-modal">
<div class="qr-modal-content"> <div class="qr-modal-content">
@ -297,7 +309,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<script> <script>
let intervalId; let intervalId;
@ -316,7 +328,7 @@
position: 'top', position: 'top',
icon: 'info', icon: 'info',
title: '获取失败', title: '获取失败',
text: '请先填写客户端ID和应用机密', text: '请先填写AppID和AppKey',
showConfirmButton: true, showConfirmButton: true,
}); });
return; return;
@ -340,7 +352,7 @@
const response_data = await response.json(); const response_data = await response.json();
if (response.status === 200) { if (response.status === 200) {
if (apps_subs === "onedrive" || apps_subs === "115cloud" if (apps_subs === "onedrive" || apps_subs === "115cloud"
|| apps_subs === "baiduyun" || apps_subs === "googleui") { || apps_subs === "baiduyun" || apps_subs === "googleui" || apps_subs === "yandex") {
window.location.href = response_data.text; window.location.href = response_data.text;
} }
if (apps_subs === "123cloud") { if (apps_subs === "123cloud") {
@ -483,6 +495,9 @@
if (siteSelect.value === "baiduyun_go") { if (siteSelect.value === "baiduyun_go") {
document.getElementById('secret-key-view').hidden = false; document.getElementById('secret-key-view').hidden = false;
document.getElementById('client-id-view').hidden = true;
}else{
document.getElementById('client-id-view').hidden = false;
} }
}); });
@ -503,7 +518,7 @@
position: 'top', position: 'top',
icon: 'error', icon: 'error',
title: '暂不支持', title: '暂不支持',
html: "阿里云、123云盘、OneDrive非官方区域暂不支持使用官方密钥", html: "阿里云、123云盘、OneDrive非美国区域暂不支持使用官方密钥",
showConfirmButton: true, showConfirmButton: true,
}); });
return; return;
@ -540,6 +555,113 @@
} }
} }
// 获取站点ID
function getSiteID() {
const siteUrl = document.getElementById("sharepoint-url").value.trim();
const access_token = document.getElementById("access-token").value.trim();
const refresh_token = document.getElementById("refresh-token").value.trim();
const client_uid = document.getElementById("client-id").value.trim();
const client_key = document.getElementById("app-secret").value.trim();
const site_type = document.getElementById("site-select").value;
const idElement = document.getElementById("sharepoint-id");
// 定义站点的API Endpoint
const GATEWAYS = {
"onedrive_go": "https://graph.microsoft.com/v1.0/sites/",
"onedrive_cn": "https://microsoftgraph.chinacloudapi.cn/v1.0/sites/",
"onedrive_us": "https://graph.microsoft.us/v1.0/sites/",
"onedrive_de": "https://graph.microsoft.de/v1.0/sites/"
};
// 定义错误信息
const ERROR_MESSAGES = {
MISSING_CREDENTIALS: "请先填写客户端ID和应用机密",
MISSING_TOKENS: "请获取Token",
MISSING_URL: "请填写您的SharePoint URL",
NOT_SUPPORTED: "仅支持OneDrive相关API",
NOT_FOUND: "站点不存在",
BAD_REQUEST: "获取出现问题请检查权限和站点URL站点URL示例https://demo.sharepoint.com/site/demo",
DEFAULT: "请求发生错误"
};
// 验证
if (!client_uid || !client_key) {
idElement.value = ERROR_MESSAGES.MISSING_CREDENTIALS;
return;
}
if (!access_token || !refresh_token) {
idElement.value = ERROR_MESSAGES.MISSING_TOKENS;
return;
}
if (!siteUrl) {
idElement.value = ERROR_MESSAGES.MISSING_URL;
return;
}
if (!site_type.includes("onedrive")) {
idElement.value = ERROR_MESSAGES.NOT_SUPPORTED;
return;
}
if (!GATEWAYS[site_type]) {
idElement.value = ERROR_MESSAGES.DEFAULT;
return;
}
// 获取ID
try {
const urlParts = siteUrl.replace("https://", "").split("/");
const site_hostname = urlParts[0];
const site_sub_path = urlParts[1];
const site_name = urlParts[2];
const site_path = site_sub_path + "/" +site_name;
const reqUrl = `${GATEWAYS[site_type]}${site_hostname}:/${site_path}`;
const headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": `Bearer ${access_token}`
};
fetch(reqUrl, {
method: "GET",
headers: headers
})
.then(async (res) => {
if (!res.ok) {
if (res.status === 404) {
idElement.value = ERROR_MESSAGES.NOT_FOUND;
return;
} else if (res.status === 400) {
idElement.value = ERROR_MESSAGES.BAD_REQUEST;
return;
} else {
idElement.value = `${ERROR_MESSAGES.DEFAULT} (HTTP ${res.status})`;
return;
}
}
try {
const result = await res.json();
if (result.id) {
idElement.value = result.id;
} else if (result.error) {
idElement.value = result.error.message || ERROR_MESSAGES.DEFAULT;
} else {
idElement.value = ERROR_MESSAGES.DEFAULT;
}
} catch (error) {
idElement.value = ERROR_MESSAGES.DEFAULT;
console.error("处理响应时出错:", error);
}
})
.catch((error) => {
idElement.value = ERROR_MESSAGES.BAD_REQUEST;
console.error("请求失败:", error);
});
} catch (error) {
idElement.value = ERROR_MESSAGES.BAD_REQUEST;
console.error("URL解析失败:", error);
}
}
//手动切换主题模式 //手动切换主题模式
function toggleTheme() { function toggleTheme() {
const html = document.documentElement; const html = document.documentElement;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

View File

@ -1,58 +0,0 @@
/* 手动设置字体链接 */
@font-face {
font-family: 'MapleMonoNL';
src: url('/static/maple-min.ttf');
}
* {
font-family: MapleMonoNL, sans-serif;
}
body {
margin: 0;
padding: 0;
font-family: MapleMonoNL, sans-serif;
background: url('/static/cdn-image.jpg') no-repeat center center fixed;
background-size: cover;
}
.container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.form-container {
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
padding: 30px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
max-width: 400px;
}
h1 {
text-align: center;
color: #333;
margin-bottom: 20px;
}
.input-group {
margin-bottom: 20px;
}
.input-group label {
display: block;
margin-bottom: 5px;
color: #555;
}
.input-group input, .input-group select,.input-group textarea{
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 10px; /* 修改圆角为10px */
box-sizing: border-box;
}

View File

@ -10,17 +10,15 @@ const driver_map: string[] = [
// 登录申请 ############################################################################## // 登录申请 ##############################################################################
export async function oneLogin(c: Context) { export async function oneLogin(c: Context) {
const client_uid: string = <string>c.req.query('client_uid');
const client_key: string = <string>c.req.query('client_key'); const client_key: string = <string>c.req.query('client_key');
const secret_key: string = <string>c.req.query('secret_key'); const secret_key: string = <string>c.req.query('secret_key');
const driver_txt: string = <string>c.req.query('apps_types'); const driver_txt: string = <string>c.req.query('apps_types');
const server_use: string = <string>c.req.query('server_use'); const server_use: string = <string>c.req.query('server_use');
if (server_use == "false" && (!driver_txt || !client_uid || !client_key || !secret_key)) if (server_use == "false" && (!driver_txt || !client_key || !secret_key))
return c.json({text: "参数缺少"}, 500); return c.json({text: "参数缺少"}, 500);
// 请求参数 ========================================================================== // 请求参数 ==========================================================================
const params_all: Record<string, any> = { const params_all: Record<string, any> = {
client_id: server_use == "true" ? c.env.baiduyun_key : client_key, client_id: server_use == "true" ? c.env.baiduyun_key : client_key,
device_id: server_use == "true" ? c.env.baiduyun_uid : client_uid,
scope: "basic,netdisk", scope: "basic,netdisk",
response_type: 'code', response_type: 'code',
redirect_uri: 'https://' + c.env.MAIN_URLS + '/baiduyun/callback' redirect_uri: 'https://' + c.env.MAIN_URLS + '/baiduyun/callback'
@ -35,7 +33,6 @@ export async function oneLogin(c: Context) {
method: 'GET', method: 'GET',
}); });
if (server_use == "false") { if (server_use == "false") {
local.setCookie(c, 'client_uid', client_uid);
local.setCookie(c, 'client_key', client_key); local.setCookie(c, 'client_key', client_key);
local.setCookie(c, 'secret_key', secret_key); local.setCookie(c, 'secret_key', secret_key);
} }
@ -50,18 +47,17 @@ export async function oneLogin(c: Context) {
// 令牌申请 ############################################################################## // 令牌申请 ##############################################################################
export async function oneToken(c: Context) { export async function oneToken(c: Context) {
let login_data, client_uid, client_key, secret_key, client_url; let login_data, client_key, secret_key, client_url;
let driver_txt, server_use, params_all: Record<string, any>; let driver_txt, server_use, params_all: Record<string, any>;
try { // 请求参数 ==================================================================== try { // 请求参数 ====================================================================
login_data = c.req.query('code'); login_data = c.req.query('code');
server_use = local.getCookie(c, 'server_use') server_use = local.getCookie(c, 'server_use')
driver_txt = local.getCookie(c, 'driver_txt') driver_txt = local.getCookie(c, 'driver_txt')
client_uid = client_key = secret_key = "" client_key = secret_key = ""
if (server_use == "false") { if (server_use == "false") {
client_uid = local.getCookie(c, 'client_uid')
client_key = local.getCookie(c, 'client_key') client_key = local.getCookie(c, 'client_key')
secret_key = local.getCookie(c, 'secret_key') secret_key = local.getCookie(c, 'secret_key')
if (!login_data || !client_uid || !client_key || !secret_key) if (!login_data || !client_key || !secret_key)
return c.redirect(showErr("Cookie缺少", "", "")); return c.redirect(showErr("Cookie缺少", "", ""));
} }
@ -87,7 +83,6 @@ export async function oneToken(c: Context) {
}); });
const response: Response = await fetch(urlWithParams, {method: 'GET'}); const response: Response = await fetch(urlWithParams, {method: 'GET'});
if (server_use == "false") { if (server_use == "false") {
local.deleteCookie(c, 'client_uid');
local.deleteCookie(c, 'client_key'); local.deleteCookie(c, 'client_key');
local.deleteCookie(c, 'secret_key'); local.deleteCookie(c, 'secret_key');
} }
@ -99,15 +94,14 @@ export async function oneToken(c: Context) {
return c.redirect( return c.redirect(
`/?access_token=${json.access_token}` `/?access_token=${json.access_token}`
+ `&refresh_token=${json.refresh_token}` + `&refresh_token=${json.refresh_token}`
+ `&client_uid=${client_uid}`
+ `&client_key=${server_use == "true" ? "" : client_key}` + `&client_key=${server_use == "true" ? "" : client_key}`
+ `&secret_key=${server_use == "true" ? "" : secret_key}` + `&secret_key=${server_use == "true" ? "" : secret_key}`
+ `&driver_txt=${driver_txt}` + `&driver_txt=${driver_txt}`
); );
} }
return c.redirect(showErr(json.error_description, client_uid, client_key)); return c.redirect(showErr(json.error_description, client_key));
} catch (error) { } catch (error) {
return c.redirect(showErr(<string>error, client_uid, client_key)); return c.redirect(showErr(<string>error, client_key));
} }
} }

View File

@ -10,6 +10,7 @@ import * as ui115 from './115ui';
import * as ui123 from './123ui'; import * as ui123 from './123ui';
import * as baidu from './baidu'; import * as baidu from './baidu';
import * as goapi from './goapi'; import * as goapi from './goapi';
import * as yandex from './yandex';
export type Bindings = { export type Bindings = {
MAIN_URLS: string, baiduyun_ext: string, MAIN_URLS: string, baiduyun_ext: string,
@ -18,7 +19,9 @@ export type Bindings = {
baiduyun_uid: string, baiduyun_key: string, baiduyun_uid: string, baiduyun_key: string,
cloud115_uid: string, cloud115_key: string, cloud115_uid: string, cloud115_key: string,
googleui_uid: string, googleui_key: string, googleui_uid: string, googleui_key: string,
YANDEX_CLIENT_ID: string, YANDEX_CLIENT_SECRET: string,
} }
const app = new Hono<{ Bindings: Bindings }>() const app = new Hono<{ Bindings: Bindings }>()
app.use("*", serveStatic({manifest: manifest, root: "./"})); app.use("*", serveStatic({manifest: manifest, root: "./"}));
@ -101,5 +104,8 @@ app.get('/googleui/callback', async (c: Context) => {
return goapi.oneToken(c); return goapi.oneToken(c);
}); });
app.get('/yandex/requests', async (c: Context) => {return yandex.yandexLogin(c)});
app.get('/yandex/callback', async (c: Context) => {return yandex.yandexCallBack(c)});
export default app export default app

101
src/yandex.ts Normal file
View File

@ -0,0 +1,101 @@
import {Context} from "hono";
import {showErr} from "./error";
import * as local from "hono/cookie";
interface Token {
token_type?: string;
access_token?: string;
expires_in?: number;
refresh_token?: string;
scope?: string;
error?: string;
error_description?: string;
}
export async function yandexLogin(c: Context) {
const env = c.env
const client_uid: string = <string>c.req.query('client_uid');
const client_key: string = <string>c.req.query('client_key');
const server_use: string = <string>c.req.query('server_use');
if (server_use == "false" && (!client_uid || !client_key))
return c.json({text: "参数缺少"}, 500);
const params_all: Record<string, any> = {
response_type: 'code',
client_id: server_use == "true" ? env.YANDEX_CLIENT_ID : client_uid,
};
if (server_use == "false") {
local.setCookie(c, 'client_uid', client_uid);
local.setCookie(c, 'client_key', client_key);
}
local.setCookie(c, 'server_use', server_use);
const urlWithParams = new URL("https://oauth.yandex.com/authorize");
Object.keys(params_all).forEach(key => {
urlWithParams.searchParams.append(key, params_all[key]);
});
try {
return c.json({text: urlWithParams.href}, 200);
} catch (error) {
return c.json({text: error}, 500);
}
}
export async function yandexCallBack(c: Context) {
const env = c.env;
const code = <string>c.req.query("code");
const error = <string>c.req.query("error");
const error_description = <string>c.req.query("error_description");
const getToken = async (): Promise<Token> => {
const params = new URLSearchParams();
params.append("grant_type", "authorization_code");
params.append("client_id", env.YANDEX_CLIENT_ID);
params.append("client_secret", env.YANDEX_CLIENT_SECRET);
params.append("code", code);
const resp = await fetch("https://oauth.yandex.com/token", {
method: "POST",
body: params,
});
if (!resp.ok) {
throw new Error(`HTTP ${resp.status}: ${resp.statusText}`);
}
return await resp.json();
};
if (error) {
return c.redirect(showErr(error_description || error, "", ""));
}
if (!code) {
return c.redirect(showErr("Authorization code missing", "", ""));
}
try {
const token: Token = await getToken();
console.log("Yandex token response:", token);
if (!token.error && token.access_token) {
const server_use = local.getCookie(c, 'server_use');
const client_uid = local.getCookie(c, 'client_uid');
const client_key = local.getCookie(c, 'client_key');
local.deleteCookie(c, 'server_use');
local.deleteCookie(c, 'client_uid');
local.deleteCookie(c, 'client_key');
return c.redirect(
`/?access_token=${token.access_token}`
+ `&refresh_token=${token.refresh_token}`
+ `&client_uid=${server_use == "true" ? "" : client_uid || ""}`
+ `&client_key=${server_use == "true" ? "" : client_key || ""}`
+ `&driver_txt=yandex_go`
);
} else {
return c.redirect(showErr(token.error_description || token.error || "Token request failed", "", ""));
}
} catch (error) {
console.error("Token request error:", error);
return c.redirect(showErr("Failed to get access token", "", ""));
}
}

View File

@ -14,6 +14,7 @@
"alicloud_key": "", "alicloud_key": "",
"baiduyun_uid": "", "baiduyun_uid": "",
"baiduyun_key": "", "baiduyun_key": "",
"baiduyun_ext": "",
"115cloud_uid": "", "115cloud_uid": "",
"115cloud_key": "", "115cloud_key": "",
"googleui_uid": "", "googleui_uid": "",