This commit is contained in:
iLay 2024-12-06 03:20:18 +08:00
parent 941b1b64a4
commit 64df57e1a8
33 changed files with 17209 additions and 4284 deletions

51
.gitignore vendored
View File

@ -1,41 +1,28 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# dependencies # Node dependencies
/node_modules node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing # Logs
/coverage logs
*.log
# next.js # Misc
/.next/
/out/
# production
/build
# misc
.DS_Store .DS_Store
*.pem .fleet
.idea
# debug # Local env files
npm-debug.log* .env
yarn-debug.log* .env.*
yarn-error.log* !.env.example
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# wrangler files # wrangler files
.wrangler .wrangler
.dev.vars .dev.vars
bun.lockb

View File

@ -1,48 +1,75 @@
# 阿里云盘TV版token获取与刷新 # Nuxt Minimal Starter
## 路由: Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
- `/` 在线扫码 ## Setup
- `/refresh` 刷新令牌
Make sure to install dependencies:
```bash
# npm
npm install
# Docker部署教程 # pnpm
``` pnpm install
docker run --name=alipan-tv-token -d -p 3000:3000 ghcr.io/ilay1678/alipan-tv-token:latest
# yarn
yarn install
# bun
bun install
``` ```
# vercel部署 ## Development Server
[<img src="https://vercel.com/button" alt="Deploy on vercel" height="30">](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FiLay1678%2Falipan-tv-token&&project-name=alipan-tv-token&repository-name=alipan-tv-token)
# Cloudflare Pages 部署教程 Start the development server on `http://localhost:3000`:
## 1. Fork 项目仓库 ```bash
# npm
npm run dev
1. 点击右上角的 "Fork" 按钮创建你自己的副本 # pnpm
pnpm dev
## 2. 在 Cloudflare 中部署 # yarn
yarn dev
### 2.1 前置准备 # bun
1. 登录 [Cloudflare Dashboard](https://dash.cloudflare.com) bun run dev
2. 在左侧菜单找到并点击 "Pages" ```
3. 点击 "连接到 Git" 按钮
4. 按提示关联你的 GitHub 账号
### 2.2 创建项目 ## Production
1. 选择你刚才 fork 的仓库
2. 点击 "开始设置"
3. 在项目配置页面:
- 框架预设: 选择 `Next.js`**注意:不要选择 Next.js Static HTML Export**
- 构建命令: `pnpm dlx @cloudflare/next-on-pages@1`
> 提示:首次部署可能会出现错误提示,这是正常现象。按照步骤 2.3 启用 Node.js 兼容性并重新部署即可解决。 Build the application for production:
### 2.3 启用 Node.js 兼容性 ```bash
1. 部署完成后,进入项目设置 # npm
2. 在“运行时”类目下找到“兼容性标志”,填入 `nodejs_compat` npm run build
### 2.4 完成部署 # pnpm
1. 回到 "部署" 页面 pnpm build
2. 点击 "重新部署" 按钮
3. 等待部署完成,访问分配的域名即可使用 # yarn
yarn build
# bun
bun run build
```
Locally preview production build:
```bash
# npm
npm run preview
# pnpm
pnpm preview
# yarn
yarn preview
# bun
bun run preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.

View File

@ -2,7 +2,8 @@
import { decrypt, getParams } from '../utils/decode'; import { decrypt, getParams } from '../utils/decode';
export const runtime = 'edge' export const runtime = 'edge'
export async function POST(request) {
export async function POST(request: { json: () => PromiseLike<{ refresh_token: any; }> | { refresh_token: any; }; }) {
try { try {
const { refresh_token } = await request.json(); const { refresh_token } = await request.json();
const t = Math.floor(Date.now() / 1000); const t = Math.floor(Date.now() / 1000);
@ -22,7 +23,7 @@ export async function POST(request) {
body: JSON.stringify(sendData) body: JSON.stringify(sendData)
}); });
const tokenData = await response.json(); const tokenData:any = await response.json();
const jsonp = tokenData.data; const jsonp = tokenData.data;
const plainData = decrypt(jsonp.ciphertext, jsonp.iv, t); const plainData = decrypt(jsonp.ciphertext, jsonp.iv, t);
const tokenInfo = JSON.parse(plainData); const tokenInfo = JSON.parse(plainData);
@ -33,7 +34,7 @@ export async function POST(request) {
refresh_token: tokenInfo.refresh_token, refresh_token: tokenInfo.refresh_token,
expires_in: tokenInfo.expires_in expires_in: tokenInfo.expires_in
}); });
} catch (error) { } catch (error:any) {
return Response.json( return Response.json(
{ error: error.message }, { error: error.message },
{ status: 500 } { status: 500 }

8
app.vue Normal file
View File

@ -0,0 +1,8 @@
<template>
<head>
<title>阿里云盘 TV 版令牌生成器</title>
</head>
<div>
<NuxtPage />
</div>
</template>

21
assets/css/main.scss Normal file
View File

@ -0,0 +1,21 @@
.max-w-3xl {
max-width: 48rem;
}
.mx-auto {
margin-left: auto;
margin-right: auto;
}
.mt-4 {
margin-top: 1rem;
}
.space-y-4 > :not([hidden]) ~ :not([hidden]) {
margin-top: 1rem;
}
.h-\[52px\] {
height: 52px;
}

View File

@ -0,0 +1,27 @@
import { type NitroPreset } from "nitropack";
export default <NitroPreset>{
extends: "cloudflare",
exportConditions: ["workerd"],
output: {
dir: "{{ rootDir }}/dist",
publicDir: "{{ output.dir }}/public",
serverDir: "{{ output.dir }}/worker",
},
commands: {
preview: "npx wrangler dev",
deploy: "npx wrangler deploy",
},
wasm: {
lazy: false,
esmImport: true,
},
rollupConfig: {
output: {
entryFileNames: "index.js",
format: "esm",
exports: "named",
inlineDynamicImports: false,
},
},
};

14
env.d.ts vendored Normal file
View File

@ -0,0 +1,14 @@
/// <reference types="@cloudflare/workers-types/2023-07-01" />
declare module "h3" {
interface H3EventContext {
cf: CfProperties;
cloudflare: {
request: Request;
env: Env;
context: ExecutionContext;
};
}
}
export {};

View File

@ -1,7 +0,0 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}

View File

@ -1,13 +0,0 @@
import { setupDevPlatform } from '@cloudflare/next-on-pages/next-dev';
// Here we use the @cloudflare/next-on-pages next-dev module to allow us to use bindings during local development
// (when running the application with `next dev`), for more information see:
// https://github.com/cloudflare/next-on-pages/blob/main/internal-packages/next-dev/README.md
if (process.env.NODE_ENV === 'development') {
await setupDevPlatform();
}
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default nextConfig;

54
nuxt.config.ts Normal file
View File

@ -0,0 +1,54 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: '2024-11-01',
devtools: { enabled: true },
nitro: {
preset: "./cloudflare-preset"
},
experimental: {
},
modules: [
"nitro-cloudflare-dev",
"@ant-design-vue/nuxt",
'@nuxtjs/tailwindcss'
],
routeRules: {
'/': { prerender: true },
'/**': { ssr: true },
// '/refresh': {
// cors: true,
// headers: {
// 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
// 'Access-Control-Allow-Headers': 'Content-Type'
// },
// redirect:{
// to:'/api/refresh',
// statusCode: 301
// }
// }
},
antd: {
},
vite: {
// 启用内联样式以避免闪烁
build: {
cssCodeSplit: false
}
},
// 或者如果想更精细地控制
features: {
// 启用内联样式
inlineStyles: true
},
css: [
'@/assets/css/main.scss'
]
})

