diff --git a/packages/core/src/bridge/apis/group-album.ts b/packages/core/src/bridge/apis/group-album.ts index 8b707ff0..1c977158 100644 --- a/packages/core/src/bridge/apis/group-album.ts +++ b/packages/core/src/bridge/apis/group-album.ts @@ -2,6 +2,7 @@ import type { JsonObject, JsonValue } from '@snowluma/common/json'; import { createLogger } from '@snowluma/common/logger'; import type { AlbumCreator, + CommentReqPhotoInfo, CommentRespData, DeleteMediasRequest, DeleteMediasResponse, @@ -326,7 +327,13 @@ export class GroupAlbumApi { } const mediaLloc = commentMediaLloc(resolved, lloc); const feed = await this.getQunFeedDetail(groupId, albumId, batchId, mediaLloc); - const media = mediaInfoForComment(resolved, mediaLloc); + const ownerUin = feed.ownerUin || resolved?.uploader || ''; + const photoInfo = commentPhotoInfo( + feed.media, + mediaInfoForComment(resolved, mediaLloc), + albumId, + batchId, + ); const body = protobuf_encode({ field1: DO_QUN_COMMENT_SEQ, @@ -340,14 +347,8 @@ export class GroupAlbumApi { time: feed.time, feedId: feed.feedId, }, - field2: { - field1: { uin }, - }, - field5: { - medias: [media], - albumId, - batchId, - }, + ...(ownerUin ? { field2: { field1: { uin: ownerUin } } } : {}), + field5: photoInfo, }, field5: { user: { uin }, @@ -557,7 +558,7 @@ export class GroupAlbumApi { albumId: string, batchId: bigint, lloc: string, - ): Promise<{ time: bigint; feedId: string }> { + ): Promise<{ time: bigint; feedId: string; ownerUin: string; media?: CommentReqPhotoInfo }> { const traceId = `_${Date.now()}_${Math.floor(Math.random() * 100000)}`; const body = protobuf_encode({ seq: 0, @@ -590,7 +591,8 @@ export class GroupAlbumApi { ); } - const cell = resp.data?.feed?.feed?.cellCommon; + const feed = resp.data?.feed?.feed; + const cell = feed?.cellCommon; const feedId = cell?.feedId ?? ''; if (!feedId) { throw new Error('comment album media error: empty feed'); @@ -598,6 +600,8 @@ export class GroupAlbumApi { return { time: cell?.time ?? 0n, feedId, + ownerUin: feed?.cellUserInfo?.user?.uin ?? '', + media: feed?.cellMedia, }; } } @@ -667,18 +671,41 @@ function commentMediaLloc(item: AlbumCommentMediaItem | undefined, lloc: string) } function mediaInfoForComment(item: AlbumCommentMediaItem | undefined, lloc: string): MediaInfo { + const batchId = optionalBatchId(item?.batchId); + const shared: MediaInfo = { + ...(item?.uploader ? { uploader: item.uploader } : {}), + ...(batchId !== undefined ? { batchId } : {}), + }; if (item?.video) { return { + ...shared, type: 1, video: { cover: { lloc: item.video.cover?.lloc || lloc } }, }; } return { + ...shared, type: 0, image: { lloc: item?.image?.lloc || lloc }, }; } +function commentPhotoInfo( + feedMedia: CommentReqPhotoInfo | undefined, + fallback: MediaInfo, + albumId: string, + batchId: bigint, +): CommentReqPhotoInfo { + if (feedMedia?.medias?.length) { + return { + medias: feedMedia.medias, + albumId: feedMedia.albumId || albumId, + batchId: feedMedia.batchId ?? batchId, + }; + } + return { medias: [fallback], albumId, batchId }; +} + function findCommentMedia( mediaList: ReadonlyArray, mediaKey: string, diff --git a/packages/core/tests/actions/group-album.test.ts b/packages/core/tests/actions/group-album.test.ts index 1fbdc416..88565adf 100644 --- a/packages/core/tests/actions/group-album.test.ts +++ b/packages/core/tests/actions/group-album.test.ts @@ -584,6 +584,14 @@ describe('apis/group-album', () => { function packFeedDetail( feedId = 'official-feed-id', time = 1700000123n, + extra: { + ownerUin?: string; + cellMedia?: { + medias?: Array<{ type?: number; image?: { lloc?: string }; video?: { cover?: { lloc?: string } } }>; + albumId?: string; + batchId?: bigint; + }; + } = {}, ): ReturnType { return { success: true, @@ -596,6 +604,8 @@ describe('apis/group-album', () => { feed: { feed: { cellCommon: { time, feedId }, + ...(extra.ownerUin ? { cellUserInfo: { user: { uin: extra.ownerUin } } } : {}), + ...(extra.cellMedia ? { cellMedia: extra.cellMedia } : {}), }, }, }, @@ -616,7 +626,9 @@ describe('apis/group-album', () => { const override = extra(cmd); if (override) return override; if (cmd.endsWith('GetMediaList')) return packPhotoMedia(); - if (cmd.endsWith('GetQunFeedDetail')) return packFeedDetail(); + if (cmd.endsWith('GetQunFeedDetail')) { + return packFeedDetail('official-feed-id', 1700000123n, { ownerUin: '3119936551' }); + } return packCommentOk({ data: { id: 'cmt-1', @@ -629,14 +641,18 @@ describe('apis/group-album', () => { }; } - function commentRequestOf(bridge: ReturnType): DoQunCommentRequest { + function commentCallBytes(bridge: ReturnType): Uint8Array { const commentCall = bridge.sendRawPacket.mock.calls.find((call) => String(call[0]).endsWith('DoQunComment'), ); expect(commentCall?.[0]).toBe( 'QunAlbum.trpc.qzone.webapp_qun_operation.FeedsWriter.DoQunComment', ); - return protobuf_decode(commentCall![1] as Uint8Array); + return commentCall![1] as Uint8Array; + } + + function commentRequestOf(bridge: ReturnType): DoQunCommentRequest { + return protobuf_decode(commentCallBytes(bridge)); } function feedDetailRequestOf(bridge: ReturnType): GetQunFeedDetailRequest { @@ -682,7 +698,7 @@ describe('apis/group-album', () => { time: 1700000123n, feedId: 'official-feed-id', }, - field2: { field1: { uin: '10001' } }, + field2: { field1: { uin: '3119936551' } }, field5: { albumId: 'album-id', batchId: 77n, @@ -697,6 +713,56 @@ describe('apis/group-album', () => { }, }); expect(request.body?.reqBody?.field5?.medias?.[0]?.type ?? 0).toBe(0); + expect(Buffer.from(commentCallBytes(bridge)).subarray(0, 7).toString('hex')) + .toBe('08cf4212001a00'); + }); + + it('falls back to the media uploader when the official feed has no owner cell', async () => { + const bridge = mockBridge(); + bridge.sendRawPacket.mockImplementation(commentMocks((cmd) => { + if (cmd.endsWith('GetMediaList')) { + return packMediaList({ + mediaList: [{ type: 1, image: { lloc: 'photo-lloc' }, batchId: 77n, uploader: '3119936551' }], + }); + } + if (cmd.endsWith('GetQunFeedDetail')) return packFeedDetail(); + return undefined; + })); + + await new GroupAlbumApi(bridge as never).comment(12345, 'album-id', 'photo-lloc', 'hello'); + + expect(commentRequestOf(bridge).body?.reqBody?.field2?.field1?.uin).toBe('3119936551'); + expect(commentRequestOf(bridge).body?.field5?.user?.uin).toBe('10001'); + }); + + it('copies the official feed media cell instead of a reconstructed lloc-only cell', async () => { + const bridge = mockBridge(); + bridge.sendRawPacket.mockImplementation(commentMocks((cmd) => { + if (cmd.endsWith('GetQunFeedDetail')) { + return packFeedDetail('official-feed-id', 1700000123n, { + ownerUin: '3119936551', + cellMedia: { + albumId: 'feed-album', + batchId: 88n, + medias: [{ + type: 0, + image: { lloc: 'feed-lloc' }, + }], + }, + }); + } + return undefined; + })); + + await new GroupAlbumApi(bridge as never).comment(12345, 'album-id', 'photo-lloc', 'hello'); + + expect(commentRequestOf(bridge).body?.reqBody?.field5).toMatchObject({ + albumId: 'feed-album', + batchId: 88n, + medias: [{ + image: { lloc: 'feed-lloc' }, + }], + }); }); it('comments a video with the cover location and video media type', async () => { diff --git a/packages/proto-defs/src/oidb-actions/group-album.ts b/packages/proto-defs/src/oidb-actions/group-album.ts index df125233..a3349a3c 100644 --- a/packages/proto-defs/src/oidb-actions/group-album.ts +++ b/packages/proto-defs/src/oidb-actions/group-album.ts @@ -179,8 +179,8 @@ export interface DoQunCommentRequestBody { } export interface DoQunCommentRequest { field1?: pb<1, int_32>; - field2?: pb<2, bytes>; - field3?: pb<3, bytes>; + field2?: pb_optional<2, bytes>; + field3?: pb_optional<3, bytes>; body?: pb<4, DoQunCommentRequestBody>; traceId?: pb<5, string>; extMap?: pb_repeated<10, ExtMapEntry>; @@ -284,8 +284,17 @@ export interface QunFeedCellCommon { time?: pb<3, uint_64>; feedId?: pb<4, string>; } +export interface QunFeedCellUser { + uin?: pb<13, string>; +} +export interface QunFeedCellUserInfo { + user?: pb<1, QunFeedCellUser>; +} export interface QunFeed { - cellCommon?: pb<1, QunFeedCellCommon>; + cellCommon?: pb<1, QunFeedCellCommon>; + cellUserInfo?: pb<2, QunFeedCellUserInfo>; + // FeedWorker copies this cell into DoQunComment body.4.5. + cellMedia?: pb<5, CommentReqPhotoInfo>; } export interface QunClientFeed { feed?: pb<1, QunFeed>;