[+] Netease Login page
This commit is contained in:
+2
-1
@@ -24,7 +24,8 @@ export const API = {
|
||||
|
||||
netease: {
|
||||
startImport: async (link: string) => await post('/api/import/netease/start', { link }),
|
||||
checkProgress: async (id: string) => await post('/api/import/netease/progress', { id })
|
||||
checkProgress: async (id: string) => await post('/api/import/netease/progress', { id }),
|
||||
checkLogin: async () => await post('/admin/netease-login', {})
|
||||
},
|
||||
|
||||
user: {
|
||||
|
||||
@@ -11,7 +11,10 @@ import path from 'path'
|
||||
|
||||
const CACHE_DIR = path.resolve('static/audio')
|
||||
|
||||
const checkNetease = async () => (await ne.login_status({}) as any).body.data.account
|
||||
const neCookie = async () => (await db.collection('server_props')
|
||||
.findOne({ name: 'global_settings' }))?.netease_login_cookie
|
||||
?.let((cookie: string) => ({ cookie }))
|
||||
const checkNetease = async () => (await ne.login_status({ ...await neCookie() }) as any).body.data.account
|
||||
const eToString = (e: any) => e.message ?? e.body.message
|
||||
|
||||
/**
|
||||
@@ -96,8 +99,9 @@ export const getLyricsProcessed = cached('lyrics_processed',
|
||||
export const getSongUrl = async (id: number | string) => {
|
||||
await fs.mkdir(CACHE_DIR, { recursive: true })
|
||||
|
||||
const filePath = path.join(CACHE_DIR, `${id}/standard.mp3`)
|
||||
const publicUrl = `/audio/${id}/standard.mp3`
|
||||
const level = 'exhigh'
|
||||
const filePath = path.join(CACHE_DIR, `${id}/${level}.mp3`)
|
||||
const publicUrl = `/audio/${id}/${level}.mp3`
|
||||
if (await fs.exists(filePath)) return publicUrl
|
||||
|
||||
// Check netease api status
|
||||
@@ -105,8 +109,9 @@ export const getSongUrl = async (id: number | string) => {
|
||||
|
||||
console.log(`Downloading song ${id}...`)
|
||||
// @ts-ignore
|
||||
const res = await ne.song_url_v1({ id: id.toString(), level: 'standard' })
|
||||
const res = await ne.song_url_v1({ id: id.toString(), level, ...await neCookie() })
|
||||
const url = (res.body as any).data?.[0]?.url
|
||||
console.log(`Download URL: ${url}`)
|
||||
|
||||
if (!url) throw error(404, '没获取到歌曲 URL(是不是被下架了)')
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
<!--
|
||||
|
||||
|
||||
WARNING
|
||||
|
||||
This page is vibe-coded. It's not a part of the regular UI intended for users anyway so I don't really want to clean it up :(
|
||||
|
||||
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { API } from '$lib/client';
|
||||
import { fade, scale } from 'svelte/transition';
|
||||
import AppBar from "../../../components/appbar/AppBar.svelte";
|
||||
import { Layer } from "m3-svelte";
|
||||
|
||||
let status = $state<'loading' | 'waiting_scan' | 'waiting_confirm' | 'success' | 'error'>('loading');
|
||||
let qrImg = $state('');
|
||||
let errorMessage = $state('');
|
||||
let timer: any;
|
||||
|
||||
async function check() {
|
||||
try {
|
||||
const res = await API.netease.checkLogin();
|
||||
if (res.code === 801) {
|
||||
if (status !== 'waiting_scan') {
|
||||
status = 'waiting_scan';
|
||||
qrImg = res.img;
|
||||
}
|
||||
} else if (res.code === 802) {
|
||||
status = 'waiting_confirm';
|
||||
} else if (res.code === 803) {
|
||||
status = 'success';
|
||||
clearInterval(timer);
|
||||
// Redirect or show success
|
||||
setTimeout(() => {
|
||||
history.back();
|
||||
}, 1500);
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
errorMessage = e.message || 'Unknown error';
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
check();
|
||||
timer = setInterval(check, 2000);
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (timer) clearInterval(timer);
|
||||
});
|
||||
</script>
|
||||
|
||||
<AppBar title="网易云登录" />
|
||||
|
||||
<div class="vbox flex-1 cbox p-content">
|
||||
<div class="vbox items-center gap-24px p-32px rounded-24px relative overflow-hidden w-full max-w-400px" in:scale={{ duration: 400, start: 0.95 }}>
|
||||
<div class="vbox items-center gap-8px z-1">
|
||||
<h1 class="m3-font-headline-medium mfg-on-surface m-0">扫码登录</h1>
|
||||
<p class="m3-font-body-medium mfg-on-surface-variant m-0">请使用网易云音乐 APP 扫码</p>
|
||||
</div>
|
||||
|
||||
<div class="cbox min-h-200px w-full z-1">
|
||||
{#if status === 'loading'}
|
||||
<div class="vbox items-center gap-16px" in:fade>
|
||||
<div class="i-material-symbols:sync animate-spin text-40px mfg-primary"></div>
|
||||
<p class="m3-font-body-medium mfg-on-surface-variant">正在生成二维码...</p>
|
||||
</div>
|
||||
{:else if status === 'waiting_scan'}
|
||||
<div class="p-16px bg-white rounded-16px shadow-sm" in:fade>
|
||||
<img src={qrImg} alt="QR Code" class="block size-180px rounded-8px" />
|
||||
</div>
|
||||
{:else if status === 'waiting_confirm'}
|
||||
<div class="vbox items-center gap-16px" in:fade>
|
||||
<div class="size-80px rounded-full cbox mbg-primary-container mfg-primary">
|
||||
<div class="i-material-symbols:check-circle-outline text-48px"></div>
|
||||
</div>
|
||||
<div class="vbox items-center">
|
||||
<p class="m3-font-title-large mfg-on-surface font-bold">已扫描</p>
|
||||
<p class="m3-font-body-medium mfg-on-surface-variant">请在手机上确认登录</p>
|
||||
</div>
|
||||
</div>
|
||||
{:else if status === 'success'}
|
||||
<div class="vbox items-center gap-16px" in:fade>
|
||||
<div class="size-80px rounded-full cbox mbg-tertiary-container mfg-tertiary">
|
||||
<div class="i-material-symbols:check text-48px"></div>
|
||||
</div>
|
||||
<div class="vbox items-center">
|
||||
<p class="m3-font-title-large mfg-on-surface font-bold">登录成功</p>
|
||||
</div>
|
||||
</div>
|
||||
{:else if status === 'error'}
|
||||
<div class="vbox items-center gap-16px" in:fade>
|
||||
<div class="size-80px rounded-full cbox mbg-error-container mfg-error">
|
||||
<div class="i-material-symbols:error-outline text-48px"></div>
|
||||
</div>
|
||||
<p class="m3-font-body-medium mfg-error text-center">错误: {errorMessage}</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,38 @@
|
||||
import * as ne from '@neteasecloudmusicapienhanced/api'
|
||||
import { error, json } from '@sveltejs/kit'
|
||||
import { loginWithSyncCode } from '$lib/server/user'
|
||||
import type { RequestHandler } from './$types'
|
||||
import { db } from '$lib/server/db'
|
||||
|
||||
const globalSession = {
|
||||
key: "",
|
||||
qrImg: "",
|
||||
}
|
||||
|
||||
async function createQr() {
|
||||
globalSession.key = (await ne.login_qr_key({}) as any).body.data.unikey
|
||||
globalSession.qrImg = (await ne.login_qr_create({ key: globalSession.key, qrimg: true }) as any).body.data.qrimg
|
||||
}
|
||||
|
||||
export const POST: RequestHandler = async ({ request, cookies }) => {
|
||||
if (!globalSession.key) await createQr()
|
||||
|
||||
// Check key validity
|
||||
const check = (await ne.login_qr_check({ key: globalSession.key }) as any).body
|
||||
// 800: 过期, 801: 等待扫码, 802: 等待确认, 803: 登录成功
|
||||
if (check.code === 800) {
|
||||
await createQr()
|
||||
check.code = 801
|
||||
}
|
||||
if (check.code === 801) return json({ code: 801, img: globalSession.qrImg })
|
||||
if (check.code === 802) return json({ code: 802 })
|
||||
if (check.code === 803) {
|
||||
await db.collection('server_props').updateOne(
|
||||
{ name: 'global_settings' },
|
||||
{ $set: { netease_login_cookie: check.cookie } },
|
||||
{ upsert: true }
|
||||
)
|
||||
return json({ code: 803, cookie: check.cookie })
|
||||
}
|
||||
throw error(500, `未知返回值 ${check.code}`)
|
||||
}
|
||||
Reference in New Issue
Block a user