Compare commits

..

5 Commits

Author SHA1 Message Date
8f4873f2e2
perf: 更新接口定义 2026-08-01 19:11:37 +08:00
a8b45a8b8b
Merge branch 'pr/225' 2026-08-01 17:34:54 +08:00
1a70281c76
ci: 更新问题管理工作流 2026-08-01 17:20:19 +08:00
tanjuntao
f5f4ff6eda feat: add complete current-user event listing 2026-08-01 10:25:00 +08:00
tanjuntao
eb1b5ba0ea feat: add event privacy endpoint 2026-08-01 10:25:00 +08:00
11 changed files with 698 additions and 37 deletions

View File

@ -19,10 +19,11 @@ jobs:
with: with:
issue-number: ${{ github.event.issue.number }} issue-number: ${{ github.event.issue.number }}
body: | body: |
## 快速链接 感谢您在该仓库提出问题!我们非常重视您的反馈,并希望尽快为您提供帮助。
在我们处理您的问题之前,您可以先查看以下资源: 如果您在一周左右的时间里没有收到我们的回复,请在下方留言,以便我们注意到。我们的大部分审核都是志愿者,有时难免会有疏漏。
你可能会想要尝试以下方法来获得更快的帮助:
- 📖 查看[项目文档](https://neteasecloudmusicapienhanced.js.org) - 📖 查看[项目文档](https://neteasecloudmusicapienhanced.js.org)
- 💬 加入[QQ交流群](https://qm.qq.com/q/TpeP9Uv2yk) - 💬 加入[QQ交流群](https://qm.qq.com/q/TpeP9Uv2yk)
- 🔍 搜索[现有issues](https://github.com/neteasecloudmusicapienhanced/api-enhanced/issues) 看是否有类似问题 - 🔍 搜索[现有issues](https://github.com/neteasecloudmusicapienhanced/api-enhanced/issues) 看是否有类似问题

33
.github/workflows/opencode.yml vendored Normal file
View File

@ -0,0 +1,33 @@
name: opencode
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
jobs:
opencode:
if: |
contains(github.event.comment.body, ' /oc') ||
startsWith(github.event.comment.body, '/oc') ||
contains(github.event.comment.body, ' /opencode') ||
startsWith(github.event.comment.body, '/opencode')
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
pull-requests: read
issues: read
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Run opencode
uses: anomalyco/opencode/github@latest
env:
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
with:
model: opencode/deepseek-v4-flash-free

253
interface.d.ts vendored
View File

@ -37,6 +37,33 @@ export const enum SubAction {
unsub = 0, unsub = 0,
} }
export function ad_get(
params: { type_ids?: string } & RequestBaseConfig,
): Promise<Response>
export function ad_listening_rights(
params: RequestBaseConfig,
): Promise<Response>
export function ad_listening_rights_gain(
params: {
reqUid?: string
creativeType?: string | number
exposureTime?: string | number
clickTime?: string | number
rightsGainMethod?: string | number
rightsGainDuration?: string | number
extraRightsGainMethod?: string | number
extraRightsGainDuration?: string | number
nextRightsGainDuration?: string | number
source?: string
rightsExtJson?: string
appInfo?: string
installed?: string | number
type_ids?: string
} & RequestBaseConfig,
): Promise<Response>
export function activate_init_profile( export function activate_init_profile(
params: { nickname: string } & RequestBaseConfig, params: { nickname: string } & RequestBaseConfig,
): Promise<Response> ): Promise<Response>
@ -119,6 +146,14 @@ export function album_sublist(
params: MultiPageConfig & RequestBaseConfig, params: MultiPageConfig & RequestBaseConfig,
): Promise<Response> ): Promise<Response>
export function api(
params: {
uri: string
data?: string | Record<string, unknown>
crypto?: string
} & RequestBaseConfig,
): Promise<Response>
export function artist_album( export function artist_album(
params: { id: string | number } & MultiPageConfig & RequestBaseConfig, params: { id: string | number } & MultiPageConfig & RequestBaseConfig,
): Promise<Response> ): Promise<Response>
@ -271,10 +306,21 @@ export function batch(
params: { [index: string]: unknown } & RequestBaseConfig, params: { [index: string]: unknown } & RequestBaseConfig,
): Promise<Response> ): Promise<Response>
export function captcha_safe_sent(
params: { ctcode?: number | string } & RequestBaseConfig,
): Promise<Response>
export function captcha_sent( export function captcha_sent(
params: { phone: string; ctcode?: number | string } & RequestBaseConfig, params: { phone: string; ctcode?: number | string } & RequestBaseConfig,
): Promise<Response> ): Promise<Response>
export function captcha_sent_v1(
params: {
phone: number | string
ctcode?: number | string
} & RequestBaseConfig,
): Promise<Response>
export function captcha_verify( export function captcha_verify(
params: { params: {
ctcode?: number | string ctcode?: number | string
@ -387,6 +433,14 @@ export function comment(
} & RequestBaseConfig, } & RequestBaseConfig,
): Promise<Response> ): Promise<Response>
export function comment_add(
params: {
id: string | number
type: CommentType
content: string
} & RequestBaseConfig,
): Promise<Response>
export function comment_album( export function comment_album(
params: { params: {
id: string | number id: string | number
@ -395,6 +449,14 @@ export function comment_album(
RequestBaseConfig, RequestBaseConfig,
): Promise<Response> ): Promise<Response>
export function comment_delete(
params: {
id: string | number
type: CommentType
cid: string | number
} & RequestBaseConfig,
): Promise<Response>
export function comment_dj( export function comment_dj(
params: { params: {
id: string | number id: string | number
@ -469,6 +531,15 @@ export function comment_playlist(
RequestBaseConfig, RequestBaseConfig,
): Promise<Response> ): Promise<Response>
export function comment_reply(
params: {
id: string | number
type: CommentType
cid: string | number
content: string
} & RequestBaseConfig,
): Promise<Response>
export function comment_video( export function comment_video(
params: { params: {
id: string | number id: string | number
@ -490,6 +561,21 @@ export function daily_signin(
params: { type?: DailySigninType } & RequestBaseConfig, params: { type?: DailySigninType } & RequestBaseConfig,
): Promise<Response> ): Promise<Response>
export function decrypt(
params: {
crypto?: string
data?: string
hexString?: string
isReq?: boolean | string
} & RequestBaseConfig,
): Promise<Response>
export function device_kickoff(
params: { deviceKey: string | number; captcha?: string } & RequestBaseConfig,
): Promise<Response>
export function device_list(params: RequestBaseConfig): Promise<Response>
export function digitalAlbum_ordering( export function digitalAlbum_ordering(
params: { params: {
payment: string payment: string
@ -618,6 +704,10 @@ export function dj_toplist_popular(
params: { limit?: string | number } & RequestBaseConfig, params: { limit?: string | number } & RequestBaseConfig,
): Promise<Response> ): Promise<Response>
export function eapi_decrypt(
params: { hexString: string; isReq?: boolean | string } & RequestBaseConfig,
): Promise<Response>
export function event( export function event(
params: { pagesize?: number; lasttime?: number } & RequestBaseConfig, params: { pagesize?: number; lasttime?: number } & RequestBaseConfig,
): Promise<Response> ): Promise<Response>
@ -626,6 +716,20 @@ export function event_del(
params: { evId: string | number } & RequestBaseConfig, params: { evId: string | number } & RequestBaseConfig,
): Promise<Response> ): Promise<Response>
export const enum EventPrivacy {
everyone = 0,
following = 1,
onlyMe = 2,
mutualFollowing = 6,
}
export function event_privacy(
params: {
evId: string | number
privacy: EventPrivacy
} & RequestBaseConfig,
): Promise<Response>
export function event_forward( export function event_forward(
params: { params: {
forwords: string forwords: string
@ -665,6 +769,8 @@ export function hot_topic(
params: MultiPageConfig & RequestBaseConfig, params: MultiPageConfig & RequestBaseConfig,
): Promise<Response> ): Promise<Response>
export function inner_version(params: RequestBaseConfig): Promise<Response>
export function like( export function like(
params: { params: {
like?: 'true' | 'false' | boolean like?: 'true' | 'false' | boolean
@ -678,6 +784,10 @@ export function likelist(
params: { uid: string | number } & RequestBaseConfig, params: { uid: string | number } & RequestBaseConfig,
): Promise<Response> ): Promise<Response>
export function listentogether_status(
params: RequestBaseConfig,
): Promise<Response>
export function login( export function login(
params: { email: string; password: string } & RequestBaseConfig, params: { email: string; password: string } & RequestBaseConfig,
): Promise<Response> ): Promise<Response>
@ -724,6 +834,17 @@ export function lyric_new(
params: { id: string | number } & RequestBaseConfig, params: { id: string | number } & RequestBaseConfig,
): Promise<Response> ): Promise<Response>
export function middle_play_do_lottery(
params: {
activityId?: string | number
drawCount?: string | number
} & RequestBaseConfig,
): Promise<Response>
export function middle_play_lottery_remain_chance(
params: { activityId?: string | number } & RequestBaseConfig,
): Promise<Response>
export function msg_comments( export function msg_comments(
params: { params: {
uid: string | number uid: string | number
@ -946,6 +1067,18 @@ export function register_cellphone(
} & RequestBaseConfig, } & RequestBaseConfig,
): Promise<Response> ): Promise<Response>
export function register_checktoken_v2(
params: { refresh?: boolean | string } & RequestBaseConfig,
): Promise<Response>
export function register_checktoken_v3(
params: { refresh?: boolean | string } & RequestBaseConfig,
): Promise<Response>
export function register_xeapikey(
params: { deviceId?: string; currentKeyVersion?: string } & RequestBaseConfig,
): Promise<Response>
export function related_allvideo( export function related_allvideo(
params: { id: string | number } & RequestBaseConfig, params: { id: string | number } & RequestBaseConfig,
): Promise<Response> ): Promise<Response>
@ -954,6 +1087,67 @@ export function related_playlist(
params: { id: string | number } & RequestBaseConfig, params: { id: string | number } & RequestBaseConfig,
): Promise<Response> ): Promise<Response>
export function relay_play_state_submit(
params: {
id: string | number
sessionId?: string
progress?: string | number
playMode?: string
type?: string
} & RequestBaseConfig,
): Promise<Response>
export function rep_ugc_activity_collect(
params: { activityId?: string | number } & RequestBaseConfig,
): Promise<Response>
export function rep_ugc_activity_get(
params: RequestBaseConfig,
): Promise<Response>
export function rep_ugc_exam_info_get(
params: { examType: string | number } & RequestBaseConfig,
): Promise<Response>
export function rep_ugc_exam_question_single_get(
params: {
examType: string | number
taskId: string | number
} & RequestBaseConfig,
): Promise<Response>
export function rep_ugc_exam_result_get(
params: {
examType: string | number
taskId: string | number
} & RequestBaseConfig,
): Promise<Response>
export function rep_ugc_exam_start(
params: { examType: string | number } & RequestBaseConfig,
): Promise<Response>
export function rep_ugc_exam_submit(
params: {
examType: string | number
taskId: string | number
questionId: string | number
answer: string
} & RequestBaseConfig,
): Promise<Response>
declare const rep_ugc_user_collect_vip: (
params: { activityId?: string | number } & RequestBaseConfig,
) => Promise<Response>
export { rep_ugc_user_collect_vip as 'rep_ugc_user_collect-vip' }
export function rep_ugc_user_get(params: RequestBaseConfig): Promise<Response>
export function rep_ugc_user_sign(params: RequestBaseConfig): Promise<Response>
export function rep_ugc_user_vip(params: RequestBaseConfig): Promise<Response>
export const enum ResourceType { export const enum ResourceType {
mv = 1, mv = 1,
dj = 4, dj = 4,
@ -979,6 +1173,22 @@ export function scrobble(
} & RequestBaseConfig, } & RequestBaseConfig,
): Promise<Response> ): Promise<Response>
export function scrobble_v1(
params: {
id: string | number
time: string | number
total?: string | number
sourceid?: string | number
sourceId?: string | number
source?: string
name?: string
artist?: string
bitrate?: string | number
level?: string
vip?: boolean | string
} & RequestBaseConfig,
): Promise<Response>
export function search( export function search(
params: { params: {
keywords: string keywords: string
@ -1267,6 +1477,25 @@ export function user_event(
} & RequestBaseConfig, } & RequestBaseConfig,
): Promise<Response> ): Promise<Response>
export interface UserEventAllResponse {
code: number
events: Record<string, unknown>[]
/** Upstream account statistic; it can include events no longer returned. */
size: number | null
/** Number of unique events in `events`. */
retrievedCount: number
/** Positive difference between `size` and `retrievedCount`. */
unavailableCount: number | null
sizeMismatch: boolean | null
pageCount: number
more: false
lasttime: string | number | null
}
export function user_event_all(
params: RequestBaseConfig,
): Promise<Response<UserEventAllResponse>>
export function user_followeds( export function user_followeds(
params: { params: {
uid: string | number uid: string | number
@ -2292,6 +2521,10 @@ export function song_chorus(
} & RequestBaseConfig, } & RequestBaseConfig,
): Promise<Response> ): Promise<Response>
export function song_cloud_download(
params: { id: string | number } & RequestBaseConfig,
): Promise<Response>
export function song_creators( export function song_creators(
params: { params: {
id: string | number id: string | number
@ -2403,6 +2636,18 @@ export function threshold_detail_get(
params: RequestBaseConfig, params: RequestBaseConfig,
): Promise<Response> ): Promise<Response>
export function thinktank_audit_resource_detail(
params: { type?: string | number } & RequestBaseConfig,
): Promise<Response>
export function thinktank_audit_resource_update(
params: {
type?: string | number
taskId: string | number
judgement: string | number
} & RequestBaseConfig,
): Promise<Response>
export function toplist_detail_v2(params: RequestBaseConfig): Promise<Response> export function toplist_detail_v2(params: RequestBaseConfig): Promise<Response>
export function ugc_album_get( export function ugc_album_get(
@ -2529,6 +2774,14 @@ export function verify_qrcodestatus(
export function vip_sign(params: RequestBaseConfig): Promise<Response> export function vip_sign(params: RequestBaseConfig): Promise<Response>
export function vip_sign_detail(
params: { timestamp: string | number } & RequestBaseConfig,
): Promise<Response>
export function vip_sign_history(
params: { type?: string | number } & RequestBaseConfig,
): Promise<Response>
export function vip_sign_info(params: RequestBaseConfig): Promise<Response> export function vip_sign_info(params: RequestBaseConfig): Promise<Response>
export function vip_tasks_v1( export function vip_tasks_v1(

34
module/event_privacy.js Normal file
View File

@ -0,0 +1,34 @@
// 修改本人动态的可见权限
const createOption = require('../util/option.js')
const PRIVACY_VALUES = new Set([0, 1, 2, 6])
module.exports = (query, request) => {
const eventId = String(query.evId ?? '').trim()
const rawPrivacy = String(query.privacy ?? '').trim()
const privacy = Number(rawPrivacy)
if (
!eventId ||
!rawPrivacy ||
!Number.isInteger(privacy) ||
!PRIVACY_VALUES.has(privacy)
) {
return Promise.resolve({
status: 400,
body: {
code: 400,
message: 'evId is required and privacy must be one of 0, 1, 2, 6',
},
cookie: [],
})
}
const data = {
eventId,
privacy,
}
return request(`/api/event/privacy/op`, data, createOption(query))
}

View File

@ -4,9 +4,10 @@ const createOption = require('../util/option.js')
module.exports = (query, request) => { module.exports = (query, request) => {
const data = { const data = {
getcounts: true, getcounts: true,
time: query.lasttime || -1, time: query.lasttime ?? -1,
limit: query.limit || 30, limit: query.limit ?? 30,
total: false, total: false,
fromRN: 'true',
} }
return request(`/api/event/get/${query.uid}`, data, createOption(query)) return request(`/api/event/get/${query.uid}`, data, createOption(query))
} }

135
module/user_event_all.js Normal file
View File

@ -0,0 +1,135 @@
// 获取当前登录用户可被上游枚举的全部动态
const userAccount = require('./user_account.js')
const userEvent = require('./user_event.js')
const PAGE_SIZE = 100
const MAX_PAGES = 1000
const errorResponse = (message, cookie = []) => ({
status: 502,
body: {
code: 502,
message,
},
cookie,
})
module.exports = async (query, request) => {
const accountResult = await userAccount(query, request)
const uid =
accountResult.body?.account?.id || accountResult.body?.profile?.userId
if (!uid) {
if (
accountResult.status !== 200 ||
(accountResult.body?.code && accountResult.body.code !== 200)
) {
return accountResult
}
return {
status: 401,
body: {
code: 401,
message: 'A valid login cookie is required',
},
cookie: accountResult.cookie || [],
}
}
const cookies = [...(accountResult.cookie || [])]
const events = []
const eventIds = new Set()
const cursors = new Set()
let lasttime = -1
let more = true
let pageCount = 0
let size = null
while (more) {
pageCount += 1
const pageResult = await userEvent(
{
...query,
uid,
lasttime,
limit: PAGE_SIZE,
},
request,
)
cookies.push(...(pageResult.cookie || []))
if (pageResult.status !== 200 || pageResult.body?.code !== 200) {
return {
...pageResult,
cookie: cookies,
}
}
if (pageCount === 1) {
const reportedSize = pageResult.body.size
const numericSize = Number(reportedSize)
size =
reportedSize != null && Number.isFinite(numericSize)
? numericSize
: null
}
for (const event of pageResult.body.events || []) {
if (event?.id == null) {
events.push(event)
continue
}
const eventId = String(event.id)
if (!eventIds.has(eventId)) {
eventIds.add(eventId)
events.push(event)
}
}
more = Boolean(pageResult.body.more)
lasttime = pageResult.body.lasttime
if (more) {
const cursor = String(lasttime ?? '')
if (!cursor || cursors.has(cursor)) {
return errorResponse(
'Upstream event pagination cursor stalled',
cookies,
)
}
cursors.add(cursor)
}
if (more && pageCount >= MAX_PAGES) {
return errorResponse(
`Upstream event pagination exceeded ${MAX_PAGES} pages`,
cookies,
)
}
}
const retrievedCount = events.length
const unavailableCount =
size == null ? null : Math.max(size - retrievedCount, 0)
return {
status: 200,
body: {
code: 200,
events,
size,
retrievedCount,
unavailableCount,
sizeMismatch: size == null ? null : size !== retrievedCount,
pageCount,
more: false,
lasttime: lasttime ?? null,
},
cookie: cookies,
}
}

View File

@ -1,10 +1,11 @@
import { banner, lyric } from '@neteasecloudmusicapienhanced/api' import { banner, lyric } from '@neteasecloudmusicapienhanced/api'
import { logger } from '../util/logger.js' import type { Response } from '@neteasecloudmusicapienhanced/api'
banner({ type: 0 }).then((res) => { import logger from '../util/logger.js'
banner({ type: 0 }).then((res: Response) => {
logger.info(res) logger.info(res)
}) })
lyric({ lyric({
id: '33894312', id: '33894312',
}).then((res) => { }).then((res: Response) => {
logger.info(res) logger.info(res)
}) })

View File

@ -826,6 +826,8 @@ tags: 歌单标签
`lasttime` : 返回数据的 `lasttime` ,默认-1,传入上一次返回结果的 lasttime,将会返回下一页的数据 `lasttime` : 返回数据的 `lasttime` ,默认-1,传入上一次返回结果的 lasttime,将会返回下一页的数据
接口会传入官方客户端使用的 `fromRN=true`,登录用户读取自己的动态时可获得网易云允许本人查看的非公开动态。返回的 `size` 是网易云账户侧统计值,不保证等于分页后实际可获取的动态数量。
**接口地址 :** `/user/event` **接口地址 :** `/user/event`
**调用例子 :** `/user/event?uid=32953014` `/user/event?uid=32953014&limit=1&lasttime=1558011138743` **调用例子 :** `/user/event?uid=32953014` `/user/event?uid=32953014&limit=1&lasttime=1558011138743`
@ -843,6 +845,38 @@ tags: 歌单标签
41、21 分享视频 41、21 分享视频
``` ```
### 获取当前登录用户的全部可枚举动态
说明 : 登录后调用此接口,会组合 `/user/account``/user/event` 的原子能力:先从 Cookie 取得当前用户 id再自动跟随 `lasttime` 游标读取至 `more=false`。接口返回网易云允许当前用户本人查看的公开及非公开动态,不能读取其他用户的私密动态。
本接口不接受 `limit``lasttime`,一次请求会完成全部上游分页。账号动态较多时,请预留足够的请求时间。
数量字段说明:
- `size`:网易云返回的账户统计数量,可能包含已删除、被屏蔽、资源失效或旧类型且不再下发的记录。
- `retrievedCount`:本次实际取得的唯一动态数量,始终等于 `events.length`
- `unavailableCount``size - retrievedCount` 的正差值;缺失记录没有返回 id无法继续读取或修改。
- `sizeMismatch``size``retrievedCount` 是否不一致。若上游未返回 `size`,相关字段为 `null`
- `pageCount`:本次实际请求的上游分页数量。
每条动态的 `privacySetting` 表示当前可见权限:`0` 为所有人,`1` 为我关注的人,`2` 为仅自己,`6` 为互相关注的人。
**接口地址 :** `/user/event/all`
**调用例子 :** `/user/event/all`
### 修改动态可见权限
说明 : 登录后调用此接口,可以修改当前账号本人发布的单条动态的可见权限。此接口只负责一次原子修改;上游没有批量修改接口。如需批量操作,可先调用 `/user/event/all` 并按 `privacySetting` 筛选,再由调用方逐条调用本接口,同时自行处理限速、失败重试和部分成功。
**必选参数 :** `evId` : 动态 id
`privacy` : 目标可见权限。`0` 为所有人,`1` 为我关注的人,`2` 为仅自己,`6` 为互相关注的人
**接口地址 :** `/event/privacy`
**调用例子 :** `/event/privacy?evId=6712917601&privacy=0`
### 转发用户动态 ### 转发用户动态
说明 : 登录后调用此接口 ,可以转发用户动态 说明 : 登录后调用此接口 ,可以转发用户动态

View File

@ -0,0 +1,34 @@
const assert = require('assert')
const eventPrivacy = require('../module/event_privacy')
describe('event privacy modules', () => {
it('updates event privacy with the official client parameters', async () => {
let captured
const expected = { status: 200, body: { code: 200 }, cookie: [] }
const result = await eventPrivacy(
{ evId: '123456789012345678', privacy: '0', cookie: { MUSIC_U: 'x' } },
async (uri, data, options) => {
captured = { uri, data, options }
return expected
},
)
assert.strictEqual(result, expected)
assert.strictEqual(captured.uri, '/api/event/privacy/op')
assert.deepStrictEqual(captured.data, {
eventId: '123456789012345678',
privacy: 0,
})
assert.strictEqual(captured.options.crypto, '')
})
it('rejects unknown event privacy values before making a request', async () => {
let requested = false
const result = await eventPrivacy({ evId: '1', privacy: '3' }, async () => {
requested = true
})
assert.strictEqual(requested, false)
assert.strictEqual(result.status, 400)
})
})

145
test/user_event_all.test.js Normal file
View File

@ -0,0 +1,145 @@
const assert = require('assert')
const userEventAll = require('../module/user_event_all')
describe('all current user events module', () => {
it('resolves the current user and aggregates every upstream page', async () => {
const calls = []
const pages = [
{
status: 200,
body: {
code: 200,
events: [{ id: 1 }, { id: 2 }],
size: 5,
more: true,
lasttime: 100,
},
cookie: ['page=1'],
},
{
status: 200,
body: {
code: 200,
events: [{ id: 2 }, { id: 3 }],
size: 5,
more: true,
lasttime: 50,
},
cookie: ['page=2'],
},
{
status: 200,
body: {
code: 200,
events: [{ id: 4 }],
size: 1,
more: false,
lasttime: 0,
},
cookie: ['page=3'],
},
]
const result = await userEventAll(
{ cookie: { MUSIC_U: 'x' } },
async (uri, data, options) => {
calls.push({ uri, data, options })
if (uri === '/api/nuser/account/get') {
return {
status: 200,
body: { code: 200, account: { id: 42 } },
cookie: ['account=1'],
}
}
return pages.shift()
},
)
assert.deepStrictEqual(
result.body.events.map((event) => event.id),
[1, 2, 3, 4],
)
assert.strictEqual(result.body.size, 5)
assert.strictEqual(result.body.retrievedCount, 4)
assert.strictEqual(result.body.unavailableCount, 1)
assert.strictEqual(result.body.sizeMismatch, true)
assert.strictEqual(result.body.pageCount, 3)
assert.strictEqual(result.body.more, false)
assert.deepStrictEqual(result.cookie, [
'account=1',
'page=1',
'page=2',
'page=3',
])
assert.strictEqual(calls[0].uri, '/api/nuser/account/get')
assert.strictEqual(calls[0].options.crypto, 'weapi')
assert.deepStrictEqual(
calls.slice(1).map((call) => call.data),
[
{
getcounts: true,
time: -1,
limit: 100,
total: false,
fromRN: 'true',
},
{
getcounts: true,
time: 100,
limit: 100,
total: false,
fromRN: 'true',
},
{
getcounts: true,
time: 50,
limit: 100,
total: false,
fromRN: 'true',
},
],
)
})
it('requires a valid login cookie', async () => {
const result = await userEventAll({}, async () => ({
status: 200,
body: { code: 200, account: null, profile: null },
cookie: [],
}))
assert.strictEqual(result.status, 401)
assert.strictEqual(result.body.code, 401)
})
it('fails instead of returning a partial list when the cursor stalls', async () => {
let page = 0
const result = await userEventAll({}, async (uri) => {
if (uri === '/api/nuser/account/get') {
return {
status: 200,
body: { code: 200, account: { id: 42 } },
cookie: [],
}
}
page += 1
return {
status: 200,
body: {
code: 200,
events: [],
size: 1,
more: true,
lasttime: 100,
},
cookie: [],
}
})
assert.strictEqual(page, 2)
assert.strictEqual(result.status, 502)
assert.match(result.body.message, /cursor stalled/)
})
})

View File

@ -1,31 +1,21 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2015", "target": "ES2015",
"module": "commonjs", "module": "node16",
"experimentalDecorators": true, "experimentalDecorators": true,
"ignoreDeprecations": "6.0", "moduleResolution": "node16",
"moduleResolution": "node", "lib": ["esnext", "esnext.asynciterable", "dom"],
"lib": [ "esModuleInterop": true,
"esnext", "allowJs": true,
"esnext.asynciterable", "sourceMap": true,
"dom" "strict": true,
], "noEmit": true,
"esModuleInterop": true, "baseUrl": ".",
"allowJs": true, "paths": {
"sourceMap": true, "~/*": ["./*"],
"strict": true, "@/*": ["./*"],
"noEmit": true, "@neteasecloudmusicapienhanced/api": ["./interface.d.ts"]
"baseUrl": ".", }
"paths": { },
"~/*": [ "exclude": ["node_modules"]
"./*" }
],
"@/*": [
"./*"
]
},
},
"exclude": [
"node_modules"
]
}