refactor: move web action into web-actions from bridge-actions

This commit is contained in:
H3CoF6
2026-05-09 04:08:52 +08:00
parent c862a157db
commit 51c2fa285a
4 changed files with 231 additions and 215 deletions
-212
View File
@@ -10,10 +10,6 @@ import { buildSendElems } from './builders/element-builder';
import { parseMsgPush } from './handlers/msg-push-handler';
import type { ForwardNodePayload, MessageElement } from './events';
import { PushMsgSchema } from './proto/message';
import { RequestUtil } from './web/request-util';
import { getHonorListWebAPI, WebHonorType } from './web/group-honor';
import { getGroupEssenceMsg, getGroupEssenceMsgAll } from './web/group-essence';
import { setGroupNoticeWebAPI } from './web/group-notice'
import {
OidbMuteMemberSchema,
OidbMuteAllSchema,
@@ -48,10 +44,6 @@ import {
GroupRecallRequestSchema,
C2CRecallRequestSchema,
SsoReadedReportReqSchema,
OidbClientKeyRespSchema,
OidbClientKeyReqSchema,
OidbGetPskeyReqSchema,
OidbGetPskeyRespSchema
} from './proto/oidb-action';
import { FileUploadExtSchema } from './proto/highway';
import {
@@ -1298,208 +1290,4 @@ export async function markGroupMessageRead(
if (!result.success) {
throw new Error(result.errorMessage || 'mark group message read failed');
}
}
export async function forceFetchClientKey(bridge: Bridge) {
const resp = await sendOidbAndDecode<any>(
bridge,
'OidbSvcTrpcTcp.0x102a_1',
0x102A,
1,
{},
OidbClientKeyReqSchema,
OidbClientKeyRespSchema
);
const clientKey = resp?.clientKey || '';
const keyIndex = String(resp?.keyIndex || '19'); // 不知道是什么
return {
clientKey,
keyIndex,
expireTime: String(resp?.expireTime || '1800')
};
}
export async function getPSkey(bridge: Bridge, domainList: string[]) {
const resp = await sendOidbAndDecode<any>(
bridge,
'OidbSvcTrpcTcp.0x102a_0',
0x102A,
0,
{ domainList },
OidbGetPskeyReqSchema,
OidbGetPskeyRespSchema
);
const domainPskeyMap = new Map<string, string>();
if (resp?.pskeyItems && Array.isArray(resp.pskeyItems)) {
for (const item of resp.pskeyItems) {
if (item.domain && item.pskey) {
domainPskeyMap.set(item.domain, item.pskey);
}
}
}
return {
domainPskeyMap,
};
}
export async function getCookies(bridge: Bridge, domain: string) {
const ClientKeyData = await forceFetchClientKey(bridge);
// 构造 ptlogin2 跳转 URL
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + bridge.qqInfo.uin +
'&clientkey=' + ClientKeyData.clientKey +
'&u1=https%3A%2F%2F' + domain + '%2F' + bridge.qqInfo.uin + '%2Finfocenter&keyindex=' + ClientKeyData.keyIndex;
const data = await RequestUtil.HttpsGetCookies(requestUrl);
if (!data['p_skey'] || data['p_skey'].length === 0) {
try {
const pskeyData = await getPSkey(bridge, [domain]);
const pskey = pskeyData.domainPskeyMap.get(domain);
if (pskey) {
data['p_skey'] = pskey;
}
} catch {
return data;
}
}
return data;
}
export async function getGroupHonorInfo(bridge: Bridge, groupId: number, type: WebHonorType | string) {
const groupCode = groupId.toString();
const cookieObject = await getCookies(bridge, 'qun.qq.com');
const honorInfo: any = {
group_id: groupId,
current_talkative: null,
talkative_list: [],
performer_list: [],
legend_list: [],
emotion_list: [],
strong_newbie_list: [],
};
if (type === WebHonorType.TALKATIVE || type === WebHonorType.ALL) {
const talkativeList = await getHonorListWebAPI(cookieObject, groupCode, 1);
if (talkativeList.length > 0) {
honorInfo.current_talkative = talkativeList[0];
honorInfo.talkative_list = talkativeList;
}
}
if (type === WebHonorType.PERFORMER || type === WebHonorType.ALL) {
honorInfo.performer_list = await getHonorListWebAPI(cookieObject, groupCode, 2);
}
if (type === WebHonorType.LEGEND || type === WebHonorType.ALL) {
honorInfo.legend_list = await getHonorListWebAPI(cookieObject, groupCode, 3);
}
if (type === WebHonorType.EMOTION || type === WebHonorType.ALL) {
honorInfo.emotion_list = await getHonorListWebAPI(cookieObject, groupCode, 6);
}
return honorInfo;
}
export async function getSKey(bridge: Bridge): Promise<string> {
const ClientKeyData = await forceFetchClientKey(bridge);
if (!ClientKeyData.clientKey) {
throw new Error('getClientKey Error: clientKey is empty');
}
const u1 = encodeURIComponent('https://h5.qzone.qq.com/qqnt/qzoneinpcqq/friend?refresh=0&clientuin=0&darkMode=0');
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033' +
'&clientuin=' + bridge.qqInfo.uin +
'&clientkey=' + ClientKeyData.clientKey +
'&u1=' + u1 +
'&keyindex=' + ClientKeyData.keyIndex;
const cookies: { [key: string]: string } = await RequestUtil.HttpsGetCookies(requestUrl);
const skey = cookies['skey'];
if (!skey) {
throw new Error('SKey is Empty');
}
return skey;
}
/**
* 分页获取群精华消息
*/
export async function getGroupEssence(bridge: Bridge, groupId: number, pageStart: number = 0, pageLimit: number = 50) {
const groupCode = groupId.toString();
const cookieObject = await getCookies(bridge, 'qun.qq.com');
const essenceData = await getGroupEssenceMsg(cookieObject, groupCode, pageStart, pageLimit);
return essenceData || { retcode: -1, data: { is_end: true, msg_list: [] } };
}
/**
* 获取所有群精华消息
*/
export async function getGroupEssenceAll(bridge: Bridge, groupId: number) {
const groupCode = groupId.toString();
const cookieObject = await getCookies(bridge, 'qun.qq.com');
const essenceDataAll = await getGroupEssenceMsgAll(cookieObject, groupCode);
return essenceDataAll;
}
export async function sendGroupNotice(
bridge: Bridge,
groupId: number,
content: string,
options?: {
image?: string; // 未来如果要发图片,传路径进来
pinned?: number;
type?: number;
confirm_required?: number;
}
) {
const groupCode = groupId.toString();
const cookieObject = await getCookies(bridge, 'qun.qq.com');
let picId = '';
let imgWidth = 540;
let imgHeight = 300;
// 📝 预留的图片上传逻辑:
// if (options?.image) {
// const picInfo = await bridge.uploadGroupBulletinPic(groupCode, options.image);
// picId = picInfo.id;
// imgWidth = picInfo.width;
// imgHeight = picInfo.height;
// }
const ret = await setGroupNoticeWebAPI(
cookieObject,
groupCode,
content,
options?.pinned ?? 0,
options?.type ?? 1,
1,
1,
options?.confirm_required ?? 1,
picId,
imgWidth,
imgHeight
);
if (!ret || ret.ec !== 0) {
throw new Error(`设置群公告失败: ${ret?.em || '未知错误(Cookie过期或权限不足)'}`);
}
return ret;
}
+3 -1
View File
@@ -65,12 +65,14 @@ import {
markGroupMessageRead as markPrivateMsgAsRead_,
setFriendRemark as setFriendRemark_,
fetchGroupFileCount as fetchGroupFileCount_,
} from './bridge-actions';
import {
getGroupHonorInfo as getGroupHonorInfo_,
forceFetchClientKey as forceFetchClientKey_,
getGroupEssence as getGroupEssence_,
getGroupEssenceAll as getGroupEssenceAll_,
sendGroupNotice as sendGroupNotice_,
} from './bridge-actions';
} from './web-actions';
import type { GroupFilesResult } from './bridge-actions';
import type { MediaIndexNode } from './bridge-actions';
+221
View File
@@ -0,0 +1,221 @@
// Web API operations extracted from Bridge.
import type { Bridge } from './bridge';
import { sendOidbAndDecode } from './bridge-oidb';
import { resolveUserUid } from './bridge-oidb';
import {
OidbClientKeyRespSchema,
OidbClientKeyReqSchema,
OidbGetPskeyReqSchema,
OidbGetPskeyRespSchema
} from './proto/oidb-action';
import { RequestUtil } from './web/request-util';
import { getHonorListWebAPI, WebHonorType } from './web/group-honor';
import { getGroupEssenceMsg, getGroupEssenceMsgAll } from './web/group-essence';
import { setGroupNoticeWebAPI } from './web/group-notice';
export async function forceFetchClientKey(bridge: Bridge) {
const resp = await sendOidbAndDecode<any>(
bridge,
'OidbSvcTrpcTcp.0x102a_1',
0x102A,
1,
{},
OidbClientKeyReqSchema,
OidbClientKeyRespSchema
);
const clientKey = resp?.clientKey || '';
const keyIndex = String(resp?.keyIndex || '19'); // 不知道是什么
return {
clientKey,
keyIndex,
expireTime: String(resp?.expireTime || '1800')
};
}
export async function getPSkey(bridge: Bridge, domainList: string[]) {
const resp = await sendOidbAndDecode<any>(
bridge,
'OidbSvcTrpcTcp.0x102a_0',
0x102A,
0,
{ domainList },
OidbGetPskeyReqSchema,
OidbGetPskeyRespSchema
);
const domainPskeyMap = new Map<string, string>();
if (resp?.pskeyItems && Array.isArray(resp.pskeyItems)) {
for (const item of resp.pskeyItems) {
if (item.domain && item.pskey) {
domainPskeyMap.set(item.domain, item.pskey);
}
}
}
return {
domainPskeyMap,
};
}
export async function getCookies(bridge: Bridge, domain: string) {
const ClientKeyData = await forceFetchClientKey(bridge);
// 构造 ptlogin2 跳转 URL
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033&clientuin=' + bridge.qqInfo.uin +
'&clientkey=' + ClientKeyData.clientKey +
'&u1=https%3A%2F%2F' + domain + '%2F' + bridge.qqInfo.uin + '%2Finfocenter&keyindex=' + ClientKeyData.keyIndex;
const data = await RequestUtil.HttpsGetCookies(requestUrl);
if (!data['p_skey'] || data['p_skey'].length === 0) {
try {
const pskeyData = await getPSkey(bridge, [domain]);
const pskey = pskeyData.domainPskeyMap.get(domain);
if (pskey) {
data['p_skey'] = pskey;
}
} catch {
return data;
}
}
return data;
}
export async function getGroupHonorInfo(bridge: Bridge, groupId: number, type: WebHonorType | string) {
const groupCode = groupId.toString();
const cookieObject = await getCookies(bridge, 'qun.qq.com');
const honorInfo: any = {
group_id: groupId,
current_talkative: null,
talkative_list: [],
performer_list: [],
legend_list: [],
emotion_list: [],
strong_newbie_list: [],
};
if (type === WebHonorType.TALKATIVE || type === WebHonorType.ALL) {
const talkativeList = await getHonorListWebAPI(cookieObject, groupCode, 1);
if (talkativeList.length > 0) {
honorInfo.current_talkative = talkativeList[0];
honorInfo.talkative_list = talkativeList;
}
}
if (type === WebHonorType.PERFORMER || type === WebHonorType.ALL) {
honorInfo.performer_list = await getHonorListWebAPI(cookieObject, groupCode, 2);
}
if (type === WebHonorType.LEGEND || type === WebHonorType.ALL) {
honorInfo.legend_list = await getHonorListWebAPI(cookieObject, groupCode, 3);
}
if (type === WebHonorType.EMOTION || type === WebHonorType.ALL) {
honorInfo.emotion_list = await getHonorListWebAPI(cookieObject, groupCode, 6);
}
return honorInfo;
}
export async function getSKey(bridge: Bridge): Promise<string> {
const ClientKeyData = await forceFetchClientKey(bridge);
if (!ClientKeyData.clientKey) {
throw new Error('getClientKey Error: clientKey is empty');
}
const u1 = encodeURIComponent('https://h5.qzone.qq.com/qqnt/qzoneinpcqq/friend?refresh=0&clientuin=0&darkMode=0');
const requestUrl = 'https://ssl.ptlogin2.qq.com/jump?ptlang=1033' +
'&clientuin=' + bridge.qqInfo.uin +
'&clientkey=' + ClientKeyData.clientKey +
'&u1=' + u1 +
'&keyindex=' + ClientKeyData.keyIndex;
const cookies: { [key: string]: string } = await RequestUtil.HttpsGetCookies(requestUrl);
const skey = cookies['skey'];
if (!skey) {
throw new Error('SKey is Empty');
}
return skey;
}
/**
* 分页获取群精华消息
*/
export async function getGroupEssence(bridge: Bridge, groupId: number, pageStart: number = 0, pageLimit: number = 50) {
const groupCode = groupId.toString();
const cookieObject = await getCookies(bridge, 'qun.qq.com');
const essenceData = await getGroupEssenceMsg(cookieObject, groupCode, pageStart, pageLimit);
return essenceData || { retcode: -1, data: { is_end: true, msg_list: [] } };
}
/**
* 获取所有群精华消息
*/
export async function getGroupEssenceAll(bridge: Bridge, groupId: number) {
const groupCode = groupId.toString();
const cookieObject = await getCookies(bridge, 'qun.qq.com');
const essenceDataAll = await getGroupEssenceMsgAll(cookieObject, groupCode);
return essenceDataAll;
}
export async function sendGroupNotice(
bridge: Bridge,
groupId: number,
content: string,
options?: {
image?: string; // 未来如果要发图片,传路径进来
pinned?: number;
type?: number;
confirm_required?: number;
}
) {
const groupCode = groupId.toString();
const cookieObject = await getCookies(bridge, 'qun.qq.com');
let picId = '';
let imgWidth = 540;
let imgHeight = 300;
// 暂时不知道怎么实现,后面有空再看看
// if (options?.image) {
// const picInfo = await bridge.uploadGroupBulletinPic(groupCode, options.image);
// picId = picInfo.id;
// imgWidth = picInfo.width;
// imgHeight = picInfo.height;
// }
const ret = await setGroupNoticeWebAPI(
cookieObject,
groupCode,
content,
options?.pinned ?? 0,
options?.type ?? 1,
1,
1,
options?.confirm_required ?? 1,
picId,
imgWidth,
imgHeight
);
if (!ret || ret.ec !== 0) {
throw new Error(`设置群公告失败: ${ret?.em || '未知错误(Cookie过期或权限不足)'}`);
}
return ret;
}
+7 -2
View File
@@ -230,20 +230,25 @@ export function register(h: ApiHandler, ctx: ApiActionContext): void {
// --- Group notice stubs ---
h.registerAction('send_group_notice', async (params) => {
h.registerAction('_send_group_notice', async (params) => {
const groupId = asNumber(params.group_id);
const content = asString(params.content);
const image = asString(params.image);
if (!groupId || !content) {
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: asString(params.image),
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,