修复cf pages无法使用

This commit is contained in:
iLay 2024-12-08 08:16:12 +08:00
parent 815a2542ef
commit 2b83ecd8dd
8 changed files with 134 additions and 126 deletions

17
package-lock.json generated
View File

@ -11,13 +11,12 @@
"@nuxtjs/tailwindcss": "^6.12.2",
"ant-design-vue": "^4.2.6",
"clipboard": "^2.0.11",
"crypto-js": "^4.2.0",
"crypto-es": "^2.1.0",
"nuxt": "^3.14.1592",
"vue": "latest",
"vue-router": "latest"
},
"devDependencies": {
"@types/crypto-js": "^4.2.2",
"nitropack": "^2.10.4",
"sass-embedded": "^1.82.0"
}
@ -2514,12 +2513,6 @@
"node": ">=10.13.0"
}
},
"node_modules/@types/crypto-js": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz",
"integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==",
"dev": true
},
"node_modules/@types/estree": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
@ -3900,10 +3893,10 @@
"uncrypto": "^0.1.3"
}
},
"node_modules/crypto-js": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
"node_modules/crypto-es": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/crypto-es/-/crypto-es-2.1.0.tgz",
"integrity": "sha512-C5Dbuv4QTPGuloy5c5Vv/FZHtmK+lobLAypFfuRaBbwCsk3qbCWWESCH3MUcBsrgXloRNMrzwUAiPg4U6+IaKA=="
},
"node_modules/css-declaration-sorter": {
"version": "7.2.0",

View File

@ -7,20 +7,20 @@
"dev": "nuxt dev",
"start": "nuxt start",
"generate": "nuxt generate",
"postinstall": "nuxt prepare"
"postinstall": "nuxt prepare",
"cf-dev": "npx nuxi build --preset=cloudflare_pages && npx wrangler pages dev dist/"
},
"dependencies": {
"@ant-design-vue/nuxt": "^1.4.6",
"@nuxtjs/tailwindcss": "^6.12.2",
"ant-design-vue": "^4.2.6",
"clipboard": "^2.0.11",
"crypto-js": "^4.2.0",
"crypto-es": "^2.1.0",
"nuxt": "^3.14.1592",
"vue": "latest",
"vue-router": "latest"
},
"devDependencies": {
"@types/crypto-js": "^4.2.2",
"nitropack": "^2.10.4",
"sass-embedded": "^1.82.0"
}

View File

