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 { 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 { const cookieObject = await this.ctx.apis.web.getCookies('qzone.qq.com'); await setQzoneProfile(cookieObject, this.ctx.identity.uin, profile); } async getPublicCapabilities(): Promise { const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com'); return getQzonePublicCapabilities(cookies, this.ctx.identity.uin); } async getPublicFeeds(cursor?: string): Promise { 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 { 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 { const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com'); return getQzoneFeedDetail(cookies, tid); } async getShareArk(feed: JsonObject): Promise { const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com'); return getQzoneShareArk(cookies, feed); } async getPublicFeedDetail(feed: JsonObject): Promise { const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com'); return getQzonePublicFeedDetail(cookies, feed); } async getPublicComments(feed: JsonObject, cursor: string): Promise { 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 { 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 { const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com'); return setQzonePublicUpvote(cookies, feed, enable); } async setPublicLike(feed: JsonObject, enable = true): Promise { 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 { 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 { 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 { 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 { 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 { const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com'); return deleteQzonePublicReply(cookies, feed, comment, reply); } async setPublicFollow(feed: JsonObject, enable = true): Promise { const cookies = await this.ctx.apis.web.getCookies('qzone.qq.com'); return setQzonePublicFollow(cookies, feed, enable); } async forwardPublicFeed(feed: JsonObject, reason = ''): Promise { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { const selfUin = this.ctx.identity.uin; const cookieObject = await this.ctx.apis.web.getCookies('qzone.qq.com'); await setQzoneBlack(cookieObject, selfUin, targetUin.toString(), ban); } }