mirror of
https://github.com/iLay1678/i-tools.git
synced 2025-07-04 11:12:14 +08:00
爱拓工具箱
This commit is contained in:
parent
946beb4f36
commit
3cd1e1b935
4
.github/workflows/docker-build.yml
vendored
4
.github/workflows/docker-build.yml
vendored
@ -73,8 +73,8 @@ jobs:
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/${{ env.OWNER_LC }}/alipan-tv-token:latest
|
||||
${{ vars.DOCKERHUB_USERNAME }}/alipan-tv-token:latest
|
||||
ghcr.io/${{ env.OWNER_LC }}/i-tools:latest
|
||||
${{ vars.DOCKERHUB_USERNAME }}/i-tools:latest
|
||||
|
||||
- name: Post build cleanup
|
||||
run: docker builder prune --force
|
||||
|
50
README.md
50
README.md
@ -1,3 +1,7 @@
|
||||
|
||||
# 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)
|
||||
|
||||
# 阿里云盘TV版token获取与刷新
|
||||
|
||||
## 路由:
|
||||
@ -10,52 +14,8 @@
|
||||
# Docker部署教程
|
||||
## 直接部署
|
||||
```
|
||||
docker run --name=alipan-tv-token -d -p 3000:3000 ilay/alipan-tv-token:latest
|
||||
docker run --name=alipan-tv-token -d -p 3000:3000 ilay/i-tools:latest
|
||||
```
|
||||
## docker compose 配合alist
|
||||
### docker-compose.yaml
|
||||
```
|
||||
version: '3.3'
|
||||
services:
|
||||
alist:
|
||||
image: 'xhofe/alist:latest'
|
||||
container_name: alist
|
||||
volumes:
|
||||
- '/etc/alist:/opt/alist/data'
|
||||
ports:
|
||||
- '5244:5244'
|
||||
environment:
|
||||
- PUID=0
|
||||
- PGID=0
|
||||
- UMASK=022
|
||||
- TZ=Asia/Shanghai
|
||||
restart: always
|
||||
networks:
|
||||
- alist-net
|
||||
alipan-tv-token:
|
||||
image: 'ilay/alipan-tv-token:latest'
|
||||
container_name: alipan-tv-token
|
||||
ports:
|
||||
- '3000:3000'
|
||||
networks:
|
||||
- alist-net
|
||||
restart: always
|
||||
networks:
|
||||
alist-net:
|
||||
driver: bridge
|
||||
```
|
||||
|
||||
### 在 alist 中配置 alipan-tv-token 的地址
|
||||
|
||||
在 alist 的阿里云盘open配置中:
|
||||
- Oauth令牌链接填写: `http://alipan-tv-token:3000/refresh`
|
||||
- 如果需要获取 token,访问: `http://yaoIp:3000`
|
||||
|
||||
|
||||
# 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)
|
||||
|
||||
|
||||
# Cloudflare Pages 部署教程
|
||||
## 1. Fork 项目仓库
|
||||
1. 点击右上角的 "Fork" 按钮创建你自己的副本
|
||||
|
2
app.vue
2
app.vue
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<head>
|
||||
<title>阿里云盘 TV 版令牌生成器</title>
|
||||
<title>爱拓工具箱</title>
|
||||
</head>
|
||||
<div>
|
||||
<NuxtPage />
|
||||
|
187
pages/alipan-tv-token/index.vue
Normal file
187
pages/alipan-tv-token/index.vue
Normal file
@ -0,0 +1,187 @@
|
||||
<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 mb-8">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-800">与「阿里云盘」连接</h1>
|
||||
</div>
|
||||
|
||||
<div class="space-y-8">
|
||||
<div class="space-y-2">
|
||||
<div class="relative">
|
||||
<a-textarea :rows="5" id="accessToken" :value="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">
|
||||
<a-textarea :rows="4" id="refreshToken" :value="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>
|
||||
|
||||
<!-- 文档部分 -->
|
||||
<div class="mx-auto w-full max-w-3xl bg-white shadow-lg rounded-lg p-8">
|
||||
<div class="prose prose-sm max-w-none">
|
||||
<h2 class="text-xl font-bold mb-4">使用说明</h2>
|
||||
<h3 class="text-lg font-semibold mt-6 mb-2">路由:</h3>
|
||||
<ul class="list-disc pl-5 mb-4">
|
||||
<li><code class="bg-gray-100 px-1 rounded">/api/oauth/alipan/token</code> 刷新令牌</li>
|
||||
</ul>
|
||||
<h3 class="text-lg font-semibold mt-6 mb-2">在 alist 中配置 alipan-tv-token 的地址</h3>
|
||||
<p>在 alist 的阿里云盘open配置中:</p>
|
||||
<ul class="list-disc pl-5">
|
||||
<li>Oauth令牌链接填写: <code
|
||||
class="bg-gray-100 px-1 rounded">http://alipan-tv-token:3000/api/oauth/alipan/token</code></li>
|
||||
<li>如果需要获取 token,访问: <code class="bg-gray-100 px-1 rounded">http://你的IP:3000/alipan-tv-token</code></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<a-modal v-model:open="isNoticeOpen" title="使用说明" @ok="closeNotice" :maskClosable="false" :closable="false"
|
||||
:keyboard="false">
|
||||
<p>本工具能帮助你一键获取「阿里云盘TV版」的刷新令牌,完全免费。TV接口能绕过三方应用权益包的速率限制,但前提你得是SVIP。</p>
|
||||
<template #footer>
|
||||
<a-button type="primary" @click="closeNotice">我知道了</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } 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)
|
||||
const accessToken = ref('')
|
||||
const refreshToken = ref('')
|
||||
|
||||
async function generateAuthUrl() {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const data = await $fetch("/api/alipan-tv-token/generate_qr", { method: "POST" })
|
||||
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 data = await $fetch("/api/alipan-tv-token/check_status/" + sid)
|
||||
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('登录失败,请刷新页面重试')
|
||||
} else if (data.status === "QRCodeExpired") {
|
||||
message.error('链接过期,请刷新页面重试')
|
||||
} 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(() => {
|
||||
// 先显示弹框
|
||||
isNoticeOpen.value = true
|
||||
if (!hasGenerated.value) {
|
||||
generateAuthUrl()
|
||||
hasGenerated.value = true
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
216
pages/index.vue
216
pages/index.vue
@ -1,176 +1,64 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 py-12 px-4 flex flex-col">
|
||||
<div class="max-w-4xl mx-auto flex-grow">
|
||||
<div class="text-center mb-12">
|
||||
<h1 class="text-4xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-blue-600 to-blue-400">
|
||||
爱拓工具箱
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<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://hub.docker.com/r/ilay/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" />
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 px-4">
|
||||
<a-card hoverable class="transform transition-all duration-300 hover:-translate-y-1 hover:shadow-lg min-h-[200px]">
|
||||
<a-card-meta>
|
||||
<template #title>
|
||||
<div class="flex items-center space-x-2 mb-4">
|
||||
<span class="text-xl font-medium">阿里云盘TV授权</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #description>
|
||||
<div class="mt-4 flex flex-col justify-between h-full">
|
||||
<p class="text-gray-600 mb-8 text-base">获取阿里云盘TV端的授权令牌</p>
|
||||
<NuxtLink to="/alipan-tv-token" target="_blank"
|
||||
class="inline-block px-8 py-2.5 bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-lg hover:from-blue-600 hover:to-blue-700 transition-all shadow-sm">
|
||||
立即使用
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
</a-card-meta>
|
||||
</a-card>
|
||||
|
||||
<a-card hoverable class="transform transition-all duration-300 hover:-translate-y-1 hover:shadow-lg min-h-[200px]">
|
||||
<a-card-meta>
|
||||
<template #title>
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="text-lg font-medium text-gray-400">敬请期待</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #description>
|
||||
<div class="mt-4">
|
||||
<p class="text-gray-400">更多工具正在开发中...</p>
|
||||
</div>
|
||||
</template>
|
||||
</a-card-meta>
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- GitHub 链接放在页面最底部 -->
|
||||
<div class="max-w-4xl mx-auto w-full mt-12">
|
||||
<div class="text-center border-t pt-6">
|
||||
<a href="https://github.com/iLay1678/i-tools"
|
||||
target="_blank"
|
||||
class="inline-flex items-center space-x-2 text-gray-500 hover:text-gray-700 transition-colors">
|
||||
<svg height="20" width="20" viewBox="0 0 16 16" class="fill-current">
|
||||
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="space-y-8">
|
||||
<div class="space-y-2">
|
||||
<div class="relative">
|
||||
<a-textarea :rows="5" id="accessToken" :value="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">
|
||||
<a-textarea :rows="4" id="refreshToken" :value="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:open="isNoticeOpen" title="使用说明" @ok="closeNotice" :maskClosable="false" :closable="false"
|
||||
:keyboard="false">
|
||||
<p>本工具能帮助你一键获取「阿里云盘TV版」的刷新令牌,完全免费。TV接口能绕过三方应用权益包的速率限制,但前提你得是SVIP。</p>
|
||||
<template #footer>
|
||||
<a-button type="primary" @click="closeNotice">我知道了</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } 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)
|
||||
const accessToken = ref('')
|
||||
const refreshToken = ref('')
|
||||
|
||||
async function generateAuthUrl() {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const data = await $fetch("/generate_qr", { method: "POST" })
|
||||
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 data = await $fetch("/check_status/" + sid)
|
||||
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('登录失败,请刷新页面重试')
|
||||
} else if (data.status === "QRCodeExpired") {
|
||||
message.error('链接过期,请刷新页面重试')
|
||||
} 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(() => {
|
||||
// 先显示弹框
|
||||
isNoticeOpen.value = true
|
||||
if (!hasGenerated.value) {
|
||||
generateAuthUrl()
|
||||
hasGenerated.value = true
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
Binary file not shown.
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 270 KiB |
43
server/api/oauth/alipan/token.ts
Normal file
43
server/api/oauth/alipan/token.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { decrypt, getParams } from "@/utils/decode";
|
||||
import { defineEventHandler } from 'h3'
|
||||
import type { ApiResponse, TokenResponseEncrypt } from '~/types/api'
|
||||
|
||||
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 tokenData = await $fetch<TokenResponseEncrypt>('http://api.extscreen.com/aliyundrive/v3/token', {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: JSON.stringify(sendData)
|
||||
});
|
||||
const plainData = decrypt(tokenData.data.ciphertext, tokenData.data.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 {
|
||||
code: 500,
|
||||
message: error.message,
|
||||
data: null
|
||||
} as ApiResponse
|
||||
}
|
||||
})
|
@ -32,8 +32,7 @@ export default defineEventHandler(async (event) => {
|
||||
refresh_token: tokenInfo.refresh_token,
|
||||
expires_in: tokenInfo.expires_in
|
||||
};
|
||||
// 直接返回响应数据
|
||||
return tokenData;
|
||||
|
||||
|
||||
} catch (error:any) {
|
||||
return {
|
||||
|
Loading…
x
Reference in New Issue
Block a user