refactor: move web action into web-actions from bridge-actions
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user