fix(core): write album comments as the feed owner
DoQunComment still returned 10016 after the feed lookup because we stamped the bot onto StFeed.cellUserInfo. Official FeedWorker copies the feed owner's uin there and only puts the bot on StComment. Fixes #436
This commit is contained in:
@@ -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<DoQunCommentRequest>({
|
||||
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<GetQunFeedDetailRequest>({
|
||||
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<unknown>,
|
||||
mediaKey: string,
|
||||
|
||||
@@ -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<typeof packDeleteOk> {
|
||||
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<typeof mockBridge>): DoQunCommentRequest {
|
||||
function commentCallBytes(bridge: ReturnType<typeof mockBridge>): 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<DoQunCommentRequest>(commentCall![1] as Uint8Array);
|
||||
return commentCall![1] as Uint8Array;
|
||||
}
|
||||
|
||||
function commentRequestOf(bridge: ReturnType<typeof mockBridge>): DoQunCommentRequest {
|
||||
return protobuf_decode<DoQunCommentRequest>(commentCallBytes(bridge));
|
||||
}
|
||||
|
||||
function feedDetailRequestOf(bridge: ReturnType<typeof mockBridge>): 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 () => {
|
||||
|
||||
@@ -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>;
|
||||
|
||||
Reference in New Issue
Block a user