From 1050f9e0dcbe34c5c9e6195477498bfbc02b8cec Mon Sep 17 00:00:00 2001 From: He Hang Date: Wed, 1 Nov 2023 15:47:36 +0800 Subject: [PATCH] Login all in one --- .../login/{CloudMusicLogin.vue => Login.vue} | 85 +++++++------- web/src/components/login/MiguMusicLogin.vue | 108 ------------------ web/src/components/login/QQMusicLogin.vue | 50 -------- web/src/main.ts | 7 +- web/src/stores/play.ts | 4 + web/src/stores/setting.ts | 2 +- web/src/utils/api/api.ts | 23 ++-- web/src/utils/api/cloud.ts | 72 +++++++++--- web/src/utils/api/migu.ts | 63 +++++----- web/src/utils/api/qq.ts | 40 +++++-- web/src/utils/type.ts | 4 +- web/src/utils/utils.ts | 5 +- web/src/views/setting.vue | 39 ++----- 13 files changed, 199 insertions(+), 303 deletions(-) rename web/src/components/login/{CloudMusicLogin.vue => Login.vue} (62%) delete mode 100644 web/src/components/login/MiguMusicLogin.vue delete mode 100644 web/src/components/login/QQMusicLogin.vue diff --git a/web/src/components/login/CloudMusicLogin.vue b/web/src/components/login/Login.vue similarity index 62% rename from web/src/components/login/CloudMusicLogin.vue rename to web/src/components/login/Login.vue index 43a63ea..8ff1307 100644 --- a/web/src/components/login/CloudMusicLogin.vue +++ b/web/src/components/login/Login.vue @@ -1,15 +1,25 @@ -

使用 网易云音乐APP 扫码登录

+