20204
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +1,34 @@
{ {
"name": "alipan-tv-token-next", "name": "nuxt-app",
"version": "0.1.0",
"private": true, "private": true,
"type": "module",
"scripts": { "scripts": {
"dev": "next dev", "build": "nuxt build",
"build": "next build", "dev": "nuxt dev",
"start": "next start", "generate": "nuxt generate",
"lint": "next lint", "preview": "npm run build && wrangler dev",
"pages:build": "npx @cloudflare/next-on-pages", "postinstall": "nuxt prepare",
"preview": "npm run pages:build && wrangler pages dev", "deploy": "npm run build && wrangler deploy",
"deploy": "npm run pages:build && wrangler pages deploy" "cf-typegen": "wrangler types"
}, },
"dependencies": { "dependencies": {
"@ant-design-vue/nuxt": "^1.4.6",
"@nuxtjs/tailwindcss": "^6.12.2",
"ant-design-vue": "^4.2.6",
"clipboard": "^2.0.11", "clipboard": "^2.0.11",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"js-md5": "^0.8.3", "js-md5": "^0.8.3",
"next": "14.2.5", "nuxt": "^3.14.1592",
"react": "^18", "vue": "latest",
"react-dom": "^18" "vue-router": "latest"
}, },
"devDependencies": { "devDependencies": {
"@cloudflare/next-on-pages": "^1.13.6",
"@cloudflare/workers-types": "^4.20241202.0", "@cloudflare/workers-types": "^4.20241202.0",
"postcss": "^8", "@types/crypto-js": "^4.2.2",
"tailwindcss": "^3.4.1", "@types/js-md5": "^0.7.2",
"vercel": "39.1.1", "nitro-cloudflare-dev": "^0.2.1",
"nitropack": "^2.10.4",
"sass-embedded": "^1.82.0",
"wrangler": "^3.92.0" "wrangler": "^3.92.0"
},
"overrides": {
"vercel": "$vercel"
} }
} }

