refactor(onebot): inline ctx.bridge.apis.profile/web/groupAlbum direct calls

Remove the remaining ~20 passthrough fields from ApiActionContext
spanning four Api areas — most were already dead (no caller) since
extended.ts had migrated to direct `ctx.bridge.apis.X` paths in
earlier #6 commits.

  profile area:
    - setProfile / setOnlineStatus / fetchCustomFace /
      setGroupAvatar     ── all already dead
    - getProfileLike    ── one live caller in extended.ts inlined

  web area:
    - getGroupHonorInfo / getGroupEssence / getGroupEssenceAll /
      sendGroupNotice / getGroupNotice / deleteGroupNotice /
      getCookiesStr / getCsrfToken / getCredentials /
      forceFetchClientKey  ── all already dead

  groupAlbum area:
    - getGroupAlbumList / uploadImageToGroupAlbum /
      getGroupAlbumMediaList / commentGroupAlbumMedia /
      deleteGroupAlbumMedia / likeGroupAlbumMedia  ── all 7 live
      callers in actions/group-album.ts inlined to
      `ctx.bridge.apis.groupAlbum.<m>(...)`

  friend area:
    - setFriendRemark   ── already dead

Behavior preserved: getProfileLike's `userId = 0 → self` semantics
keep working because ProfileApi.getLike uses `isSelf = !userId`
internally (0 and undefined are equivalent for that check); same
default args at the action site (`start = 0`, `count = 10`).

Group-album action handlers now wrap the typed Api return values
(GroupAlbumMediaResult, GroupAlbumCommentResult) with
`as unknown as JsonValue` for okResponse compatibility — the old
ctx wrapper was typed `Promise<any>` which silently hid the cast;
making the cast explicit doesn't change runtime.

