mirror of
https://github.com/iLay1678/i-tools.git
synced 2025-05-23 19:17:42 +08:00
nuxt版
This commit is contained in:
parent
941b1b64a4
commit
64df57e1a8
51
.gitignore
vendored
51
.gitignore
vendored
@ -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_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
# Node dependencies
|
||||
node_modules
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
# Misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
.fleet
|
||||
.idea
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
# Local env files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# wrangler files
|
||||
.wrangler
|
||||
.dev.vars
|
||||
bun.lockb
|
91
README.md
91
README.md
@ -1,48 +1,75 @@
|
||||
# 阿里云盘TV版token获取与刷新
|
||||
# Nuxt Minimal Starter
|
||||
|
||||
## 路由:
|
||||
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||
|
||||
- `/` 在线扫码
|
||||
- `/refresh` 刷新令牌
|
||||
## Setup
|
||||
|
||||
Make sure to install dependencies:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm install
|
||||
|
||||
# Docker部署教程
|
||||
```
|
||||
docker run --name=alipan-tv-token -d -p 3000:3000 ghcr.io/ilay1678/alipan-tv-token:latest
|
||||
# pnpm
|
||||
pnpm install
|
||||
|
||||
# yarn
|
||||
yarn install
|
||||
|
||||
# bun
|
||||
bun install
|
||||
```
|
||||
|
||||
# vercel部署
|
||||
[<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)
|
||||
## Development Server
|
||||
|
||||
# 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 前置准备
|
||||
1. 登录 [Cloudflare Dashboard](https://dash.cloudflare.com)
|
||||
2. 在左侧菜单找到并点击 "Pages"
|
||||
3. 点击 "连接到 Git" 按钮
|
||||
4. 按提示关联你的 GitHub 账号
|
||||
# bun
|
||||
bun run dev
|
||||
```
|
||||
|
||||
### 2.2 创建项目
|
||||
1. 选择你刚才 fork 的仓库
|
||||
2. 点击 "开始设置"
|
||||
3. 在项目配置页面:
|
||||
- 框架预设: 选择 `Next.js`(**注意:不要选择 Next.js Static HTML Export**)
|
||||
- 构建命令: `pnpm dlx @cloudflare/next-on-pages@1`
|
||||
## Production
|
||||
|
||||
> 提示:首次部署可能会出现错误提示,这是正常现象。按照步骤 2.3 启用 Node.js 兼容性并重新部署即可解决。
|
||||
Build the application for production:
|
||||
|
||||
### 2.3 启用 Node.js 兼容性
|
||||
1. 部署完成后,进入项目设置
|
||||
2. 在“运行时”类目下找到“兼容性标志”,填入 `nodejs_compat`。
|
||||
```bash
|
||||
# npm
|
||||
npm run build
|
||||
|
||||
### 2.4 完成部署
|
||||
1. 回到 "部署" 页面
|
||||
2. 点击 "重新部署" 按钮
|
||||
3. 等待部署完成,访问分配的域名即可使用
|
||||
# pnpm
|
||||
pnpm build
|
||||
|
||||
# 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.
|
||||
|
@ -2,7 +2,8 @@
|
||||
import { decrypt, getParams } from '../utils/decode';
|
||||
|
||||
export const runtime = 'edge'
|
||||
export async function POST(request) {
|
||||
|
||||
export async function POST(request: { json: () => PromiseLike<{ refresh_token: any; }> | { refresh_token: any; }; }) {
|
||||
try {
|
||||
const { refresh_token } = await request.json();
|
||||
const t = Math.floor(Date.now() / 1000);
|
||||
@ -22,7 +23,7 @@ export async function POST(request) {
|
||||
body: JSON.stringify(sendData)
|
||||
});
|
||||
|
||||
const tokenData = await response.json();
|
||||
const tokenData:any = await response.json();
|
||||
const jsonp = tokenData.data;
|
||||
const plainData = decrypt(jsonp.ciphertext, jsonp.iv, t);
|
||||
const tokenInfo = JSON.parse(plainData);
|
||||
@ -33,7 +34,7 @@ export async function POST(request) {
|
||||
refresh_token: tokenInfo.refresh_token,
|
||||
expires_in: tokenInfo.expires_in
|
||||
});
|
||||
} catch (error) {
|
||||
} catch (error:any) {
|
||||
return Response.json(
|
||||
{ error: error.message },
|
||||
{ status: 500 }
|
8
app.vue
Normal file
8
app.vue
Normal file
@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<head>
|
||||
<title>阿里云盘 TV 版令牌生成器</title>
|
||||
</head>
|
||||
<div>
|
||||
<NuxtPage />
|
||||
</div>
|
||||
</template>
|
21
assets/css/main.scss
Normal file
21
assets/css/main.scss
Normal 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;
|
||||
}
|
27
cloudflare-preset/nitro.config.ts
Normal file
27
cloudflare-preset/nitro.config.ts
Normal 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
14
env.d.ts
vendored
Normal 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 {};
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
@ -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
54
nuxt.config.ts
Normal 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
20204
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
39
package.json
39
package.json
@ -1,33 +1,34 @@
|
||||
{
|
||||
"name": "alipan-tv-token-next",
|
||||
"version": "0.1.0",
|
||||
"name": "nuxt-app",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"pages:build": "npx @cloudflare/next-on-pages",
|
||||
"preview": "npm run pages:build && wrangler pages dev",
|
||||
"deploy": "npm run pages:build && wrangler pages deploy"
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "npm run build && wrangler dev",
|
||||
"postinstall": "nuxt prepare",
|
||||
"deploy": "npm run build && wrangler deploy",
|
||||
"cf-typegen": "wrangler types"
|
||||
},
|
||||
"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",
|
||||
"js-md5": "^0.8.3",
|
||||
"next": "14.2.5",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
"nuxt": "^3.14.1592",
|
||||
"vue": "latest",
|
||||
"vue-router": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/next-on-pages": "^1.13.6",
|
||||
"@cloudflare/workers-types": "^4.20241202.0",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"vercel": "39.1.1",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/js-md5": "^0.7.2",
|
||||
"nitro-cloudflare-dev": "^0.2.1",
|
||||
"nitropack": "^2.10.4",
|
||||
"sass-embedded": "^1.82.0",
|
||||
"wrangler": "^3.92.0"
|
||||
},
|
||||
"overrides": {
|
||||
"vercel": "$vercel"
|
||||
}
|
||||
}
|
||||
|
206
pages/index.vue
Normal file
206
pages/index.vue
Normal 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>
|
@ -1,8 +0,0 @@
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
@ -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
1
public/robots.txt
Normal file
@ -0,0 +1 @@
|
||||
|
@ -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
20
server.ts
Normal 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
|
@ -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 async function GET(request, { params }) {
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const { sid } = params;
|
||||
const response = await fetch(`https://openapi.alipan.com/oauth/qrcode/${sid}/status`,{next: { revalidate: 0 }});
|
||||
const statusData = await response.json();
|
||||
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') {
|
||||
try {
|
||||
@ -23,26 +24,26 @@ export async function GET(request, { params }) {
|
||||
body: JSON.stringify(sendData)
|
||||
});
|
||||
|
||||
const tokenData = await tokenResponse.json();
|
||||
const tokenData:any = await tokenResponse.json();
|
||||
const jsonp = tokenData.data;
|
||||
const plainData = decrypt(jsonp.ciphertext, jsonp.iv, t);
|
||||
const tokenInfo = JSON.parse(plainData);
|
||||
|
||||
return Response.json({
|
||||
return {
|
||||
status: 'LoginSuccess',
|
||||
refresh_token: tokenInfo.refresh_token,
|
||||
access_token: tokenInfo.access_token
|
||||
});
|
||||
};
|
||||
} catch (error) {
|
||||
return Response.json({ status: 'LoginFailed' });
|
||||
return { status: 'LoginFailed' };
|
||||
}
|
||||
} else {
|
||||
return Response.json({ status: statusData.status });
|
||||
return { status: statusData.status };
|
||||
}
|
||||
} catch (error) {
|
||||
return Response.json(
|
||||
{ error: error.message },
|
||||
{ status: 500 }
|
||||
);
|
||||
} catch (error:any) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
import { defineEventHandler } from 'h3'
|
||||
|
||||
export const runtime = 'edge'
|
||||
export async function POST() {
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const response = await fetch('http://api.extscreen.com/aliyundrive/qrcode', {
|
||||
method: 'POST',
|
||||
@ -10,16 +10,16 @@ export async function POST() {
|
||||
width: 500,
|
||||
height: 500,
|
||||
})
|
||||
});
|
||||
const data = await response.json();
|
||||
return Response.json({
|
||||
})
|
||||
const data:any = await response.json()
|
||||
return {
|
||||
qr_link: data.data.qrCodeUrl,
|
||||
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
43
server/routes/refresh.ts
Normal 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
3
server/tsconfig.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../.nuxt/tsconfig.server.json"
|
||||
}
|
@ -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)
|
||||
}
|
@ -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;
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
@ -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,
|
||||
},
|
||||
};
|
269
src/app/page.js
269
src/app/page.js
@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
@ -1,18 +1,9 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
export default {
|
||||
content: [],
|
||||
theme: {
|
||||
extend: {
|
||||
backgroundImage: {
|
||||
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
|
||||
"gradient-conic":
|
||||
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
|
||||
},
|
||||
},
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
}
|
||||
|
||||
|
9
tsconfig.json
Normal file
9
tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
// https://nuxt.com/docs/guide/concepts/typescript
|
||||
"extends": "./.nuxt/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"@cloudflare/workers-types/2023-07-01"
|
||||
]
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import md5 from "js-md5";
|
||||
import { md5 } from "js-md5";
|
||||
import crypto from "crypto-js";
|
||||
const decrypt = function (ciphertext, iv, t) {
|
||||
|
||||
const decrypt = function (ciphertext: string, iv: string, t: number): string {
|
||||
try {
|
||||
const key = generateKey(t);
|
||||
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,
|
||||
padding: crypto.pad.Pkcs7
|
||||
});
|
||||
var dec = crypto.enc.Utf8.stringify(decrypted).toString();
|
||||
const dec = crypto.enc.Utf8.stringify(decrypted).toString();
|
||||
return dec;
|
||||
} catch (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 numericModifier = Number(modifier.toString().slice(7));
|
||||
const transformedString = uniqueChars.map(char => {
|
||||
@ -31,7 +32,7 @@ function h(charArray, modifier) {
|
||||
return transformedString;
|
||||
}
|
||||
|
||||
function getParams(t) {
|
||||
function getParams(t: number): Record<string, string | number> {
|
||||
return {
|
||||
'akv': '2.8.1496', // apk_version_name 版本号
|
||||
'apv': '1.3.6', // 内部版本号
|
||||
@ -45,8 +46,8 @@ function getParams(t) {
|
||||
};
|
||||
}
|
||||
|
||||
const generateKey = function (t) {
|
||||
const params = getParams();
|
||||
const generateKey = function (t: number): string {
|
||||
const params = getParams(t);
|
||||
const sortedKeys = Object.keys(params).sort();
|
||||
let concatenatedParams = "";
|
||||
|
||||
@ -61,4 +62,5 @@ const generateKey = function (t) {
|
||||
console.log(md5(hashedKey));
|
||||
return md5(hashedKey);
|
||||
};
|
||||
|
||||
export { decrypt, getParams };
|
4
worker-configuration.d.ts
vendored
Normal file
4
worker-configuration.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
// Generated by Wrangler
|
||||
// After adding bindings to `wrangler.toml`, regenerate this interface via `npm run cf-typegen`
|
||||
interface Env {
|
||||
}
|
@ -1,87 +1,11 @@
|
||||
#:schema node_modules/wrangler/config-schema.json
|
||||
name = "alipan-tv-token-next"
|
||||
compatibility_date = "2023-05-18"
|
||||
compatibility_flags = ["nodejs_compat"]
|
||||
pages_build_output_dir = ".vercel/output/static"
|
||||
name = "alipan-tv-nuxt"
|
||||
compatibility_date = "2024-11-06"
|
||||
main = "./dist/worker/index.js"
|
||||
assets = { directory = "./dist/public", binding = "ASSETS" }
|
||||
|
||||
# Automatically place your workloads in an optimal location to minimize latency.
|
||||
# If you are running back-end logic in a Pages Function, running it closer to your back-end infrastructure
|
||||
# rather than the end user may result in better performance.
|
||||
# Docs: https://developers.cloudflare.com/pages/functions/smart-placement/#smart-placement
|
||||
# [placement]
|
||||
# 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 Cloudflare’s global network
|
||||
# Docs: https://developers.cloudflare.com/pages/functions/bindings/#workers-ai
|
||||
# [ai]
|
||||
# binding = "AI"
|
||||
|
||||
# Bind a D1 database. D1 is Cloudflare’s 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>"
|
||||
# Workers Logs
|
||||
# Docs: https://developers.cloudflare.com/workers/observability/logs/workers-logs/
|
||||
# Configuration: https://developers.cloudflare.com/workers/observability/logs/workers-logs/#enable-workers-logs
|
||||
[observability]
|
||||
enabled = true
|
||||
|
Loading…
x
Reference in New Issue
Block a user