206
pages/index.vue Normal file
View File

@ -0,0 +1,206 @@
<template>
<main class="min-h-screen bg-gray-100 p-4">
<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">
<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"/>
</svg>
</a>
</div>
<div class="space-y-8">
<div class="space-y-2">
<div class="relative">
<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" />
</svg>
</button>
</div>
</div>
<div class="space-y-2">
<div class="relative">
<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" />
</svg>
</button>
</div>
</div>
<div id="authSection" class="h-[52px]">
<div v-if="isLoading" class="flex justify-center items-center h-full">
<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'
}`"
>
<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" />
</svg>
{{ authorizing ? '授权中...' : '授权登录' }}
</div>
</button>
</div>
</div>
</div>
</main>
<a-modal
v-model:visible="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>
</template>
</a-modal>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import { message } from 'ant-design-vue'
import ClipboardJS from 'clipboard'
const hasGenerated = ref(false)
const authUrl = ref('')
const isLoading = ref(true)
const hasAccessToken = ref(false)
const hasRefreshToken = ref(false)
const authorizing = ref(false)
const isNoticeOpen = ref(false) // showNotice isNoticeOpen
const accessToken = ref('')
const refreshToken = ref('')
async function generateAuthUrl() {
try {
isLoading.value = true
const response = await fetch("/generate_qr", { method: "POST" })
const data = await response.json()
authUrl.value = `https://www.alipan.com/o/oauth/authorize?sid=${data.sid}`
checkStatus(data.sid)
} finally {
isLoading.value = false
}
}
function closeNotice() {
isNoticeOpen.value = false
}
async function checkStatus(sid) {
try {
const response = await fetch("/check_status/" + sid)
const data = await response.json()
if (data.status === "LoginSuccess") {
accessToken.value = data.access_token
refreshToken.value = data.refresh_token
document.getElementById("authSection").style.visibility = "hidden"
hasAccessToken.value = !!data.access_token
hasRefreshToken.value = !!data.refresh_token
message.success('登录成功')
initializeClipboard() // token ClipboardJS
} else if (data.status === "ScanSuccess") {
setTimeout(() => checkStatus(sid), 2000)
} else if (data.status === "LoginFailed") {
message.error('登录失败,请刷新页面重试')
location.reload()
} else {
setTimeout(() => checkStatus(sid), 2000)
}
} catch (error) {
console.error("检查状态时出错:", error)
message.error('发生错误,请稍后重试')
}
}
// ClipboardJS
function initializeClipboard() {
const accessTokenClipboard = new ClipboardJS('[data-clipboard-target="#accessToken"]')
accessTokenClipboard.on('success', () => {
message.success('已复制访问令牌')
})
accessTokenClipboard.on('error', () => {
message.error('复制失败')
})
const refreshTokenClipboard = new ClipboardJS('[data-clipboard-target="#refreshToken"]')
refreshTokenClipboard.on('success', () => {
message.success('已复制刷新令牌')
})
refreshTokenClipboard.on('error', () => {
message.error('复制失败')
})
}
const handleAuth = (url) => {
authorizing.value = true
window.open(url, '_blank')
}
onMounted(() => {
if (!hasGenerated.value) {
generateAuthUrl()
hasGenerated.value = true
isNoticeOpen.value = true
}
})
watch()
</script>
<style scoped>
</style>