Part 4 of the OneBot-side slim-down. 552/552 tests still green.
This commit is contained in:
DrSmoothl
2026-05-21 09:29:28 +08:00
parent 8bf2ff3f36
commit d983635c8f
4 changed files with 43 additions and 75 deletions
+4 -1
View File
@@ -790,7 +790,10 @@ export function register(h: ApiHandler, ctx: ApiActionContext): void {
try {
const data = await ctx.getProfileLike(userId, start, count);
// getLike treats falsy userId as "self" (it does `isSelf = !userId`
// internally), so passing 0 or undefined is equivalent — matches the
// old `ctx.getProfileLike(userId, start, count)` wrapper exactly.
const data = await ctx.bridge.apis.profile.getLike(userId, start, count);
return okResponse(data);
} catch (e) {
return failedResponse(RETCODE.ACTION_FAILED, String(e));
+17 -9
View File
@@ -1,6 +1,7 @@
import type { ApiHandler, ApiActionContext } from '../api-handler';
import { asNumber, asString } from '../api-handler';
import { RETCODE, failedResponse, okResponse } from '../types';
import type { JsonValue } from '../types';
export function register(h: ApiHandler, ctx: ApiActionContext): void {
h.registerAction('get_group_album_list', async (params) => {
@@ -8,7 +9,7 @@ export function register(h: ApiHandler, ctx: ApiActionContext): void {
if (!groupId) return failedResponse(RETCODE.BAD_REQUEST, 'group_id is required');
try {
const albumList = await ctx.getGroupAlbumList(groupId);
const albumList = await ctx.bridge.apis.groupAlbum.list(groupId);
return okResponse(albumList);
} catch (error) {
const message = error instanceof Error ? error.message : 'failed to get group album list';
@@ -28,7 +29,7 @@ export function register(h: ApiHandler, ctx: ApiActionContext): void {
if (!file) return failedResponse(RETCODE.BAD_REQUEST, 'file is required');
try {
await ctx.uploadImageToGroupAlbum(groupId, albumId, albumName, file);
await ctx.bridge.apis.groupAlbum.upload(groupId, albumId, albumName, file);
return okResponse(null);
} catch (error) {
const message = error instanceof Error ? error.message : 'failed to upload image to group album';
@@ -45,8 +46,11 @@ export function register(h: ApiHandler, ctx: ApiActionContext): void {
if (!albumId) return failedResponse(RETCODE.BAD_REQUEST, 'album_id is required');
try {
const mediaList = await ctx.getGroupAlbumMediaList(groupId, albumId, attachInfo);
return okResponse(mediaList);
const mediaList = await ctx.bridge.apis.groupAlbum.getMediaList(groupId, albumId, attachInfo);
// GroupAlbumMediaResult is a concrete domain type; okResponse
// expects JsonValue. The shape is JSON-compatible — same as the
// old ctx wrapper returning `Promise<any>`.
return okResponse(mediaList as unknown as JsonValue);
} catch (error) {
const message = error instanceof Error ? error.message : 'failed to get group album media list';
return failedResponse(RETCODE.INTERNAL_ERROR, message);
@@ -65,8 +69,12 @@ export function register(h: ApiHandler, ctx: ApiActionContext): void {
if (!content) return failedResponse(RETCODE.BAD_REQUEST, 'content is required');
try {
const comment = await ctx.commentGroupAlbumMedia(groupId, albumId, lloc, content);
return okResponse(comment);
const comment = await ctx.bridge.apis.groupAlbum.comment(groupId, albumId, lloc, content);
// GroupAlbumCommentResult is a concrete domain type; okResponse
// expects JsonValue. The shape is JSON-compatible (no functions
// / classes / bigints), so cast is safe — same as the old ctx
// wrapper returning `Promise<any>`.
return okResponse(comment as unknown as JsonValue);
} catch (error) {
const message = error instanceof Error ? error.message : 'failed to comment on album media';
return failedResponse(RETCODE.INTERNAL_ERROR, message);
@@ -84,7 +92,7 @@ export function register(h: ApiHandler, ctx: ApiActionContext): void {
}
try {
const res = await ctx.likeGroupAlbumMedia(groupId, albumId, batchId, lloc, true);
const res = await ctx.bridge.apis.groupAlbum.like(groupId, albumId, batchId, lloc, true);
return okResponse(res);
} catch (error) {
const message = error instanceof Error ? error.message : 'failed to set like on album media';
@@ -104,7 +112,7 @@ export function register(h: ApiHandler, ctx: ApiActionContext): void {
}
try {
const res = await ctx.likeGroupAlbumMedia(groupId, albumId, batchId, lloc, false);
const res = await ctx.bridge.apis.groupAlbum.like(groupId, albumId, batchId, lloc, false);
return okResponse(res);
} catch (error) {
const message = error instanceof Error ? error.message : 'failed to cancel like on album media';
@@ -122,7 +130,7 @@ export function register(h: ApiHandler, ctx: ApiActionContext): void {
}
try {
const res = await ctx.deleteGroupAlbumMedia(groupId, albumId, lloc);
const res = await ctx.bridge.apis.groupAlbum.delete(groupId, albumId, lloc);
return okResponse(res);
} catch (error) {
const message = error instanceof Error ? error.message : 'failed to delete album media';
+14 -28
View File
@@ -80,9 +80,9 @@ export interface ApiActionContext {
// Essence (adapter: bakes the set/unset boolean)
setEssenceMsg: (messageId: number) => Promise<void>;
deleteEssenceMsg: (messageId: number) => Promise<void>;
// Profile reads with OneBot-specific default args
getProfileLike: (userId?: number, start?: number, limit?: number) => Promise<any>;
getGroupEssence: (groupId: number, pageStart?: number, pageLimit?: number) => Promise<GroupEssenceMsgRet>;
// Profile reads and group essence reads: direct via
// `ctx.bridge.apis.profile.getLike(...)` and
// `ctx.bridge.apis.web.getEssence(...)`.
// Friend deletion: direct via `ctx.bridge.apis.friend.delete(userId, block)`.
// Module wrappers / multi-dep compositions
getGroupMsgHistory: (groupId: number, messageId?: number, count?: number) => Promise<JsonObject[]>;
@@ -94,32 +94,18 @@ export interface ApiActionContext {
sendForwardMsg: (messages: JsonValue, groupId?: number) => Promise<{ forwardId: string }>;
getForwardMsg: (resId: string) => Promise<JsonObject[]>;
forwardSingleMsg: (messageId: number, target: { groupId?: number; userId?: number }) => Promise<{ messageId: number }>;
// Extended NapCat-compatible
setFriendRemark: (userId: number, remark: string) => Promise<void>;
setGroupAvatar: (groupId: number, source: string) => Promise<void>;
// setMsgEmojiLike: stays because it threads messageStore meta lookup.
setMsgEmojiLike: (messageId: number, emojiId: string, set: boolean) => Promise<void>;
// markGroupMsgAsRead / markPrivateMsgAsRead: direct via
// `ctx.bridge.apis.message.{markGroupRead,markPrivateRead}`.
setOnlineStatus: (status: number, extStatus?: number, batteryStatus?: number) => Promise<void>;
setProfile: (nickname?: string, personalNote?: string) => Promise<void>;
fetchCustomFace: (count?: number) => Promise<string[]>;
// getEmojiLikes: direct via `ctx.bridge.apis.interaction.getEmojiLikes(...)`.
// Web
getGroupHonorInfo: (groupId: number, type: WebHonorType | string) => Promise<any>;
getGroupEssenceAll: (groupId: number) => Promise<GroupEssenceMsgRet[]>;
getGroupAlbumList: (groupId: number) => Promise<any>;
uploadImageToGroupAlbum: (groupId: number, albumId: string, albumName: string, filePath: string) => Promise<void>;
getGroupAlbumMediaList: (groupId: number, albumId: string, attachInfo?: string) => Promise<any>;
commentGroupAlbumMedia: (groupId: number, albumId: string, lloc: string, content: string) => Promise<any>;
deleteGroupAlbumMedia: (groupId: number, albumId: string, lloc: string) => Promise<any>;
likeGroupAlbumMedia: (groupId: number, albumId: string, batchId: string, lloc: string | undefined, isLike: boolean) => Promise<any>;
sendGroupNotice: (groupId: number, content: string, options?: any) => Promise<any>;
getGroupNotice: (groupId: number) => Promise<any[]>;
deleteGroupNotice: (groupId: number, fid: string) => Promise<boolean>;
getCookiesStr: (domain: string) => Promise<string>;
getCsrfToken: () => Promise<number>;
getCredentials: (domain: string) => Promise<{ cookies: string; token: number; csrf_token: number }>;
forceFetchClientKey: () => Promise<ClientKeyInfo>;
// All other extended / NapCat-compatible / Web / Album actions are
// accessed directly through `ctx.bridge.apis.<area>.<method>()`:
// * profile area: setProfile / setOnlineStatus / fetchCustomFace /
// setGroupAvatar / getLike
// * friend area: setRemark / delete / handleRequest
// * web area: getHonorInfo / getEssence(All) / sendNotice /
// getNotice / deleteNotice / getCookiesStr /
// getCsrfToken / getCredentials / forceFetchClientKey
// * groupAlbum area: list / upload / getMediaList / comment /
// delete / like
// Media lookup (populated from previously dispatched message segments)
getImageInfo: (file: string) => Promise<JsonObject | null>;
getRecordInfo: (file: string) => Promise<JsonObject | null>;
+8 -37
View File
@@ -124,15 +124,6 @@ export function buildApiContext(ref: OneBotInstanceContext): ApiActionContext {
// request handling moved to direct `ctx.bridge.apis.friend.handleRequest`.
handleGroupRequest: (flag, _subType, approve, reason) => handleGroupAddRequest(bridge, flag, approve, reason),
// Essence — bake the set/unset boolean.
setEssenceMsg: (messageId) => setEssenceMessage(bridge, messageStore, messageId, true),
deleteEssenceMsg: (messageId) => setEssenceMessage(bridge, messageStore, messageId, false),
// Profile-like / get_group_essence — OneBot-specific defaults that the
// action handlers don't supply.
getProfileLike: (userId = undefined, start = 0, limit = 10) => bridge.apis.profile.getLike(userId, start, limit),
getGroupEssence: (groupId, pageStart = 0, pageLimit = 50) => bridge.apis.web.getEssence(groupId, pageStart, pageLimit),
// Message-store-backed history reads.
getGroupMsgHistory: (groupId, messageId, count) => getGroupMsgHistory(messageStore, groupId, messageId, count),
getFriendMsgHistory: (userId, messageId, count) => getFriendMsgHistory(messageStore, userId, messageId, count),
@@ -145,41 +136,21 @@ export function buildApiContext(ref: OneBotInstanceContext): ApiActionContext {
sendForwardMsg: (messages, groupId) => uploadForwardMessage(ref, messages, groupId),
getForwardMsg: (resId) => getForwardMessage(ref, resId),
forwardSingleMsg: (messageId, target) => forwardSingleMessage(ref, messageId, target),
forceFetchClientKey: () => bridge.apis.web.forceFetchClientKey(),
setFriendRemark: (userId, remark) => bridge.apis.friend.setRemark(userId, remark),
setGroupAvatar: (groupId, source) => bridge.apis.profile.setGroupAvatar(groupId, source),
// Cross-store: looks up the meta then routes through Bridge.
// Essence — bakes the set/unset boolean (the only "compose with
// messageStore meta lookup" entry remaining outside of dedicated
// module helpers).
setEssenceMsg: (messageId) => setEssenceMessage(bridge, messageStore, messageId, true),
deleteEssenceMsg: (messageId) => setEssenceMessage(bridge, messageStore, messageId, false),
// Emoji-like — same shape: meta lookup → bridge call. Group-only
// (QQ doesn't expose emoji reactions on private chats).
setMsgEmojiLike: async (messageId, emojiId, set) => {
const meta = messageStore.findMeta(messageId);
if (!meta) throw new Error('message not found');
// QQ itself doesn't expose emoji reactions on private chats, so
// there is no wire path to forward this to; fail loudly instead
// of silently no-op'ing.
if (!meta.isGroup) throw new Error('emoji reactions are not supported on private messages');
await bridge.apis.interaction.setReaction(meta.targetId, meta.sequence, emojiId, set);
},
setOnlineStatus: (status: number, extStatus?: number, batteryStatus?: number) => bridge.apis.profile.setOnlineStatus(status, extStatus, batteryStatus),
setProfile: (nickname?: string, personalNote?: string) => bridge.apis.profile.setProfile(nickname, personalNote),
// Web-backed actions.
getGroupHonorInfo: (groupId: number, type: WebHonorType | string) => bridge.apis.web.getHonorInfo(groupId, type),
getGroupEssenceAll: (groupId) => bridge.apis.web.getEssenceAll(groupId),
getGroupAlbumList: (groupId) => bridge.apis.groupAlbum.list(groupId),
uploadImageToGroupAlbum: (groupId, albumId, albumName, filePath) => bridge.apis.groupAlbum.upload(groupId, albumId, albumName, filePath),
getGroupAlbumMediaList: (groupId, albumId, attachInfo) => bridge.apis.groupAlbum.getMediaList(groupId, albumId, attachInfo),
commentGroupAlbumMedia: (groupId, albumId, lloc, content) => bridge.apis.groupAlbum.comment(groupId, albumId, lloc, content),
deleteGroupAlbumMedia: (groupId, albumId, lloc) => bridge.apis.groupAlbum.delete(groupId, albumId, lloc),
likeGroupAlbumMedia: (groupId, albumId, batchId, lloc, isLike) => bridge.apis.groupAlbum.like(groupId, albumId, batchId, lloc, isLike),
sendGroupNotice: (groupId, content, options) => bridge.apis.web.sendNotice(groupId, content, options),
getGroupNotice: (groupId) => bridge.apis.web.getNotice(groupId),
deleteGroupNotice: (groupId, fid) => bridge.apis.web.deleteNotice(groupId, fid),
getCookiesStr: (domain) => bridge.apis.web.getCookiesStr(domain),
getCsrfToken: () => bridge.apis.web.getCsrfToken(),
getCredentials: (domain) => bridge.apis.web.getCredentials(domain),
// Extended
fetchCustomFace: (count) => bridge.apis.profile.fetchCustomFace(count),
// Media lookup.
getImageInfo: (file) => getCachedImageInfo(mediaStore, file),