diff --git a/packages/core/src/bridge/apis/group-album.ts b/packages/core/src/bridge/apis/group-album.ts index 13d42682..e6a6b86a 100644 --- a/packages/core/src/bridge/apis/group-album.ts +++ b/packages/core/src/bridge/apis/group-album.ts @@ -14,6 +14,7 @@ import type { GetMediaListRequest, GetMediaListResponse, MediaInfo, + UrlInfo, } from '@snowluma/proto-defs/oidb-actions/group-album'; import { uploadImageToGroupAlbum } from '@snowluma/protocol/web/group-album'; import { protobuf_decode, protobuf_encode } from '@snowluma/proton'; @@ -53,6 +54,35 @@ function normalizeAlbumCreator(creator: AlbumCreator | undefined): QunAlbumCreat }; } +function normalizeCoverUrl(url: UrlInfo | null | undefined): QunAlbumCoverUrl | null { + if (!url) return null; + return { + url: url.url ?? '', + width: url.width ?? 0, + height: url.height ?? 0, + }; +} + +function normalizeAlbumCover(cover: MediaInfo | undefined): QunAlbumCover | null { + if (!cover) return null; + const image = cover.image; + return { + type: cover.type ?? 0, + image: image ? { + name: image.name ?? '', + sloc: image.sloc ?? '', + lloc: image.lloc ?? '', + photoUrls: (image.photoUrls ?? []).map((photoUrl) => ({ + spec: photoUrl.spec ?? 0, + url: normalizeCoverUrl(photoUrl.url), + })), + defaultUrl: normalizeCoverUrl(image.defaultUrl), + isGif: image.isGif ?? false, + hasRaw: image.hasRaw ?? false, + } : null, + }; +} + function normalizeQunAlbum(album: GroupAlbumInfoWire): QunAlbumInfo { const normalized: QunAlbumInfo = { album_id: album.albumId ?? '', @@ -63,6 +93,7 @@ function normalizeQunAlbum(album: GroupAlbumInfoWire): QunAlbumInfo { modify_time: uint64ToString(album.modifyTime), last_upload_time: uint64ToString(album.lastUploadTime), upload_number: uint64ToString(album.uploadNumber), + cover: normalizeAlbumCover(album.cover), top_flag: uint64ToString(album.topFlag), busi_type: album.busiType ?? 0, status: album.status ?? 0, @@ -94,6 +125,8 @@ function toLegacyAlbum(album: GroupAlbumInfoWire): GroupAlbumInfo { // Unlike the legacy Qzone HTTP endpoint, it does not rewrite Unicode // emoji into ambiguous [em]...[/em] tags. createnickname: creator?.nick ?? '', + last_upload_time: uint64ToSafeNumber(album.lastUploadTime, 'last_upload_time'), + cover: normalizeAlbumCover(album.cover), }; } @@ -426,6 +459,8 @@ export interface GroupAlbumInfo { owner: string; createuin: string; createnickname: string; + last_upload_time: number; + cover: QunAlbumCover | null; [key: string]: JsonValue; } @@ -446,6 +481,32 @@ export interface QunAlbumCreator { ditto_uin: string; } +export interface QunAlbumCoverUrl extends JsonObject { + url: string; + width: number; + height: number; +} + +export interface QunAlbumCoverPhotoUrl extends JsonObject { + spec: number; + url: QunAlbumCoverUrl | null; +} + +export interface QunAlbumCoverImage extends JsonObject { + name: string; + sloc: string; + lloc: string; + photoUrls: QunAlbumCoverPhotoUrl[]; + defaultUrl: QunAlbumCoverUrl | null; + isGif: boolean; + hasRaw: boolean; +} + +export interface QunAlbumCover extends JsonObject { + type: number; + image: QunAlbumCoverImage | null; +} + export interface QunAlbumInfo { album_id: string; owner: string; @@ -455,6 +516,7 @@ export interface QunAlbumInfo { modify_time: string; last_upload_time: string; upload_number: string; + cover: QunAlbumCover | null; creator?: QunAlbumCreator; top_flag: string; busi_type: number; diff --git a/packages/core/tests/actions/group-album-wire-fixture.ts b/packages/core/tests/actions/group-album-wire-fixture.ts index a6dd4afa..f9b02b50 100644 --- a/packages/core/tests/actions/group-album-wire-fixture.ts +++ b/packages/core/tests/actions/group-album-wire-fixture.ts @@ -1,4 +1,12 @@ -import type { bytes, int_32, pb, pb_repeated } from '@snowluma/proton'; +import type { + bool, + bytes, + int_32, + pb, + pb_repeated, + uint_32, + uint_64, +} from '@snowluma/proton'; interface GroupAlbumListReqDataWireOracle { groupId?: pb<1, string>; @@ -19,6 +27,52 @@ export interface GroupAlbumListRequestWireOracle { extMap?: pb_repeated<10, GroupAlbumListExtWireOracle>; } +interface GroupAlbumCoverUrlWireOracle { + url?: pb<1, string>; + width?: pb<2, uint_32>; + height?: pb<3, uint_32>; +} + +interface GroupAlbumCoverPhotoUrlWireOracle { + spec?: pb<1, uint_32>; + url?: pb<2, GroupAlbumCoverUrlWireOracle>; +} + +interface GroupAlbumCoverImageWireOracle { + name?: pb<1, string>; + sloc?: pb<2, string>; + lloc?: pb<3, string>; + photoUrls?: pb_repeated<4, GroupAlbumCoverPhotoUrlWireOracle>; + defaultUrl?: pb<5, GroupAlbumCoverUrlWireOracle>; + isGif?: pb<6, bool>; + hasRaw?: pb<7, bool>; +} + +interface GroupAlbumCoverWireOracle { + type?: pb<1, uint_32>; + image?: pb<2, GroupAlbumCoverImageWireOracle>; +} + +interface GroupAlbumListItemWireOracle { + albumId?: pb<1, string>; + owner?: pb<2, string>; + name?: pb<3, string>; + description?: pb<4, string>; + createTime?: pb<5, uint_64>; + lastUploadTime?: pb<7, uint_64>; + uploadNumber?: pb<8, uint_64>; + cover?: pb<9, GroupAlbumCoverWireOracle>; +} + +interface GroupAlbumListResponseDataWireOracle { + albumList?: pb_repeated<1, GroupAlbumListItemWireOracle>; +} + +export interface GroupAlbumListResponseWireOracle { + result?: pb<2, int_32>; + data?: pb<4, GroupAlbumListResponseDataWireOracle>; +} + interface GroupAlbumMediaListReqInfoWireOracle { reserved?: pb<4, string>; pageInfo?: pb<5, string>; diff --git a/packages/core/tests/actions/group-album.test.ts b/packages/core/tests/actions/group-album.test.ts index 8730db27..b3dcd37a 100644 --- a/packages/core/tests/actions/group-album.test.ts +++ b/packages/core/tests/actions/group-album.test.ts @@ -8,9 +8,41 @@ import { GroupAlbumApi } from '../../src/bridge/apis/group-album'; import { mockBridge } from './_helpers'; import type { GroupAlbumListRequestWireOracle, + GroupAlbumListResponseWireOracle, GroupAlbumMediaListRequestWireOracle, } from './group-album-wire-fixture'; +function albumListResponseWithCover(): Uint8Array { + return protobuf_encode({ + data: { + albumList: [{ + albumId: 'album-id', + owner: '10001', + name: '测试相册', + description: 'desc', + createTime: 1700000000n, + lastUploadTime: 1700000123n, + uploadNumber: 5n, + cover: { + type: 1, + image: { + name: 'cover.jpg', + sloc: 'small-location', + lloc: 'large-location', + photoUrls: [{ + spec: 3, + url: { url: 'https://example.test/cover-320.jpg', width: 320, height: 180 }, + }], + defaultUrl: { url: 'https://example.test/cover.jpg', width: 1280, height: 720 }, + isGif: false, + hasRaw: true, + }, + }, + }], + }, + }); +} + describe('apis/group-album', () => { it('uses QQ NT AlbumService and puts the pagination cursor in request body field 2', async () => { const bridge = mockBridge(); @@ -70,6 +102,8 @@ describe('apis/group-album', () => { owner: '10001', createuin: '10001', createnickname: '😂[em]e328514[/em]', + last_upload_time: 0, + cover: null, }]); }); @@ -108,6 +142,64 @@ describe('apis/group-album', () => { }); }); + it('decodes the native album cover field for get_qun_album_list', async () => { + const bridge = mockBridge(); + bridge.sendRawPacket.mockResolvedValueOnce({ + success: true, + gotResponse: true, + errorCode: 0, + errorMessage: '', + responseData: Buffer.from(albumListResponseWithCover()), + }); + + const result = await new GroupAlbumApi(bridge as never).listQun(12345); + + expect(result.albumList[0]).toMatchObject({ + album_id: 'album-id', + last_upload_time: '1700000123', + cover: { + type: 1, + image: { + name: 'cover.jpg', + sloc: 'small-location', + lloc: 'large-location', + photoUrls: [{ + spec: 3, + url: { url: 'https://example.test/cover-320.jpg', width: 320, height: 180 }, + }], + defaultUrl: { url: 'https://example.test/cover.jpg', width: 1280, height: 720 }, + isGif: false, + hasRaw: true, + }, + }, + }); + }); + + it('returns cover and last-upload metadata from the legacy album list action', async () => { + const bridge = mockBridge(); + bridge.sendRawPacket.mockResolvedValueOnce({ + success: true, + gotResponse: true, + errorCode: 0, + errorMessage: '', + responseData: Buffer.from(albumListResponseWithCover()), + }); + + const result = await new GroupAlbumApi(bridge as never).list(12345); + + expect(result[0]).toMatchObject({ + id: 'album-id', + last_upload_time: 1700000123, + cover: { + type: 1, + image: { + name: 'cover.jpg', + defaultUrl: { url: 'https://example.test/cover.jpg', width: 1280, height: 720 }, + }, + }, + }); + }); + it('surfaces AlbumService response errors instead of falling back to the lossy web endpoint', async () => { const bridge = mockBridge(); bridge.sendRawPacket.mockResolvedValueOnce({ diff --git a/packages/mcp/src/generated/catalog.json b/packages/mcp/src/generated/catalog.json index 2d8dad48..ed9def79 100644 --- a/packages/mcp/src/generated/catalog.json +++ b/packages/mcp/src/generated/catalog.json @@ -3435,6 +3435,132 @@ "type": "integer", "description": "相册创建时间(unix 秒)" }, + "last_upload_time": { + "type": "integer", + "description": "最后上传时间(unix 秒)" + }, + "cover": { + "type": [ + "object", + "null" + ], + "description": "相册封面;没有封面时为 null", + "properties": { + "type": { + "type": "integer", + "description": "封面媒体类型" + }, + "image": { + "type": [ + "object", + "null" + ], + "description": "封面图片信息", + "properties": { + "name": { + "type": "string", + "description": "图片名称" + }, + "sloc": { + "type": "string", + "description": "图片短定位标识" + }, + "lloc": { + "type": "string", + "description": "图片长定位标识" + }, + "photoUrls": { + "type": "array", + "description": "不同规格的封面地址", + "items": { + "type": "object", + "properties": { + "spec": { + "type": "integer", + "description": "图片规格" + }, + "url": { + "type": [ + "object", + "null" + ], + "properties": { + "url": { + "type": "string", + "description": "封面地址" + }, + "width": { + "type": "integer", + "description": "封面宽度" + }, + "height": { + "type": "integer", + "description": "封面高度" + } + }, + "required": [ + "url", + "width", + "height" + ] + } + }, + "required": [ + "spec", + "url" + ] + } + }, + "defaultUrl": { + "type": [ + "object", + "null" + ], + "properties": { + "url": { + "type": "string", + "description": "封面地址" + }, + "width": { + "type": "integer", + "description": "封面宽度" + }, + "height": { + "type": "integer", + "description": "封面高度" + } + }, + "required": [ + "url", + "width", + "height" + ] + }, + "isGif": { + "type": "boolean", + "description": "是否为动图" + }, + "hasRaw": { + "type": "boolean", + "description": "是否有原图" + } + }, + "required": [ + "name", + "sloc", + "lloc", + "photoUrls", + "defaultUrl", + "isGif", + "hasRaw" + ] + } + }, + "required": [ + "type", + "image" + ] + }, "createuin": { "type": "string", "description": "相册创建者 QQ 号" @@ -3449,6 +3575,8 @@ "name", "picNum", "createTime", + "last_upload_time", + "cover", "createuin", "createnickname" ] @@ -5514,10 +5642,136 @@ "type": "string", "description": "相册创建时间(unix 秒)" }, + "last_upload_time": { + "type": "string", + "description": "最后上传时间(unix 秒)" + }, "upload_number": { "type": "string", "description": "相册内媒体数量" }, + "cover": { + "type": [ + "object", + "null" + ], + "description": "相册封面;没有封面时为 null", + "properties": { + "type": { + "type": "integer", + "description": "封面媒体类型" + }, + "image": { + "type": [ + "object", + "null" + ], + "description": "封面图片信息", + "properties": { + "name": { + "type": "string", + "description": "图片名称" + }, + "sloc": { + "type": "string", + "description": "图片短定位标识" + }, + "lloc": { + "type": "string", + "description": "图片长定位标识" + }, + "photoUrls": { + "type": "array", + "description": "不同规格的封面地址", + "items": { + "type": "object", + "properties": { + "spec": { + "type": "integer", + "description": "图片规格" + }, + "url": { + "type": [ + "object", + "null" + ], + "properties": { + "url": { + "type": "string", + "description": "封面地址" + }, + "width": { + "type": "integer", + "description": "封面宽度" + }, + "height": { + "type": "integer", + "description": "封面高度" + } + }, + "required": [ + "url", + "width", + "height" + ] + } + }, + "required": [ + "spec", + "url" + ] + } + }, + "defaultUrl": { + "type": [ + "object", + "null" + ], + "properties": { + "url": { + "type": "string", + "description": "封面地址" + }, + "width": { + "type": "integer", + "description": "封面宽度" + }, + "height": { + "type": "integer", + "description": "封面高度" + } + }, + "required": [ + "url", + "width", + "height" + ] + }, + "isGif": { + "type": "boolean", + "description": "是否为动图" + }, + "hasRaw": { + "type": "boolean", + "description": "是否有原图" + } + }, + "required": [ + "name", + "sloc", + "lloc", + "photoUrls", + "defaultUrl", + "isGif", + "hasRaw" + ] + } + }, + "required": [ + "type", + "image" + ] + }, "creator": { "type": "object", "description": "相册创建者信息", @@ -5541,7 +5795,9 @@ "album_id", "name", "create_time", - "upload_number" + "last_upload_time", + "upload_number", + "cover" ] } }, diff --git a/packages/mcp/src/generated/catalog.ts b/packages/mcp/src/generated/catalog.ts index 2b8c3a50..8e424992 100644 --- a/packages/mcp/src/generated/catalog.ts +++ b/packages/mcp/src/generated/catalog.ts @@ -3438,6 +3438,132 @@ export const ACTIONS: CatalogAction[] = [ "type": "integer", "description": "相册创建时间(unix 秒)" }, + "last_upload_time": { + "type": "integer", + "description": "最后上传时间(unix 秒)" + }, + "cover": { + "type": [ + "object", + "null" + ], + "description": "相册封面;没有封面时为 null", + "properties": { + "type": { + "type": "integer", + "description": "封面媒体类型" + }, + "image": { + "type": [ + "object", + "null" + ], + "description": "封面图片信息", + "properties": { + "name": { + "type": "string", + "description": "图片名称" + }, + "sloc": { + "type": "string", + "description": "图片短定位标识" + }, + "lloc": { + "type": "string", + "description": "图片长定位标识" + }, + "photoUrls": { + "type": "array", + "description": "不同规格的封面地址", + "items": { + "type": "object", + "properties": { + "spec": { + "type": "integer", + "description": "图片规格" + }, + "url": { + "type": [ + "object", + "null" + ], + "properties": { + "url": { + "type": "string", + "description": "封面地址" + }, + "width": { + "type": "integer", + "description": "封面宽度" + }, + "height": { + "type": "integer", + "description": "封面高度" + } + }, + "required": [ + "url", + "width", + "height" + ] + } + }, + "required": [ + "spec", + "url" + ] + } + }, + "defaultUrl": { + "type": [ + "object", + "null" + ], + "properties": { + "url": { + "type": "string", + "description": "封面地址" + }, + "width": { + "type": "integer", + "description": "封面宽度" + }, + "height": { + "type": "integer", + "description": "封面高度" + } + }, + "required": [ + "url", + "width", + "height" + ] + }, + "isGif": { + "type": "boolean", + "description": "是否为动图" + }, + "hasRaw": { + "type": "boolean", + "description": "是否有原图" + } + }, + "required": [ + "name", + "sloc", + "lloc", + "photoUrls", + "defaultUrl", + "isGif", + "hasRaw" + ] + } + }, + "required": [ + "type", + "image" + ] + }, "createuin": { "type": "string", "description": "相册创建者 QQ 号" @@ -3452,6 +3578,8 @@ export const ACTIONS: CatalogAction[] = [ "name", "picNum", "createTime", + "last_upload_time", + "cover", "createuin", "createnickname" ] @@ -5517,10 +5645,136 @@ export const ACTIONS: CatalogAction[] = [ "type": "string", "description": "相册创建时间(unix 秒)" }, + "last_upload_time": { + "type": "string", + "description": "最后上传时间(unix 秒)" + }, "upload_number": { "type": "string", "description": "相册内媒体数量" }, + "cover": { + "type": [ + "object", + "null" + ], + "description": "相册封面;没有封面时为 null", + "properties": { + "type": { + "type": "integer", + "description": "封面媒体类型" + }, + "image": { + "type": [ + "object", + "null" + ], + "description": "封面图片信息", + "properties": { + "name": { + "type": "string", + "description": "图片名称" + }, + "sloc": { + "type": "string", + "description": "图片短定位标识" + }, + "lloc": { + "type": "string", + "description": "图片长定位标识" + }, + "photoUrls": { + "type": "array", + "description": "不同规格的封面地址", + "items": { + "type": "object", + "properties": { + "spec": { + "type": "integer", + "description": "图片规格" + }, + "url": { + "type": [ + "object", + "null" + ], + "properties": { + "url": { + "type": "string", + "description": "封面地址" + }, + "width": { + "type": "integer", + "description": "封面宽度" + }, + "height": { + "type": "integer", + "description": "封面高度" + } + }, + "required": [ + "url", + "width", + "height" + ] + } + }, + "required": [ + "spec", + "url" + ] + } + }, + "defaultUrl": { + "type": [ + "object", + "null" + ], + "properties": { + "url": { + "type": "string", + "description": "封面地址" + }, + "width": { + "type": "integer", + "description": "封面宽度" + }, + "height": { + "type": "integer", + "description": "封面高度" + } + }, + "required": [ + "url", + "width", + "height" + ] + }, + "isGif": { + "type": "boolean", + "description": "是否为动图" + }, + "hasRaw": { + "type": "boolean", + "description": "是否有原图" + } + }, + "required": [ + "name", + "sloc", + "lloc", + "photoUrls", + "defaultUrl", + "isGif", + "hasRaw" + ] + } + }, + "required": [ + "type", + "image" + ] + }, "creator": { "type": "object", "description": "相册创建者信息", @@ -5544,7 +5798,9 @@ export const ACTIONS: CatalogAction[] = [ "album_id", "name", "create_time", - "upload_number" + "last_upload_time", + "upload_number", + "cover" ] } }, diff --git a/packages/onebot/src/actions/group-album.ts b/packages/onebot/src/actions/group-album.ts index 4090cd62..b96fbf54 100644 --- a/packages/onebot/src/actions/group-album.ts +++ b/packages/onebot/src/actions/group-album.ts @@ -2,6 +2,50 @@ import { groupAction, f } from '../action-kit'; import type { JsonValue } from '../types'; import { okResponse } from '../types'; +const albumCoverUrlSchema = { + type: ['object', 'null'], + properties: { + url: { type: 'string', description: '封面地址' }, + width: { type: 'integer', description: '封面宽度' }, + height: { type: 'integer', description: '封面高度' }, + }, + required: ['url', 'width', 'height'], +}; + +const albumCoverSchema = { + type: ['object', 'null'], + description: '相册封面;没有封面时为 null', + properties: { + type: { type: 'integer', description: '封面媒体类型' }, + image: { + type: ['object', 'null'], + description: '封面图片信息', + properties: { + name: { type: 'string', description: '图片名称' }, + sloc: { type: 'string', description: '图片短定位标识' }, + lloc: { type: 'string', description: '图片长定位标识' }, + photoUrls: { + type: 'array', + description: '不同规格的封面地址', + items: { + type: 'object', + properties: { + spec: { type: 'integer', description: '图片规格' }, + url: albumCoverUrlSchema, + }, + required: ['spec', 'url'], + }, + }, + defaultUrl: albumCoverUrlSchema, + isGif: { type: 'boolean', description: '是否为动图' }, + hasRaw: { type: 'boolean', description: '是否有原图' }, + }, + required: ['name', 'sloc', 'lloc', 'photoUrls', 'defaultUrl', 'isGif', 'hasRaw'], + }, + }, + required: ['type', 'image'], +}; + export const actions = [ groupAction({ name: 'get_group_album_list', @@ -17,10 +61,21 @@ export const actions = [ name: { type: 'string', description: '相册名称' }, picNum: { type: 'integer', description: '相册内照片数量' }, createTime: { type: 'integer', description: '相册创建时间(unix 秒)' }, + last_upload_time: { type: 'integer', description: '最后上传时间(unix 秒)' }, + cover: albumCoverSchema, createuin: { type: 'string', description: '相册创建者 QQ 号' }, createnickname: { type: 'string', description: '相册创建者昵称(原始 Unicode)' }, }, - required: ['id', 'name', 'picNum', 'createTime', 'createuin', 'createnickname'], + required: [ + 'id', + 'name', + 'picNum', + 'createTime', + 'last_upload_time', + 'cover', + 'createuin', + 'createnickname', + ], }, }, run: async (p, ctx) => { @@ -46,7 +101,9 @@ export const actions = [ album_id: { type: 'string', description: '相册 id' }, name: { type: 'string', description: '相册名称' }, create_time: { type: 'string', description: '相册创建时间(unix 秒)' }, + last_upload_time: { type: 'string', description: '最后上传时间(unix 秒)' }, upload_number: { type: 'string', description: '相册内媒体数量' }, + cover: albumCoverSchema, creator: { type: 'object', description: '相册创建者信息', @@ -57,7 +114,14 @@ export const actions = [ }, }, }, - required: ['album_id', 'name', 'create_time', 'upload_number'], + required: [ + 'album_id', + 'name', + 'create_time', + 'last_upload_time', + 'upload_number', + 'cover', + ], }, }, attach_info: { type: 'string', description: '下一页分页游标' }, diff --git a/packages/onebot/tests/action-docs.test.ts b/packages/onebot/tests/action-docs.test.ts index 5e4964b0..f45d1b58 100644 --- a/packages/onebot/tests/action-docs.test.ts +++ b/packages/onebot/tests/action-docs.test.ts @@ -37,6 +37,30 @@ describe('action-docs', () => { }, ); + it.each(['get_group_album_list', 'get_qun_album_list'])( + 'documents album cover and last-upload metadata for %s', + (name) => { + const action = docs.find((d) => d.name === name); + const rootProperties = action?.returnsSchema?.properties as Record | undefined; + const itemProperties = name === 'get_group_album_list' + ? (action?.returnsSchema?.items as any)?.properties + : rootProperties?.album_list?.items?.properties; + + expect(itemProperties).toEqual(expect.objectContaining({ + last_upload_time: expect.objectContaining({ + type: name === 'get_group_album_list' ? 'integer' : 'string', + }), + cover: expect.objectContaining({ type: ['object', 'null'] }), + })); + expect(itemProperties.cover.properties.image.properties).toEqual(expect.objectContaining({ + photoUrls: expect.objectContaining({ type: 'array' }), + defaultUrl: expect.objectContaining({ type: ['object', 'null'] }), + isGif: expect.objectContaining({ type: 'boolean' }), + hasRaw: expect.objectContaining({ type: 'boolean' }), + })); + }, + ); + it('renders markdown with header + an action section', () => { const md = renderActionDocsMarkdown(docs); expect(md).toContain('# OneBot Actions'); diff --git a/packages/onebot/tests/extended-actions.test.ts b/packages/onebot/tests/extended-actions.test.ts index 7255970f..667958ff 100644 --- a/packages/onebot/tests/extended-actions.test.ts +++ b/packages/onebot/tests/extended-actions.test.ts @@ -1324,13 +1324,39 @@ describe('extended-actions / TierB ③ share + doubt + robot-option', () => { }); // ─── napcat-parity: get_qun_album_list (QQ NT AlbumService) ─── +describe('extended-actions / get_group_album_list', () => { + it('returns the cover and last-upload metadata from the core facade', async () => { + const albumList = [{ + id: 'a1', + name: '相册一', + picNum: 5, + createTime: 1700000000, + desc: '', + owner: '10001', + createuin: '10001', + createnickname: '测试用户', + last_upload_time: 1700000123, + cover: { type: 1, image: null }, + }]; + const list = vi.fn(async () => albumList); + const bridge = fakeBridge({ apis: { groupAlbum: { list } } }); + + const res = await makeHandler(fakeCtx(bridge)).handle('get_group_album_list', { group_id: 12345 }); + + expect(list).toHaveBeenCalledWith(12345); + expect(res).toMatchObject({ status: 'ok', data: albumList }); + }); +}); + describe('extended-actions / get_qun_album_list', () => { it('forwards the pagination cursor and returns the native AlbumService envelope', async () => { const albumList = [{ album_id: 'a1', name: '相册一', create_time: '1700000000', + last_upload_time: '1700000123', upload_number: '5', + cover: { type: 1, image: null }, creator: { uin: '10001', nick: '😂[em]e328514[/em]' }, }]; const listQun = vi.fn(async () => ({ diff --git a/packages/proto-defs/src/oidb-actions/group-album.ts b/packages/proto-defs/src/oidb-actions/group-album.ts index 7fc290e3..2f091a14 100644 --- a/packages/proto-defs/src/oidb-actions/group-album.ts +++ b/packages/proto-defs/src/oidb-actions/group-album.ts @@ -46,6 +46,8 @@ export interface GroupAlbumInfo { modifyTime?: pb<6, uint_64>; lastUploadTime?: pb<7, uint_64>; uploadNumber?: pb<8, uint_64>; + // QQ NT AlbumCodec_DecodeAlbumInfo tag 9 reuses the media-list MediaInfo codec. + cover?: pb<9, MediaInfo>; creator?: pb<10, AlbumCreator>; topFlag?: pb<11, uint_64>; busiType?: pb<12, int_32>;