View File

@ -1,8 +0,0 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};
export default config;

View File

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

1
public/robots.txt Normal file
View File

@ -0,0 +1 @@

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>

Before

Width:  |  Height:  |  Size: 629 B

20
server.ts Normal file
View File

@ -0,0 +1,20 @@
// ...existing code...
export const runtime = 'edge';
export async function generate_qr() {
try {
const response = await fetch('http://api.extscreen.com/aliyundrive/qrcode', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
scopes: ["user:base", "file:all:read", "file:all:write"].join(','),
width: 500,
height: 500,
})
});
const data = await response.json();
return Response.json({
qr_link: data.data.qrCodeUrl,
sid: data.data.sid

View File

@ -1,12 +1,13 @@
import { decrypt, getParams } from '../../../utils/decode';
import { decrypt, getParams } from '../../utils/decode'; import { defineEventHandler, getRouterParams } from 'h3'
export const runtime = 'edge' export const runtime = 'edge'
export async function GET(request, { params }) {
export default defineEventHandler(async (event) => {
try { try {
const { sid } = params; const { sid } = getRouterParams(event);
const response = await fetch(`https://openapi.alipan.com/oauth/qrcode/${sid}/status`,{next: { revalidate: 0 }}); const response = await fetch(`https://openapi.alipan.com/oauth/qrcode/${sid}/status`);
const statusData = await response.json(); const statusData:any = await response.json();
if (statusData.status === 'LoginSuccess') { if (statusData.status === 'LoginSuccess') {
try { try {
@ -23,26 +24,26 @@ export async function GET(request, { params }) {
body: JSON.stringify(sendData) body: JSON.stringify(sendData)
}); });
const tokenData = await tokenResponse.json(); const tokenData:any = await tokenResponse.json();
const jsonp = tokenData.data; const jsonp = tokenData.data;
const plainData = decrypt(jsonp.ciphertext, jsonp.iv, t); const plainData = decrypt(jsonp.ciphertext, jsonp.iv, t);
const tokenInfo = JSON.parse(plainData); const tokenInfo = JSON.parse(plainData);
return Response.json({ return {
status: 'LoginSuccess', status: 'LoginSuccess',
refresh_token: tokenInfo.refresh_token, refresh_token: tokenInfo.refresh_token,
access_token: tokenInfo.access_token access_token: tokenInfo.access_token
}); };
} catch (error) { } catch (error) {
return Response.json({ status: 'LoginFailed' }); return { status: 'LoginFailed' };
} }
} else { } else {
return Response.json({ status: statusData.status }); return { status: statusData.status };
} }
} catch (error) { } catch (error:any) {
return Response.json( throw createError({
{ error: error.message }, statusCode: 500,
{ status: 500 } message: error.message
); });
} }
} });

View File

@ -1,6 +1,6 @@
import { defineEventHandler } from 'h3'
export const runtime = 'edge' export default defineEventHandler(async (event) => {
export async function POST() {
try { try {
const response = await fetch('http://api.extscreen.com/aliyundrive/qrcode', { const response = await fetch('http://api.extscreen.com/aliyundrive/qrcode', {
method: 'POST', method: 'POST',
@ -10,16 +10,16 @@ export async function POST() {
width: 500, width: 500,
height: 500, height: 500,
}) })
}); })
const data = await response.json(); const data:any = await response.json()
return Response.json({ return {
qr_link: data.data.qrCodeUrl, qr_link: data.data.qrCodeUrl,
sid: data.data.sid sid: data.data.sid
});
} catch (error) {
return Response.json(
{ error: error.message },
{ status: 500 }
);
} }
} } catch (error: any) {
throw createError({
statusCode: 500,
message: error.message
})
}
})

43
server/routes/refresh.ts Normal file
View File

