增加/fetch_emoji_likes_all (#1548)

* Update FetchEmojiLike.ts

* 减少getMsgEmojiLikesList参数,一次性全部拉取

* Update FetchEmojiLike.ts

* Refactor API message schema and update descriptions

* Update and rename FetchEmojiLike.ts to FetchEmojiLikesAll.ts

* Create FetchEmojiLike.ts

* Update router.ts

* Update index.ts

* Update index.ts

* Update FetchEmojiLikesAll.ts

* Update FetchEmojiLikesAll.ts

* Refactor emoji likes API and update related logic

Replaces FetchEmojiLikesAll with GetEmojiLikes, updating the API to use a new payload and return schema. Adjusts action registration, router action names, and frontend API mapping accordingly. Adds isShortId utility to MessageUnique for improved message ID handling.

---------

Co-authored-by: 手瓜一十雪 <nanaeonn@outlook.com>
This commit is contained in:
Hans155922 2026-01-25 13:18:16 +08:00 committed by GitHub
parent 246269b519
commit b75b733bb0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 111 additions and 11 deletions

View File

@ -58,12 +58,12 @@ export class LimitedHashTable<K, V> {
} }
// 获取最近刚写入的几个值 // 获取最近刚写入的几个值
getHeads (size: number): { key: K; value: V }[] | undefined { getHeads (size: number): { key: K; value: V; }[] | undefined {
const keyList = this.getKeyList(); const keyList = this.getKeyList();
if (keyList.length === 0) { if (keyList.length === 0) {
return undefined; return undefined;
} }
const result: { key: K; value: V }[] = []; const result: { key: K; value: V; }[] = [];
const listSize = Math.min(size, keyList.length); const listSize = Math.min(size, keyList.length);
for (let i = 0; i < listSize; i++) { for (let i = 0; i < listSize; i++) {
const key = keyList[listSize - i]; const key = keyList[listSize - i];
@ -108,7 +108,7 @@ class MessageUniqueWrapper {
return shortId; return shortId;
} }
getMsgIdAndPeerByShortId (shortId: number): { MsgId: string; Peer: Peer } | undefined { getMsgIdAndPeerByShortId (shortId: number): { MsgId: string; Peer: Peer; } | undefined {
const data = this.msgDataMap.getKey(shortId); const data = this.msgDataMap.getKey(shortId);
if (data) { if (data) {
const [msgId, chatTypeStr, peerUid] = data.split('|'); const [msgId, chatTypeStr, peerUid] = data.split('|');
@ -136,6 +136,12 @@ class MessageUniqueWrapper {
this.msgIdMap.resize(maxSize); this.msgIdMap.resize(maxSize);
this.msgDataMap.resize(maxSize); this.msgDataMap.resize(maxSize);
} }
isShortId (message_id: string): boolean {
const num = Number(message_id);
// 判断是否是整数并且在 INT32 的范围内
return Number.isInteger(num) && num >= -2147483648 && num <= 2147483647;
}
} }
export const MessageUnique: MessageUniqueWrapper = new MessageUniqueWrapper(); export const MessageUnique: MessageUniqueWrapper = new MessageUniqueWrapper();

View File

@ -0,0 +1,70 @@
import { Type, Static } from '@sinclair/typebox';
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
import { ActionName } from '@/napcat-onebot/action/router';
import { MessageUnique } from 'napcat-common/src/message-unique';
import { Peer, ChatType } from '@/napcat-core';
const PayloadSchema = Type.Object({
group_id: Type.Optional(Type.String({ description: '群号短ID可不传' })),
message_id: Type.String({ description: '消息ID可以传递长ID或短ID' }),
emoji_id: Type.String({ description: '表情ID' }),
emoji_type: Type.Optional(Type.String({ description: '表情类型' })),
count: Type.Number({ default: 0, description: '数量0代表全部' }),
});
type PayloadType = Static<typeof PayloadSchema>;
const ReturnSchema = Type.Object({
emoji_like_list: Type.Array(
Type.Object({
user_id: Type.String({ description: '点击者QQ号' }),
nick_name: Type.String({ description: '昵称?' }),
}),
{ description: '表情回应列表' }
),
});
type ReturnType = Static<typeof ReturnSchema>;
export class GetEmojiLikes extends OneBotAction<PayloadType, ReturnType> {
override actionName = ActionName.GetEmojiLikes;
override payloadSchema = PayloadSchema;
async _handle (payload: PayloadType) {
let peer: Peer;
let msgId: string;
if (MessageUnique.isShortId(payload.message_id)) {
const msgIdPeer = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id);
if (!msgIdPeer) throw new Error('消息不存在');
peer = msgIdPeer.Peer;
msgId = msgIdPeer.MsgId;
} else {
if (!payload.group_id) throw new Error('长ID模式下必须提供群号');
peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id };
msgId = payload.message_id;
}
const msg = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgId])).msgList[0];
if (!msg) throw new Error('消息不存在');
const emojiType = payload.emoji_type ?? (payload.emoji_id.length > 3 ? '2' : '1');
const emojiLikeList: Array<{ user_id: string; nick_name: string; }> = [];
let cookie = '';
for (let page = 0; page < 200; page++) {
const res = await this.core.apis.MsgApi.getMsgEmojiLikesList(
peer, msg.msgSeq, payload.emoji_id.toString(), emojiType, cookie, 15
);
if (Array.isArray(res.emojiLikesList)) {
for (const like of res.emojiLikesList) {
emojiLikeList.push({ user_id: like.tinyId, nick_name: like.nickName });
}
}
if (res.isLastPage || !res.cookie) break;
cookie = res.cookie;
}
return { emoji_like_list: emojiLikeList };
}
}