@ -4,13 +4,11 @@
<div class="mx-auto w-full max-w-3xl bg-white shadow-lg rounded-lg p-8">
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-800">阿里云盘连接</h1>
<a href="https://ghcr.io/ilay1678/alipan-tv-token"
target="_blank"
rel="noopener noreferrer"
class="text-gray-600 hover:text-blue-500 transition-colors"
title="Docker Image">
<a href="https://ghcr.io/ilay1678/alipan-tv-token" target="_blank" rel="noopener noreferrer"
class="text-gray-600 hover:text-blue-500 transition-colors" title="Docker Image">
<svg class="h-6 w-6" viewBox="0 0 24 24" fill="currentColor">
<path d="M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.376 11.376 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 003.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288z"/>
<path
d="M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.376 11.376 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 003.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288z" />
</svg>
</a>
</div>
@ -18,25 +16,17 @@
<div class="space-y-8">
<div class="space-y-2">
<div class="relative">
<textarea
id="accessToken"
v-model="accessToken"
<textarea id="accessToken" v-model="accessToken"
class="w-full rounded font-mono text-sm leading-normal border-2 border-dashed border-gray-300 p-3 pr-10 bg-white resize-none focus:outline-none focus:border-blue-500 transition-colors min-h-[120px] whitespace-pre-wrap overflow-auto placeholder:text-gray-400"
readonly
spellcheck="false"
placeholder="访问令牌"
/>
<button
data-clipboard-target="#accessToken"
:class="`absolute top-2 right-2 p-1 rounded transition-colors ${
hasAccessToken
? 'hover:bg-gray-100 text-gray-500 hover:text-blue-500'
: 'text-gray-300 cursor-not-allowed'
}`"
:disabled="!hasAccessToken"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
readonly spellcheck="false" placeholder="访问令牌" />
<button data-clipboard-target="#accessToken" :class="`absolute top-2 right-2 p-1 rounded transition-colors ${hasAccessToken
? 'hover:bg-gray-100 text-gray-500 hover:text-blue-500'
: 'text-gray-300 cursor-not-allowed'
}`" :disabled="!hasAccessToken">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg>
</button>
</div>
@ -44,25 +34,17 @@
<div class="space-y-2">
<div class="relative">
<textarea
id="refreshToken"
v-model="refreshToken"
<textarea id="refreshToken" v-model="refreshToken"
class="w-full rounded font-mono text-sm leading-normal border-2 border-dashed border-gray-300 p-3 pr-10 bg-white resize-none focus:outline-none focus:border-blue-500 transition-colors min-h-[120px] whitespace-pre-wrap overflow-auto placeholder:text-gray-400"
readonly
spellcheck="false"
placeholder="刷新令牌"
/>
<button
data-clipboard-target="#refreshToken"
:class="`absolute top-2 right-2 p-1 rounded transition-colors ${
hasRefreshToken
? 'hover:bg-gray-100 text-gray-500 hover:text-blue-500'
: 'text-gray-300 cursor-not-allowed'
}`"
:disabled="!hasRefreshToken"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
readonly spellcheck="false" placeholder="刷新令牌" />
<button data-clipboard-target="#refreshToken" :class="`absolute top-2 right-2 p-1 rounded transition-colors ${hasRefreshToken
? 'hover:bg-gray-100 text-gray-500 hover:text-blue-500'
: 'text-gray-300 cursor-not-allowed'
}`" :disabled="!hasRefreshToken">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg>
</button>
</div>
@ -73,18 +55,15 @@
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
<span class="ml-3 text-gray-600">正在获取授权链接...</span>
</div>
<button
v-else
@click="handleAuth(authUrl)"
:disabled="authorizing"
:class="`block w-full bg-blue-500 text-white text-center py-3 px-4 rounded transition-colors relative ${
authorizing ? 'bg-blue-400 cursor-not-allowed' : 'hover:bg-blue-600'
}`"
>
<button v-else @click="handleAuth(authUrl)" :disabled="authorizing" :class="`block w-full bg-blue-500 text-white text-center py-3 px-4 rounded transition-colors relative ${authorizing ? 'bg-blue-400 cursor-not-allowed' : 'hover:bg-blue-600'
}`">
<div class="flex items-center justify-center">
<div v-if="authorizing" class="animate-spin rounded-full h-5 w-5 border-2 border-white border-t-transparent mr-2"></div>
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1" />
<div v-if="authorizing"
class="animate-spin rounded-full h-5 w-5 border-2 border-white border-t-transparent mr-2"></div>
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1" />
</svg>
{{ authorizing ? '授权中...' : '授权登录' }}
</div>
@ -94,14 +73,8 @@
</div>
</main>
<a-modal
v-model:open="isNoticeOpen"
title="使用说明"
@ok="closeNotice"
:maskClosable="false"
:closable="false"
:keyboard="false"
>
<a-modal v-model:open="isNoticeOpen" title="使用说明" @ok="closeNotice" :maskClosable="false" :closable="false"
:keyboard="false">
<p>本工具能帮助你一键获取阿里云盘TV版的刷新令牌完全免费TV接口能绕过三方应用权益包的速率限制但前提你得是SVIP</p>
<template #footer>
<a-button type="primary" @click="closeNotice">我知道了</a-button>
@ -110,7 +83,7 @@
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import { ref, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import ClipboardJS from 'clipboard'
@ -127,8 +100,7 @@ const refreshToken = ref('')
async function generateAuthUrl() {
try {
isLoading.value = true
const response = await fetch("/generate_qr", { method: "POST" })
const data = await response.json()
const data = await $fetch("/generate_qr", { method: "POST" })
authUrl.value = `https://www.alipan.com/o/oauth/authorize?sid=${data.sid}`
checkStatus(data.sid)
} finally {
@ -142,8 +114,7 @@ function closeNotice() {
async function checkStatus(sid) {
try {
const response = await fetch("/check_status/" + sid)
const data = await response.json()
const data = await $fetch("/check_status/" + sid)
if (data.status === "LoginSuccess") {
accessToken.value = data.access_token
refreshToken.value = data.refresh_token
@ -191,6 +162,7 @@ const handleAuth = (url) => {
}
onMounted(() => {
//
isNoticeOpen.value = true
if (!hasGenerated.value) {
generateAuthUrl()
@ -200,6 +172,4 @@ onMounted(() => {
</script>
<style scoped>
</style>
<style scoped></style>

View File

@ -1,32 +1,32 @@
import { decrypt, getParams } from '@/utils/decode';
import { defineEventHandler, getRouterParams } from 'h3'
import type { QrCodeStatus, TokenResponseEncrypt, TokenRequest } from '~/types/api'
export const runtime = 'edge'
export default defineEventHandler(async (event) => {
try {
const { sid } = getRouterParams(event);
const response = await fetch(`https://openapi.alipan.com/oauth/qrcode/${sid}/status`);
const statusData:any = await response.json();
if (statusData.status === 'LoginSuccess') {
const statusData = await $fetch<QrCodeStatus>(`https://openapi.alipan.com/oauth/qrcode/${sid}/status`);
if (statusData.status === 'LoginSuccess' && statusData.authCode) {
try {
const authCode = statusData.authCode;
const t = Math.floor(Date.now() / 1000);
const sendData = { ...getParams(t), code: authCode, "Content-Type": "application/json"};
const sendData = {
...getParams(t),
code: statusData.authCode,
"Content-Type": "application/json"
} as TokenRequest;
const headers = Object.fromEntries(
Object.entries(sendData).map(([k, v]) => [k, String(v)])
);
const tokenResponse = await fetch('http://api.extscreen.com/aliyundrive/v3/token', {
const tokenResponse = await $fetch<TokenResponseEncrypt>('http://api.extscreen.com/aliyundrive/v3/token', {
method: 'POST',
headers: headers,
body: JSON.stringify(sendData)
});
const tokenData:any = await tokenResponse.json();
const jsonp = tokenData.data;
const plainData = decrypt(jsonp.ciphertext, jsonp.iv, t);
const plainData = decrypt(tokenResponse.data.ciphertext, tokenResponse.data.iv, t);
const tokenInfo = JSON.parse(plainData);
return {
@ -34,13 +34,14 @@ export default defineEventHandler(async (event) => {
refresh_token: tokenInfo.refresh_token,
access_token: tokenInfo.access_token
};
} catch (error) {
return { status: 'LoginFailed' };
return { status: 'LoginFailed' } as QrCodeStatus;
}
} else {
return { status: statusData.status };
}
} catch (error:any) {
return statusData;
} catch (error: any) {
throw createError({
statusCode: 500,
message: error.message

View File

@ -1,8 +1,8 @@
import { defineEventHandler } from 'h3'
import type { ApiResponse, QrCodeData } from '~/types/api'
export default defineEventHandler(async (event) => {
try {
const response = await fetch('http://api.extscreen.com/aliyundrive/qrcode', {
const response = await $fetch<ApiResponse<QrCodeData>>('http://api.extscreen.com/aliyundrive/qrcode', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@ -11,10 +11,9 @@ export default defineEventHandler(async (event) => {
height: 500,
})
})
const data:any = await response.json()
return {
qr_link: data.data.qrCodeUrl,
sid: data.data.sid
qr_link: response.data.qrCodeUrl,
sid: response.data.sid
}
} catch (error: any) {
throw createError({

View File

@ -1,5 +1,6 @@
import { decrypt, getParams } from "@/utils/decode";
import { defineEventHandler } from 'h3'
import type { ApiResponse, TokenResponseEncrypt } from '~/types/api'
export const runtime = 'edge'
@ -17,15 +18,12 @@ export default defineEventHandler(async (event) => {
Object.entries(sendData).map(([k, v]) => [k, String(v)])
);
const response = await fetch('http://api.extscreen.com/aliyundrive/v3/token', {
const tokenData = await $fetch<TokenResponseEncrypt>('http://api.extscreen.com/aliyundrive/v3/token', {
method: 'POST',
headers: headers,
body: JSON.stringify(sendData)
});
const tokenData :any = await response.json();
const jsonp = tokenData.data;
const plainData = decrypt(jsonp.ciphertext, jsonp.iv, t);
const plainData = decrypt(tokenData.data.ciphertext, tokenData.data.iv, t);
const tokenInfo = JSON.parse(plainData);
return {
@ -34,10 +32,14 @@ export default defineEventHandler(async (event) => {
refresh_token: tokenInfo.refresh_token,
expires_in: tokenInfo.expires_in
};
// 直接返回响应数据
return tokenData;
} catch (error:any) {
return {
error: error.message,
statusCode: 500
}
code: 500,
message: error.message,
data: null
} as ApiResponse
}
})

45
types/api.ts Normal file
View File

@ -0,0 +1,45 @@
export interface ApiResponse<T = any> {
code: number;
message: string;
data: T;
}
export interface QrCodeData {
qrCodeUrl: string;
sid: string;
}
export interface TokenInfo {
token_type: string;
access_token: string;
refresh_token: string;
expires_in: number;
}
export interface TokenResponseEncrypt extends ApiResponse<{
ciphertext: string;
iv: string;
}> {}
export interface QrCodeStatus {
status: 'WaitLogin' | 'LoginSuccess' | 'LoginFailed';
authCode?: string;
}
export interface DeviceInfo {
akv: string;
apv: string;
b: string;
d: string;
m: string;
mac: string;
n: string;
t: number;
wifiMac: string;
}
export interface TokenRequest extends DeviceInfo {
refresh_token?: string;
code?: string;
'Content-Type': string;
}

View File

@ -1,18 +1,16 @@
import pkg from 'crypto-js';
const { AES, enc, mode, pad, MD5 } = pkg;
import CryptoES from 'crypto-es';
const decrypt = function (ciphertext: string, iv: string, t: number): string {
try {
const key = generateKey(t);
const decrypted = AES.decrypt(ciphertext, enc.Utf8.parse(key), {
iv: enc.Hex.parse(iv),
mode:mode.CBC,
padding: pad.Pkcs7
const decrypted = CryptoES.AES.decrypt(ciphertext, CryptoES.enc.Utf8.parse(key), {
iv: CryptoES.enc.Hex.parse(iv),
mode: CryptoES.mode.CBC,
padding: CryptoES.pad.Pkcs7
});
const dec = enc.Utf8.stringify(decrypted).toString();
const dec = CryptoES.enc.Utf8.stringify(decrypted).toString();
return dec;
} catch (error) {
console.error("Decryption failed", error);
console.error("Decryption failed", error);
throw error;
}
@ -60,7 +58,7 @@ const generateKey = function (t: number): string {
const keyArray = concatenatedParams.split("");
const hashedKey = h(keyArray, t);
return MD5(hashedKey).toString(enc.Hex);
return CryptoES.MD5(hashedKey).toString(CryptoES.enc.Hex);
};
export { decrypt, getParams };