@ -0,0 +1,43 @@
import { decrypt, getParams } from '../../utils/decode';
import { defineEventHandler } from 'h3'
export const runtime = 'edge'
export default defineEventHandler(async (event) => {
try {
const { refresh_token } = await readBody(event);
const t = Math.floor(Date.now() / 1000);
const sendData = {
...getParams(t),
refresh_token: refresh_token,
"Content-Type": "application/json"
};
const headers = Object.fromEntries(
Object.entries(sendData).map(([k, v]) => [k, String(v)])
);
const response = await fetch('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 tokenInfo = JSON.parse(plainData);
return {
token_type: 'Bearer',
access_token: tokenInfo.access_token,
refresh_token: tokenInfo.refresh_token,
expires_in: tokenInfo.expires_in
};
} catch (error:any) {
return {
error: error.message,
statusCode: 500
}
}
})

3
server/tsconfig.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "../.nuxt/tsconfig.server.json"
}

View File

@ -1,21 +0,0 @@
import { getRequestContext } from '@cloudflare/next-on-pages'
export const runtime = 'edge'
export async function GET(request) {
let responseText = 'Hello World'
// In the edge runtime you can use Bindings that are available in your application
// (for more details see:
// - https://developers.cloudflare.com/pages/framework-guides/deploy-a-nextjs-site/#use-bindings-in-your-nextjs-application
// - https://developers.cloudflare.com/pages/functions/bindings/
// )
//
// KV Example:
// const myKv = getRequestContext().env.MY_KV_NAMESPACE
// await myKv.put('suffix', ' from a KV store!')
// const suffix = await myKv.get('suffix')
// responseText += suffix
return new Response(responseText)
}

View File

@ -1,71 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}
body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(
to bottom,
transparent,
rgb(var(--background-end-rgb))
)
rgb(var(--background-start-rgb));
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
@keyframes fade-in {
from {
opacity: 0;
transform: translate(-50%, -20px);
}
to {
opacity: 1;
transform: translate(-50%, 0);
}
}
.animate-fade-in {
animation: fade-in 0.3s ease-out forwards;
}
@keyframes fade-in-out {
0% {
opacity: 0;
transform: translate(-50%, -20px);
}
10% {
opacity: 1;
transform: translate(-50%, 0);
}
90% {
opacity: 1;
transform: translate(-50%, 0);
}
100% {
opacity: 0;
transform: translate(-50%, -20px);
}
}
.animate-fade-in-out {
animation: fade-in-out 3s ease-out forwards;
}

View File

@ -1,20 +0,0 @@
import { Inter } from "next/font/google";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export const metadata = {
title: '阿里云盘授权',
description: '获取阿里云盘TV版授权',
icons: {
icon: '/favicon.ico',
},
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
);
}

View File

@ -1,58 +0,0 @@
export const runtime = "edge";
export default function NotFound() {
return (
<>
<title>404: This page could not be found.</title>
<div style={styles.error}>
<div>
<style
dangerouslySetInnerHTML={{
__html: `body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}`,
}}
/>
<h1 className="next-error-h1" style={styles.h1}>
404
</h1>
<div style={styles.desc}>
<h2 style={styles.h2}>This page could not be found.</h2>
</div>
</div>
</div>
</>
);
}
const styles = {
error: {
fontFamily:
'system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',
height: "100vh",
textAlign: "center",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
},
desc: {
display: "inline-block",
},
h1: {
display: "inline-block",
margin: "0 20px 0 0",
padding: "0 23px 0 0",
fontSize: 24,
fontWeight: 500,
verticalAlign: "top",
lineHeight: "49px",
},
h2: {
fontSize: 14,
fontWeight: 400,
lineHeight: "49px",
margin: 0,
},
};

View File

