Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions interface.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export interface RequestBaseConfig {
randomCNIP?: boolean // Whether to use a random Chinese IP address filled in X-Real-IP
}

export type LoginPlatform = 'web' | 'pc' | 'ios' | 'iphone' | 'mobile' | string

export interface MultiPageConfig {
limit?: string | number
offset?: string | number
Expand Down Expand Up @@ -272,14 +274,21 @@ export function batch(
): Promise<Response>

export function captcha_sent(
params: { phone: string; ctcode?: number | string } & RequestBaseConfig,
params: {
phone: string
ctcode?: number | string
countrycode?: number | string
platform?: LoginPlatform
} & RequestBaseConfig,
): Promise<Response>

export function captcha_verify(
params: {
ctcode?: number | string
countrycode?: number | string
phone: number | string
captcha: string
platform?: LoginPlatform
} & RequestBaseConfig,
): Promise<Response>

Expand Down Expand Up @@ -691,6 +700,7 @@ export function login_cellphone(
phone: number | string
countrycode?: number | string
password: string
platform?: LoginPlatform
} & RequestBaseConfig,
): Promise<Response>

Expand All @@ -699,6 +709,7 @@ export function login_cellphone(
phone: number | string
countrycode?: number | string
md5_password: string
platform?: LoginPlatform
} & RequestBaseConfig,
): Promise<Response>

Expand All @@ -707,6 +718,7 @@ export function login_cellphone(
phone: number | string
countrycode?: number | string
captcha: number | string
platform?: LoginPlatform
} & RequestBaseConfig,
): Promise<Response>

Expand Down Expand Up @@ -1501,18 +1513,26 @@ export function topic_detail_event_hot(
} & RequestBaseConfig,
): Promise<Response>

export function login_qr_key(params: RequestBaseConfig): Promise<Response>
export function login_qr_key(
params: { platform?: LoginPlatform; lastUnikey?: string } & RequestBaseConfig,
): Promise<Response>

export function login_qr_create(
params: {
key?: number | string
qrimg?: boolean | string
platform?: LoginPlatform
chainId?: string
} & RequestBaseConfig,
): Promise<Response>

export function login_qr_check(
params: {
key?: number | string
platform?: LoginPlatform
chainId?: string
secureCaptcha?: string
ydDeviceToken?: string
} & RequestBaseConfig,
): Promise<Response>

Expand Down
20 changes: 19 additions & 1 deletion module/captcha_sent.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
// 发送验证码

