SnowCat 雪猫底层全源码:NapCat 4.18.28 底座 + SnowLuma 能力移植 + V8 通道适配 9.9.37
26 包 monorepo(TS 全源码): - snowcat-core: NT 内核 API 封装 + listener(含 9.9.37 六回调适配)+ Packet 发包 - snowcat-onebot: OneBot v11 全动作 + MCP Streamable HTTP 适配器 + 稳定线 - snowcat-webui-frontend/backend: 雪猫控制台(React+HeroUI) - snowcat-shell: 单文件 bundle 出口(vite inlineDynamicImports) - snowcat-native: napi2native / MoeHoo / ffmpeg / pty 运行时 - 56 项 SnowLuma 独门动作移植(qzone 全家桶/群相册/装扮/闪照/收藏) - 工具件: 解包器/启动器/便携制作器/doctor GitHub 关联已全量指向 DandanLLab/SnowCatQQ
This commit is contained in:
commit
b9655a5ab1
1048 files changed
+130851
No files matched your search
@@ -0,0 +1,1000 @@
|
||||
import { RequestUtil } from 'snowcat-common/src/request';
|
||||
import {
|
||||
GroupEssenceMsgRet,
|
||||
InstanceContext,
|
||||
WebApiGroupMember,
|
||||
WebApiGroupMemberRet,
|
||||
WebApiGroupNoticeRet,
|
||||
WebHonorType, SnowCatCore,
|
||||
} from '@/snowcat-core/index';
|
||||
|
||||
import { createReadStream, readFileSync, statSync } from 'node:fs';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { basename } from 'node:path';
|
||||
import { qunAlbumControl } from '../data/webapi';
|
||||
import { createAlbumCommentRequest, createAlbumFeedPublish, createAlbumMediaFeed } from '../data/album';
|
||||
import {
|
||||
buildQzoneDeleteBody,
|
||||
buildQzonePublishBody,
|
||||
buildQzoneUploadImageBody,
|
||||
parseQzoneDeleteResponse,
|
||||
parseQzonePublishResponse,
|
||||
parseQzoneUploadImageResponseText,
|
||||
toQzoneRichval,
|
||||
QzoneDeleteResponse,
|
||||
QzonePublishResponse,
|
||||
} from '../data/qzone';
|
||||
// SnowCat · qzone Web 线扩展(SnowLuma 移植):读列表/详情/点赞/评论/回复/资料/拉黑/权限
|
||||
import {
|
||||
decodeQzoneFeedCursor,
|
||||
encodeQzoneFeedCursor,
|
||||
initialQzoneFeedState,
|
||||
mapQzoneFeeds,
|
||||
nextQzoneFeedState,
|
||||
qzoneFeedPosition,
|
||||
type QzoneFeedState,
|
||||
type RawFeedsResponse,
|
||||
} from '../data/qzone-feeds';
|
||||
import { parseQzoneCallback } from '../data/qzone-web';
|
||||
import {
|
||||
buildQzoneBlackBody,
|
||||
buildQzoneCommentBody,
|
||||
buildQzoneLikeBody,
|
||||
buildQzoneMsgDetailQuery,
|
||||
buildQzoneProfileQuery,
|
||||
buildQzoneProfileUpdateBody,
|
||||
buildQzoneReplyBody,
|
||||
legacyMsgListRoute,
|
||||
mapQzoneMsgDetail,
|
||||
mapQzoneMsgList,
|
||||
parseQzoneBlackResponse,
|
||||
parseQzoneCommentResponse,
|
||||
parseQzoneDetailError,
|
||||
parseQzoneJson,
|
||||
parseQzoneLikeResponse,
|
||||
parseQzoneProfileResponse,
|
||||
parseQzoneProfileUpdateResponse,
|
||||
parseQzonePublishFull,
|
||||
parseQzoneUpdateRightResponse,
|
||||
userMsgListRoute,
|
||||
buildQzoneUpdatePayload,
|
||||
type QzoneProfile,
|
||||
type QzoneProfileUpdate,
|
||||
type QzoneReplyTarget,
|
||||
type QzoneUpdateRightResult,
|
||||
type RawMsgListResponse,
|
||||
type RawMsgDetailResponse,
|
||||
type QzoneRoute,
|
||||
} from '../data/qzone-web';
|
||||
export interface SetNoticeRetSuccess {
|
||||
ec: number;
|
||||
em: string;
|
||||
id: number;
|
||||
ltsm: number;
|
||||
new_fid: string;
|
||||
read_only: number;
|
||||
role: number;
|
||||
srv_code: number;
|
||||
}
|
||||
|
||||
export class NTQQWebApi {
|
||||
context: InstanceContext;
|
||||
core: SnowCatCore;
|
||||
|
||||
constructor (context: InstanceContext, core: SnowCatCore) {
|
||||
this.context = context;
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
async shareDigest (groupCode: string, msgSeq: string, msgRandom: string, targetGroupCode: string) {
|
||||
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
|
||||
const url = `https://qun.qq.com/cgi-bin/group_digest/share_digest?${new URLSearchParams({
|
||||
bkn: this.getBknFromCookie(cookieObject),
|
||||
group_code: groupCode,
|
||||
msg_seq: msgSeq,
|
||||
msg_random: msgRandom,
|
||||
target_group_code: targetGroupCode,
|
||||
}).toString()}`;
|
||||
try {
|
||||
return RequestUtil.HttpGetText(url, 'GET', '', { Cookie: this.cookieToString(cookieObject) });
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async getGroupEssenceMsgAll (GroupCode: string) {
|
||||
const ret: GroupEssenceMsgRet[] = [];
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const data = await this.getGroupEssenceMsg(GroupCode, i, 50);
|
||||
if (!data) break;
|
||||
ret.push(data);
|
||||
if (data.data.is_end) break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
async getGroupEssenceMsg (GroupCode: string, page_start: number = 0, page_limit: number = 50) {
|
||||
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
|
||||
const url = `https://qun.qq.com/cgi-bin/group_digest/digest_list?${new URLSearchParams({
|
||||
bkn: this.getBknFromCookie(cookieObject),
|
||||
page_start: page_start.toString(),
|
||||
page_limit: page_limit.toString(),
|
||||
group_code: GroupCode,
|
||||
}).toString()}`;
|
||||
try {
|
||||
const ret = await RequestUtil.HttpGetJson<GroupEssenceMsgRet>(
|
||||
url,
|
||||
'GET',
|
||||
'',
|
||||
{ Cookie: this.cookieToString(cookieObject) }
|
||||
);
|
||||
return ret.retcode === 0 ? ret : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async getGroupMembers (GroupCode: string): Promise<WebApiGroupMember[]> {
|
||||
// logDebug('webapi 获取群成员', GroupCode);
|
||||
const memberData: Array<WebApiGroupMember> = new Array<WebApiGroupMember>();
|
||||
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
|
||||
const retList: Promise<WebApiGroupMemberRet>[] = [];
|
||||
const fastRet = await RequestUtil.HttpGetJson<WebApiGroupMemberRet>(
|
||||
`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${new URLSearchParams({
|
||||
st: '0',
|
||||
end: '40',
|
||||
sort: '1',
|
||||
gc: GroupCode,
|
||||
bkn: this.getBknFromCookie(cookieObject),
|
||||
}).toString()}`,
|
||||
'POST',
|
||||
'',
|
||||
{ Cookie: this.cookieToString(cookieObject) }
|
||||
);
|
||||
if (!fastRet?.count || fastRet?.errcode !== 0 || !fastRet?.mems) {
|
||||
return [];
|
||||
} else {
|
||||
for (const key in fastRet.mems) {
|
||||
if (fastRet.mems[key]) {
|
||||
memberData.push(fastRet.mems[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 初始化获取PageNum
|
||||
const PageNum = Math.ceil(fastRet.count / 40);
|
||||
// 遍历批量请求
|
||||
for (let i = 2; i <= PageNum; i++) {
|
||||
const ret = RequestUtil.HttpGetJson<WebApiGroupMemberRet>(
|
||||
`https://qun.qq.com/cgi-bin/qun_mgr/search_group_members?${new URLSearchParams({
|
||||
st: ((i - 1) * 40).toString(),
|
||||
end: (i * 40).toString(),
|
||||
sort: '1',
|
||||
gc: GroupCode,
|
||||
bkn: this.getBknFromCookie(cookieObject),
|
||||
}).toString()}`,
|
||||
'POST',
|
||||
'',
|
||||
{ Cookie: this.cookieToString(cookieObject) }
|
||||
);
|
||||
retList.push(ret);
|
||||
}
|
||||
// 批量等待
|
||||
for (let i = 1; i <= PageNum; i++) {
|
||||
const ret = await (retList[i]);
|
||||
if (!ret?.count || ret?.errcode !== 0 || !ret?.mems) {
|
||||
continue;
|
||||
}
|
||||
for (const key in ret.mems) {
|
||||
if (ret.mems[key]) {
|
||||
memberData.push(ret.mems[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return memberData;
|
||||
}
|
||||
|
||||
// public async addGroupDigest(groupCode: string, msgSeq: string) {
|
||||
// const url = `https://qun.qq.com/cgi-bin/group_digest/cancel_digest?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&msg_seq=${msgSeq}&msg_random=444021292`;
|
||||
// const res = await this.request(url);
|
||||
// return await res.json();
|
||||
// }
|
||||
|
||||
// public async getGroupDigest(groupCode: string) {
|
||||
// const url = `https://qun.qq.com/cgi-bin/group_digest/digest_list?random=665&X-CROSS-ORIGIN=fetch&group_code=${groupCode}&page_start=0&page_limit=20`;
|
||||
// const res = await this.request(url);
|
||||
// return await res.json();
|
||||
// }
|
||||
|
||||
async setGroupNotice (
|
||||
GroupCode: string,
|
||||
Content: string,
|
||||
pinned: number = 0,
|
||||
type: number = 1,
|
||||
is_show_edit_card: number = 1,
|
||||
tip_window_type: number = 1,
|
||||
confirm_required: number = 1,
|
||||
picId: string = '',
|
||||
imgWidth: number = 540,
|
||||
imgHeight: number = 300
|
||||
) {
|
||||
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
|
||||
|
||||
try {
|
||||
const settings = JSON.stringify({
|
||||
is_show_edit_card,
|
||||
tip_window_type,
|
||||
confirm_required,
|
||||
});
|
||||
const externalParam = {
|
||||
pic: picId,
|
||||
imgWidth: imgWidth.toString(),
|
||||
imgHeight: imgHeight.toString(),
|
||||
};
|
||||
const ret: SetNoticeRetSuccess = await RequestUtil.HttpGetJson<SetNoticeRetSuccess>(
|
||||
`https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?${new URLSearchParams({
|
||||
bkn: this.getBknFromCookie(cookieObject),
|
||||
qid: GroupCode,
|
||||
text: Content,
|
||||
pinned: pinned.toString(),
|
||||
type: type.toString(),
|
||||
settings,
|
||||
...(picId === '' ? {} : externalParam),
|
||||
}).toString()}`,
|
||||
'POST',
|
||||
'',
|
||||
{ Cookie: this.cookieToString(cookieObject) }
|
||||
);
|
||||
return ret;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async getGroupNotice (GroupCode: string): Promise<undefined | WebApiGroupNoticeRet> {
|
||||
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
|
||||
try {
|
||||
const ret = await RequestUtil.HttpGetJson<WebApiGroupNoticeRet>(
|
||||
`https://web.qun.qq.com/cgi-bin/announce/get_t_list?${new URLSearchParams({
|
||||
bkn: this.getBknFromCookie(cookieObject),
|
||||
qid: GroupCode,
|
||||
ft: '23',
|
||||
ni: '1',
|
||||
n: '1',
|
||||
i: '1',
|
||||
log_read: '1',
|
||||
platform: '1',
|
||||
s: '-1',
|
||||
}).toString()}&n=20`,
|
||||
'GET',
|
||||
'',
|
||||
{ Cookie: this.cookieToString(cookieObject) }
|
||||
);
|
||||
return ret?.ec === 0 ? ret : undefined;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private async getDataInternal (cookieObject: { [key: string]: string; }, groupCode: string, type: number) {
|
||||
let resJson;
|
||||
try {
|
||||
const res = await RequestUtil.HttpGetText(
|
||||
`https://qun.qq.com/interactive/honorlist?${new URLSearchParams({
|
||||
gc: groupCode,
|
||||
type: type.toString(),
|
||||
}).toString()}`,
|
||||
'GET',
|
||||
'',
|
||||
{ Cookie: this.cookieToString(cookieObject) }
|
||||
);
|
||||
const match = /window\.__INITIAL_STATE__=(.*?);/.exec(res);
|
||||
if (match?.[1]) {
|
||||
resJson = JSON.parse(match[1].trim());
|
||||
}
|
||||
return type === 1 ? resJson?.talkativeList : resJson?.actorList;
|
||||
} catch (e) {
|
||||
this.context.logger.logDebug('获取当前群荣耀失败', e);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private async getHonorList (cookieObject: { [key: string]: string; }, groupCode: string, type: number) {
|
||||
const data = await this.getDataInternal(cookieObject, groupCode, type);
|
||||
if (!data) {
|
||||
this.context.logger.logError(`获取类型 ${type} 的荣誉信息失败`);
|
||||
return [];
|
||||
}
|
||||
return data.map((item: {
|
||||
uin: string,
|
||||
name: string,
|
||||
avatar: string,
|
||||
desc: string,
|
||||
}) => ({
|
||||
user_id: item?.uin,
|
||||
nickname: item?.name,
|
||||
avatar: item?.avatar,
|
||||
description: item?.desc,
|
||||
}));
|
||||
}
|
||||
|
||||
async getGroupHonorInfo (groupCode: string, getType: WebHonorType) {
|
||||
const cookieObject = await this.core.apis.UserApi.getCookies('qun.qq.com');
|
||||
const HonorInfo = {
|
||||
group_id: Number(groupCode),
|
||||
current_talkative: {},
|
||||
talkative_list: [],
|
||||
performer_list: [],
|
||||
legend_list: [],
|
||||
emotion_list: [],
|
||||
strong_newbie_list: [],
|
||||
};
|
||||
|
||||
if (getType === WebHonorType.TALKATIVE || getType === WebHonorType.ALL) {
|
||||
const talkativeList = await this.getHonorList(cookieObject, groupCode, 1);
|
||||
if (talkativeList.length > 0) {
|
||||
HonorInfo.current_talkative = talkativeList[0];
|
||||
HonorInfo.talkative_list = talkativeList;
|
||||
}
|
||||
}
|
||||
|
||||
if (getType === WebHonorType.PERFORMER || getType === WebHonorType.ALL) {
|
||||
HonorInfo.performer_list = await this.getHonorList(cookieObject, groupCode, 2);
|
||||
}
|
||||
|
||||
if (getType === WebHonorType.LEGEND || getType === WebHonorType.ALL) {
|
||||
HonorInfo.legend_list = await this.getHonorList(cookieObject, groupCode, 3);
|
||||
}
|
||||
|
||||
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
|
||||
HonorInfo.emotion_list = await this.getHonorList(cookieObject, groupCode, 6);
|
||||
}
|
||||
|
||||
// 冒尖小春笋好像已经被tx扬了 R.I.P.
|
||||
if (getType === WebHonorType.EMOTION || getType === WebHonorType.ALL) {
|
||||
HonorInfo.strong_newbie_list = [];
|
||||
}
|
||||
|
||||
return HonorInfo;
|
||||
}
|
||||
|
||||
private cookieToString (cookieObject: { [key: string]: string; }) {
|
||||
return Object.entries(cookieObject).map(([key, value]) => `${key}=${value}`).join('; ');
|
||||
}
|
||||
|
||||
public getBknFromCookie (cookieObject: { [key: string]: string; }) {
|
||||
const sKey = cookieObject['skey'] as string;
|
||||
|
||||
let hash = 5381;
|
||||
for (let i = 0; i < sKey.length; i++) {
|
||||
const code = sKey.charCodeAt(i);
|
||||
hash = hash + (hash << 5) + code;
|
||||
}
|
||||
return (hash & 0x7FFFFFFF).toString();
|
||||
}
|
||||
|
||||
public getBknFromSKey (sKey: string) {
|
||||
let hash = 5381;
|
||||
for (let i = 0; i < sKey.length; i++) {
|
||||
const code = sKey.charCodeAt(i);
|
||||
hash = hash + (hash << 5) + code;
|
||||
}
|
||||
return (hash & 0x7FFFFFFF).toString();
|
||||
}
|
||||
|
||||
public getBknFromPSKey (psKey: string) {
|
||||
return this.getBknFromSKey(psKey);
|
||||
}
|
||||
|
||||
async getAlbumListByNTQQ (gc: string, attach_info: string = '') {
|
||||
return await this.context.session.getAlbumService().getAlbumList({
|
||||
qun_id: gc,
|
||||
attach_info,
|
||||
seq: 3331,
|
||||
request_time_line: {
|
||||
request_invoke_time: '0',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getAlbumList (gc: string) {
|
||||
const skey = await this.core.apis.UserApi.getSKey() || '';
|
||||
const pskey = (await this.core.apis.UserApi.getPSkey(['qzone.qq.com'])).domainPskeyMap.get('qzone.qq.com') || '';
|
||||
const bkn = this.getBknFromSKey(skey);
|
||||
const uin = this.core.selfInfo.uin || '10001';
|
||||
const cookies = `p_uin=o${this.core.selfInfo.uin}; p_skey=${pskey}; skey=${skey}; uin=o${uin} `;
|
||||
const api = 'https://h5.qzone.qq.com/proxy/domain/u.photo.qzone.qq.com/cgi-bin/upp/qun_list_album_v2?';
|
||||
const params = new URLSearchParams({
|
||||
random: '7570',
|
||||
g_tk: bkn,
|
||||
format: 'json',
|
||||
inCharset: 'utf-8',
|
||||
outCharset: 'utf-8',
|
||||
qua: 'V1_IPH_SQ_6.2.0_0_HDBM_T',
|
||||
cmd: 'qunGetAlbumList',
|
||||
qunId: gc,
|
||||
qunid: gc,
|
||||
start: '0',
|
||||
num: '1000',
|
||||
uin,
|
||||
getMemberRole: '0',
|
||||
});
|
||||
const response = await RequestUtil.HttpGetJson<{ data: { album: Array<{ id: string, title: string; }>; }; }>(api + params.toString(), 'GET', '', {
|
||||
Cookie: cookies,
|
||||
});
|
||||
return response.data.album;
|
||||
}
|
||||
|
||||
async createQunAlbumSession (gc: string, sAlbumID: string, sAlbumName: string, path: string, skey: string, pskey: string, img_md5: string, uin: string) {
|
||||
const img = readFileSync(path);
|
||||
const img_size = img.length;
|
||||
const img_name = basename(path);
|
||||
const GTK = this.getBknFromSKey(skey);
|
||||
const cookie = `p_uin=o${uin}; p_skey=${pskey}; skey=${skey}; uin=o${uin}`;
|
||||
const body = qunAlbumControl({
|
||||
uin,
|
||||
group_id: gc,
|
||||
pskey,
|
||||
pic_md5: img_md5,
|
||||
img_size,
|
||||
img_name,
|
||||
sAlbumName,
|
||||
sAlbumID,
|
||||
});
|
||||
const api = `https://h5.qzone.qq.com/webapp/json/sliceUpload/FileBatchControl/${img_md5}?g_tk=${GTK}`;
|
||||
const post = await RequestUtil.HttpGetJson<{ data: { session: string; }, ret: number, msg: string; }>(api, 'POST', body, {
|
||||
Cookie: cookie,
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
return post;
|
||||
}
|
||||
|
||||
async uploadQunAlbumSlice (path: string, session: string, skey: string, pskey: string, uin: string, slice_size: number) {
|
||||
const img_size = statSync(path).size;
|
||||
let seq = 0;
|
||||
let offset = 0;
|
||||
const GTK = this.getBknFromSKey(skey);
|
||||
const cookie = `p_uin=o${uin}; p_skey=${pskey}; skey=${skey}; uin=o${uin}`;
|
||||
|
||||
const stream = createReadStream(path, { highWaterMark: slice_size });
|
||||
|
||||
for await (const chunk of stream) {
|
||||
const end = Math.min(offset + chunk.length, img_size);
|
||||
const form = new FormData();
|
||||
form.append('uin', uin);
|
||||
form.append('appid', 'qun');
|
||||
form.append('session', session);
|
||||
form.append('offset', offset.toString());
|
||||
form.append('data', new Blob([chunk], { type: 'application/octet-stream' }), 'blob');
|
||||
form.append('checksum', '');
|
||||
form.append('check_type', '0');
|
||||
form.append('retry', '0');
|
||||
form.append('seq', seq.toString());
|
||||
form.append('end', end.toString());
|
||||
form.append('cmd', 'FileUpload');
|
||||
form.append('slice_size', slice_size.toString());
|
||||
form.append('biz_req.iUploadType', '0');
|
||||
|
||||
const api = `https://h5.qzone.qq.com/webapp/json/sliceUpload/FileUpload?seq=${seq}&retry=0&offset=${offset}&end=${end}&total=${img_size}&type=form&g_tk=${GTK}`;
|
||||
const response = await fetch(api, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Cookie: cookie,
|
||||
},
|
||||
body: form,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const post = await response.json() as { ret: number, msg: string; }; if (post.ret !== 0) {
|
||||
throw new Error(`分片 ${seq} 上传失败: ${post.msg}`);
|
||||
}
|
||||
offset += chunk.length;
|
||||
seq++;
|
||||
}
|
||||
|
||||
return { success: true, message: '上传完成' };
|
||||
}
|
||||
|
||||
async uploadImageToQunAlbum (gc: string, sAlbumID: string, sAlbumName: string, path: string) {
|
||||
const skey = await this.core.apis.UserApi.getSKey() || '';
|
||||
const pskey = (await this.core.apis.UserApi.getPSkey(['qzone.qq.com'])).domainPskeyMap.get('qzone.qq.com') || '';
|
||||
const img_md5 = createHash('md5').update(readFileSync(path)).digest('hex');
|
||||
const uin = this.core.selfInfo.uin || '10001';
|
||||
const session = (await this.createQunAlbumSession(gc, sAlbumID, sAlbumName, path, skey, pskey, img_md5, uin)).data.session;
|
||||
if (!session) throw new Error('创建群相册会话失败');
|
||||
await this.uploadQunAlbumSlice(path, session, skey, pskey, uin, 16384);
|
||||
}
|
||||
|
||||
async getAlbumMediaListByNTQQ (gc: string, albumId: string, attach_info: string = '') {
|
||||
return (await this.context.session.getAlbumService().getMediaList({
|
||||
qun_id: gc,
|
||||
attach_info,
|
||||
seq: 0,
|
||||
request_time_line: {
|
||||
request_invoke_time: '0',
|
||||
},
|
||||
album_id: albumId,
|
||||
lloc: '',
|
||||
batch_id: '',
|
||||
})).response;
|
||||
}
|
||||
|
||||
async doAlbumMediaPlainCommentByNTQQ (
|
||||
qunId: string,
|
||||
albumId: string,
|
||||
lloc: string,
|
||||
content: string) {
|
||||
const random_seq = Math.floor(Math.random() * 9000) + 1000;
|
||||
const uin = this.core.selfInfo.uin || '10001';
|
||||
// 16位number数字
|
||||
const client_key = Date.now() * 1000;
|
||||
return await this.context.session.getAlbumService().doQunComment(
|
||||
random_seq, {
|
||||
map_info: [],
|
||||
map_bytes_info: [],
|
||||
map_user_account: [],
|
||||
},
|
||||
qunId,
|
||||
2,
|
||||
createAlbumMediaFeed(uin, albumId, lloc),
|
||||
createAlbumCommentRequest(uin, content, client_key)
|
||||
);
|
||||
}
|
||||
|
||||
async deleteAlbumMediaByNTQQ (
|
||||
qunId: string,
|
||||
albumId: string,
|
||||
lloc: string) {
|
||||
const random_seq = Math.floor(Math.random() * 9000) + 1000;
|
||||
return await this.context.session.getAlbumService().deleteMedias(
|
||||
random_seq,
|
||||
qunId,
|
||||
albumId,
|
||||
[lloc],
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
async doAlbumMediaLikeByNTQQ (
|
||||
qunId: string,
|
||||
albumId: string,
|
||||
batchId: string,
|
||||
lloc: string | undefined,
|
||||
isLike: boolean
|
||||
) {
|
||||
const random_seq = Math.floor(Math.random() * 9000) + 1000;
|
||||
const uin = this.core.selfInfo.uin || '10001';
|
||||
|
||||
const type = isLike ? 2 : 1;
|
||||
const status = isLike ? 0 : 1;
|
||||
|
||||
let id = '';
|
||||
if (lloc) {
|
||||
id = `421_1_0_${qunId}|${albumId}|${batchId}^||^421_1_0_${qunId}|${albumId}|${lloc}^||^0`;
|
||||
} else {
|
||||
id = `421_1_0_${qunId}|${albumId}|${batchId}`;
|
||||
}
|
||||
|
||||
return await this.context.session.getAlbumService().doQunLike(
|
||||
random_seq,
|
||||
{
|
||||
map_info: [],
|
||||
map_bytes_info: [],
|
||||
map_user_account: [],
|
||||
},
|
||||
type,
|
||||
{
|
||||
id,
|
||||
status,
|
||||
},
|
||||
createAlbumFeedPublish(qunId, uin, albumId, batchId)
|
||||
);
|
||||
}
|
||||
|
||||
private async getQzoneAuth () {
|
||||
const skey = await this.core.apis.UserApi.getSKey() || '';
|
||||
const pskey = (await this.core.apis.UserApi.getPSkey(['qzone.qq.com'])).domainPskeyMap.get('qzone.qq.com') || '';
|
||||
const uin = this.core.selfInfo.uin || '10001';
|
||||
const g_tk = this.getBknFromSKey(skey);
|
||||
const cookie = `p_uin=o${uin}; p_skey=${pskey}; skey=${skey}; uin=o${uin}`;
|
||||
return { skey, pskey, uin, g_tk, cookie };
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传图片到 QQ 空间相册, 返回可用于发说说的 richval 字符串
|
||||
* @param base64 图片Base64编码内容(不带data:前缀)
|
||||
*/
|
||||
async uploadImageToQzone (base64: string): Promise<string> {
|
||||
const { skey, pskey, uin, g_tk, cookie } = await this.getQzoneAuth();
|
||||
const body = buildQzoneUploadImageBody({ uin, skey, pskey, g_tk, base64 });
|
||||
const api = `https://up.qzone.qq.com/cgi-bin/upload/cgi_upload_image?g_tk=${g_tk}`;
|
||||
const resultText = await RequestUtil.HttpGetText(api, 'POST', body, {
|
||||
Cookie: cookie,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
});
|
||||
const parsed = parseQzoneUploadImageResponseText(resultText);
|
||||
if (parsed.code !== undefined && parsed.code !== 0) {
|
||||
throw new Error(parsed.msg || `QQ空间图片上传失败, code=${parsed.code}`);
|
||||
}
|
||||
if (!parsed.data) {
|
||||
throw new Error('QQ空间图片上传失败, 响应缺少data字段');
|
||||
}
|
||||
return toQzoneRichval(parsed.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发表QQ空间说说
|
||||
* @param content 说说正文
|
||||
* @param richvals 每张图片对应的richval数组, 为空表示纯文字说说
|
||||
* @param ugcRight 查看权限 1所有人可见 4好友可见 16部分好友可见 64仅自己可见 128部分好友不可见
|
||||
* @param targetUins 权限作用QQ号数组, ugcRight为16/128时使用
|
||||
*/
|
||||
async publishQzoneMsg (content: string, richvals: string[], ugcRight: number, targetUins?: number[]) {
|
||||
const { uin, g_tk, cookie } = await this.getQzoneAuth();
|
||||
const richval = richvals.length > 0 ? richvals.join('\t') : undefined;
|
||||
const allowUins = targetUins && targetUins.length > 0 ? targetUins.join('|') : undefined;
|
||||
const body = buildQzonePublishBody({
|
||||
hostuin: uin,
|
||||
content,
|
||||
richval,
|
||||
ugcRight,
|
||||
allowUins,
|
||||
});
|
||||
const api = `https://user.qzone.qq.com/proxy/domain/taotao.qzone.qq.com/cgi-bin/emotion_cgi_publish_v6?g_tk=${g_tk}`;
|
||||
const result = await RequestUtil.HttpGetJson<QzonePublishResponse>(api, 'POST', body, {
|
||||
Cookie: cookie,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
}, true, false);
|
||||
return parseQzonePublishResponse(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除QQ空间说说
|
||||
* @param tid 说说Tid, 来自 publishQzoneMsg 的返回值
|
||||
*/
|
||||
async deleteQzoneMsg (tid: string) {
|
||||
const { uin, g_tk, cookie } = await this.getQzoneAuth();
|
||||
const body = buildQzoneDeleteBody({ hostuin: uin, tid });
|
||||
const api = `https://user.qzone.qq.com/proxy/domain/taotao.qzone.qq.com/cgi-bin/emotion_cgi_delete_v6?g_tk=${g_tk}`;
|
||||
const result = await RequestUtil.HttpGetJson<QzoneDeleteResponse>(api, 'POST', body, {
|
||||
Cookie: cookie,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
}, true, false);
|
||||
return parseQzoneDeleteResponse(result);
|
||||
}
|
||||
|
||||
async getDaySignedList (groupCode: string) {
|
||||
const pSkey = (await this.core.apis.UserApi.getPSkey(['qun.qq.com'])).domainPskeyMap.get('qun.qq.com')!;
|
||||
const selfUin = this.core.selfInfo.uin;
|
||||
const cookie = `p_uin=o${selfUin}; p_skey=${pSkey}; uin=o${selfUin}`;
|
||||
const post = await RequestUtil.HttpGetJson<{
|
||||
retCode: number,
|
||||
costTime: number,
|
||||
response: {
|
||||
ret?: {
|
||||
code: string,
|
||||
msg: string,
|
||||
},
|
||||
page?: {
|
||||
infos?: {
|
||||
uid: string,
|
||||
uidGroupNick: string,
|
||||
signedTimeStamp: string,
|
||||
signInRank: number,
|
||||
}[],
|
||||
offset: number,
|
||||
total: number,
|
||||
}[],
|
||||
},
|
||||
funcCode: number,
|
||||
}>(`https://qun.qq.com/v2/signin/trpc/GetDaySignedList?g_tk=${this.getBknFromPSKey(pSkey)}`, 'POST', {
|
||||
dayYmd: new Date().toISOString().slice(0, 10).replace(/-/g, ''),
|
||||
offset: 0,
|
||||
limit: 100,
|
||||
uid: selfUin,
|
||||
groupId: groupCode,
|
||||
}, {
|
||||
Cookie: cookie,
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
return post;
|
||||
}
|
||||
|
||||
// ───────────── SnowCat · qzone Web 线扩展(SnowLuma 移植) ─────────────
|
||||
|
||||
/** 获取说说列表(默认自己空间;-10000 限频自动切 user.qzone 备路) */
|
||||
async getQzoneMsgList (targetUin?: string, pos = 0, num = 20) {
|
||||
const { uin, g_tk, cookie } = await this.getQzoneAuth();
|
||||
const host = targetUin ?? uin;
|
||||
let route: QzoneRoute = legacyMsgListRoute(cookie, host, pos, num, g_tk);
|
||||
let text = await RequestUtil.HttpGetText(route.url, 'GET', '', route.headers);
|
||||
let data = parseQzoneJson<RawMsgListResponse>(text);
|
||||
// -10000「使用人数过多」是 legacy 网关限频,切备路重试一次
|
||||
if (data.code === -10000) {
|
||||
route = userMsgListRoute(cookie, host, pos, num, g_tk);
|
||||
text = await RequestUtil.HttpGetText(route.url, 'GET', '', route.headers);
|
||||
data = parseQzoneJson<RawMsgListResponse>(text);
|
||||
}
|
||||
if (typeof data.code === 'number' && data.code !== 0) {
|
||||
throw new Error(`qzone msglist failed: code=${data.code} ${data.message ?? ''}`.trim());
|
||||
}
|
||||
return mapQzoneMsgList(data);
|
||||
}
|
||||
|
||||
/** 获取单条说说详情 + 一页评论(含内联回复) */
|
||||
async getQzoneMsgDetail (targetUin: string, tid: string, pos = 0, num = 20) {
|
||||
const { g_tk, cookie } = await this.getQzoneAuth();
|
||||
const query = buildQzoneMsgDetailQuery(targetUin, tid, g_tk, { pos, num });
|
||||
const url = `https://h5.qzone.qq.com/proxy/domain/taotao.qq.com/cgi-bin/emotion_cgi_msgdetail_v6?${query}`;
|
||||
const text = await RequestUtil.HttpGetText(url, 'GET', '', { Cookie: cookie });
|
||||
const data = parseQzoneJson<RawMsgDetailResponse>(text);
|
||||
parseQzoneDetailError(data, tid);
|
||||
return mapQzoneMsgDetail(data, tid);
|
||||
}
|
||||
|
||||
/** 点赞/取消赞一条说说(abstime 传目标帖的发帖时间更稳,0 可容忍) */
|
||||
async setQzoneLike (targetUin: string, tid: string, like: boolean, abstime = 0) {
|
||||
const { uin, g_tk, cookie } = await this.getQzoneAuth();
|
||||
const verb = like ? 'like' : 'unlike';
|
||||
const cgi = like ? 'internal_dolike_app' : 'internal_unlike_app';
|
||||
const url = `https://h5.qzone.qq.com/proxy/domain/w.qzone.qq.com/cgi-bin/likes/${cgi}?g_tk=${g_tk}`;
|
||||
const body = buildQzoneLikeBody(uin, targetUin, tid, abstime);
|
||||
const text = await RequestUtil.HttpGetText(url, 'POST', body, {
|
||||
Cookie: cookie,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
});
|
||||
parseQzoneLikeResponse(text, verb);
|
||||
}
|
||||
|
||||
/** 评论一条说说(richval 传图片直链,与发说说的 richval 串不同) */
|
||||
async commentQzoneMsg (hostUin: string, tid: string, content: string, richval?: string) {
|
||||
const { uin, g_tk, cookie } = await this.getQzoneAuth();
|
||||
const url = `https://h5.qzone.qq.com/proxy/domain/taotao.qzone.qq.com/cgi-bin/emotion_cgi_re_feeds?g_tk=${g_tk}`;
|
||||
const body = buildQzoneCommentBody(uin, hostUin, tid, content, richval ? 1 : undefined, richval);
|
||||
const text = await RequestUtil.HttpGetText(url, 'POST', body, {
|
||||
Cookie: cookie,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
});
|
||||
return parseQzoneCommentResponse(text, hostUin, tid);
|
||||
}
|
||||
|
||||
/** 楼中楼回复(独立端点 emotion_cgi_addreply_ugc;replyTo 自动转 @ 提及) */
|
||||
async replyQzoneComment (hostUin: string, tid: string, content: string, target: QzoneReplyTarget) {
|
||||
const { uin, g_tk, cookie } = await this.getQzoneAuth();
|
||||
const url = `https://h5.qzone.qq.com/proxy/domain/taotao.qzone.qq.com/cgi-bin/emotion_cgi_addreply_ugc?g_tk=${g_tk}`;
|
||||
const body = buildQzoneReplyBody(uin, hostUin, tid, content, target);
|
||||
const text = await RequestUtil.HttpGetText(url, 'POST', body, {
|
||||
Cookie: cookie,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
});
|
||||
// 复用评论解析:回复的 id 在 data.id(与评论的 commentid 不同名)
|
||||
const response = parseQzoneJson<{ code?: number; subcode?: number; message?: string; data?: { id?: string | number } }>(text);
|
||||
if (typeof response.code === 'number' && response.code !== 0) {
|
||||
throw new Error(`qzone reply failed: code=${response.code} ${response.message ?? ''}`.trim());
|
||||
}
|
||||
const id = response.data?.id;
|
||||
return { comment_id: id !== undefined ? String(id) : '' };
|
||||
}
|
||||
|
||||
/** 发说说(富返回:tid + time;SnowCat 原版 publishQzoneMsg 只返回 tid,保留不动) */
|
||||
async publishQzoneMsgFull (content: string, richvals: string[], ugcRight: number, targetUins?: string[]) {
|
||||
const { uin, g_tk, cookie } = await this.getQzoneAuth();
|
||||
const richval = richvals.length > 0 ? richvals.join('\t') : undefined;
|
||||
const allowUins = targetUins && targetUins.length > 0 ? targetUins.join('|') : undefined;
|
||||
const body = buildQzonePublishBody({ hostuin: uin, content, richval, ugcRight, allowUins });
|
||||
const api = `https://user.qzone.qq.com/proxy/domain/taotao.qzone.qq.com/cgi-bin/emotion_cgi_publish_v6?g_tk=${g_tk}`;
|
||||
const resultText = await RequestUtil.HttpGetText(api, 'POST', body, {
|
||||
Cookie: cookie,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
});
|
||||
return parseQzonePublishFull(resultText);
|
||||
}
|
||||
|
||||
/** 获取空间名称与说明(默认自己,可查别人——需有访问权) */
|
||||
async getQzoneProfile (targetUin?: string): Promise<QzoneProfile> {
|
||||
const { uin, g_tk, cookie } = await this.getQzoneAuth();
|
||||
const query = buildQzoneProfileQuery(uin, targetUin ?? uin, g_tk);
|
||||
const url = `https://h5.qzone.qq.com/proxy/domain/base.qzone.qq.com/cgi-bin/user/cgi_userinfo_get_all?${query}`;
|
||||
const text = await RequestUtil.HttpGetText(url, 'GET', '', { Cookie: cookie });
|
||||
return parseQzoneProfileResponse(text);
|
||||
}
|
||||
|
||||
/** 修改空间名称/说明(只动传了的字段;空串=清空) */
|
||||
async setQzoneProfile (profile: QzoneProfileUpdate): Promise<void> {
|
||||
const { uin, g_tk, cookie } = await this.getQzoneAuth();
|
||||
const body = buildQzoneProfileUpdateBody(uin, profile);
|
||||
const url = `https://h5.qzone.qq.com/proxy/domain/w.qzone.qq.com/cgi-bin/user/cgi_apply_updateuserinfo_new?g_tk=${g_tk}`;
|
||||
const text = await RequestUtil.HttpGetText(url, 'POST', body, {
|
||||
Cookie: cookie,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
});
|
||||
parseQzoneProfileUpdateResponse(text);
|
||||
}
|
||||
|
||||
/** 空间拉黑/解拉黑(action=1 拉黑 / 2 解拉黑) */
|
||||
async setQzoneBlack (targetUin: string, ban: boolean): Promise<void> {
|
||||
const { uin, g_tk, cookie } = await this.getQzoneAuth();
|
||||
const url = `https://h5.qzone.qq.com/proxy/domain/w.qzone.qq.com/cgi-bin/right/cgi_black_action_new?g_tk=${g_tk}`;
|
||||
const body = buildQzoneBlackBody(uin, targetUin, ban);
|
||||
const text = await RequestUtil.HttpGetText(url, 'POST', body, {
|
||||
Cookie: cookie,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
});
|
||||
parseQzoneBlackResponse(text, ban);
|
||||
}
|
||||
|
||||
/** 改已有说说的查看权限(msgdetail_v6 → emotion_cgi_update 两步全量重提) */
|
||||
async setQzoneMsgRight (tid: string, ugcRight: number, targetUins?: string[]): Promise<QzoneUpdateRightResult> {
|
||||
const { uin, g_tk, cookie } = await this.getQzoneAuth();
|
||||
// 第一步:拉详情
|
||||
const detailQuery = buildQzoneMsgDetailQuery(uin, tid, g_tk);
|
||||
const detailUrl = `https://h5.qzone.qq.com/proxy/domain/taotao.qq.com/cgi-bin/emotion_cgi_msgdetail_v6?${detailQuery}`;
|
||||
const detailText = await RequestUtil.HttpGetText(detailUrl, 'GET', '', { Cookie: cookie });
|
||||
const detail = parseQzoneJson<RawMsgDetailResponse>(detailText);
|
||||
parseQzoneDetailError(detail, tid);
|
||||
// 第二步:全量重提 + 覆写权限
|
||||
const bodyParams = buildQzoneUpdatePayload(detail, uin);
|
||||
bodyParams.set('ugc_right', String(ugcRight));
|
||||
if (targetUins && targetUins.length > 0) {
|
||||
bodyParams.set('allow_uins', targetUins.join('|'));
|
||||
}
|
||||
const url = `https://h5.qzone.qq.com/proxy/domain/taotao.qzone.qq.com/cgi-bin/emotion_cgi_update?g_tk=${g_tk}`;
|
||||
const text = await RequestUtil.HttpGetText(url, 'POST', bodyParams.toString(), {
|
||||
Cookie: cookie,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
});
|
||||
return parseQzoneUpdateRightResponse(text, tid, ugcRight);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取空间好友动态流(feeds3_html_more,HTML 载荷数据级解析)。
|
||||
* cursor 传上次返回的 next_cursor 续翻;不传从第 page_num 页走。
|
||||
*/
|
||||
async getQzoneFeeds (pageNum = 1, count = 10, cursor?: string) {
|
||||
const { uin, g_tk, cookie } = await this.getQzoneAuth();
|
||||
if (!Number.isSafeInteger(pageNum) || pageNum < 1 || !Number.isSafeInteger(count) || count < 1 || count > 50) {
|
||||
throw new Error('空间好友动态页码或数量无效');
|
||||
}
|
||||
const start = cursor === undefined
|
||||
? { page: 1, state: initialQzoneFeedState() }
|
||||
: decodeQzoneFeedCursor(cursor, uin);
|
||||
if (cursor !== undefined && pageNum !== 1 && pageNum !== start.page) {
|
||||
throw new Error('page_num 与空间好友动态 cursor 不匹配');
|
||||
}
|
||||
const target = cursor === undefined ? pageNum : start.page;
|
||||
let state: QzoneFeedState = start.state;
|
||||
const seen = new Set([qzoneFeedPosition(state)]);
|
||||
|
||||
for (let page = start.page; page <= target; page++) {
|
||||
const query = new URLSearchParams({
|
||||
uin,
|
||||
scope: '0',
|
||||
view: '1',
|
||||
gid: '',
|
||||
flag: '1',
|
||||
filter: 'all',
|
||||
applist: 'all',
|
||||
refresh: '0',
|
||||
firstGetGroup: '0',
|
||||
mixnocache: '0',
|
||||
scene: '0',
|
||||
sidomain: 'qzonestyle.gtimg.cn',
|
||||
count: String(count),
|
||||
...state,
|
||||
getAisort: state.aisortNextTime,
|
||||
g_tk: g_tk,
|
||||
callback: '_preloadCallback',
|
||||
format: 'jsonp',
|
||||
useutf8: '1',
|
||||
outputhtmlfeed: '1',
|
||||
usertime: String(Date.now()),
|
||||
rd: String(Math.random()),
|
||||
windowId: String(Math.random()),
|
||||
});
|
||||
query.delete('aisortNextTime');
|
||||
const url = `https://h5.qzone.qq.com/proxy/domain/ic2.qzone.qq.com/cgi-bin/feeds/feeds3_html_more?${query}`;
|
||||
const body = await RequestUtil.HttpGetText(url, 'GET', '', { Cookie: cookie });
|
||||
const data = parseQzoneCallback<RawFeedsResponse>(body);
|
||||
if (data.code !== 0 || (data.subcode !== undefined && data.subcode !== 0)) {
|
||||
throw new Error(
|
||||
`qzone feeds failed: code=${data.code} subcode=${data.subcode ?? 0} ${typeof data.message === 'string' ? data.message : ''}`.trim(),
|
||||
);
|
||||
}
|
||||
const main = data.data?.main;
|
||||
if (main !== undefined && (main === null || typeof main !== 'object' || Array.isArray(main))) {
|
||||
throw new Error('空间好友动态分页信息结构异常');
|
||||
}
|
||||
if (main?.['error'] !== undefined && main['error'] !== '') {
|
||||
throw new Error('空间好友动态服务端报告拉取失败');
|
||||
}
|
||||
const result = mapQzoneFeeds(data);
|
||||
if (result.has_more) {
|
||||
if (!main) throw new Error('空间好友动态缺少下一页游标');
|
||||
state = nextQzoneFeedState(main, state);
|
||||
const position = qzoneFeedPosition(state);
|
||||
if (seen.has(position)) throw new Error('空间好友动态分页游标未推进');
|
||||
seen.add(position);
|
||||
result.next_cursor = encodeQzoneFeedCursor(uin, page + 1, state);
|
||||
}
|
||||
if (page === target) return result;
|
||||
if (!result.has_more) {
|
||||
return { feeds: [], has_more: false, next_cursor: null };
|
||||
}
|
||||
}
|
||||
throw new Error('空间好友动态分页未完成');
|
||||
}
|
||||
|
||||
// ───────────── SnowCat · qzone 公共空间(空友)代理(SnowLuma 移植) ─────────────
|
||||
|
||||
async getQzonePublicCapabilities (cookie: string, bkn: string, uin: string) {
|
||||
const impl = await import('./qzone-public-web');
|
||||
return impl.getQzonePublicCapabilities(cookie, bkn, uin);
|
||||
}
|
||||
|
||||
async getQzonePublicFeeds (cookie: string, bkn: string, uin: string, cursor?: string) {
|
||||
const impl = await import('./qzone-public-web');
|
||||
return impl.getQzonePublicFeeds(cookie, bkn, uin, cursor);
|
||||
}
|
||||
|
||||
async getQzonePublicFeedDetail (cookie: string, bkn: string, feed: Record<string, unknown>) {
|
||||
const impl = await import('./qzone-public-web');
|
||||
return impl.getQzonePublicFeedDetail(cookie, bkn, feed);
|
||||
}
|
||||
|
||||
async getQzonePublicComments (cookie: string, bkn: string, feed: Record<string, unknown>, cursor: string) {
|
||||
const impl = await import('./qzone-public-web');
|
||||
return impl.getQzonePublicComments(cookie, bkn, feed, cursor);
|
||||
}
|
||||
|
||||
async getQzonePublicReplies (cookie: string, bkn: string, feed: Record<string, unknown>, comment: Record<string, unknown>, cursor: string, pageSize: number) {
|
||||
const impl = await import('./qzone-public-web');
|
||||
return impl.getQzonePublicReplies(cookie, bkn, feed, comment, cursor, pageSize);
|
||||
}
|
||||
|
||||
async setQzonePublicUpvote (cookie: string, bkn: string, feed: Record<string, unknown>, enable: boolean) {
|
||||
const impl = await import('./qzone-public-web');
|
||||
return impl.setQzonePublicUpvote(cookie, bkn, feed, enable);
|
||||
}
|
||||
|
||||
async setQzonePublicLike (cookie: string, bkn: string, feed: Record<string, unknown>, enable: boolean) {
|
||||
const impl = await import('./qzone-public-web');
|
||||
return impl.setQzonePublicLike(cookie, bkn, feed, enable);
|
||||
}
|
||||
|
||||
async setQzonePublicCommentLike (cookie: string, bkn: string, feed: Record<string, unknown>, comment: Record<string, unknown>, enable: boolean, reply?: Record<string, unknown>) {
|
||||
const impl = await import('./qzone-public-web');
|
||||
return impl.setQzonePublicCommentLike(cookie, bkn, feed, comment, enable, reply);
|
||||
}
|
||||
|
||||
async commentQzonePublicFeed (cookie: string, bkn: string, feed: Record<string, unknown>, user: Record<string, unknown>, content: string | Record<string, unknown>[]) {
|
||||
const impl = await import('./qzone-public-web');
|
||||
return impl.commentQzonePublicFeed(cookie, bkn, feed, user, content);
|
||||
}
|
||||
|
||||
async replyQzonePublicFeed (cookie: string, bkn: string, feed: Record<string, unknown>, user: Record<string, unknown>, comment: Record<string, unknown>, content: string | Record<string, unknown>[], reply?: Record<string, unknown>) {
|
||||
const impl = await import('./qzone-public-web');
|
||||
return impl.replyQzonePublicFeed(cookie, bkn, feed, user, comment, content, reply);
|
||||
}
|
||||
|
||||
async deleteQzonePublicComment (cookie: string, bkn: string, feed: Record<string, unknown>, comment: Record<string, unknown>) {
|
||||
const impl = await import('./qzone-public-web');
|
||||
return impl.deleteQzonePublicComment(cookie, bkn, feed, comment);
|
||||
}
|
||||
|
||||
async deleteQzonePublicReply (cookie: string, bkn: string, feed: Record<string, unknown>, comment: Record<string, unknown>, reply: Record<string, unknown>) {
|
||||
const impl = await import('./qzone-public-web');
|
||||
return impl.deleteQzonePublicReply(cookie, bkn, feed, comment, reply);
|
||||
}
|
||||
|
||||
async setQzonePublicFollow (cookie: string, bkn: string, feed: Record<string, unknown>, enable: boolean) {
|
||||
const impl = await import('./qzone-public-web');
|
||||
return impl.setQzonePublicFollow(cookie, bkn, feed, enable);
|
||||
}
|
||||
|
||||
async forwardQzonePublicFeed (cookie: string, bkn: string, feed: Record<string, unknown>, reason: string) {
|
||||
const impl = await import('./qzone-public-web');
|
||||
return impl.forwardQzonePublicFeed(cookie, bkn, feed, reason);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user