[+] Netease Login page

This commit is contained in:
2025-11-21 21:53:28 +08:00
parent 9eef604c74
commit b2eaeb7b8d
4 changed files with 153 additions and 5 deletions
+2 -1
View File
@@ -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: {
+9 -4
View File
@@ -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(是不是被下架了)')
+104
View File
@@ -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>
+38
View File
@@ -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}`)
}