@ -1,269 +0,0 @@
'use client'
import { useEffect, useState, useRef } from 'react'
import ClipboardJS from 'clipboard'
export default function Home() {
const [alertMsg, setAlertMsg] = useState('')
const [alertType, setAlertType] = useState('success')
const hasGenerated = useRef(false)
const [authUrl, setAuthUrl] = useState('') // 新增授权URL状态
const [isLoading, setIsLoading] = useState(true) // 新增加载状态
const [hasAccessToken, setHasAccessToken] = useState(false)
const [hasRefreshToken, setHasRefreshToken] = useState(false)
const [authorizing, setAuthorizing] = useState(false) // 新增授权中状态
const [showNotice, setShowNotice] = useState(true) // 新增弹窗控制状态
async function generateAuthUrl() {
try {
setIsLoading(true)
const response = await fetch("/generate_qr", {
method: "POST",
});
const data = await response.json();
// 生成授权URL
const url = `https://www.alipan.com/o/oauth/authorize?sid=${data.sid}`
setAuthUrl(url);
checkStatus(data.sid);
} finally {
setIsLoading(false)
}
}
async function checkStatus(sid) {
try {
const response = await fetch("/check_status/" + sid);
const data = await response.json();
if (data.status === "LoginSuccess") {
document.getElementById("accessToken").value = data.access_token;
document.getElementById("refreshToken").value = data.refresh_token;
document.getElementById("authSection").style.visibility = "hidden"; // 改为隐藏而不是display:none
setHasAccessToken(!!data.access_token)
setHasRefreshToken(!!data.refresh_token)
// 初始化复制功能
initializeClipboard();
} else if (data.status === "ScanSuccess") {
// 继续轮询
setTimeout(() => checkStatus(sid), 2000);
} else if (data.status === "LoginFailed") {
setAlertMsg("登录失败,请刷新页面重试");
setAlertType('error');
location.reload();
} else {
// 其他状态,继续轮询
setTimeout(() => checkStatus(sid), 2000);
}
} catch (error) {
console.error("检查状态时出错:", error);
setAlertMsg("发生错误,请稍后重试");
setAlertType('error');
}
}
function initializeClipboard() {
// 初始化 access token 复制
const accessTokenClipboard = new ClipboardJS('[data-clipboard-target="#accessToken"]');
accessTokenClipboard.on('success', () => {
setAlertMsg('已复制访问令牌');
setAlertType('success');
});
accessTokenClipboard.on('error', () => {
setAlertMsg('复制失败');
setAlertType('error');
});
// 初始化 refresh token 复制
const refreshTokenClipboard = new ClipboardJS('[data-clipboard-target="#refreshToken"]');
refreshTokenClipboard.on('success', () => {
setAlertMsg('已复制刷新令牌');
setAlertType('success');
});
refreshTokenClipboard.on('error', () => {
setAlertMsg('复制失败');
setAlertType('error');
});
}
// 新增处理点击授权的函数
const handleAuth = (url) => {
setAuthorizing(true)
window.open(url, '_blank')
}
useEffect(() => {
if (!hasGenerated.current) {
generateAuthUrl();
hasGenerated.current = true;
}
}, []);
// 添加消息自动清除的 Effect
useEffect(() => {
if (alertMsg) {
const timer = setTimeout(() => {
setAlertMsg('');
}, 3000);
return () => clearTimeout(timer);
}
}, [alertMsg]);
return (
<>
{showNotice && (
<div className="fixed inset-0 bg-black bg-opacity-50 z-50">
<div className="relative w-full max-w-md mx-auto mt-16 px-4"> {/* 修改定位和边距 */}
<div className="bg-white rounded-lg p-6 space-y-4 shadow-xl">
<h2 className="text-xl font-bold text-gray-800">使用须知</h2>
<p className="text-gray-600 text-sm leading-relaxed">
本工具能帮助你一键获取阿里云盘TV版的刷新令牌完全免费TV接口能绕过三方应用权益包的速率限制但前提你得是SVIP
</p>
<div className="flex justify-end pt-2"> {/* 调整按钮位置 */}
<button
onClick={() => setShowNotice(false)}
className="min-w-[100px] bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600 transition-colors text-sm"
>
知道了
</button>
</div>
</div>
</div>
</div>
)}
{alertMsg && (
<div className="fixed top-4 left-1/2 transform -translate-x-1/2 z-50 w-auto min-w-[200px] max-w-[90%] animate-fade-in-out">
<div className={`px-6 py-3 rounded-lg shadow-lg text-white text-center ${
alertType === 'success' ? 'bg-green-500' : 'bg-red-500'
}`}>
{alertMsg}
</div>
</div>
)}
<main className="min-h-screen bg-gray-100 p-4"> {/* 移除 pt-16只保留基础内边距 */}
<div className="mx-auto w-full max-w-3xl bg-white shadow-lg rounded-lg p-8"> {/* <20><><EFBFBD>改这里添加水平居中 */}
<div className="flex justify-between items-center mb-6">
<h1 className="text-2xl font-bold text-gray-800">阿里云盘连接</h1>
{/* 将 Docker 标移到这里 */}
<a
href="https://ghcr.io/ilay1678/alipan-tv-token"
target="_blank"
rel="noopener noreferrer"
className="text-gray-600 hover:text-blue-500 transition-colors"
title="Docker Image"
>
<svg className="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"/>
</svg>
</a>
</div>
<div className="space-y-8">
<div className="space-y-2">
<div className="relative">
<textarea
id="accessToken"
className="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"
className={`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'
}`}
title="复制访问令牌"
disabled={!hasAccessToken}
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={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>
</div>
<div className="space-y-2">
<div className="relative">
<textarea
id="refreshToken"
className="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"
className={`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'
}`}
title="复制刷新令牌"
disabled={!hasRefreshToken}
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={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>
</div>
<div id="authSection" className="h-[52px]"> {/* 固定高度 */}
{isLoading ? (
<div className="flex justify-center items-center h-full">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
<span className="ml-3 text-gray-600">正在获取授权链接...</span>
</div>
) : authUrl && (
<button
onClick={() => handleAuth(authUrl)}
disabled={authorizing}
className={`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'}`}
>
{authorizing ? (
<div className="flex items-center justify-center">
<div className="animate-spin rounded-full h-5 w-5 border-2 border-white border-t-transparent mr-2"></div>
授权中...
</div>
) : (
<div className="flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={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>
授权登录
</div>
)}
</button>
)}
</div>
</div>
</div>
</main>
</>
)
}

View File

@ -1,18 +1,9 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { export default {
content: [ content: [],
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: { theme: {
extend: { extend: {},
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
},
}, },
plugins: [], plugins: [],
}; }

9
tsconfig.json Normal file
View File

@ -0,0 +1,9 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json",
"compilerOptions": {
"types": [
"@cloudflare/workers-types/2023-07-01"
]
}
}

View File

@ -1,6 +1,7 @@
import md5 from "js-md5"; import { md5 } from "js-md5";
import crypto from "crypto-js"; import crypto from "crypto-js";
const decrypt = function (ciphertext, iv, t) {
const decrypt = function (ciphertext: string, iv: string, t: number): string {
try { try {
const key = generateKey(t); const key = generateKey(t);
const decrypted = crypto.AES.decrypt(ciphertext, crypto.enc.Utf8.parse(key), { const decrypted = crypto.AES.decrypt(ciphertext, crypto.enc.Utf8.parse(key), {
@ -8,7 +9,7 @@ const decrypt = function (ciphertext, iv, t) {
mode: crypto.mode.CBC, mode: crypto.mode.CBC,
padding: crypto.pad.Pkcs7 padding: crypto.pad.Pkcs7
}); });
var dec = crypto.enc.Utf8.stringify(decrypted).toString(); const dec = crypto.enc.Utf8.stringify(decrypted).toString();
return dec; return dec;
} catch (error) { } catch (error) {
console.error("Decryption failed", error); console.error("Decryption failed", error);
@ -16,7 +17,7 @@ const decrypt = function (ciphertext, iv, t) {
} }
}; };
function h(charArray, modifier) { function h(charArray: string[], modifier: number): string {
const uniqueChars = Array.from(new Set(charArray)); const uniqueChars = Array.from(new Set(charArray));
const numericModifier = Number(modifier.toString().slice(7)); const numericModifier = Number(modifier.toString().slice(7));
const transformedString = uniqueChars.map(char => { const transformedString = uniqueChars.map(char => {
@ -31,7 +32,7 @@ function h(charArray, modifier) {
return transformedString; return transformedString;
} }
function getParams(t) { function getParams(t: number): Record<string, string | number> {
return { return {
'akv': '2.8.1496', // apk_version_name 版本号 'akv': '2.8.1496', // apk_version_name 版本号
'apv': '1.3.6', // 内部版本号 'apv': '1.3.6', // 内部版本号
@ -45,8 +46,8 @@ function getParams(t) {
}; };
} }
const generateKey = function (t) { const generateKey = function (t: number): string {
const params = getParams(); const params = getParams(t);
const sortedKeys = Object.keys(params).sort(); const sortedKeys = Object.keys(params).sort();
let concatenatedParams = ""; let concatenatedParams = "";
@ -61,4 +62,5 @@ const generateKey = function (t) {
console.log(md5(hashedKey)); console.log(md5(hashedKey));
return md5(hashedKey); return md5(hashedKey);
}; };
export { decrypt, getParams }; export { decrypt, getParams };

4
worker-configuration.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
// Generated by Wrangler
// After adding bindings to `wrangler.toml`, regenerate this interface via `npm run cf-typegen`
interface Env {
}

View File

@ -1,87 +1,11 @@
#:schema node_modules/wrangler/config-schema.json #:schema node_modules/wrangler/config-schema.json
name = "alipan-tv-token-next" name = "alipan-tv-nuxt"
compatibility_date = "2023-05-18" compatibility_date = "2024-11-06"
compatibility_flags = ["nodejs_compat"] main = "./dist/worker/index.js"
pages_build_output_dir = ".vercel/output/static" assets = { directory = "./dist/public", binding = "ASSETS" }
# Automatically place your workloads in an optimal location to minimize latency. # Workers Logs
# If you are running back-end logic in a Pages Function, running it closer to your back-end infrastructure # Docs: https://developers.cloudflare.com/workers/observability/logs/workers-logs/
# rather than the end user may result in better performance. # Configuration: https://developers.cloudflare.com/workers/observability/logs/workers-logs/#enable-workers-logs
# Docs: https://developers.cloudflare.com/pages/functions/smart-placement/#smart-placement [observability]
# [placement] enabled = true
# mode = "smart"
# Variable bindings. These are arbitrary, plaintext strings (similar to environment variables)
# Docs:
# - https://developers.cloudflare.com/pages/functions/bindings/#environment-variables
# Note: Use secrets to store sensitive data.
# - https://developers.cloudflare.com/pages/functions/bindings/#secrets
# [vars]
# MY_VARIABLE = "production_value"
# Bind the Workers AI model catalog. Run machine learning models, powered by serverless GPUs, on Cloudflares global network
# Docs: https://developers.cloudflare.com/pages/functions/bindings/#workers-ai
# [ai]
# binding = "AI"
# Bind a D1 database. D1 is Cloudflares native serverless SQL database.
# Docs: https://developers.cloudflare.com/pages/functions/bindings/#d1-databases
# [[d1_databases]]
# binding = "MY_DB"
# database_name = "my-database"
# database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# Bind a Durable Object. Durable objects are a scale-to-zero compute primitive based on the actor model.
# Durable Objects can live for as long as needed. Use these when you need a long-running "server", such as in realtime apps.
# Docs: https://developers.cloudflare.com/workers/runtime-apis/durable-objects
# [[durable_objects.bindings]]
# name = "MY_DURABLE_OBJECT"
# class_name = "MyDurableObject"
# script_name = 'my-durable-object'
# Bind a KV Namespace. Use KV as persistent storage for small key-value pairs.
# Docs: https://developers.cloudflare.com/pages/functions/bindings/#kv-namespaces
# KV Example:
# [[kv_namespaces]]
# binding = "MY_KV_NAMESPACE"
# id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# Bind a Queue producer. Use this binding to schedule an arbitrary task that may be processed later by a Queue consumer.
# Docs: https://developers.cloudflare.com/pages/functions/bindings/#queue-producers
# [[queues.producers]]
# binding = "MY_QUEUE"
# queue = "my-queue"
# Bind an R2 Bucket. Use R2 to store arbitrarily large blobs of data, such as files.
# Docs: https://developers.cloudflare.com/pages/functions/bindings/#r2-buckets
# [[r2_buckets]]
# binding = "MY_BUCKET"
# bucket_name = "my-bucket"
# Bind another Worker service. Use this binding to call another Worker without network overhead.
# Docs: https://developers.cloudflare.com/pages/functions/bindings/#service-bindings
# [[services]]
# binding = "MY_SERVICE"
# service = "my-service"
# To use different bindings for preview and production environments, follow the examples below.
# When using environment-specific overrides for bindings, ALL bindings must be specified on a per-environment basis.
# Docs: https://developers.cloudflare.com/pages/functions/wrangler-configuration#environment-specific-overrides
######## PREVIEW environment config ########
# [env.preview.vars]
# API_KEY = "xyz789"
# [[env.preview.kv_namespaces]]
# binding = "MY_KV_NAMESPACE"
# id = "<PREVIEW_NAMESPACE_ID>"
######## PRODUCTION environment config ########
# [env.production.vars]
# API_KEY = "abc123"
# [[env.production.kv_namespaces]]
# binding = "MY_KV_NAMESPACE"
# id = "<PRODUCTION_NAMESPACE_ID>"