View File

@ -65,6 +65,7 @@ import SetGroupPortrait from './go-cqhttp/SetGroupPortrait';
import { FetchCustomFace } from './extends/FetchCustomFace'; import { FetchCustomFace } from './extends/FetchCustomFace';
import GoCQHTTPUploadPrivateFile from './go-cqhttp/UploadPrivateFile'; import GoCQHTTPUploadPrivateFile from './go-cqhttp/UploadPrivateFile';
import { FetchEmojiLike } from './extends/FetchEmojiLike'; import { FetchEmojiLike } from './extends/FetchEmojiLike';
import { GetEmojiLikes } from './extends/GetEmojiLikes';
import { NapCatCore } from 'napcat-core'; import { NapCatCore } from 'napcat-core';
import type { NetworkAdapterConfig } from '../config/config'; import type { NetworkAdapterConfig } from '../config/config';
import { OneBotAction } from './OneBotAction'; import { OneBotAction } from './OneBotAction';
@ -183,6 +184,7 @@ export function createActionMap (obContext: NapCatOneBot11Adapter, core: NapCatC
new SetGroupRemark(obContext, core), new SetGroupRemark(obContext, core),
new GetGroupInfoEx(obContext, core), new GetGroupInfoEx(obContext, core),
new FetchEmojiLike(obContext, core), new FetchEmojiLike(obContext, core),
new GetEmojiLikes(obContext, core),
new GetFile(obContext, core), new GetFile(obContext, core),
new SetQQProfile(obContext, core), new SetQQProfile(obContext, core),
new ShareGroupEx(obContext, core), new ShareGroupEx(obContext, core),

View File

@ -152,6 +152,7 @@ export const ActionName = {
GetProfileLike: 'get_profile_like', GetProfileLike: 'get_profile_like',
FetchCustomFace: 'fetch_custom_face', FetchCustomFace: 'fetch_custom_face',
FetchEmojiLike: 'fetch_emoji_like', FetchEmojiLike: 'fetch_emoji_like',
GetEmojiLikes: 'get_emoji_likes',
SetInputStatus: 'set_input_status', SetInputStatus: 'set_input_status',
GetGroupInfoEx: 'get_group_info_ex', GetGroupInfoEx: 'get_group_info_ex',
GetGroupDetailInfo: 'get_group_detail_info', GetGroupDetailInfo: 'get_group_detail_info',

View File

@ -201,12 +201,8 @@ const oneBotHttpApiMessage = {
message_id: z.union([z.string(), z.number()]).describe('消息ID'), message_id: z.union([z.string(), z.number()]).describe('消息ID'),
emojiId: z.string().describe('表情ID'), emojiId: z.string().describe('表情ID'),
emojiType: z.string().describe('表情类型'), emojiType: z.string().describe('表情类型'),
group_id: z.union([z.string(), z.number()]).optional().describe('群号'),
user_id: z
.union([z.string(), z.number()])
.optional()
.describe('用户QQ号'),
count: z.number().int().positive().optional().describe('获取数量'), count: z.number().int().positive().optional().describe('获取数量'),
cookie: z.string().describe('cookie,首次为空,后续为上次返回'),
}), }),
response: baseResponseSchema.extend({ response: baseResponseSchema.extend({
data: z.object({ data: z.object({
@ -216,19 +212,44 @@ const oneBotHttpApiMessage = {
.array( .array(
z z
.object({ .object({
tinyId: z.string().describe('表情ID'), tinyId: z.string().describe('点击者QQ号'),
nickName: z.string().describe('昵称?'), nickName: z.string().describe('昵称?'),
headUrl: z.string().describe('头像?'), headUrl: z.string().describe('头像?'),
}) })
.describe('表情点列表') .describe('表情点列表')
) )
.describe('表情点列表'), .describe('表情点列表'),
cookie: z.string().describe('cookie'), cookie: z.string().describe('cookie'),
isLastPage: z.boolean().describe('是否最后一页'), isLastPage: z.boolean().describe('是否最后一页'),
isFirstPage: z.boolean().describe('是否第一页'), isFirstPage: z.boolean().describe('是否第一页'),
}), }),
}), }),
}, },
'/get_emoji_likes': {
description: '获取贴表情详情列表',
request: z.object({
message_id: z.union([z.string(), z.number()]).describe('消息ID'),
emojiId: z.string().describe('表情ID'),
emojiType: z.string().describe('表情类型'),
}),
response: baseResponseSchema.extend({
data: z.object({
result: z.number().describe('结果'),
errMsg: z.string().describe('错误信息'),
emojiLikesList: z
.array(
z
.object({
tinyId: z.string().describe('点击者QQ号'),
nickName: z.string().describe('昵称?'),
headUrl: z.string().describe('头像?'),
})
.describe('表情点击列表')
)
.describe('表情点击列表'),
}),
}),
},
'/get_forward_msg': { '/get_forward_msg': {
description: '获取合并转发消息', description: '获取合并转发消息',
request: z.object({ request: z.object({