diff --git a/web/src/components/login/QQMusicLogin.vue b/web/src/components/login/QQMusicLogin.vue deleted file mode 100644 index 13d4410..0000000 --- a/web/src/components/login/QQMusicLogin.vue +++ /dev/null @@ -1,50 +0,0 @@ - - - diff --git a/web/src/main.ts b/web/src/main.ts index 4d70257..ca5d9cb 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -2,14 +2,15 @@ import { createApp } from 'vue'; import { createPinia } from 'pinia'; import 'default-passive-events'; -import App from './App.vue'; -import router from './router'; - import 'element-plus/theme-chalk/dark/css-vars.css'; import 'element-plus/theme-chalk/el-message-box.css'; import 'element-plus/theme-chalk/el-radio-group.css'; import 'element-plus/theme-chalk/el-radio.css'; import 'element-plus/theme-chalk/el-checkbox.css'; + +import App from './App.vue'; +import router from './router'; + import './style/main.css'; const app = createApp(App); diff --git a/web/src/stores/play.ts b/web/src/stores/play.ts index cc8a355..aafa3d5 100644 --- a/web/src/stores/play.ts +++ b/web/src/stores/play.ts @@ -244,6 +244,10 @@ export const usePlayStore = defineStore('play', { const m = await api.musicDetail(music); if (!m || !m.url) { console.log('fail', music); + if (this.playStatus.playing) { + this.add([this.music]); + return; + } const musicIndex = this.musicList.findIndex( n => music && music.id == n.id && music.type == n.type ); diff --git a/web/src/stores/setting.ts b/web/src/stores/setting.ts index 012fd63..02f8e25 100644 --- a/web/src/stores/setting.ts +++ b/web/src/stores/setting.ts @@ -437,7 +437,7 @@ export const useSettingStore = defineStore('setting', { const userInfo = userInfoCache[musicType as MusicType]; if (this.userInfo[musicType as MusicType]) { this.userInfo[musicType as MusicType].cookie = - userInfo?.cookie || {}; + userInfo?.cookie || ''; this.setUserInfo(musicType as MusicType); } }); diff --git a/web/src/utils/api/api.ts b/web/src/utils/api/api.ts index cc8af51..cded706 100644 --- a/web/src/utils/api/api.ts +++ b/web/src/utils/api/api.ts @@ -4,7 +4,8 @@ import { Playlist, RankingType, Lyric, - UserInfo + UserInfo, + LoginStatus } from '../type'; import * as cloud from './cloud'; import * as qq from './qq'; @@ -65,7 +66,7 @@ export async function recommend( export async function playlistDetail( type: MusicType, id: string, - cookies?: Record + cookies?: Record | string ): Promise<{ total: number; list: Music[]; @@ -149,7 +150,7 @@ export async function musicDetail(music: Music): Promise { export async function qrCodeKey(type: MusicType): Promise<{ key: string; - url?: string; + url: string; } | null> { const func = getFunction(type, 'qrCodeKey'); try { @@ -160,25 +161,25 @@ export async function qrCodeKey(type: MusicType): Promise<{ return null; } -export async function qrCodeState( +export async function loginStatus( type: MusicType, key: string ): Promise<{ - state: number | string; - cookie: string; -} | null> { - const func = getFunction(type, 'qrCodeState'); + status: LoginStatus; + user?: UserInfo; +}> { + const func = getFunction(type, 'loginStatus'); try { if (func) return await func(key); } catch (e) { console.error(e); } - return null; + return { status: 'fail' }; } export async function userInfo( type: MusicType, - cookie: Record + cookie: Record | string ): Promise { const func = getFunction(type, 'userInfo'); try { @@ -191,7 +192,7 @@ export async function userInfo( export async function yours( type: MusicType, - cookie: Record, + cookie: Record | string, offset: number ): Promise<{ total: number; diff --git a/web/src/utils/api/cloud.ts b/web/src/utils/api/cloud.ts index 6630171..e6d6c32 100644 --- a/web/src/utils/api/cloud.ts +++ b/web/src/utils/api/cloud.ts @@ -1,10 +1,26 @@ import * as CryptoJS from 'crypto-js'; -import { Music, MusicType, Playlist, RankingType, UserInfo } from '../type'; +import { + Music, + MusicType, + Playlist, + RankingType, + UserInfo, + LoginStatus +} from '../type'; import { httpProxy } from '../http'; -import { millisecond2Duration } from '../utils'; +import { millisecond2Duration, parseCookie } from '../utils'; import RankingHotImage from '../../assets/images/ranking-hot.jpg'; import RankingNewImage from '../../assets/images/ranking-new.jpg'; import RankingSoarImage from '../../assets/images/ranking-soar.jpg'; +const QRCode = () => import('qrcode'); + +var qrcodeGenerate: (text: string) => Promise = async ( + text: string +) => { + const qrcode = await QRCode(); + qrcodeGenerate = qrcode.toDataURL as any; + return qrcodeGenerate(text); +}; function aesEncrypt(plain: string, key: string): string { var iv = '0102030405060708'; @@ -509,11 +525,7 @@ export async function musicById(id: string): Promise { method: 'POST', data: paramData, headers: { - // Cookie: 'os=ios;MUSIC_U=', - // Referer: 'https://music.163.com', ContentType: 'application/x-www-form-urlencoded' - // UserAgent: - // 'Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1', } }); const ret = await res.json(); @@ -559,8 +571,8 @@ export async function parseLink(link: string) { export async function qrCodeKey(): Promise<{ key: string; - url?: string; -}> { + url: string; +} | null> { var url = 'https://music.163.com/weapi/login/qrcode/unikey?csrf_token='; const data = { type: 1, @@ -585,14 +597,20 @@ export async function qrCodeKey(): Promise<{ } }); const ret = await res.json(); - return { - key: (ret && ret.unikey) || '' - }; + if (ret && ret.unikey) { + return { + key: ret.unikey, + url: await qrcodeGenerate( + 'http://music.163.com/login?codekey=' + ret.unikey + ) + }; + } + return null; } -export async function qrCodeState(key: string): Promise<{ - state: number | string; - cookie: string; +export async function loginStatus(key: string): Promise<{ + status: LoginStatus; + user?: UserInfo; }> { var url = 'https://music.163.com/weapi/login/qrcode/client/login?csrf_token='; const data = { @@ -620,10 +638,28 @@ export async function qrCodeState(key: string): Promise<{ } }); const ret = await res.json(); - return { - state: (ret && ret.code) || 0, - cookie: res.headers.get('Set-Cookie-Renamed') || '' - }; + switch (ret && ret.code) { + case 803: + const cookies = parseCookie(res.headers.get('Set-Cookie-Renamed') || ''); + const user = await userInfo(cookies); + if (user && user.id) { + user.cookie = { + __csrf: cookies['__csrf'] || '', + MUSIC_U: cookies['MUSIC_U'] || '', + uid: user.id.toString() + }; + return { + status: 'success', + user + }; + } + break; + case 801: + return { status: 'waiting' }; + case 802: + return { status: 'authorizing' }; + } + return { status: 'fail' }; } export async function userInfo( diff --git a/web/src/utils/api/migu.ts b/web/src/utils/api/migu.ts index b5de8d7..c979923 100644 --- a/web/src/utils/api/migu.ts +++ b/web/src/utils/api/migu.ts @@ -1,9 +1,17 @@ import { httpProxy, parseHttpProxyAddress } from '../http'; -import { Music, MusicType, Playlist, RankingType, UserInfo } from '../type'; +import { + LoginStatus, + Music, + MusicType, + Playlist, + RankingType, + UserInfo +} from '../type'; import { duration2Millisecond, durationTrim, formatCookies, + parseCookie, second2Duration } from '../utils'; import RankingHotImage from '../../assets/images/ranking-hot.jpg'; @@ -54,9 +62,7 @@ export async function search(keywords: string, offset: number) { }; } -export async function daily( - cookies: Record -): Promise { +export async function daily(cookies: string): Promise { const user = await userInfo(cookies); return { id: 'daily', @@ -67,7 +73,7 @@ export async function daily( } export async function yours( - cookies: Record, + cookies: string, _offset: number ): Promise<{ total: number; @@ -162,7 +168,7 @@ export async function playlistInfo(id: string) { }; } -export async function dailyPlayList(cookies: Record) { +export async function dailyPlayList(cookies: string) { const headers: Record = { Referer: 'https://music.migu.cn/v3', Cookie: formatCookies(cookies) @@ -220,10 +226,7 @@ export async function dailyPlayList(cookies: Record) { }; } -export async function digitalDetail( - id: string, - _cookies?: Record -) { +export async function digitalDetail(id: string, _cookies?: string) { var url = 'https://m.music.migu.cn/migumusic/h5/digitalAlbum/info?pageSize=500&digitalAlbumId=' + id; @@ -273,10 +276,7 @@ export async function digitalDetail( }; } -export async function playlistDetail( - id: string, - cookies?: Record -) { +export async function playlistDetail(id: string, cookies?: string) { if (id == 'daily') { return dailyPlayList(cookies!); } @@ -610,9 +610,9 @@ export async function qrCodeKey(): Promise<{ }; } -export async function qrCodeState(key: string): Promise<{ - state: number | string; - cookie: string; +export async function loginStatus(key: string): Promise<{ + status: LoginStatus; + user?: UserInfo; }> { let res = await httpProxy({ url: 'https://passport.migu.cn/api/qrcWeb/qrcquery', @@ -624,8 +624,8 @@ export async function qrCodeState(key: string): Promise<{ } }); let ret = await res.json(); - if (ret.status == 2000 && ret.result && ret.result.token) { - const lastCookie = res.headers.get('Set-Cookie-Renamed') || ''; + if (ret && ret.status == 2000 && ret.result && ret.result.token) { + const lastCookie = parseCookie(res.headers.get('Set-Cookie-Renamed') || ''); res = await httpProxy({ url: ret.result.redirectURL + '?token=' + ret.result.token, method: 'GET', @@ -635,21 +635,28 @@ export async function qrCodeState(key: string): Promise<{ } }); if (res.ok) { - return { - state: 2000, - cookie: lastCookie + ';' + res.headers.get('Set-Cookie-Renamed') || '' - }; + const cookie = formatCookies({ + ...lastCookie, + ...parseCookie(res.headers.get('Set-Cookie-Renamed') || '') + }); + const user = await userInfo(cookie); + if (user && user.id) { + user.cookie = cookie; + return { + status: 'success', + user + }; + } } + } else if (ret && ret.status == 4074) { + return { status: 'waiting' }; } return { - state: ret.status, - cookie: '' + status: 'fail' }; } -export async function userInfo( - cookies: Record -): Promise { +export async function userInfo(cookies: string): Promise { var res = await httpProxy({ url: 'https://music.migu.cn/v3/api/user/getUserInfo', method: 'GET', diff --git a/web/src/utils/api/qq.ts b/web/src/utils/api/qq.ts index 5b7c133..fc70733 100644 --- a/web/src/utils/api/qq.ts +++ b/web/src/utils/api/qq.ts @@ -1,5 +1,12 @@ import { httpProxy, parseHttpProxyAddress } from '../http'; -import { Music, Playlist, MusicType, RankingType, UserInfo } from '../type'; +import { + Music, + Playlist, + MusicType, + RankingType, + UserInfo, + LoginStatus +} from '../type'; import { formatCookies, generateGuid, millisecond2Duration } from '../utils'; import RankingHotImage from '../../assets/images/ranking-hot.jpg'; import RankingNewImage from '../../assets/images/ranking-new.jpg'; @@ -128,9 +135,7 @@ function removeExtJson(jsonStr: string) { return jsonStr; } -export async function daily( - cookies: Record -): Promise { +export async function daily(cookies: string): Promise { const cookie = formatCookies(cookies); var url = 'https://c.y.qq.com/node/musicmac/v6/index.html'; var res = await httpProxy({ @@ -156,7 +161,7 @@ export async function daily( return null; } -export async function yours(cookies: Record): Promise<{ +export async function yours(cookies: string): Promise<{ total: number; list: Playlist[]; }> { @@ -707,12 +712,25 @@ export async function parseLink(link: string) { return null; } -export async function userInfo( - cookies: Record -): Promise { - const cookie = Object.keys(cookies) - .map(m => `${m}=${cookies[m]}`) - .join('; '); +export async function loginStatus(cookie: string): Promise<{ + status: LoginStatus; + user?: UserInfo; +}> { + const user = await userInfo(cookie); + if (user && user.id) { + user.cookie = cookie; + return { + status: 'success', + user + }; + } + return { + status: 'fail' + }; +} + +export async function userInfo(cookies: string): Promise { + const cookie = formatCookies(cookies); var res = await httpProxy({ url: 'https://c.y.qq.com/rsc/fcgi-bin/fcg_get_profile_homepage.fcg?cid=205360838&reqfrom=1', method: 'GET', diff --git a/web/src/utils/type.ts b/web/src/utils/type.ts index 55c101f..d7d64b7 100644 --- a/web/src/utils/type.ts +++ b/web/src/utils/type.ts @@ -117,9 +117,11 @@ export interface ShortcutKey { status?: string; } +export type LoginStatus = 'success' | 'fail' | 'waiting' | 'authorizing'; + export interface UserInfo { id: string; name?: string; image?: string; - cookie?: Record; + cookie?: Record | string; } diff --git a/web/src/utils/utils.ts b/web/src/utils/utils.ts index 4c17c69..190d138 100644 --- a/web/src/utils/utils.ts +++ b/web/src/utils/utils.ts @@ -149,8 +149,11 @@ export function parseCookie(cookie: string): Record { return cookieObj; } -export function formatCookies(cookies: Record): string { +export function formatCookies( + cookies: Record | string +): string { if (!cookies) return ''; + if (typeof cookies === 'string') return cookies; return Object.keys(cookies) .map(m => `${m}=${cookies[m]}`) .join('; '); diff --git a/web/src/views/setting.vue b/web/src/views/setting.vue index 0a12823..dacf89a 100644 --- a/web/src/views/setting.vue +++ b/web/src/views/setting.vue @@ -10,9 +10,7 @@ import MiguMusicImage from '../assets/images/migu-music.webp'; import { useSettingStore } from '../stores/setting'; import { CloseType, MusicType, ShortcutType } from '../utils/type'; import { ElMessageBox } from 'element-plus'; -import CloudMusicLogin from '../components/login/CloudMusicLogin.vue'; -import MiguMusicLogin from '../components/login/MiguMusicLogin.vue'; -import QQMusicLogin from '../components/login/QQMusicLogin.vue'; +import Login from '../components/login/Login.vue'; const { currentRoute, replace } = useRouter(); const subItems = [ @@ -109,28 +107,6 @@ function setItemsIdTitle() { const onSettingScroll = useDebounceFn(checkSettingScroll, 300); -// function elementInView( -// ele: Element | null, -// tableTop?: number, -// onlyTop?: boolean -// ) { -// if (!ele || !tableEle.value) return false; -// if (tableTop == undefined) { -// tableTop = -// tableEle.value.parentElement?.parentElement?.getBoundingClientRect() -// ?.top || 0; -// const { top, bottom } = ele.getBoundingClientRect(); -// if (onlyTop) return top <= tableTop && bottom > tableTop; -// else { -// const pageBottom = -// tableEle.value.parentElement?.parentElement?.getBoundingClientRect() -// ?.bottom || 0; -// return top >= tableTop && bottom <= pageBottom; -// } -// } -// return false; -// } - function checkSettingScroll() { if (scrollByRouter) { scrollByRouter = false; @@ -159,20 +135,20 @@ function loginSuccess() { function login(type: MusicType) { let title = '网易云'; - let components = null; + let text = ''; switch (type) { case MusicType.QQMusic: title = 'QQ'; - components = QQMusicLogin; + text = '从QQ音乐获取cookie并填写'; break; case MusicType.MiguMusic: title = '咪咕'; - components = MiguMusicLogin; + text = '打开 咪咕音乐app
点击顶部菜单图标,然后找到扫一扫并点击'; break; default: title = '网易云'; - components = CloudMusicLogin; + text = '使用 网易云音乐APP 扫码登录'; break; } ElMessageBox({ @@ -181,7 +157,10 @@ function login(type: MusicType) { showCancelButton: false, showConfirmButton: false, closeOnClickModal: false, - message: h(components, { + message: h(Login, { + type, + qrcode: type != MusicType.QQMusic, + text, onLogon: loginSuccess }) }).catch(() => {});