Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run
325 lines
12 KiB
TypeScript
325 lines
12 KiB
TypeScript
import type { JsonObject } from '@snowluma/common/json';
|
||
import {
|
||
commentQzoneMsg,
|
||
deleteQzoneMsg,
|
||
getQzoneFeeds,
|
||
getQzoneMsgDetail,
|
||
getQzoneMsgList,
|
||
getQzoneProfile,
|
||
publishQzoneMsg,
|
||
replyQzoneComment,
|
||
setQzoneBlack,
|
||
setQzoneLike,
|
||
setQzoneProfile,
|
||
updateQzoneMsgRight,
|
||
uploadQzoneImageFromSource,
|
||
type QzoneCommentResult,
|
||
type QzoneFeedsPage,
|
||
type QzoneMsgDetailResult,
|
||
type QzoneMsgListResult,
|
||
type QzoneProfile,
|
||
type QzoneProfileUpdate,
|
||
type QzonePublishResult,
|
||
type QzoneReplyTarget,
|
||
type QzoneUpdateRightResult,
|
||
type QzoneUploadImageResult,
|
||
} from '@snowluma/protocol/web/qzone';
|
||
import {
|
||
getQzonePublicCapabilities,
|
||
getQzoneHomeFeeds,
|
||
getQzoneFeedDetail,
|
||
type QzoneFeedDetail,
|
||
getQzoneShareArk,
|
||
type QzoneShareArk,
|
||
getQzonePublicFeeds,
|
||
getQzonePublicFeedDetail,
|
||
getQzonePublicComments,
|
||
getQzonePublicReplies,
|
||
setQzonePublicUpvote,
|
||
setQzonePublicLike,
|
||
setQzonePublicCommentLike,
|
||
commentQzonePublicFeed,
|
||
replyQzonePublicFeed,
|
||
deleteQzonePublicComment,
|
||
deleteQzonePublicReply,
|
||
setQzonePublicFollow,
|
||
forwardQzonePublicFeed,
|
||
type QzonePublicContent,
|
||
type QzonePublicFeedsPage,
|
||
} from '@snowluma/protocol/web/qzone-public';
|
||
import type { BridgeContext } from '../bridge-context';
|
||
|
||
/**
|
||
* Personal QQ-Zone (个人空间) web API: 说说, public recommendations, likes,
|
||
* comments and image upload over the cookie-backed Qzone web interfaces,
|
||
* reusing the same `getCookies('qzone.qq.com')` plumbing as GroupAlbumApi.
|
||
* Distinct from the group-album surface, which lives on GroupAlbumApi.
|
||
*/
|
||
export class QzoneApi {
|
||
constructor(private readonly ctx: BridgeContext) {}
|
||
|
||
async getProfile(targetUin?: number): Promise<QzoneProfile> {
|
||
const cookieObject = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
return getQzoneProfile(cookieObject, this.ctx.identity.uin, targetUin?.toString());
|
||
}
|
||
|
||
async setProfile(profile: QzoneProfileUpdate): Promise<void> {
|
||
const cookieObject = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
await setQzoneProfile(cookieObject, this.ctx.identity.uin, profile);
|
||
}
|
||
|
||
async getPublicCapabilities(): Promise<JsonObject> {
|
||
const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
return getQzonePublicCapabilities(cookies, this.ctx.identity.uin);
|
||
}
|
||
|
||
async getPublicFeeds(cursor?: string): Promise<QzonePublicFeedsPage> {
|
||
const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
return getQzonePublicFeeds(cookies, this.ctx.identity.uin, cursor);
|
||
}
|
||
|
||
async getHomeFeeds(targetUin?: number, cursor?: string): Promise<QzonePublicFeedsPage> {
|
||
const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
return getQzoneHomeFeeds(cookies, this.ctx.identity.uin, targetUin?.toString(), cursor);
|
||
}
|
||
|
||
async getFeedDetail(tid: string): Promise<QzoneFeedDetail> {
|
||
const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
return getQzoneFeedDetail(cookies, tid);
|
||
}
|
||
|
||
async getShareArk(feed: JsonObject): Promise<QzoneShareArk> {
|
||
const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
return getQzoneShareArk(cookies, feed);
|
||
}
|
||
|
||
async getPublicFeedDetail(feed: JsonObject): Promise<JsonObject> {
|
||
const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
return getQzonePublicFeedDetail(cookies, feed);
|
||
}
|
||
|
||
async getPublicComments(feed: JsonObject, cursor: string): Promise<JsonObject> {
|
||
const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
return getQzonePublicComments(cookies, feed, cursor);
|
||
}
|
||
|
||
async getPublicReplies(
|
||
feed: JsonObject,
|
||
comment: JsonObject,
|
||
cursor = '',
|
||
pageSize = 20,
|
||
): Promise<JsonObject> {
|
||
const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
return getQzonePublicReplies(cookies, feed, comment, cursor, pageSize);
|
||
}
|
||
|
||
async setPublicUpvote(feed: JsonObject, enable = true): Promise<JsonObject> {
|
||
const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
return setQzonePublicUpvote(cookies, feed, enable);
|
||
}
|
||
|
||
async setPublicLike(feed: JsonObject, enable = true): Promise<JsonObject> {
|
||
const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
return setQzonePublicLike(cookies, feed, enable);
|
||
}
|
||
|
||
async setPublicCommentLike(
|
||
feed: JsonObject,
|
||
comment: JsonObject,
|
||
enable = true,
|
||
reply?: JsonObject,
|
||
): Promise<JsonObject> {
|
||
const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
return setQzonePublicCommentLike(cookies, feed, comment, enable, reply);
|
||
}
|
||
|
||
async commentPublicFeed(feed: JsonObject, content: QzonePublicContent): Promise<JsonObject> {
|
||
const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
const user = {
|
||
uin: this.ctx.identity.uin,
|
||
uid: this.ctx.identity.selfUid ?? '',
|
||
nick: this.ctx.identity.nickname,
|
||
};
|
||
return commentQzonePublicFeed(cookies, feed, user, content);
|
||
}
|
||
|
||
async replyPublicFeed(
|
||
feed: JsonObject,
|
||
comment: JsonObject,
|
||
content: QzonePublicContent,
|
||
reply?: JsonObject,
|
||
): Promise<JsonObject> {
|
||
const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
const user = {
|
||
uin: this.ctx.identity.uin,
|
||
uid: this.ctx.identity.selfUid ?? '',
|
||
nick: this.ctx.identity.nickname,
|
||
};
|
||
return replyQzonePublicFeed(cookies, feed, user, comment, content, reply);
|
||
}
|
||
|
||
async deletePublicComment(feed: JsonObject, comment: JsonObject): Promise<JsonObject> {
|
||
const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
return deleteQzonePublicComment(cookies, feed, comment);
|
||
}
|
||
|
||
async deletePublicReply(
|
||
feed: JsonObject,
|
||
comment: JsonObject,
|
||
reply: JsonObject,
|
||
): Promise<JsonObject> {
|
||
const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
return deleteQzonePublicReply(cookies, feed, comment, reply);
|
||
}
|
||
|
||
async setPublicFollow(feed: JsonObject, enable = true): Promise<JsonObject> {
|
||
const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
return setQzonePublicFollow(cookies, feed, enable);
|
||
}
|
||
|
||
async forwardPublicFeed(feed: JsonObject, reason = ''): Promise<JsonObject> {
|
||
const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
return forwardQzonePublicFeed(cookies, feed, reason);
|
||
}
|
||
|
||
/**
|
||
* 获取说说列表。`targetUin` 省略时取机器人自己的空间。
|
||
* `pos` 为起始偏移,`num` 为本页数量(服务端上限约 20/页)。
|
||
*/
|
||
async getMsgList(targetUin?: number, pos = 0, num = 20): Promise<QzoneMsgListResult> {
|
||
const uin = targetUin && targetUin > 0 ? targetUin.toString() : this.ctx.identity.uin;
|
||
const cookieObject = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
return getQzoneMsgList(cookieObject, uin, pos, num);
|
||
}
|
||
|
||
/** 获取说说详情和一页评论。省略 targetUin 时使用当前账号。 */
|
||
async getMsgDetail(
|
||
tid: string,
|
||
targetUin?: number,
|
||
pos = 0,
|
||
num = 20,
|
||
): Promise<QzoneMsgDetailResult> {
|
||
const uin = targetUin === undefined ? this.ctx.identity.uin : String(targetUin);
|
||
const cookieObject = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
return getQzoneMsgDetail(cookieObject, uin, tid, pos, num);
|
||
}
|
||
|
||
/**
|
||
* 获取好友动态。页码调用从首页顺序定位;续页优先传上一页的 cursor。
|
||
* 始终以机器人自己的身份拉取好友动态。
|
||
*/
|
||
async getFeeds(pageNum = 1, count = 10, cursor?: string): Promise<QzoneFeedsPage> {
|
||
const cookieObject = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
return getQzoneFeeds(cookieObject, this.ctx.identity.uin, pageNum, count, cursor);
|
||
}
|
||
|
||
/**
|
||
* 从来源上传图片到 QQ 空间。
|
||
* `source` 支持: file:// 本地路径 | http(s):// URL | base64:// base64数据
|
||
*/
|
||
async uploadImageFromSource(source: string): Promise<QzoneUploadImageResult> {
|
||
const cookieObject = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
return uploadQzoneImageFromSource(cookieObject, this.ctx.identity.uin, source);
|
||
}
|
||
|
||
/**
|
||
* 发表说说,返回新说说的 tid。始终发到机器人自己的空间。
|
||
* `richType` / `richval`: 带图说说时传 richType=1 和 richval 字符串
|
||
* (多图用 `\t` 连接多个 richval)。纯文字说说省略这两个参数。
|
||
* `ugcRight` / `targetUins`: 查看权限及其作用名单(ugcRight=16/128 时必填)。
|
||
*/
|
||
async publish(
|
||
content: string,
|
||
richType?: number,
|
||
richval?: string,
|
||
ugcRight = 1,
|
||
targetUins?: string,
|
||
): Promise<QzonePublishResult> {
|
||
const cookieObject = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
return publishQzoneMsg(
|
||
cookieObject,
|
||
this.ctx.identity.uin,
|
||
content,
|
||
richType,
|
||
richval,
|
||
ugcRight,
|
||
targetUins,
|
||
);
|
||
}
|
||
|
||
/** 删除机器人自己空间的一条说说(按 tid)。 */
|
||
async delete(tid: string): Promise<void> {
|
||
const cookieObject = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
await deleteQzoneMsg(cookieObject, this.ctx.identity.uin, tid);
|
||
}
|
||
|
||
/**
|
||
* 修改机器人自己空间一条已发说说的查看权限(按 tid)。
|
||
* `ugcRight` / `targetUins` 含义同 publish(16/128 时 targetUins 必填)。
|
||
*/
|
||
async updateRight(
|
||
tid: string,
|
||
ugcRight: number,
|
||
targetUins?: string,
|
||
): Promise<QzoneUpdateRightResult> {
|
||
const cookieObject = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
return updateQzoneMsgRight(cookieObject, this.ctx.identity.uin, tid, ugcRight, targetUins);
|
||
}
|
||
|
||
/**
|
||
* 给一条说说点赞/取消赞。`targetUin` 省略时点赞机器人自己空间的说说;
|
||
* 点赞好友说说时传好友 uin(tid 来自 get_qzone_feeds / get_qzone_msg_list)。
|
||
*/
|
||
async like(
|
||
tid: string,
|
||
targetUin: number | undefined,
|
||
like: boolean,
|
||
abstime = 0,
|
||
): Promise<void> {
|
||
const selfUin = this.ctx.identity.uin;
|
||
const owner = targetUin && targetUin > 0 ? targetUin.toString() : selfUin;
|
||
const cookieObject = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
await setQzoneLike(cookieObject, selfUin, owner, tid, like, abstime);
|
||
}
|
||
|
||
/**
|
||
* 评论一条说说。`targetUin` 为说说所属 QQ 号(省略=机器人自己空间);
|
||
* 始终以机器人身份发表评论。返回新评论 id(尽力而为)。
|
||
* `richType` / `richval`: 带图评论时传 richType=1 和图片直链 URL
|
||
* (从 uploadImageFromSource 的 url 字段获取)。
|
||
* `reply` 指定父评论及可选回复对象;楼中楼回复仅支持文字。
|
||
*/
|
||
async comment(
|
||
tid: string,
|
||
content: string,
|
||
targetUin: number | undefined,
|
||
richType?: number,
|
||
richval?: string,
|
||
reply?: QzoneReplyTarget,
|
||
): Promise<QzoneCommentResult> {
|
||
if (reply !== undefined && (richType !== undefined || richval !== undefined)) {
|
||
throw new Error('images are not supported for Qzone comment replies');
|
||
}
|
||
|
||
const selfUin = this.ctx.identity.uin;
|
||
const owner = targetUin && targetUin > 0 ? targetUin.toString() : selfUin;
|
||
const cookieObject = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
|
||
if (reply !== undefined) {
|
||
return replyQzoneComment(cookieObject, selfUin, owner, tid, content, reply);
|
||
}
|
||
|
||
return commentQzoneMsg(cookieObject, selfUin, owner, tid, content, richType, richval);
|
||
}
|
||
|
||
/**
|
||
* 拉黑或解除拉黑某人(修改机器人自身 QQ 空间黑名单)。
|
||
* `ban=true` 拉黑,`ban=false` 解除拉黑。
|
||
*/
|
||
async setBlack(targetUin: number, ban: boolean): Promise<void> {
|
||
const selfUin = this.ctx.identity.uin;
|
||
const cookieObject = await this.ctx.apis.web.getCookies('qzone.qq.com');
|
||
await setQzoneBlack(cookieObject, selfUin, targetUin.toString(), ban);
|
||
}
|
||
}
|