const createOption = require('../util/option.js')
const {
createMobileEapiOption,
isMobilePlatform,
} = require('../util/mobile.js')
module.exports = (query, request) => {
if (isMobilePlatform(query)) {
const data = {
ctcode: query.ctcode || query.countrycode || '86',
cellphone: query.phone,
os: 'iOS',
fromPage: query.fromPage || 'RN',
rnBundleVersion: query.rnBundleVersion || '0.0.5',
rnBundleName: query.rnBundleName || 'new-rn-login',
verifyId: 1,
e_r: query.e_r === undefined ? true : query.e_r,
}
return request(`/api/sms/captcha/sent`, data, createMobileEapiOption(query))
}

const data = {
ctcode: query.ctcode || '86',
ctcode: query.ctcode || query.countrycode || '86',
secrete: 'music_middleuser_pclogin',
cellphone: query.phone,
}
Expand Down
25 changes: 24 additions & 1 deletion module/captcha_verify.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
// 校验验证码

const createOption = require('../util/option.js')
const {
createMobileEapiOption,
isMobilePlatform,
} = require('../util/mobile.js')
module.exports = (query, request) => {
if (isMobilePlatform(query)) {
const data = {
ctcode: query.ctcode || query.countrycode || '86',
cellphone: query.phone,
captcha: query.captcha,
os: 'iOS',
fromPage: query.fromPage || 'RN',
rnBundleVersion: query.rnBundleVersion || '0.0.5',
rnBundleName: query.rnBundleName || 'new-rn-login',
verifyId: 1,
e_r: query.e_r === undefined ? true : query.e_r,
}
return request(
`/api/sms/captcha/verify`,
data,
createMobileEapiOption(query),
)
}

const data = {
ctcode: query.ctcode || '86',
ctcode: query.ctcode || query.countrycode || '86',
cellphone: query.phone,
captcha: query.captcha,
}
Expand Down
24 changes: 20 additions & 4 deletions module/login_cellphone.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,38 @@
const CryptoJS = require('crypto-js')

const createOption = require('../util/option.js')
const {
createMobileEapiOption,
isMobilePlatform,
} = require('../util/mobile.js')
module.exports = async (query, request) => {
const countrycode = query.countrycode || query.ctcode || '86'
const data = {
type: '1',
https: 'true',
phone: query.phone,
countrycode: query.countrycode || '86',
captcha: query.captcha,
countrycode,
[query.captcha ? 'captcha' : 'password']: query.captcha
? query.captcha
: query.md5_password || CryptoJS.MD5(query.password).toString(),
remember: 'true',
}
const mobile = isMobilePlatform(query)

if (mobile) {
data.rememberLogin = 'true'
data.os = 'iOS'
data.fromPage = query.fromPage || 'RN'
data.rnBundleVersion = query.rnBundleVersion || '0.0.5'
data.rnBundleName = query.rnBundleName || 'new-rn-login'
data.verifyId = 1
data.e_r = query.e_r === undefined ? true : query.e_r
}

let result = await request(
`/api/w/login/cellphone`,
mobile ? `/api/login/cellphone` : `/api/w/login/cellphone`,
data,
createOption(query, 'weapi'),
mobile ? createMobileEapiOption(query) : createOption(query, 'weapi'),
)

if (result.body.code === 200) {
Expand Down
76 changes: 66 additions & 10 deletions module/login_qr_check.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,85 @@
const createOption = require('../util/option.js')
const { generateChainId } = require('../util/index')
const {
cookieHeaderToList,
createWebQrOption,
mergeCookieLists,
} = require('../util/web_qr_login.js')

const qrLoginChainIds =
globalThis.__neteaseQrLoginChainIds ||
(globalThis.__neteaseQrLoginChainIds = new Map())

module.exports = async (query, request) => {
const platform = query.platform || 'web'
const chainId =
query.chainId ||
qrLoginChainIds.get(String(query.key)) ||
(platform === 'web' ? generateChainId(query.cookie || '') : '')
Comment on lines +14 to +18

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using a global Map (qrLoginChainIds) to store chainId values can lead to a memory leak if clients generate QR codes but never poll or complete the login flow. To prevent this, we should store the chainId along with a timestamp and periodically clean up expired entries.

To support this safely, retrieve the chainId by checking if the cached value is an object.

  const platform = query.platform || 'web'
  const cached = qrLoginChainIds.get(String(query.key))
  const chainId =
    query.chainId ||
    (cached && typeof cached === 'object' ? cached.chainId : cached) ||
    (platform === 'web' ? generateChainId(query.cookie || '') : '')

const data = {
key: query.key,
type: 3,
type: platform === 'web' ? 1 : 3,
}
if (platform === 'web') {
data.noCheckToken = true
if (query.secureCaptcha) data.secureCaptcha = query.secureCaptcha
data.ydDeviceToken = query.ydDeviceToken || ''
}

let option = createOption(query, '')
if (platform === 'web') {
option = createWebQrOption(query, {
'X-loginMethod': 'QrCode',
'x-login-chain-id': chainId,
})
}

try {
let result = await request(
`/api/login/qrcode/client/login`,
data,
createOption(query),
)
let result = await request(`/api/login/qrcode/client/login`, data, option)
let responseCookies = result.cookie
if (platform === 'web') {
responseCookies = mergeCookieLists(
option.webQrCookieSet,
result.cookie,
cookieHeaderToList(result.body.cookie),
)
}
const responseCookie = result.body.cookie || responseCookies.join(';')
if ([800, 803].includes(result.body.code)) {
qrLoginChainIds.delete(String(query.key))
}
result = {
status: 200,
body: {
...result.body,
cookie: result.cookie.join(';'),
cookie: responseCookie,
},
cookie: result.cookie,
cookie: responseCookies,
}
return result
} catch (error) {
if ([800, 803].includes(error.body && error.body.code)) {
qrLoginChainIds.delete(String(query.key))
}
let body = error.body || {}
let cookies = error.cookie || []
if (platform === 'web') {
cookies = mergeCookieLists(
option.webQrCookieSet,
error.cookie,
cookieHeaderToList(error.body && error.body.cookie),
)
if (error.body) {
body = {
...error.body,
cookie: error.body.cookie || cookies.join(';'),
}
}
}
return {
status: 200,
body: {},
cookie: result.cookie,
body,
cookie: cookies,
}
}
}
25 changes: 16 additions & 9 deletions module/login_qr_create.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
const QRCode = require('qrcode')
const { generateChainId } = require('../util/index')

const qrLoginChainIds =
globalThis.__neteaseQrLoginChainIds ||
(globalThis.__neteaseQrLoginChainIds = new Map())

module.exports = (query) => {
return new Promise(async (resolve) => {
const platform = query.platform || 'pc'
const platform = query.platform || 'web'
const cookie = query.cookie || ''
const chainId =
platform === 'web' ? query.chainId || generateChainId(cookie) : ''

// 构建基础URL
let url = `https://music.163.com/login?codekey=${query.key}`

// 如果是web平台,则添加chainId参数

if (platform === 'web') {
const chainId = generateChainId(cookie)
url += `&chainId=${chainId}`
if (query.key && chainId) {
qrLoginChainIds.set(String(query.key), chainId)
}
Comment on lines +15 to 17

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

To prevent memory leaks from abandoned QR code sessions, store the chainId with a creation timestamp and periodically evict entries older than 5 minutes when the map grows.

    if (query.key && chainId) {
      qrLoginChainIds.set(String(query.key), { chainId, timestamp: Date.now() })
      if (qrLoginChainIds.size > 100) {
        const now = Date.now()
        for (const [k, v] of qrLoginChainIds.entries()) {
          if (!v || !v.timestamp || now - v.timestamp > 300000) {
            qrLoginChainIds.delete(k)
          }
        }
      }
    }


// 构建基础URL
let url =
platform === 'web'
? `https://music.163.com/st/platform/scanlogin?codekey=${query.key}&chainId=${encodeURIComponent(chainId)}&hdw_device=web&hdw_appid=web&hitExp=1`
: `https://music.163.com/login?codekey=${query.key}`
return resolve({
code: 200,
status: 200,
Expand All @@ -23,6 +29,7 @@ module.exports = (query) => {
data: {
qrurl: url,
qrimg: query.qrimg ? await QRCode.toDataURL(url) : '',
chainId,
},
},
})
Expand Down
26 changes: 19 additions & 7 deletions module/login_qr_key.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
const createOption = require('../util/option.js')
const {
createWebQrOption,
mergeCookieLists,
} = require('../util/web_qr_login.js')
module.exports = async (query, request) => {
const platform = query.platform || 'web'
const data = {
type: 3,
type: platform === 'web' ? 1 : 3,
}
const result = await request(
`/api/login/qrcode/unikey`,
data,
createOption(query),
)
if (platform === 'web') {
data.noCheckToken = true
if (query.lastUnikey) data.lastUnikey = query.lastUnikey
}
const option =
platform === 'web' ? createWebQrOption(query) : createOption(query, '')

const result = await request(`/api/login/qrcode/unikey`, data, option)
const cookies =
platform === 'web'
? mergeCookieLists(option.webQrCookieSet, result.cookie)
: result.cookie
return {
status: 200,
body: {
data: result.body,
code: 200,
},
cookie: result.cookie,
cookie: cookies,
}
}
Loading