补全一些onebot action实现 2 (#24)
* feat: implement get profile like * feat: implement get at all remain * feat: implement get undirectional friends * feat: implement set self long nick * feat: implement set input status * feat: implement EN 2 ZH * feat: implement get mini app ark * feat: implement click inline button * feat: implement set and send group sign * feat: implement set qq avatar * feat: group notice with pic and delete notice implement * fix(group-notice): POST api and fix new bkn logic
This commit is contained in:
@@ -47,6 +47,24 @@ import {
|
||||
SetStatusReqSchema,
|
||||
SetStatusRespSchema,
|
||||
OidbSetProfileSchema,
|
||||
Oidb0x7edReqSchema,
|
||||
Oidb0x7edRespSchema,
|
||||
Oidb0x8a7RespSchema,
|
||||
Oidb0x8a7ReqSchema,
|
||||
Oidb0xe17RespSchema,
|
||||
Oidb0xe17ReqSchema,
|
||||
Oidb0x112aReqSchema,
|
||||
Oidb0x112aRespSchema,
|
||||
Oidb0xcd4ReqSchema,
|
||||
Oidb0xcd4RespSchema,
|
||||
Oidb0x990ReqSchema,
|
||||
Oidb0x990RespSchema,
|
||||
MiniAppShareReqSchema,
|
||||
MiniAppShareRespSchema,
|
||||
Oidb0x112eReqSchema,
|
||||
Oidb0x112eRespSchema,
|
||||
Oidb0xeb7ReqSchema,
|
||||
Oidb0xeb7RespSchema,
|
||||
} from './proto/oidb-action';
|
||||
import { FileUploadExtSchema } from './proto/highway';
|
||||
import {
|
||||
@@ -1358,4 +1376,359 @@ export async function setProfile(
|
||||
req,
|
||||
OidbSetProfileSchema
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function getProfileLike(
|
||||
bridge: Bridge,
|
||||
userId?: number,
|
||||
start: number = 0,
|
||||
limit: number = 10
|
||||
) {
|
||||
|
||||
// const isSelf = !userId || userId === bridge.qqInfo.uin;
|
||||
// const targetUid = isSelf
|
||||
// ? bridge.qqInfo.uid
|
||||
// : await bridge.getUidByUinV2(userId!);
|
||||
|
||||
|
||||
const isSelf = !userId
|
||||
const targetUid = isSelf
|
||||
? await resolveSelfUid(bridge)
|
||||
: await resolveUserUid(bridge, userId)
|
||||
|
||||
if (!targetUid) {
|
||||
throw new Error('target uid not found');
|
||||
}
|
||||
|
||||
const req = {
|
||||
targetUid: targetUid,
|
||||
basic: 1,
|
||||
vote: 1,
|
||||
favorite: 1,
|
||||
start: start,
|
||||
limit: limit,
|
||||
};
|
||||
|
||||
const result = await sendOidbAndDecode<any>(
|
||||
bridge,
|
||||
'OidbSvcTrpcTcp.0x7ed_12',
|
||||
0x7ED,
|
||||
12,
|
||||
req,
|
||||
Oidb0x7edReqSchema,
|
||||
Oidb0x7edRespSchema
|
||||
);
|
||||
|
||||
const data = result?.userLikeInfos?.[0];
|
||||
if (!data) {
|
||||
throw new Error('get profile like info empty');
|
||||
}
|
||||
|
||||
return {
|
||||
uid: data.uid,
|
||||
time: Number(data.time),
|
||||
favoriteInfo: {
|
||||
total_count: data.favoriteInfo?.totalCount || 0,
|
||||
last_time: Number(data.favoriteInfo?.lastTime || 0),
|
||||
today_count: data.favoriteInfo?.newCount || 0,
|
||||
userInfos: []
|
||||
},
|
||||
voteInfo: {
|
||||
total_count: data.voteInfo?.totalCount || 0,
|
||||
new_count: data.voteInfo?.newCount || 0,
|
||||
new_nearby_count: 0,
|
||||
last_visit_time: Number(data.voteInfo?.lastTime || 0),
|
||||
userInfos: []
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export async function getGroupAtAllRemain(
|
||||
bridge: Bridge,
|
||||
groupId: number
|
||||
) {
|
||||
const req = {
|
||||
basic1: 1,
|
||||
basic2: 2,
|
||||
basic3: 1,
|
||||
uin: BigInt(bridge.qqInfo.uin),
|
||||
groupId: BigInt(groupId),
|
||||
type: 0,
|
||||
};
|
||||
|
||||
const result = await sendOidbAndDecode<any>(
|
||||
bridge,
|
||||
'OidbSvcTrpcTcp.0x8a7_0',
|
||||
0x8A7,
|
||||
0,
|
||||
req,
|
||||
Oidb0x8a7ReqSchema,
|
||||
Oidb0x8a7RespSchema
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
throw new Error('get group at all remain result empty');
|
||||
}
|
||||
|
||||
return {
|
||||
can_at_all: !!result.canAtAll,
|
||||
remain_at_all_count_for_group: Number(result.groupRemain || 0), // 【修改点】:防止底层库将 uint32 也返回成 BigInt 导致无法被 JSON 序列化
|
||||
remain_at_all_count_for_uin: Number(result.uinRemain || 0) // 【修改点】:同上
|
||||
};
|
||||
}
|
||||
|
||||
export async function getUnidirectionalFriendList(
|
||||
bridge: Bridge
|
||||
) {
|
||||
const reqObj = {
|
||||
uint64_uin: String(bridge.qqInfo.uin),
|
||||
uint64_top: 0,
|
||||
uint32_req_num: 99,
|
||||
bytes_cookies: ""
|
||||
};
|
||||
|
||||
const req = {
|
||||
jsonBody: JSON.stringify(reqObj)
|
||||
};
|
||||
|
||||
const result = await sendOidbAndDecode<any>(
|
||||
bridge,
|
||||
'MQUpdateSvc_com_qq_ti.web.OidbSvc.0xe17_0',
|
||||
0xE17,
|
||||
0,
|
||||
req,
|
||||
Oidb0xe17ReqSchema,
|
||||
Oidb0xe17RespSchema
|
||||
);
|
||||
|
||||
if (!result || !result.jsonBody) {
|
||||
throw new Error('get unidirectional friend list empty');
|
||||
}
|
||||
|
||||
const parsed = JSON.parse(result.jsonBody);
|
||||
return parsed.rpt_block_list || [];
|
||||
}
|
||||
|
||||
export async function setSelfLongNick(
|
||||
bridge: Bridge,
|
||||
longNick: string
|
||||
) {
|
||||
const req = {
|
||||
uin: BigInt(bridge.qqInfo.uin),
|
||||
profile: {
|
||||
tag: 102,
|
||||
value: String(longNick)
|
||||
}
|
||||
};
|
||||
|
||||
await sendOidbAndDecode<any>(
|
||||
bridge,
|
||||
'OidbSvcTrpcTcp.0x112a_2',
|
||||
0x112A,
|
||||
2,
|
||||
req,
|
||||
Oidb0x112aReqSchema,
|
||||
Oidb0x112aRespSchema
|
||||
);
|
||||
}
|
||||
|
||||
export async function setInputStatus(
|
||||
bridge: Bridge,
|
||||
userId: number,
|
||||
eventType: number
|
||||
) {
|
||||
const targetUid = await resolveUserUid(bridge, userId);
|
||||
|
||||
if (!targetUid) {
|
||||
throw new Error('target uid not found');
|
||||
}
|
||||
|
||||
const req = {
|
||||
reqBody: {
|
||||
uid: targetUid,
|
||||
chatType: 0,
|
||||
eventType: eventType
|
||||
}
|
||||
};
|
||||
|
||||
await sendOidbAndDecode<any>(
|
||||
bridge,
|
||||
'OidbSvcTrpcTcp.0xcd4_1',
|
||||
0xCD4,
|
||||
1,
|
||||
req,
|
||||
Oidb0xcd4ReqSchema,
|
||||
Oidb0xcd4RespSchema
|
||||
);
|
||||
}
|
||||
|
||||
export async function translateEn2Zh(
|
||||
bridge: Bridge,
|
||||
words: string[]
|
||||
) {
|
||||
const req = {
|
||||
translateReq: {
|
||||
srcLang: 'en',
|
||||
dstLang: 'zh',
|
||||
words: words
|
||||
},
|
||||
tag10: 1,
|
||||
tag12: 1
|
||||
};
|
||||
|
||||
const result = await sendOidbAndDecode<any>(
|
||||
bridge,
|
||||
'OidbSvcTrpcTcp.0x990_2',
|
||||
0x990,
|
||||
2,
|
||||
req,
|
||||
Oidb0x990ReqSchema,
|
||||
Oidb0x990RespSchema
|
||||
);
|
||||
|
||||
const resp = result?.translateResp;
|
||||
if (!resp) {
|
||||
throw new Error('translate response empty');
|
||||
}
|
||||
|
||||
return resp.dstWords || [];
|
||||
}
|
||||
|
||||
export async function getMiniAppArk(
|
||||
bridge: Bridge,
|
||||
type: string,
|
||||
title: string,
|
||||
desc: string,
|
||||
picUrl: string,
|
||||
jumpUrl: string
|
||||
) {
|
||||
let appid = "1109937557"; // 默认 bili
|
||||
let iconUrl = "http://miniapp.gtimg.cn/public/appicon/51f90239b78a2e4994c11215f4c4ba15_200.jpg";
|
||||
|
||||
if (type === 'weibo') {
|
||||
appid = "1109224783";
|
||||
iconUrl = "http://miniapp.gtimg.cn/public/appicon/35bbb44dc68e65194cfacfb206b8f1f7_200.jpg";
|
||||
} else if (type !== 'bili') {
|
||||
throw new Error(`unsupported type: ${type}, only support bili and weibo`);
|
||||
}
|
||||
|
||||
const request = protoEncode({
|
||||
sdkVersion: "V1_PC_MINISDK_99.99.99_1_APP_A",
|
||||
body: {
|
||||
appid,
|
||||
title,
|
||||
desc,
|
||||
picUrl,
|
||||
jumpUrl,
|
||||
iconUrl
|
||||
}
|
||||
}, MiniAppShareReqSchema);
|
||||
|
||||
const result = await bridge.sendRawPacket('LightAppSvc.mini_app_share.AdaptShareInfo', request);
|
||||
|
||||
if (!result.success || !result.responseData) {
|
||||
throw new Error(result.errorMessage || 'get mini app ark failed');
|
||||
}
|
||||
|
||||
const decoded = protoDecode(result.responseData, MiniAppShareRespSchema);
|
||||
const jsonStr = decoded?.body?.jsonStr;
|
||||
|
||||
if (!jsonStr) {
|
||||
throw new Error('mini app share json empty');
|
||||
}
|
||||
|
||||
const parsed = JSON.parse(jsonStr);
|
||||
|
||||
return {
|
||||
data: {
|
||||
ver: parsed.ver,
|
||||
prompt: parsed.prompt,
|
||||
config: parsed.config,
|
||||
app: parsed.appName,
|
||||
view: parsed.appView,
|
||||
meta: parsed.metaData,
|
||||
miniappShareOrigin: 3,
|
||||
miniappOpenRefer: "10002"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export async function clickInlineKeyboardButton(
|
||||
bridge: Bridge,
|
||||
groupId: number,
|
||||
botAppid: number,
|
||||
buttonId: string,
|
||||
callbackData: string,
|
||||
msgSeq: number
|
||||
) {
|
||||
const req = {
|
||||
botAppid: BigInt(botAppid),
|
||||
msgSeq: BigInt(msgSeq),
|
||||
buttonId: String(buttonId),
|
||||
callbackData: String(callbackData || ''),
|
||||
unknown7: 0,
|
||||
groupId: BigInt(groupId),
|
||||
unknown9: 1,
|
||||
};
|
||||
|
||||
const result = await sendOidbAndDecode<any>(
|
||||
bridge,
|
||||
'OidbSvcTrpcTcp.0x112e_1',
|
||||
0x112E,
|
||||
1,
|
||||
req,
|
||||
Oidb0x112eReqSchema,
|
||||
Oidb0x112eRespSchema
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
throw new Error('click inline keyboard button result empty');
|
||||
}
|
||||
|
||||
return {
|
||||
result: Number(result.result || 0),
|
||||
errMsg: result.errMsg || '',
|
||||
status: 0,
|
||||
promptText: result.promptText || '',
|
||||
promptType: 0,
|
||||
promptIcon: 0
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export async function sendGroupSign(
|
||||
bridge: Bridge,
|
||||
groupId: number
|
||||
) {
|
||||
const req = {
|
||||
signInInfo: {
|
||||
uin: String(bridge.qqInfo.uin),
|
||||
groupId: String(groupId),
|
||||
version: "9.0.90"
|
||||
}
|
||||
};
|
||||
|
||||
await sendOidbAndDecode<any>(
|
||||
bridge,
|
||||
'OidbSvcTrpcTcp.0xEB7_1',
|
||||
0xEB7,
|
||||
1,
|
||||
req,
|
||||
Oidb0xeb7ReqSchema,
|
||||
Oidb0xeb7RespSchema
|
||||
);
|
||||
}
|
||||
|
||||
export async function setAvatar(
|
||||
bridge: Bridge,
|
||||
source: string,
|
||||
): Promise<void> {
|
||||
const loaded = await loadBinarySource(source, 'avatar');
|
||||
if (!loaded.bytes.length) throw new Error('avatar file is empty');
|
||||
|
||||
const hashes = computeHashes(loaded.bytes);
|
||||
const session = await fetchHighwaySession(bridge);
|
||||
await uploadHighwayHttp(bridge, session, 90, loaded.bytes, hashes.md5, new Uint8Array(0));
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,16 @@ import {
|
||||
fetchGroupFileCount as fetchGroupFileCount_,
|
||||
setOnlineStatus as setOnlineStatus_,
|
||||
setProfile as setProfile_,
|
||||
getProfileLike as getProfileLike_,
|
||||
getGroupAtAllRemain as getGroupAtAllRemain_,
|
||||
getUnidirectionalFriendList as getUnidirectionalFriendList_,
|
||||
setSelfLongNick as setSelfLongNick_,
|
||||
setInputStatus as setInputStatus_,
|
||||
translateEn2Zh as translateEn2Zh_,
|
||||
getMiniAppArk as getMiniAppArk_,
|
||||
clickInlineKeyboardButton as clickInlineKeyboardButton_,
|
||||
sendGroupSign as sendGroupSign_,
|
||||
setAvatar as setAvatar_,
|
||||
} from './bridge-actions';
|
||||
import {
|
||||
getGroupHonorInfo as getGroupHonorInfo_,
|
||||
@@ -75,6 +85,7 @@ import {
|
||||
getGroupEssenceAll as getGroupEssenceAll_,
|
||||
sendGroupNotice as sendGroupNotice_,
|
||||
getGroupNotice as getGroupNotice_,
|
||||
deleteGroupNoticeByFid as deleteGroupNotice_,
|
||||
getCookiesStr as getCookiesStr_,
|
||||
getCsrfToken as getCsrfToken_,
|
||||
getCredentials as getCredentials_,
|
||||
@@ -492,8 +503,15 @@ export class Bridge {
|
||||
return getGroupNotice_(this, groupId);
|
||||
}
|
||||
|
||||
async deleteGroupNotice(groupId: number, fid: string): Promise<boolean> {
|
||||
return deleteGroupNotice_(this, groupId, fid);
|
||||
}
|
||||
|
||||
async fetchGroupFileCount(groupId: number): Promise<{ fileCount: number; maxCount: number }> { return fetchGroupFileCount_(this, groupId); }
|
||||
|
||||
async getGroupAtAllRemain(groupId: number) {
|
||||
return getGroupAtAllRemain_(this, groupId);
|
||||
}
|
||||
// extend
|
||||
async setOnlineStatus(status: number, extStatus: number = 0, batteryStatus: number = 100): Promise<void> {
|
||||
return setOnlineStatus_(this, status, extStatus, batteryStatus);
|
||||
@@ -504,7 +522,33 @@ export class Bridge {
|
||||
async getCookiesStr(domain: string): Promise<string> { return getCookiesStr_(this, domain); }
|
||||
async getCsrfToken(): Promise<number> { return getCsrfToken_(this); }
|
||||
async getCredentials(domain: string) { return getCredentials_(this, domain); }
|
||||
|
||||
async getProfileLike(userId?: number, start?: number, limit?: number) {
|
||||
return getProfileLike_(this, userId, start, limit);
|
||||
}
|
||||
async getUnidirectionalFriendList() {
|
||||
return getUnidirectionalFriendList_(this);
|
||||
}
|
||||
async setSelfLongNick(longNick: string) {
|
||||
return setSelfLongNick_(this, longNick);
|
||||
}
|
||||
async setInputStatus(userId: number, eventType: number) {
|
||||
return setInputStatus_(this, userId, eventType);
|
||||
}
|
||||
async translateEn2Zh(words: string[]) {
|
||||
return translateEn2Zh_(this, words);
|
||||
}
|
||||
async getMiniAppArk(type: string, title: string, desc: string, picUrl: string, jumpUrl: string) {
|
||||
return getMiniAppArk_(this, type, title, desc, picUrl, jumpUrl);
|
||||
}
|
||||
async clickInlineKeyboardButton(groupId: number, botAppid: number, buttonId: string, callbackData: string, msgSeq: number) {
|
||||
return clickInlineKeyboardButton_(this, groupId, botAppid, buttonId, callbackData, msgSeq);
|
||||
}
|
||||
async sendGroupSign(groupId: number) {
|
||||
return sendGroupSign_(this, groupId);
|
||||
}
|
||||
async setAvatar(source: string): Promise<void> {
|
||||
return setAvatar_(this, source);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Module-level helper functions ---
|
||||
|
||||
@@ -862,4 +862,160 @@ export const OidbSetProfileSchema = {
|
||||
uin: { field: 1, type: 'uint64' as const },
|
||||
stringProfiles: { field: 2, type: 'repeated_message' as const, schema: OidbProfileStringItemSchema },
|
||||
intProfiles: { field: 3, type: 'repeated_message' as const, schema: OidbProfileIntItemSchema },
|
||||
} satisfies ProtoSchema;
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
|
||||
export const Oidb0x7edInteractionSchema = {
|
||||
totalCount: { field: 1, type: 'uint32' as const },
|
||||
newCount: { field: 2, type: 'uint32' as const },
|
||||
todayCount: { field: 3, type: 'uint32' as const },
|
||||
lastTime: { field: 4, type: 'uint64' as const },
|
||||
// userInfos: { field: 7, type: 'repeated_message' ... } // 我没有这个字段,期待未来补全
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
export const Oidb0x7edUserLikeInfoSchema = {
|
||||
uid: { field: 1, type: 'string' as const },
|
||||
time: { field: 2, type: 'uint64' as const },
|
||||
favoriteInfo: { field: 3, type: 'message' as const, schema: Oidb0x7edInteractionSchema },
|
||||
voteInfo: { field: 4, type: 'message' as const, schema: Oidb0x7edInteractionSchema },
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
export const Oidb0x7edReqSchema = {
|
||||
targetUid: { field: 1, type: 'string' as const },
|
||||
basic: { field: 2, type: 'uint32' as const },
|
||||
vote: { field: 3, type: 'uint32' as const },
|
||||
favorite: { field: 4, type: 'uint32' as const },
|
||||
start: { field: 12, type: 'uint32' as const },
|
||||
limit: { field: 103, type: 'uint32' as const }, // 0xB8 0x06
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
export const Oidb0x7edRespSchema = {
|
||||
userLikeInfos: { field: 1, type: 'repeated_message' as const, schema: Oidb0x7edUserLikeInfoSchema },
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
|
||||
export const Oidb0x8a7ReqSchema = {
|
||||
basic1: { field: 1, type: 'uint32' as const },
|
||||
basic2: { field: 2, type: 'uint32' as const },
|
||||
basic3: { field: 3, type: 'uint32' as const },
|
||||
uin: { field: 4, type: 'uint64' as const },
|
||||
groupId: { field: 5, type: 'uint64' as const },
|
||||
type: { field: 12, type: 'uint32' as const },
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
export const Oidb0x8a7RespSchema = {
|
||||
uinRemain: { field: 2, type: 'uint32' as const },
|
||||
groupRemain: { field: 3, type: 'uint32' as const },
|
||||
msg: { field: 4, type: 'string' as const },
|
||||
canAtAll: { field: 6, type: 'bool' as const },
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
export const Oidb0xe17ReqSchema = {
|
||||
jsonBody: { field: 3, type: 'string' as const },
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
export const Oidb0xe17RespSchema = {
|
||||
jsonBody: { field: 4, type: 'string' as const },
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
export const Oidb0x112aProfileInfoSchema = {
|
||||
tag: { field: 1, type: 'uint32' as const },
|
||||
value: { field: 2, type: 'string' as const },
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
export const Oidb0x112aReqSchema = {
|
||||
uin: { field: 1, type: 'uint64' as const },
|
||||
profile: { field: 2, type: 'message' as const, schema: Oidb0x112aProfileInfoSchema },
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
export const Oidb0x112aRespSchema = {} satisfies ProtoSchema;
|
||||
|
||||
export const Oidb0xcd4ReqBodySchema = {
|
||||
uid: { field: 1, type: 'string' as const },
|
||||
chatType: { field: 2, type: 'uint32' as const }, // 默认为 0
|
||||
eventType: { field: 3, type: 'uint32' as const }, // 输入状态 1 等
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
export const Oidb0xcd4ReqSchema = {
|
||||
reqBody: { field: 1, type: 'message' as const, schema: Oidb0xcd4ReqBodySchema },
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
export const Oidb0xcd4RespSchema = {} satisfies ProtoSchema;
|
||||
|
||||
|
||||
export const Oidb0x990TranslateReqSchema = {
|
||||
srcLang: { field: 1, type: 'string' as const },
|
||||
dstLang: { field: 2, type: 'string' as const },
|
||||
words: { field: 3, type: 'repeated_string' as const },
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
export const Oidb0x990ReqSchema = {
|
||||
translateReq: { field: 2, type: 'message' as const, schema: Oidb0x990TranslateReqSchema },
|
||||
tag10: { field: 10, type: 'uint32' as const },
|
||||
tag12: { field: 12, type: 'uint32' as const },
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
export const Oidb0x990TranslateRespSchema = {
|
||||
errorCode: { field: 1, type: 'uint32' as const },
|
||||
errorMsg: { field: 2, type: 'string' as const },
|
||||
srcLang: { field: 3, type: 'string' as const },
|
||||
dstLang: { field: 4, type: 'string' as const },
|
||||
srcWords: { field: 5, type: 'repeated_string' as const },
|
||||
dstWords: { field: 6, type: 'repeated_string' as const },
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
export const Oidb0x990RespSchema = {
|
||||
translateResp: { field: 2, type: 'message' as const, schema: Oidb0x990TranslateRespSchema }
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
export const MiniAppShareReqBodySchema = {
|
||||
appid: { field: 2, type: 'string' as const },
|
||||
title: { field: 3, type: 'string' as const },
|
||||
desc: { field: 4, type: 'string' as const },
|
||||
picUrl: { field: 9, type: 'string' as const },
|
||||
jumpUrl: { field: 11, type: 'string' as const },
|
||||
iconUrl: { field: 12, type: 'string' as const },
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
export const MiniAppShareReqSchema = {
|
||||
sdkVersion: { field: 2, type: 'string' as const },
|
||||
body: { field: 4, type: 'message' as const, schema: MiniAppShareReqBodySchema },
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
export const MiniAppShareRespBodySchema = {
|
||||
jsonStr: { field: 2, type: 'string' as const },
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
export const MiniAppShareRespSchema = {
|
||||
status: { field: 2, type: 'uint32' as const },
|
||||
msg: { field: 3, type: 'string' as const },
|
||||
body: { field: 4, type: 'message' as const, schema: MiniAppShareRespBodySchema },
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
export const Oidb0x112eReqSchema = {
|
||||
botAppid: { field: 3, type: 'uint64' as const },
|
||||
msgSeq: { field: 4, type: 'uint64' as const },
|
||||
buttonId: { field: 5, type: 'string' as const },
|
||||
callbackData: { field: 6, type: 'string' as const },
|
||||
unknown7: { field: 7, type: 'uint32' as const },
|
||||
groupId: { field: 8, type: 'uint64' as const },
|
||||
unknown9: { field: 9, type: 'uint32' as const },
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
export const Oidb0x112eRespSchema = {
|
||||
result: { field: 3, type: 'uint32' as const },
|
||||
promptText: { field: 4, type: 'string' as const },
|
||||
errMsg: { field: 5, type: 'string' as const },
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
export const Oidb0xeb7SignInInfoSchema = {
|
||||
uin: { field: 1, type: 'string' as const },
|
||||
groupId: { field: 2, type: 'string' as const },
|
||||
version: { field: 3, type: 'string' as const },
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
export const Oidb0xeb7ReqSchema = {
|
||||
signInInfo: { field: 2, type: 'message' as const, schema: Oidb0xeb7SignInInfoSchema },
|
||||
} satisfies ProtoSchema;
|
||||
|
||||
export const Oidb0xeb7RespSchema = {} satisfies ProtoSchema;
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { RequestUtil } from './web/request-util';
|
||||
import { getHonorListWebAPI, WebHonorType } from './web/group-honor';
|
||||
import { getGroupEssenceMsg, getGroupEssenceMsgAll } from './web/group-essence';
|
||||
import { setGroupNoticeWebAPI, getGroupNoticeWebAPI } from './web/group-notice';
|
||||
import { setGroupNoticeWebAPI, getGroupNoticeWebAPI, uploadGroupNoticeImage, deleteGroupNotice } from './web/group-notice';
|
||||
|
||||
export async function forceFetchClientKey(bridge: Bridge) {
|
||||
const resp = await sendOidbAndDecode<any>(
|
||||
@@ -189,15 +189,25 @@ export async function sendGroupNotice(
|
||||
let imgWidth = 540;
|
||||
let imgHeight = 300;
|
||||
|
||||
if (options?.image) {
|
||||
let imageBuffer: Buffer;
|
||||
|
||||
// 暂时不知道怎么实现,后面有空再看看
|
||||
if (options.image.startsWith('http://') || options.image.startsWith('https://')) {
|
||||
const response = await fetch(options.image);
|
||||
if (!response.ok) throw new Error(`Failed to download image: ${response.status}`);
|
||||
imageBuffer = Buffer.from(await response.arrayBuffer());
|
||||
} else {
|
||||
const { readFileSync } = await import('fs');
|
||||
imageBuffer = readFileSync(options.image);
|
||||
}
|
||||
|
||||
// if (options?.image) {
|
||||
// const picInfo = await bridge.uploadGroupBulletinPic(groupCode, options.image);
|
||||
// picId = picInfo.id;
|
||||
// imgWidth = picInfo.width;
|
||||
// imgHeight = picInfo.height;
|
||||
// }
|
||||
const picInfo = await uploadGroupNoticeImage(cookieObject, imageBuffer);
|
||||
if (picInfo) {
|
||||
picId = picInfo.id;
|
||||
imgWidth = picInfo.width;
|
||||
imgHeight = picInfo.height;
|
||||
}
|
||||
}
|
||||
|
||||
const ret = await setGroupNoticeWebAPI(
|
||||
cookieObject,
|
||||
@@ -261,6 +271,12 @@ export async function getGroupNotice(bridge: Bridge, groupId: number) {
|
||||
return retNotices;
|
||||
}
|
||||
|
||||
export async function deleteGroupNoticeByFid(bridge: Bridge, groupId: number, fid: string): Promise<boolean> {
|
||||
const groupCode = groupId.toString();
|
||||
const cookieObject = await getCookies(bridge, 'qun.qq.com');
|
||||
return await deleteGroupNotice(cookieObject, groupCode, fid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取的公共算法:根据 skey/p_skey 计算 bkn (也就是 token / csrf_token)
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { RequestUtil, cookieToString, getBknFromCookie } from './request-util';
|
||||
import { RequestUtil, cookieToString } from './request-util';
|
||||
import https from 'node:https';
|
||||
// import { readFileSync } from 'node:fs';
|
||||
// import * as console from "node:console";
|
||||
|
||||
export interface SetNoticeRetSuccess {
|
||||
ec?: number;
|
||||
@@ -6,6 +9,12 @@ export interface SetNoticeRetSuccess {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface UploadImageRetSuccess {
|
||||
ec?: number;
|
||||
id?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface WebApiGroupNoticeFeed {
|
||||
fid: string;
|
||||
u: number;
|
||||
@@ -26,6 +35,14 @@ export interface WebApiGroupNoticeRet {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export function calculateBkn(key: string): string {
|
||||
let hash = 5381;
|
||||
for (let i = 0; i < key.length; i++) {
|
||||
const code = key.charCodeAt(i);
|
||||
hash = hash + (hash << 5) + code;
|
||||
}
|
||||
return (hash & 0x7FFFFFFF).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送群公告 Web API
|
||||
@@ -44,33 +61,54 @@ export async function setGroupNoticeWebAPI(
|
||||
imgHeight: number = 300
|
||||
): Promise<SetNoticeRetSuccess | undefined> {
|
||||
try {
|
||||
// 分别获取 skey 和 p_skey
|
||||
const skey = cookieObject['skey'] || '';
|
||||
const pskey = cookieObject['p_skey'] || skey;
|
||||
|
||||
const bodyBkn = calculateBkn(skey);
|
||||
const urlBkn = calculateBkn(pskey);
|
||||
//
|
||||
// console.log(skey)
|
||||
// console.log(pskey)
|
||||
//
|
||||
// console.log(urlBkn);
|
||||
// console.log(bodyBkn)
|
||||
|
||||
const settings = JSON.stringify({
|
||||
is_show_edit_card: isShowEditCard,
|
||||
tip_window_type: tipWindowType,
|
||||
confirm_required: confirmRequired,
|
||||
});
|
||||
|
||||
const externalParam = {
|
||||
pic: picId,
|
||||
imgWidth: imgWidth.toString(),
|
||||
imgHeight: imgHeight.toString(),
|
||||
};
|
||||
|
||||
const url = `https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?${new URLSearchParams({
|
||||
bkn: getBknFromCookie(cookieObject),
|
||||
const bodyParams: Record<string, string> = {
|
||||
qid: groupCode,
|
||||
bkn: bodyBkn,
|
||||
text: content,
|
||||
pinned: pinned.toString(),
|
||||
type: type.toString(),
|
||||
settings,
|
||||
...(picId === '' ? {} : externalParam),
|
||||
}).toString()}`;
|
||||
};
|
||||
|
||||
if (picId !== '') {
|
||||
bodyParams.pic = picId;
|
||||
bodyParams.imgWidth = imgWidth.toString();
|
||||
bodyParams.imgHeight = imgHeight.toString();
|
||||
}
|
||||
|
||||
// 注意这里:URL 必须使用 skey 算出来的 bkn
|
||||
const url = `https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?bkn=${urlBkn}`;
|
||||
const body = new URLSearchParams(bodyParams).toString();
|
||||
|
||||
const ret = await RequestUtil.HttpGetJson<SetNoticeRetSuccess>(
|
||||
url,
|
||||
'POST', // 注意这里必须是 POST
|
||||
'',
|
||||
{ Cookie: cookieToString(cookieObject) }
|
||||
'POST',
|
||||
body,
|
||||
{
|
||||
Cookie: cookieToString(cookieObject),
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
true,
|
||||
false
|
||||
);
|
||||
return ret;
|
||||
} catch (e) {
|
||||
@@ -78,35 +116,151 @@ export async function setGroupNoticeWebAPI(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群公告列表 Web API
|
||||
*/
|
||||
export async function getGroupNoticeWebAPI(
|
||||
cookieObject: Record<string, string>,
|
||||
groupCode: string
|
||||
groupCode: string,
|
||||
start: number = -1, // -1 表示第一页
|
||||
count: number = 20 // 抓包中是 10,你可以根据需要调整
|
||||
): Promise<WebApiGroupNoticeRet | undefined> {
|
||||
const bkn = getBknFromCookie(cookieObject);
|
||||
|
||||
|
||||
const params = new URLSearchParams({
|
||||
bkn: bkn,
|
||||
qid: groupCode,
|
||||
ft: '23',
|
||||
ni: '1',
|
||||
i: '1',
|
||||
log_read: '1',
|
||||
platform: '1',
|
||||
s: '-1',
|
||||
}).toString();
|
||||
|
||||
const url = `https://web.qun.qq.com/cgi-bin/announce/get_t_list?${params}&n=20`;
|
||||
|
||||
try {
|
||||
const skey = cookieObject['skey'] || '';
|
||||
const pskey = cookieObject['p_skey'] || skey;
|
||||
|
||||
const bodyBkn = calculateBkn(skey); // URL 使用 skey 算出的 bkn
|
||||
const urlBkn = calculateBkn(pskey); // Body 使用 p_skey 算出的 bkn
|
||||
|
||||
const bodyParams = new URLSearchParams({
|
||||
qid: groupCode,
|
||||
bkn: bodyBkn, // Body 中传入 p_skey 计算结果
|
||||
ft: '23',
|
||||
s: start.toString(), // 分页游标
|
||||
n: count.toString(), // 获取数量
|
||||
i: '1',
|
||||
ni: '1'
|
||||
}).toString();
|
||||
|
||||
const url = `https://web.qun.qq.com/cgi-bin/announce/list_announce?bkn=${urlBkn}`;
|
||||
|
||||
const ret = await RequestUtil.HttpGetJson<WebApiGroupNoticeRet>(
|
||||
url,
|
||||
'GET',
|
||||
'',
|
||||
{ Cookie: cookieToString(cookieObject) }
|
||||
'POST',
|
||||
bodyParams,
|
||||
{
|
||||
Cookie: cookieToString(cookieObject),
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Referer': 'https://web.qun.qq.com/mannounce/index.html?_wv=1031&_bid=148'
|
||||
},
|
||||
true,
|
||||
false
|
||||
);
|
||||
// console.log(JSON.stringify(ret, null, 2));
|
||||
return ret?.ec === 0 ? ret : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 上传群公告图片 Web API
|
||||
*/
|
||||
export async function uploadGroupNoticeImage(
|
||||
cookieObject: Record<string, string>,
|
||||
imageBuffer: Buffer
|
||||
): Promise<{ id: string; width: number; height: number } | undefined> {
|
||||
try {
|
||||
const bkn = calculateBkn(cookieObject['skey']);
|
||||
const boundary = `-----------------------------${Date.now()}`;
|
||||
|
||||
const parts: Buffer[] = [];
|
||||
parts.push(Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="bkn"\r\n\r\n${bkn}\r\n`));
|
||||
parts.push(Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="source"\r\n\r\ntroopNotice\r\n`));
|
||||
parts.push(Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="m"\r\n\r\n0\r\n`));
|
||||
parts.push(Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="pic_up"; filename="image.jpg"\r\nContent-Type: image/jpeg\r\n\r\n`));
|
||||
parts.push(imageBuffer);
|
||||
parts.push(Buffer.from(`\r\n--${boundary}--\r\n`));
|
||||
|
||||
const body = Buffer.concat(parts);
|
||||
|
||||
const url = 'https://web.qun.qq.com/cgi-bin/announce/upload_img';
|
||||
const options = {
|
||||
hostname: 'web.qun.qq.com',
|
||||
path: '/cgi-bin/announce/upload_img',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
||||
'Content-Length': body.length,
|
||||
'Cookie': cookieToString(cookieObject),
|
||||
},
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = https.request(options, (res) => {
|
||||
let data = '';
|
||||
res.on('data', (chunk) => { data += chunk; });
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const result = JSON.parse(data) as UploadImageRetSuccess;
|
||||
if (result.ec === 0 && result.id) {
|
||||
const unescapedIdStr = result.id.replace(/"/g, '"');
|
||||
|
||||
const idObj = JSON.parse(unescapedIdStr);
|
||||
resolve({ id: idObj.id, width: parseInt(idObj.w), height: parseInt(idObj.h) });
|
||||
} else {
|
||||
resolve(undefined);
|
||||
}
|
||||
} catch {
|
||||
resolve(undefined);
|
||||
}
|
||||
});
|
||||
});
|
||||
req.on('error', () => resolve(undefined));
|
||||
req.write(body);
|
||||
req.end();
|
||||
});
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除群公告 Web API
|
||||
*/
|
||||
export async function deleteGroupNotice(
|
||||
cookieObject: Record<string, string>,
|
||||
groupCode: string,
|
||||
fid: string
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const skey = cookieObject['skey'] || '';
|
||||
const pskey = cookieObject['p_skey'] || skey;
|
||||
|
||||
const bodyBkn = calculateBkn(skey); // Body 使用 skey 算出的 bkn
|
||||
const urlBkn = calculateBkn(pskey); // URL 使用 p_skey 算出的 bkn
|
||||
|
||||
const params = new URLSearchParams({
|
||||
bkn: bodyBkn, // 注意这里:POST Body 放入 bodyBkn
|
||||
fid: fid,
|
||||
qid: groupCode,
|
||||
}).toString();
|
||||
|
||||
// 注意这里:URL 拼接 urlBkn
|
||||
const url = `https://web.qun.qq.com/cgi-bin/announce/del_feed?bkn=${urlBkn}`;
|
||||
|
||||
const ret = await RequestUtil.HttpGetJson<SetNoticeRetSuccess>(
|
||||
url,
|
||||
'POST',
|
||||
params,
|
||||
{
|
||||
Cookie: cookieToString(cookieObject),
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
true,
|
||||
false
|
||||
);
|
||||
return ret?.ec === 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -239,16 +239,13 @@ export function register(h: ApiHandler, ctx: ApiActionContext): void {
|
||||
return failedResponse(RETCODE.BAD_REQUEST, 'group_id and content are required');
|
||||
}
|
||||
|
||||
if (image) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not implemented: image upload for group notice is not supported yet');
|
||||
}
|
||||
|
||||
if (!ctx.sendGroupNotice) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not implemented');
|
||||
}
|
||||
|
||||
try {
|
||||
const options = {
|
||||
image: image || undefined,
|
||||
pinned: params.pinned !== undefined ? Number(params.pinned) : 0,
|
||||
type: params.type !== undefined ? Number(params.type) : 1,
|
||||
confirm_required: params.confirm_required !== undefined ? Number(params.confirm_required) : 1,
|
||||
@@ -277,8 +274,28 @@ export function register(h: ApiHandler, ctx: ApiActionContext): void {
|
||||
}
|
||||
});
|
||||
|
||||
h.registerAction('_del_group_notice', async () => {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not yet implemented');
|
||||
h.registerAction('_del_group_notice', async (params) => {
|
||||
const groupId = asNumber(params.group_id);
|
||||
const fid = asString(params.fid) || asString(params.notice_id);
|
||||
|
||||
if (!groupId || !fid) {
|
||||
return failedResponse(RETCODE.BAD_REQUEST, 'group_id and fid/notice_id are required');
|
||||
}
|
||||
|
||||
if (!ctx.deleteGroupNotice) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not implemented');
|
||||
}
|
||||
|
||||
try {
|
||||
const success = await ctx.deleteGroupNotice(groupId, fid);
|
||||
if (success) {
|
||||
return okResponse();
|
||||
} else {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'delete failed');
|
||||
}
|
||||
} catch (e) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, String(e));
|
||||
}
|
||||
});
|
||||
|
||||
// --- Forward messages ---
|
||||
@@ -605,12 +622,26 @@ export function register(h: ApiHandler, ctx: ApiActionContext): void {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not yet implemented');
|
||||
});
|
||||
|
||||
// todo 我的建议是引入数据库api 纯协议我不知道这种api怎么实现,ntQQ在实现这个方法的时候只进行了数据库查询,完全没碰网络
|
||||
h.registerAction('get_recent_contact', async () => {
|
||||
return okResponse([]);
|
||||
});
|
||||
|
||||
h.registerAction('get_profile_like', async () => {
|
||||
return okResponse({});
|
||||
h.registerAction('get_profile_like', async (params) => {
|
||||
const userId = asNumber(params.user_id);
|
||||
const start = asNumber(params.start) || 0;
|
||||
const count = asNumber(params.count) || 10;
|
||||
|
||||
if (!ctx.getProfileLike) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not implemented');
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await ctx.getProfileLike(userId, start, count);
|
||||
return okResponse(data);
|
||||
} catch (e) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, String(e));
|
||||
}
|
||||
});
|
||||
|
||||
h.registerAction('get_friends_with_category', async () => {
|
||||
@@ -622,6 +653,7 @@ export function register(h: ApiHandler, ctx: ApiActionContext): void {
|
||||
|
||||
// --- Additional NapCat-compatible stubs ---
|
||||
|
||||
///napcat 似乎也用不了??,暂时不管了
|
||||
h.registerAction('get_online_clients', async () => {
|
||||
return okResponse({ clients: [] });
|
||||
});
|
||||
@@ -638,16 +670,55 @@ export function register(h: ApiHandler, ctx: ApiActionContext): void {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not yet implemented');
|
||||
});
|
||||
|
||||
h.registerAction('get_group_at_all_remain', async () => {
|
||||
return okResponse({ can_at_all: false, remain_at_all_count_for_group: 0, remain_at_all_count_for_uin: 0 });
|
||||
h.registerAction('get_group_at_all_remain', async (params) => {
|
||||
const groupId = asNumber(params.group_id);
|
||||
|
||||
if (!groupId) {
|
||||
return failedResponse(RETCODE.BAD_REQUEST, 'invalid group_id');
|
||||
}
|
||||
|
||||
if (!ctx.getGroupAtAllRemain) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not implemented');
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await ctx.getGroupAtAllRemain(groupId);
|
||||
return okResponse(data);
|
||||
} catch (e) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, String(e));
|
||||
}
|
||||
});
|
||||
|
||||
h.registerAction('get_unidirectional_friend_list', async () => {
|
||||
return okResponse([]);
|
||||
if (!ctx.getUnidirectionalFriendList) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not implemented');
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await ctx.getUnidirectionalFriendList();
|
||||
return okResponse(data);
|
||||
} catch (e) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, String(e));
|
||||
}
|
||||
});
|
||||
|
||||
h.registerAction('set_self_longnick', async () => {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not yet implemented');
|
||||
h.registerAction('set_self_longnick', async (params) => {
|
||||
const longNick = params.longNick || params.long_nick;
|
||||
|
||||
if (typeof longNick !== 'string') {
|
||||
return failedResponse(RETCODE.BAD_REQUEST, 'invalid longNick');
|
||||
}
|
||||
|
||||
if (!ctx.setSelfLongNick) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not implemented');
|
||||
}
|
||||
|
||||
try {
|
||||
await ctx.setSelfLongNick(longNick);
|
||||
return okResponse({});
|
||||
} catch (e) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, String(e));
|
||||
}
|
||||
});
|
||||
|
||||
h.registerAction('get_collection_list', async () => {
|
||||
@@ -658,16 +729,66 @@ export function register(h: ApiHandler, ctx: ApiActionContext): void {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not yet implemented');
|
||||
});
|
||||
|
||||
h.registerAction('set_qq_avatar', async () => {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not yet implemented');
|
||||
h.registerAction('set_qq_avatar', async (params) => {
|
||||
const file = asString(params.file);
|
||||
if (!file) return failedResponse(RETCODE.BAD_REQUEST, 'file is required');
|
||||
|
||||
if (!ctx.setAvatar) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not implemented');
|
||||
}
|
||||
|
||||
try {
|
||||
await ctx.setAvatar(file);
|
||||
return okResponse();
|
||||
} catch (e) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, String(e));
|
||||
}
|
||||
});
|
||||
|
||||
h.registerAction('set_input_status', async () => {
|
||||
return okResponse();
|
||||
h.registerAction('set_input_status', async (params) => {
|
||||
const userId = asNumber(params.user_id);
|
||||
const eventType = asNumber(params.event_type);
|
||||
|
||||
if (!userId) {
|
||||
return failedResponse(RETCODE.BAD_REQUEST, 'invalid user_id');
|
||||
}
|
||||
|
||||
// event_type 有可能是 0 (取消输入状态),所以这里严格判断 undefined 或 isNaN
|
||||
if (eventType === undefined || isNaN(eventType)) {
|
||||
return failedResponse(RETCODE.BAD_REQUEST, 'invalid event_type');
|
||||
}
|
||||
|
||||
if (!ctx.setInputStatus) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not implemented');
|
||||
}
|
||||
|
||||
try {
|
||||
await ctx.setInputStatus(userId, eventType);
|
||||
return okResponse({});
|
||||
} catch (e) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, String(e));
|
||||
}
|
||||
});
|
||||
|
||||
h.registerAction('translate_en2zh', async () => {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not yet implemented');
|
||||
h.registerAction('translate_en2zh', async (params) => {
|
||||
const rawWords = params.words;
|
||||
|
||||
if (!Array.isArray(rawWords)) {
|
||||
return failedResponse(RETCODE.BAD_REQUEST, 'invalid words array');
|
||||
}
|
||||
|
||||
const words = rawWords.map(w => String(w));
|
||||
|
||||
if (!ctx.translateEn2Zh) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not implemented');
|
||||
}
|
||||
|
||||
try {
|
||||
const translated = await ctx.translateEn2Zh(words);
|
||||
return okResponse({ words: translated });
|
||||
} catch (e) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, String(e));
|
||||
}
|
||||
});
|
||||
|
||||
h.registerAction('get_clientkey', async () => {
|
||||
@@ -681,21 +802,81 @@ export function register(h: ApiHandler, ctx: ApiActionContext): void {
|
||||
return okResponse(clientKeyInfo);
|
||||
});
|
||||
|
||||
h.registerAction('get_mini_app_ark', async () => {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not yet implemented');
|
||||
h.registerAction('get_mini_app_ark', async (params) => {
|
||||
const type = params.type || 'bili';
|
||||
const title = params.title || '';
|
||||
const desc = params.desc || '';
|
||||
const picUrl = params.picUrl || params.pic_url || '';
|
||||
const jumpUrl = params.jumpUrl || params.jump_url || '';
|
||||
|
||||
if (!ctx.getMiniAppArk) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not implemented');
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await ctx.getMiniAppArk(
|
||||
String(type),
|
||||
String(title),
|
||||
String(desc),
|
||||
String(picUrl),
|
||||
String(jumpUrl)
|
||||
);
|
||||
return okResponse(data);
|
||||
} catch (e) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, String(e));
|
||||
}
|
||||
});
|
||||
|
||||
h.registerAction('click_inline_keyboard_button', async () => {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not yet implemented');
|
||||
h.registerAction('click_inline_keyboard_button', async (params) => {
|
||||
const groupId = asNumber(params.group_id);
|
||||
const botAppid = asNumber(params.bot_appid);
|
||||
const buttonId = params.button_id;
|
||||
const callbackData = params.callback_data || '';
|
||||
const msgSeq = asNumber(params.msg_seq);
|
||||
|
||||
if (!groupId || !botAppid || !buttonId || !msgSeq) {
|
||||
return failedResponse(RETCODE.BAD_REQUEST, 'missing required parameters');
|
||||
}
|
||||
|
||||
if (!ctx.clickInlineKeyboardButton) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not implemented');
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await ctx.clickInlineKeyboardButton(
|
||||
groupId,
|
||||
botAppid,
|
||||
String(buttonId),
|
||||
String(callbackData),
|
||||
msgSeq
|
||||
);
|
||||
return okResponse(data);
|
||||
} catch (e) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, String(e));
|
||||
}
|
||||
});
|
||||
|
||||
h.registerAction('set_group_sign', async () => {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not yet implemented');
|
||||
});
|
||||
const handleGroupSign = async (params: any) => {
|
||||
const groupId = asNumber(params.group_id);
|
||||
|
||||
h.registerAction('send_group_sign', async () => {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not yet implemented');
|
||||
});
|
||||
if (!groupId) {
|
||||
return failedResponse(RETCODE.BAD_REQUEST, 'invalid group_id');
|
||||
}
|
||||
|
||||
if (!ctx.sendGroupSign) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, 'not implemented');
|
||||
}
|
||||
|
||||
try {
|
||||
await ctx.sendGroupSign(groupId);
|
||||
return okResponse({});
|
||||
} catch (e) {
|
||||
return failedResponse(RETCODE.ACTION_FAILED, String(e));
|
||||
}
|
||||
};
|
||||
|
||||
h.registerAction('set_group_sign', handleGroupSign);
|
||||
h.registerAction('send_group_sign', handleGroupSign);
|
||||
|
||||
h.registerAction('get_group_info_ex', async (params) => {
|
||||
const groupId = asNumber(params.group_id);
|
||||
|
||||
@@ -55,6 +55,7 @@ export interface ApiActionContext {
|
||||
setGroupName?: (groupId: number, name: string) => Promise<void>;
|
||||
setGroupLeave?: (groupId: number) => Promise<void>;
|
||||
setGroupSpecialTitle?: (groupId: number, userId: number, title: string) => Promise<void>;
|
||||
getGroupAtAllRemain?: (groupId: number) => Promise<any>;
|
||||
// Group file
|
||||
uploadGroupFile?: (groupId: number, file: string, name?: string, folderId?: string, uploadFile?: boolean) => Promise<string | null>;
|
||||
uploadPrivateFile?: (userId: number, file: string, name?: string, uploadFile?: boolean) => Promise<string | null>;
|
||||
@@ -78,6 +79,13 @@ export interface ApiActionContext {
|
||||
forceFetchClientKey? : () => Promise<{clientKey: string, keyIndex: string, expireTime: string}>;
|
||||
setOnlineStatus?: (status: number, extStatus?: number, batteryStatus?: number) => Promise<void>;
|
||||
setProfile?: (nickname?: string, personalNote?: string) => Promise<void>;
|
||||
getUnidirectionalFriendList?: () => Promise<any>;
|
||||
setSelfLongNick?: (longNick: string) => Promise<void>;
|
||||
setInputStatus?: (userId: number, eventType: number) => Promise<void>;
|
||||
translateEn2Zh?: (words: string[]) => Promise<string[]>;
|
||||
getMiniAppArk?: (type: string, title: string, desc: string, picUrl: string, jumpUrl: string) => Promise<any>;
|
||||
clickInlineKeyboardButton?: (groupId: number, botAppid: number, buttonId: string, callbackData: string, msgSeq: number) => Promise<any>;
|
||||
sendGroupSign?: (groupId: number) => Promise<void>;
|
||||
// New context methods
|
||||
setGroupReaction?: (groupId: number, sequence: number, code: string, isSet: boolean) => Promise<void>;
|
||||
handleDeleteFriend?: (userId: number, block?: boolean) => Promise<void>;
|
||||
@@ -96,18 +104,22 @@ export interface ApiActionContext {
|
||||
setMsgEmojiLike?: (messageId: number, emojiId: string, set: boolean) => Promise<void>;
|
||||
markGroupMsgAsRead?: (groupId: number, sequence: number) => Promise<void>;
|
||||
markPrivateMsgAsRead?: (userId: number, sequence: number) => Promise<void>;
|
||||
getProfileLike?: (userId?: number, start?: number, limit?: number) => Promise<any>;
|
||||
// Web
|
||||
getGroupHonorInfo?: (groupId: number, type: WebHonorType | string) => Promise<any>;
|
||||
getGroupEssence?: (groupId: number, pageStart?: number, pageLimit?: number) => Promise<GroupEssenceMsgRet>;
|
||||
getGroupEssenceAll?: (groupId: number) => Promise<GroupEssenceMsgRet[]>;
|
||||
sendGroupNotice?: (groupId: number, content: string, options?: any) => Promise<any>;
|
||||
getGroupNotice?: (groupId: number) => Promise<any[]>;
|
||||
deleteGroupNotice?: (groupId: number, fid: string) => Promise<boolean>;
|
||||
getCookiesStr?: (domain: string) => Promise<string>;
|
||||
getCsrfToken?: () => Promise<number>;
|
||||
getCredentials?: (domain: string) => Promise<{ cookies: string; token: number; csrf_token: number }>;
|
||||
// Media lookup (populated from previously dispatched message segments)
|
||||
getImageInfo?: (file: string) => Promise<JsonObject | null>;
|
||||
getRecordInfo?: (file: string) => Promise<JsonObject | null>;
|
||||
// Avatar
|
||||
setAvatar?: (source: string) => Promise<void>;
|
||||
}
|
||||
|
||||
type ActionHandler = (params: JsonObject) => Promise<import('./types').ApiResponse>;
|
||||
|
||||
@@ -79,6 +79,7 @@ export function buildApiContext(ref: OneBotInstanceContext): ApiActionContext {
|
||||
setGroupName: (groupId, name) => bridge.setGroupName(groupId, name),
|
||||
setGroupLeave: (groupId) => bridge.leaveGroup(groupId),
|
||||
setGroupSpecialTitle: (groupId, userId, title) => bridge.setGroupSpecialTitle(groupId, userId, title),
|
||||
getGroupAtAllRemain: (groupId) => bridge.getGroupAtAllRemain(groupId),
|
||||
// Group file
|
||||
uploadGroupFile: async (groupId, file, name, folderId, uploadFile) => {
|
||||
const result = await bridge.uploadGroupFile(groupId, file, name ?? '', folderId ?? '/', uploadFile ?? true);
|
||||
@@ -105,6 +106,14 @@ export function buildApiContext(ref: OneBotInstanceContext): ApiActionContext {
|
||||
sendGroupPoke: (groupId, userId) => bridge.sendPoke(true, groupId, userId),
|
||||
setEssenceMsg: (messageId) => handleSetEssence(bridge, messageStore, messageId, true),
|
||||
deleteEssenceMsg: (messageId) => handleSetEssence(bridge, messageStore, messageId, false),
|
||||
getProfileLike: (userId?: number, start: number = 0, limit: number = 10) => bridge.getProfileLike(userId, start, limit),
|
||||
getUnidirectionalFriendList: () => bridge.getUnidirectionalFriendList(),
|
||||
setSelfLongNick: (longNick) => bridge.setSelfLongNick(longNick),
|
||||
setInputStatus: (userId, eventType) => bridge.setInputStatus(userId, eventType),
|
||||
translateEn2Zh: (words) => bridge.translateEn2Zh(words),
|
||||
getMiniAppArk: (type, title, desc, picUrl, jumpUrl) => bridge.getMiniAppArk(type, title, desc, picUrl, jumpUrl),
|
||||
clickInlineKeyboardButton: (groupId, botAppid, buttonId, callbackData, msgSeq) => bridge.clickInlineKeyboardButton(groupId, botAppid, buttonId, callbackData, msgSeq),
|
||||
sendGroupSign: (groupId) => bridge.sendGroupSign(groupId),
|
||||
// New extended
|
||||
setGroupReaction: (groupId, sequence, code, isSet) => bridge.setGroupReaction(groupId, sequence, code, isSet),
|
||||
handleDeleteFriend: (userId, block) => bridge.deleteFriend(userId, !!block),
|
||||
@@ -161,12 +170,15 @@ export function buildApiContext(ref: OneBotInstanceContext): ApiActionContext {
|
||||
getGroupEssenceAll: (groupId: number) => bridge.getGroupEssenceAll(groupId),
|
||||
sendGroupNotice: (groupId: number, content: string, options?: any) => bridge.sendGroupNotice(groupId, content, options),
|
||||
getGroupNotice: (groupId: number) => bridge.getGroupNotice(groupId),
|
||||
deleteGroupNotice: (groupId: number, fid: string) => bridge.deleteGroupNotice(groupId, fid),
|
||||
getCookiesStr: (domain: string) => bridge.getCookiesStr(domain),
|
||||
getCsrfToken: () => bridge.getCsrfToken(),
|
||||
getCredentials: (domain: string) => bridge.getCredentials(domain),
|
||||
// Media lookup
|
||||
getImageInfo: (file) => handleGetImageInfo(mediaStore, file),
|
||||
getRecordInfo: (file) => handleGetRecordInfo(bridge, mediaStore, file),
|
||||
// Avatar
|
||||
setAvatar: (source: string) => bridge.setAvatar(source),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user