From 80ee12ecc2d87e9e6ca88b5129e8071d840f6399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=8B=E7=93=9C=E4=B8=80=E5=8D=81=E9=9B=AA?= Date: Sun, 24 May 2026 13:38:13 +0800 Subject: [PATCH] Enforce no-explicit-any; allow in tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable @typescript-eslint/no-explicit-any as an error in the ESLint config to tighten typing across the codebase, and add an override to disable this rule for test files. Also apply non-functional cleanup in packages/proton/src/runtime.ts: reorder/imports and adjust formatting/indentation and string alignment for readability — no behavioral changes. --- eslint.config.ts | 14 +++++- packages/common/src/json.ts | 10 ----- packages/core/src/bridge/apis/contacts.ts | 14 +++++- packages/core/src/bridge/apis/group-album.ts | 33 ++++++++------ packages/core/src/bridge/apis/group-file.ts | 2 +- packages/core/src/bridge/apis/message.ts | 4 +- packages/core/src/bridge/apis/misc.ts | 7 +-- packages/core/src/bridge/apis/profile.ts | 14 +++--- packages/core/src/bridge/apis/web.ts | 44 +++++++++++++++---- packages/core/tests/highway/pipeline.test.ts | 4 +- packages/onebot/src/actions/extended.ts | 6 +-- packages/onebot/src/api-handler.ts | 6 +-- packages/onebot/src/instance.ts | 2 - packages/protocol/src/element-builder.ts | 24 +++++----- .../protocol/src/highway/highway-client.ts | 11 +++-- packages/protocol/src/highway/pipeline.ts | 24 ++++++---- .../contacts/fetch-friend-list-page.ts | 2 +- .../contacts/fetch-group-member-list-page.ts | 2 +- .../extras/get-stranger-status.ts | 2 +- .../group-file/get-group-ptt-url.ts | 2 +- .../group-file/get-group-video-url.ts | 2 +- .../group-file/get-private-ptt-url.ts | 2 +- .../group-file/get-private-video-url.ts | 2 +- .../oidb-services/group-file/ntv2-download.ts | 4 +- .../misc/click-inline-keyboard-button.ts | 1 + .../src/oidb-services/profile/get-like.ts | 3 ++ .../profile/get-unidirectional-friend-list.ts | 10 +++-- packages/protocol/src/web/group-album.ts | 9 ++-- packages/protocol/src/web/group-essence.ts | 7 +-- packages/protocol/src/web/group-honor.ts | 38 ++++++++++++---- packages/protocol/src/web/group-notice.ts | 11 ++--- packages/protocol/src/web/request-util.ts | 10 +++-- packages/proton/src/runtime.ts | 32 ++++++-------- packages/sdk/src/messages/chain.ts | 2 +- packages/sqlite/src/index.ts | 10 ++--- tsconfig.base.json | 6 +++ 36 files changed, 229 insertions(+), 147 deletions(-) diff --git a/eslint.config.ts b/eslint.config.ts index 70dce092..56fc5c50 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -48,7 +48,7 @@ export default defineConfig([ rules: { 'indent': ['error', 2, { SwitchCase: 1 }], 'prefer-const': 'off', - '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-empty-object-type': 'off', '@typescript-eslint/no-this-alias': 'off', '@typescript-eslint/no-unused-vars': [ @@ -61,6 +61,18 @@ export default defineConfig([ ], }, }, + { + files: ['**/tests/**/*.{ts,tsx}', '**/*.test.{ts,tsx}'], + rules: { + '@typescript-eslint/no-explicit-any': 'off', + }, + }, + { + files: ['**/bench/**/*.{ts,tsx}', 'packages/proton/test/**/*.{ts,tsx}'], + rules: { + '@typescript-eslint/no-explicit-any': 'off', + }, + }, { // OIDB service layer is namespace-per-cmd by design — the // structural-typing contract (namespace IS the OidbCallSpec) and diff --git a/packages/common/src/json.ts b/packages/common/src/json.ts index d87a3adb..5d8dd735 100644 --- a/packages/common/src/json.ts +++ b/packages/common/src/json.ts @@ -1,13 +1,3 @@ -// Plain JSON value types — defined here (not in @snowluma/onebot/types -// where they used to live) so @snowluma/protocol can talk about JSON-shaped -// payloads without importing OneBot. The OneBot types module keeps the -// names exported via re-export, so existing call sites that say -// `import type { JsonValue } from '<…>/onebot/types'` continue to work -// unchanged after Phase 3. -// -// Anything actually OneBot-specific (ApiResponse, OneBotRequest, …) -// stays in @snowluma/onebot — only the plain JSON quartet moves here. - export type JsonPrimitive = string | number | boolean | null; export type JsonValue = JsonPrimitive | JsonObject | JsonArray; export interface JsonObject { diff --git a/packages/core/src/bridge/apis/contacts.ts b/packages/core/src/bridge/apis/contacts.ts index 4bf5928c..6ae4f411 100644 --- a/packages/core/src/bridge/apis/contacts.ts +++ b/packages/core/src/bridge/apis/contacts.ts @@ -17,7 +17,19 @@ import type { BridgeContext } from '../bridge-context'; // ─── Helpers (previously in bridge-contacts.ts) ─────────────────── -export function buildFriendProperties(raw: any): Map { +type FriendPropertySource = { + additional?: Array<{ + type?: number; + layer1?: { + properties?: Array<{ + code?: number; + value?: string; + }>; + }; + }>; +}; + +export function buildFriendProperties(raw: FriendPropertySource): Map { const props = new Map(); for (const additional of raw.additional ?? []) { if ((additional.type ?? 0) !== 1 || !additional.layer1) continue; diff --git a/packages/core/src/bridge/apis/group-album.ts b/packages/core/src/bridge/apis/group-album.ts index c6ecf9cd..28aee08e 100644 --- a/packages/core/src/bridge/apis/group-album.ts +++ b/packages/core/src/bridge/apis/group-album.ts @@ -6,32 +6,35 @@ import type { DoQunLikeRequest, DoQunLikeResponse, GetMediaListRequest, + MediaInfo, GetMediaListResponse, } from '@snowluma/proto-defs/oidb-actions/group-album'; import { getGroupAlbumList, uploadImageToGroupAlbum } from '@snowluma/protocol/web/group-album'; import { protobuf_decode, protobuf_encode } from '@snowluma/proton'; +import type { JsonObject, JsonValue } from '@snowluma/common/json'; import type { BridgeContext } from '../bridge-context'; -function convertBigIntToString(obj: any): any { - if (obj === null || obj === undefined) return obj; +function convertBigIntToString(obj: unknown): JsonValue { + if (obj === null || obj === undefined) return null; if (typeof obj === 'bigint') return obj.toString(); if (Array.isArray(obj)) return obj.map(convertBigIntToString); if (typeof obj === 'object') { - const result: any = {}; - for (const key in obj) { - result[key] = convertBigIntToString(obj[key]); + const result: JsonObject = {}; + for (const [key, value] of Object.entries(obj)) { + result[key] = convertBigIntToString(value); } return result; } - return obj; + if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean') return obj; + return null; } export class GroupAlbumApi { constructor(private readonly ctx: BridgeContext) { } - /** List a group's albums (HTTP-cookie-based, qzone.qq.com). */ - async list(groupId: number): Promise { + // 列出群相册(基于 qzone.qq.com 的 HTTP Cookie)。 + async list(groupId: number): Promise { const groupCode = groupId.toString(); const uin = this.ctx.identity.uin; const cookieObject = await this.ctx.apis.web.getCookies('qzone.qq.com'); @@ -39,7 +42,7 @@ export class GroupAlbumApi { return albumData?.album || []; } - /** Upload an image into an existing album (HTTP slice-upload). */ + // 上传图片到现有相册(HTTP 分片上传)。 async upload(groupId: number, albumId: string, albumName: string, filePath: string): Promise { const groupCode = groupId.toString(); const uin = this.ctx.identity.uin; @@ -85,7 +88,7 @@ export class GroupAlbumApi { const mediaList = data.mediaList ?? []; const nextAttachInfo = data.nextAttachInfo ?? ''; - return convertBigIntToString({ mediaList, nextAttachInfo }); + return convertBigIntToString({ mediaList, nextAttachInfo }) as unknown as GroupAlbumMediaResult; } async comment(groupId: number, albumId: string, lloc: string, content: string): Promise { @@ -156,10 +159,10 @@ export class GroupAlbumApi { content: commentData.content ?? [], time: commentData.time ?? '0', clientKey: commentData.clientKey ?? '', - }); + }) as unknown as GroupAlbumCommentResult; } - async like(groupId: number, albumId: string, batchId: string, lloc: string | undefined, isLike: boolean): Promise { + async like(groupId: number, albumId: string, batchId: string, lloc: string | undefined, isLike: boolean): Promise { const uin = this.ctx.identity.uin; const clientKey = `${uin}_${Date.now()}_${Math.floor(Math.random() * 100000)}`; @@ -221,7 +224,7 @@ export class GroupAlbumApi { return convertBigIntToString(resp.body?.like ?? {}); } - async delete(groupId: number, albumId: string, lloc: string): Promise { + async delete(groupId: number, albumId: string, lloc: string): Promise<{ success: true }> { const uin = this.ctx.identity.uin; const clientKey = `${uin}_${Date.now()}_${Math.floor(Math.random() * 100000)}`; @@ -261,10 +264,12 @@ export class GroupAlbumApi { } } export interface GroupAlbumMediaResult { - mediaList: any[]; + mediaList: Array>; nextAttachInfo: string; } +export type GroupAlbumList = NonNullable>>['album']; + export interface GroupAlbumCommentResult { id: string; user: { uin: string }; diff --git a/packages/core/src/bridge/apis/group-file.ts b/packages/core/src/bridge/apis/group-file.ts index 75796369..86920988 100644 --- a/packages/core/src/bridge/apis/group-file.ts +++ b/packages/core/src/bridge/apis/group-file.ts @@ -440,7 +440,7 @@ export class GroupFileApi { : toInt(upload.uploadPort); if (!uploadHost || uploadPort <= 0) { const rtpDump = Array.isArray(upload.rtpMediaPlatformUploadAddress) - ? JSON.stringify(upload.rtpMediaPlatformUploadAddress.map((e: any) => ({ + ? JSON.stringify(upload.rtpMediaPlatformUploadAddress.map((e) => ({ outIP: e.outIP, outPort: e.outPort, inIP: e.inIP, inPort: e.inPort, iPType: e.iPType, }))) diff --git a/packages/core/src/bridge/apis/message.ts b/packages/core/src/bridge/apis/message.ts index 77f67ee7..7b7cbc9b 100644 --- a/packages/core/src/bridge/apis/message.ts +++ b/packages/core/src/bridge/apis/message.ts @@ -55,7 +55,7 @@ export class MessageApi { richText: { elems: protoElems, }, - } as any, + }, clientSequence: 0, random, syncCookie: new Uint8Array(0), @@ -117,7 +117,7 @@ export class MessageApi { richText: { elems: protoElems, }, - } as any, + }, clientSequence: clientSeq, random, syncCookie: new Uint8Array(0), diff --git a/packages/core/src/bridge/apis/misc.ts b/packages/core/src/bridge/apis/misc.ts index cdbd3afa..517aef0d 100644 --- a/packages/core/src/bridge/apis/misc.ts +++ b/packages/core/src/bridge/apis/misc.ts @@ -3,6 +3,7 @@ import type { MiniAppShareResp, } from '@snowluma/proto-defs/oidb-actions/base'; import { protobuf_decode, protobuf_encode } from '@snowluma/proton'; +import type { JsonObject, JsonValue } from '@snowluma/common/json'; import type { BridgeContext } from '../bridge-context'; // Migrated OIDB cmds — facade methods are one-line forwarders. import { ClickInlineKeyboardButton } from '@snowluma/protocol/oidb-services/misc/click-inline-keyboard-button'; @@ -16,7 +17,7 @@ export class MiscApi { return TranslateEnToZh.invoke(this.ctx, { words }); } - async getMiniAppArk(type: string, title: string, desc: string, picUrl: string, jumpUrl: string): Promise { + async getMiniAppArk(type: string, title: string, desc: string, picUrl: string, jumpUrl: string): Promise { let appid = '1109937557'; // default: bilibili let iconUrl = 'http://miniapp.gtimg.cn/public/appicon/51f90239b78a2e4994c11215f4c4ba15_200.jpg'; @@ -45,7 +46,7 @@ export class MiscApi { throw new Error('mini app share json empty'); } - const parsed = JSON.parse(jsonStr); + const parsed = JSON.parse(jsonStr) as Record; return { data: { @@ -67,7 +68,7 @@ export class MiscApi { buttonId: string, callbackData: string, msgSeq: number, - ): Promise { + ): Promise { return ClickInlineKeyboardButton.invoke(this.ctx, { groupId, botAppid, buttonId, callbackData, msgSeq }); } diff --git a/packages/core/src/bridge/apis/profile.ts b/packages/core/src/bridge/apis/profile.ts index a609eff1..309b717e 100644 --- a/packages/core/src/bridge/apis/profile.ts +++ b/packages/core/src/bridge/apis/profile.ts @@ -7,8 +7,8 @@ import type { } from '@snowluma/proto-defs/oidb-actions/base'; import { fetchHighwaySession, uploadHighwayHttp } from '@snowluma/protocol/highway'; import { computeHashes, loadBinarySource } from '@snowluma/protocol/highway/utils'; -import { GetLike } from '@snowluma/protocol/oidb-services/profile/get-like'; -import { GetUnidirectionalFriendList } from '@snowluma/protocol/oidb-services/profile/get-unidirectional-friend-list'; +import { GetLike, type LikeInfo } from '@snowluma/protocol/oidb-services/profile/get-like'; +import { GetUnidirectionalFriendList, type UnidirectionalFriendEntry } from '@snowluma/protocol/oidb-services/profile/get-unidirectional-friend-list'; import { SetInputStatus } from '@snowluma/protocol/oidb-services/profile/set-input-status'; import { SetProfile } from '@snowluma/protocol/oidb-services/profile/set-profile'; import { SetSelfLongNick } from '@snowluma/protocol/oidb-services/profile/set-self-long-nick'; @@ -120,11 +120,11 @@ export class ProfileApi { // ─────────────── queries on me / my contacts ─────────────── - getLike(userId?: number, start = 0, limit = 10): Promise { + getLike(userId?: number, start = 0, limit = 10): Promise { return GetLike.invoke(this.ctx, { userId, start, limit }); } - getUnidirectionalFriendList(): Promise { + getUnidirectionalFriendList(): Promise { return GetUnidirectionalFriendList.invoke(this.ctx); } @@ -141,10 +141,10 @@ export class ProfileApi { throw new Error(result.errorMessage || 'fetch custom face failed'); } const resp = protobuf_decode(result.responseData); - if (!resp || (resp as any).retCode !== 0) { - throw new Error(`fetch custom face error: ${(resp as any)?.message || 'unknown'}`); + if (!resp || resp.retCode !== 0) { + throw new Error(`fetch custom face error: ${resp?.message || 'unknown'}`); } - const faceIds = (resp as any).item?.faceIds || []; + const faceIds = resp.item?.faceIds || []; return faceIds.slice(0, count).map((id: string) => `https://p.qpic.cn/qq_expression/${this.ctx.identity.uin}/${id}/0`); } } diff --git a/packages/core/src/bridge/apis/web.ts b/packages/core/src/bridge/apis/web.ts index 43cba94d..c4eeb58b 100644 --- a/packages/core/src/bridge/apis/web.ts +++ b/packages/core/src/bridge/apis/web.ts @@ -1,11 +1,12 @@ import { ForceFetchClientKey, type ClientKeyInfo as NamespaceClientKeyInfo } from '@snowluma/protocol/oidb-services/web/force-fetch-client-key'; import { GetPskey } from '@snowluma/protocol/oidb-services/web/get-pskey'; -import { getGroupEssenceMsg, getGroupEssenceMsgAll } from '@snowluma/protocol/web/group-essence'; -import { getHonorListWebAPI, WebHonorType } from '@snowluma/protocol/web/group-honor'; +import { getGroupEssenceMsg, getGroupEssenceMsgAll, type GroupEssenceMsgRet } from '@snowluma/protocol/web/group-essence'; +import { getHonorListWebAPI, WebHonorType, type WebHonorItem } from '@snowluma/protocol/web/group-honor'; import { deleteGroupNotice as deleteGroupNoticeHttp, getGroupNoticeWebAPI, setGroupNoticeWebAPI, + type SetNoticeRetSuccess, uploadGroupNoticeImage, } from '@snowluma/protocol/web/group-notice'; import { RequestUtil } from '@snowluma/protocol/web/request-util'; @@ -16,6 +17,31 @@ function asBridge(ctx: BridgeContext): Bridge { return ctx as unknown as Bridge; export type ClientKeyInfo = NamespaceClientKeyInfo; +export interface WebHonorInfo { + [key: string]: import('@snowluma/common/json').JsonValue; + group_id: number; + current_talkative: WebHonorItem | null; + talkative_list: WebHonorItem[]; + performer_list: WebHonorItem[]; + legend_list: WebHonorItem[]; + emotion_list: WebHonorItem[]; + strong_newbie_list: WebHonorItem[]; +} + +export interface WebNoticeInfo { + [key: string]: import('@snowluma/common/json').JsonValue; + notice_id: string; + sender_id: number; + publish_time: number; + message: { + text: string; + image: Array<{ id: string; height: number; width: number }>; + images: Array<{ id: string; height: number; width: number }>; + }; + settings: import('@snowluma/common/json').JsonValue; + read_num: number; +} + // ─────────────── private helpers (cookie acquisition) ─────────────── function forceFetchClientKeyInner(bridge: Bridge): Promise { @@ -144,7 +170,7 @@ export class WebApi { // ─────────────── group essence ─────────────── /** Paginated fetch — `pageStart` is 0-indexed, `pageLimit` is server-capped at 50. */ - async getEssence(groupId: number, pageStart = 0, pageLimit = 50): Promise { + async getEssence(groupId: number, pageStart = 0, pageLimit = 50): Promise { const bridge = asBridge(this.ctx); const groupCode = groupId.toString(); const cookieObject = await getCookies(bridge, 'qun.qq.com'); @@ -153,7 +179,7 @@ export class WebApi { } /** Walks every page and returns the concatenated result. */ - async getEssenceAll(groupId: number): Promise { + async getEssenceAll(groupId: number): Promise { const bridge = asBridge(this.ctx); const groupCode = groupId.toString(); const cookieObject = await getCookies(bridge, 'qun.qq.com'); @@ -162,12 +188,12 @@ export class WebApi { // ─────────────── group honor ─────────────── - async getHonorInfo(groupId: number, type: WebHonorType | string): Promise { + async getHonorInfo(groupId: number, type: WebHonorType | string): Promise { const bridge = asBridge(this.ctx); const groupCode = groupId.toString(); const cookieObject = await getCookies(bridge, 'qun.qq.com'); - const honorInfo: any = { + const honorInfo: WebHonorInfo = { group_id: groupId, current_talkative: null, talkative_list: [], @@ -211,7 +237,7 @@ export class WebApi { type?: number; confirm_required?: number; }, - ): Promise { + ): Promise { const bridge = asBridge(this.ctx); const groupCode = groupId.toString(); const cookieObject = await getCookies(bridge, 'qun.qq.com'); @@ -261,7 +287,7 @@ export class WebApi { return ret; } - async getNotice(groupId: number): Promise { + async getNotice(groupId: number): Promise { const bridge = asBridge(this.ctx); const groupCode = groupId.toString(); const cookieObject = await getCookies(bridge, 'qun.qq.com'); @@ -271,7 +297,7 @@ export class WebApi { throw new Error('获取公告失败'); } - const retNotices: any[] = []; + const retNotices: WebNoticeInfo[] = []; if (ret.feeds) { for (const key in ret.feeds) { diff --git a/packages/core/tests/highway/pipeline.test.ts b/packages/core/tests/highway/pipeline.test.ts index 028b03c6..e50481f2 100644 --- a/packages/core/tests/highway/pipeline.test.ts +++ b/packages/core/tests/highway/pipeline.test.ts @@ -151,7 +151,7 @@ describe('pipeline — runNtv2Upload', () => { }); const upload = await runNtv2Upload(baseParams({ bridge })); expect(upload.uKey).toBe('fake-ukey'); - expect(upload.msgInfo.msgInfoBody).toHaveLength(1); + expect(upload.msgInfo?.msgInfoBody).toHaveLength(1); }); it('throws on transport failure', async () => { @@ -315,7 +315,7 @@ describe('pipeline — finalizeMediaMsgInfo', () => { it('encodes msgInfoBody + extBizInfo from the server response', () => { const out = finalizeMediaMsgInfo({ msgInfo: { - msgInfoBody: [{ index: { fileType: 1 }, fileExist: true }], + msgInfoBody: [{ index: { subType: 1 }, fileExist: true }], extBizInfo: { busiType: 1 }, }, }); diff --git a/packages/onebot/src/actions/extended.ts b/packages/onebot/src/actions/extended.ts index 3a66f8a8..cb39ba49 100644 --- a/packages/onebot/src/actions/extended.ts +++ b/packages/onebot/src/actions/extended.ts @@ -139,7 +139,7 @@ export function register(h: ApiHandler, ctx: ApiActionContext): void { try { const essenceDataAll = await ctx.bridge.apis.web.getEssenceAll(groupId); - const allMsgs = essenceDataAll.flatMap((res: any) => res.data?.msg_list || []); + const allMsgs = essenceDataAll.flatMap((res) => res.data?.msg_list || []); return okResponse(allMsgs); } catch (e) { @@ -347,7 +347,7 @@ export function register(h: ApiHandler, ctx: ApiActionContext): void { message_id: 0, }; if (groupId > 0) data.group_id = groupId; - return okResponse(data as any); + return okResponse(data as import('../types').JsonObject); }); h.registerAction('upload_foward_msg', async (params) => { @@ -985,7 +985,7 @@ export function register(h: ApiHandler, ctx: ApiActionContext): void { } }); - const handleGroupSign = async (params: any) => { + const handleGroupSign = async (params: import('../types').JsonObject) => { const groupId = asNumber(params.group_id); if (!groupId) { diff --git a/packages/onebot/src/api-handler.ts b/packages/onebot/src/api-handler.ts index e23fa093..7a9546f7 100644 --- a/packages/onebot/src/api-handler.ts +++ b/packages/onebot/src/api-handler.ts @@ -26,10 +26,10 @@ export interface GroupEssenceMsgRet { retcode: number; data: { is_end: boolean; - msg_list: any[]; - [key: string]: any; + msg_list: JsonObject[]; + [key: string]: JsonValue; }; - [key: string]: any; + [key: string]: JsonValue; } export interface ApiActionContext { diff --git a/packages/onebot/src/instance.ts b/packages/onebot/src/instance.ts index 2adc02e7..81c5a286 100644 --- a/packages/onebot/src/instance.ts +++ b/packages/onebot/src/instance.ts @@ -36,7 +36,6 @@ export class OneBotInstance { private readonly reactionStore: ReactionStore; private readonly networkManager: OneBotNetworkManager; private readonly rkeyCache: RKeyCache; - private readonly ctx: OneBotInstanceContext; private disposeEventPipeline: (() => void) | null = null; private readonly pids = new Set(); @@ -85,7 +84,6 @@ export class OneBotInstance { cacheMessageMeta: (messageId, meta) => this.cacheMessageMeta(messageId, meta), dispatchEvent: (event) => this.dispatchEvent(event), }; - this.ctx = ctx; this.apiHandler = new ApiHandler(buildApiContext(ctx), uinNum > 0 ? uinNum : undefined); this.networkManager = new OneBotNetworkManager(); diff --git a/packages/protocol/src/element-builder.ts b/packages/protocol/src/element-builder.ts index 6553c039..f903eb24 100644 --- a/packages/protocol/src/element-builder.ts +++ b/packages/protocol/src/element-builder.ts @@ -35,13 +35,13 @@ export interface SendContext { function makeTextElem(text: string): ProtoElem { return { - text: { str: text } as any, + text: { str: text }, }; } function makeFaceElem(faceId: number): ProtoElem { return { - face: { index: faceId } as any, + face: { index: faceId }, }; } @@ -83,14 +83,14 @@ function makeMentionElem(element: MessageElement, ctx?: SendContext): ProtoElem text: { str, pbReserve: extra, - } as any, + }, }; } function makeReplyElem(element: MessageElement): ProtoElem { const seq = element.replySeq! & 0xFFFFFFFF; - const srcMsg: any = { + const srcMsg: NonNullable = { origSeqs: [seq], }; @@ -116,7 +116,7 @@ function makeJsonElem(element: MessageElement): ProtoElem { richMsg: { serviceId: 1, template1: payload, - } as any, + }, }; } @@ -131,7 +131,7 @@ function makeXmlElem(element: MessageElement): ProtoElem { richMsg: { serviceId: element.subType === 0 ? 35 : (element.subType ?? 35), template1: payload, - } as any, + }, }; } @@ -143,7 +143,7 @@ function makeMarkdownElem(element: MessageElement): ProtoElem { serviceType: 45, pbElem: data, businessType: 1, - } as any, + }, }; } @@ -216,7 +216,7 @@ function makeForwardElem(element: MessageElement): ProtoElem { return { lightApp: { data: payload, - } as any, + }, }; } @@ -234,7 +234,7 @@ async function makeImageElem(ctx: SendContext, element: MessageElement): Promise serviceType: 48, pbElem: msgInfo, businessType: isGroup ? 20 : 10, - } as any, + }, }; } @@ -255,7 +255,7 @@ async function makePttElem(ctx: SendContext, element: MessageElement): Promise

['ipv4s']> = []; for (const ipv4 of ipv4s ?? []) { const ip = ipv4.outIp ?? 0; const port = ipv4.outPort ?? 0; @@ -113,7 +116,7 @@ export function buildHighwayExtend( fileUuid: selected?.index?.fileUuid ?? '', uKey, network: { ipv4s: networkIpv4s }, - msgInfoBody: msgInfoBody.map((b: any) => ({ + msgInfoBody: msgInfoBody.map((b: HighwayMsgInfoBody) => ({ index: b.index, picture: b.picture, fileExist: b.fileExist, hashSum: b.hashSum, })), blockSize: HIGHWAY_BLOCK_SIZE, diff --git a/packages/protocol/src/highway/pipeline.ts b/packages/protocol/src/highway/pipeline.ts index 7eb0e551..3307d201 100644 --- a/packages/protocol/src/highway/pipeline.ts +++ b/packages/protocol/src/highway/pipeline.ts @@ -5,8 +5,12 @@ import type { BridgeContext } from '../bridge-context'; import { makeOidbEnvelope } from '../bridge-oidb'; import type { EncodableMediaMsgInfo, + HighwayMsgInfoBody, + NTV2ExtBizInfo, + NTV2UploadInfo, NTV2UploadRichMediaReq, NTV2UploadRichMediaResp, + NTV2UploadRespBody, } from '@snowluma/proto-defs/highway'; import { OidbBase } from '@snowluma/proto-defs/oidb'; import { buildHighwayExtend, fetchHighwaySession, uploadHighwayHttp } from './highway-client'; @@ -64,11 +68,11 @@ export interface NtV2UploadParams { /** `reqHead.scene.businessType` (1=image, 3=voice, 2=video). */ businessType: number; /** `upload.uploadInfo` array. Each entry is `{ fileInfo, subFileType }`. */ - uploadInfo: unknown[]; + uploadInfo: NTV2UploadInfo[]; /** `upload.compatQmsgSceneType`. */ compatQmsgSceneType: number; /** `upload.extBizInfo` — type-specific bytes/flags. */ - extBizInfo: unknown; + extBizInfo: NTV2ExtBizInfo; /** Sub-file Highway PUTs to perform after the OIDB response. */ uploads: MediaSubFileUpload[]; /** Used in error messages. Defaults to 'media'. */ @@ -107,7 +111,7 @@ export function hexToBytes(hex: string): Uint8Array { * Sessions are cached across sub-file uploads — video does two PUTs but * only fetches the Highway session once. */ -export async function runNtv2Upload(params: NtV2UploadParams): Promise { +export async function runNtv2Upload(params: NtV2UploadParams): Promise { const { bridge, isGroup, targetIdOrUid, oidbCmd, serviceCmd, uploads } = params; const label = params.label ?? 'media'; const raw = bridge.identity?.uin; @@ -118,7 +122,7 @@ export async function runNtv2Upload(params: NtV2UploadParams): Promise { // Build the full body once. Pulling this in here means each format file // only specifies the seven fields that actually vary. - const body: any = { + const body: NTV2UploadRichMediaReq = { reqHead: { common: { requestId: params.requestId, command: 100 }, scene: { @@ -157,7 +161,7 @@ export async function runNtv2Upload(params: NtV2UploadParams): Promise { throw new Error(`OIDB error ${resp.errorCode}: ${resp.errorMsg ?? ''}`); } - const uploadBody: any = resp.body; + const uploadBody = resp.body; if (!uploadBody) throw new Error(`${label} upload response body missing`); if (uploadBody.respHead?.retCode && uploadBody.respHead.retCode !== 0) { throw new Error(uploadBody.respHead.message ?? `${label} upload failed`); @@ -186,11 +190,13 @@ export async function runNtv2Upload(params: NtV2UploadParams): Promise { continue; } + if (!target) continue; + const extend = buildHighwayExtend( uKey, upload.msgInfo, target.ipv4s ?? [], - sub.sha1 as any, // sha1 may be Uint8Array | Uint8Array[]; the helper accepts both + sub.sha1, sub.subFileIndex ?? 0, ); log.debug('%s OIDB requires bytes, PUT %d bytes (sub=%s)', label, sub.bytes.length, String(sub.source)); @@ -218,16 +224,16 @@ export async function runNtv2Upload(params: NtV2UploadParams): Promise { * pic alone unless the server populates it. */ export function finalizeMediaMsgInfo( - upload: any, + upload: NTV2UploadRespBody, defaultPic?: { bizType: number; textSummary: string }, ): Uint8Array { if (!upload?.msgInfo) throw new Error('upload response missing msgInfo'); - const msgInfoBody = (upload.msgInfo.msgInfoBody ?? []).map((b: any) => ({ + const msgInfoBody = (upload.msgInfo.msgInfoBody ?? []).map((b: HighwayMsgInfoBody) => ({ index: b.index, picture: b.picture, fileExist: b.fileExist, hashSum: b.hashSum, })); - const extBizInfo: any = {}; + const extBizInfo: NonNullable = {}; if (upload.msgInfo.extBizInfo?.pic) { extBizInfo.pic = { ...upload.msgInfo.extBizInfo.pic }; if (defaultPic) { diff --git a/packages/protocol/src/oidb-services/contacts/fetch-friend-list-page.ts b/packages/protocol/src/oidb-services/contacts/fetch-friend-list-page.ts index b6c68065..e92760ee 100644 --- a/packages/protocol/src/oidb-services/contacts/fetch-friend-list-page.ts +++ b/packages/protocol/src/oidb-services/contacts/fetch-friend-list-page.ts @@ -23,7 +23,7 @@ export namespace FetchFriendListPage { export type Deps = OidbSender; export const serialize = (_ctx: Deps, p: Params): OidbFriendListRequest => { - const body: any = { + const body: OidbFriendListRequest = { friendCount: 300, field4: 0, field6: 1, diff --git a/packages/protocol/src/oidb-services/contacts/fetch-group-member-list-page.ts b/packages/protocol/src/oidb-services/contacts/fetch-group-member-list-page.ts index 725b0d02..902405e1 100644 --- a/packages/protocol/src/oidb-services/contacts/fetch-group-member-list-page.ts +++ b/packages/protocol/src/oidb-services/contacts/fetch-group-member-list-page.ts @@ -25,7 +25,7 @@ export namespace FetchGroupMemberListPage { export type Deps = OidbSender; export const serialize = (_ctx: Deps, p: Params): OidbGroupMemberListRequest => { - const body: any = { + const body: OidbGroupMemberListRequest = { groupUin: p.groupId, field2: 5, field3: 2, diff --git a/packages/protocol/src/oidb-services/extras/get-stranger-status.ts b/packages/protocol/src/oidb-services/extras/get-stranger-status.ts index 6c1b7b61..5375ad18 100644 --- a/packages/protocol/src/oidb-services/extras/get-stranger-status.ts +++ b/packages/protocol/src/oidb-services/extras/get-stranger-status.ts @@ -41,7 +41,7 @@ export namespace GetStrangerStatus { export const deserialize = (_ctx: Deps, body: OidbStrangerStatusResp): StrangerStatus | null => { const raw = body.data?.status?.value; if (raw === undefined || raw === null) return null; - const extBig = typeof raw === 'bigint' ? raw : BigInt(raw as any); + const extBig = typeof raw === 'bigint' ? raw : BigInt(raw); if (extBig <= 10n) { return { status: Number(extBig) * 10, ext_status: 0 }; } diff --git a/packages/protocol/src/oidb-services/group-file/get-group-ptt-url.ts b/packages/protocol/src/oidb-services/group-file/get-group-ptt-url.ts index d6a33706..15158a7d 100644 --- a/packages/protocol/src/oidb-services/group-file/get-group-ptt-url.ts +++ b/packages/protocol/src/oidb-services/group-file/get-group-ptt-url.ts @@ -31,7 +31,7 @@ export namespace GetGroupPttUrl { client: { agentType: 2 }, }, download: { - node: normalizeMediaNode(p.node) as any, + node: normalizeMediaNode(p.node), download: { video: { busiType: 0, sceneType: 0 } }, }, }); diff --git a/packages/protocol/src/oidb-services/group-file/get-group-video-url.ts b/packages/protocol/src/oidb-services/group-file/get-group-video-url.ts index 82986a83..b0757a4c 100644 --- a/packages/protocol/src/oidb-services/group-file/get-group-video-url.ts +++ b/packages/protocol/src/oidb-services/group-file/get-group-video-url.ts @@ -30,7 +30,7 @@ export namespace GetGroupVideoUrl { client: { agentType: 2 }, }, download: { - node: normalizeMediaNode(p.node) as any, + node: normalizeMediaNode(p.node), download: { video: { busiType: 0, sceneType: 0 } }, }, }); diff --git a/packages/protocol/src/oidb-services/group-file/get-private-ptt-url.ts b/packages/protocol/src/oidb-services/group-file/get-private-ptt-url.ts index b649c6e2..18b31164 100644 --- a/packages/protocol/src/oidb-services/group-file/get-private-ptt-url.ts +++ b/packages/protocol/src/oidb-services/group-file/get-private-ptt-url.ts @@ -30,7 +30,7 @@ export namespace GetPrivatePttUrl { client: { agentType: 2 }, }, download: { - node: normalizeMediaNode(p.node) as any, + node: normalizeMediaNode(p.node), download: { video: { busiType: 0, sceneType: 0 } }, }, }); diff --git a/packages/protocol/src/oidb-services/group-file/get-private-video-url.ts b/packages/protocol/src/oidb-services/group-file/get-private-video-url.ts index 29c24b02..af900348 100644 --- a/packages/protocol/src/oidb-services/group-file/get-private-video-url.ts +++ b/packages/protocol/src/oidb-services/group-file/get-private-video-url.ts @@ -30,7 +30,7 @@ export namespace GetPrivateVideoUrl { client: { agentType: 2 }, }, download: { - node: normalizeMediaNode(p.node) as any, + node: normalizeMediaNode(p.node), download: { video: { busiType: 0, sceneType: 0 } }, }, }); diff --git a/packages/protocol/src/oidb-services/group-file/ntv2-download.ts b/packages/protocol/src/oidb-services/group-file/ntv2-download.ts index 0cb12f24..f34a9be4 100644 --- a/packages/protocol/src/oidb-services/group-file/ntv2-download.ts +++ b/packages/protocol/src/oidb-services/group-file/ntv2-download.ts @@ -9,7 +9,7 @@ // differ. The response is parsed into a `domain + urlPath + // rKeyParam` triple → composed into `https://`. -import type { NTV2RichMediaResp } from '@snowluma/proto-defs/oidb-actions/media'; +import type { NTV2IndexNode, NTV2RichMediaResp } from '@snowluma/proto-defs/oidb-actions/media'; import { ensureRetCodeZero } from '../shared'; export function parseNtv2DownloadUrl(body: NTV2RichMediaResp): string { @@ -24,7 +24,7 @@ export function parseNtv2DownloadUrl(body: NTV2RichMediaResp): string { /** Normalize a MediaIndexNode-shaped object into the wire-fields the * NTV2RichMedia `download.node` slot expects: `info{...}` / * `fileUuid` / `storeId` / `uploadTime` / `ttl` / `subType`. */ -export function normalizeMediaNode(node: NtMediaIndex): Record { +export function normalizeMediaNode(node: NtMediaIndex): NTV2IndexNode { const fileUuid = typeof node.fileUuid === 'string' ? node.fileUuid : ''; if (!fileUuid) throw new Error('media node fileUuid is required'); diff --git a/packages/protocol/src/oidb-services/misc/click-inline-keyboard-button.ts b/packages/protocol/src/oidb-services/misc/click-inline-keyboard-button.ts index 97ae4c50..28e1e096 100644 --- a/packages/protocol/src/oidb-services/misc/click-inline-keyboard-button.ts +++ b/packages/protocol/src/oidb-services/misc/click-inline-keyboard-button.ts @@ -8,6 +8,7 @@ import type { Oidb0x112eReq, Oidb0x112eResp } from '@snowluma/proto-defs/oidb-ac import { invokeOidb, type OidbSender } from '../../oidb-service'; export interface InlineButtonClickResult { + [key: string]: import('@snowluma/common/json').JsonValue; result: number; errMsg: string; status: 0; diff --git a/packages/protocol/src/oidb-services/profile/get-like.ts b/packages/protocol/src/oidb-services/profile/get-like.ts index 4233de18..bd42a579 100644 --- a/packages/protocol/src/oidb-services/profile/get-like.ts +++ b/packages/protocol/src/oidb-services/profile/get-like.ts @@ -10,15 +10,18 @@ import { invokeOidb, type OidbSender } from '../../oidb-service'; import type { BridgeContext } from '../../bridge-context'; export interface LikeInfo { + [key: string]: import('@snowluma/common/json').JsonValue; uid: string; time: number; favoriteInfo: { + [key: string]: import('@snowluma/common/json').JsonValue; total_count: number; last_time: number; today_count: number; userInfos: never[]; }; voteInfo: { + [key: string]: import('@snowluma/common/json').JsonValue; total_count: number; new_count: number; new_nearby_count: number; diff --git a/packages/protocol/src/oidb-services/profile/get-unidirectional-friend-list.ts b/packages/protocol/src/oidb-services/profile/get-unidirectional-friend-list.ts index 498d34a3..40d5be3f 100644 --- a/packages/protocol/src/oidb-services/profile/get-unidirectional-friend-list.ts +++ b/packages/protocol/src/oidb-services/profile/get-unidirectional-friend-list.ts @@ -13,6 +13,10 @@ import type { Oidb0xe17Req, Oidb0xe17Resp } from '@snowluma/proto-defs/oidb-acti import { invokeOidb, type OidbSender } from '../../oidb-service'; import type { BridgeContext } from '../../bridge-context'; +export interface UnidirectionalFriendEntry { + [key: string]: import('@snowluma/common/json').JsonValue; +} + export namespace GetUnidirectionalFriendList { export const command = 0xE17; export const subCommand = 0; @@ -35,9 +39,9 @@ export namespace GetUnidirectionalFriendList { return { jsonBody: JSON.stringify(reqObj) }; }; - export const deserialize = (_ctx: Deps, body: Oidb0xe17Resp): unknown[] => { + export const deserialize = (_ctx: Deps, body: Oidb0xe17Resp): UnidirectionalFriendEntry[] => { if (!body || !body.jsonBody) throw new Error('get unidirectional friend list empty'); - const parsed = JSON.parse(body.jsonBody); + const parsed = JSON.parse(body.jsonBody) as { rpt_block_list?: UnidirectionalFriendEntry[] }; return parsed.rpt_block_list || []; }; @@ -47,6 +51,6 @@ export namespace GetUnidirectionalFriendList { export const decode = (bytes: Uint8Array): OidbBase => protobuf_decode>(bytes); - export const invoke = (deps: Deps): Promise => + export const invoke = (deps: Deps): Promise => invokeOidb(deps, GetUnidirectionalFriendList, {}); } diff --git a/packages/protocol/src/web/group-album.ts b/packages/protocol/src/web/group-album.ts index 830ec25e..d4d220a9 100644 --- a/packages/protocol/src/web/group-album.ts +++ b/packages/protocol/src/web/group-album.ts @@ -3,6 +3,7 @@ import { createHash } from 'crypto'; import { statSync, createReadStream, writeFileSync, unlinkSync } from 'fs'; import { basename, join } from 'path'; import { tmpdir } from 'os'; +import type { JsonValue } from '@snowluma/common/json'; // 群相册信息 export interface GroupAlbumInfo { @@ -10,13 +11,13 @@ export interface GroupAlbumInfo { name: string; picNum: number; createTime: number; - [key: string]: any; + [key: string]: JsonValue; } // 获取群相册列表返回 export interface GroupAlbumListRet { album: GroupAlbumInfo[]; - [key: string]: any; + [key: string]: JsonValue | GroupAlbumInfo[]; } // 相册媒体信息 @@ -24,7 +25,7 @@ export interface AlbumMediaInfo { photoId: string; url: string; uploadTime: number; - [key: string]: any; + [key: string]: JsonValue; } /** @@ -150,7 +151,7 @@ async function uploadAlbumSlice( session: string, filePath: string, // 移除 offset 和 chunkSize 参数,改为在内部控制 - imgMd5: string, + _imgMd5: string, skey: string, pskey: string, uin: string diff --git a/packages/protocol/src/web/group-essence.ts b/packages/protocol/src/web/group-essence.ts index cf46eff7..bf963ced 100644 --- a/packages/protocol/src/web/group-essence.ts +++ b/packages/protocol/src/web/group-essence.ts @@ -1,5 +1,6 @@ import { RequestUtil, cookieToString, getBknFromCookie } from './request-util'; import { createLogger } from '@snowluma/common/logger'; +import type { JsonObject } from '@snowluma/common/json'; const log = createLogger('Bridge.Web'); @@ -8,10 +9,10 @@ export interface GroupEssenceMsgRet { retcode: number; data: { is_end: boolean; - msg_list: any[]; // 具体结构视需要补充 - [key: string]: any; + msg_list: JsonObject[]; // 具体结构视需要补充 + [key: string]: JsonObject[] | boolean | string | number | null; }; - [key: string]: any; + [key: string]: JsonObject | JsonObject[] | boolean | string | number | null; } diff --git a/packages/protocol/src/web/group-honor.ts b/packages/protocol/src/web/group-honor.ts index 133e4f9d..37c7039d 100644 --- a/packages/protocol/src/web/group-honor.ts +++ b/packages/protocol/src/web/group-honor.ts @@ -3,6 +3,26 @@ import { createLogger } from '@snowluma/common/logger'; const log = createLogger('Bridge.Web'); +export interface WebHonorItem { + [key: string]: import('@snowluma/common/json').JsonValue; + user_id: number | string | null; + nickname: string; + avatar: string; + description: string; +} + +interface RawHonorItem { + uin?: number | string; + name?: string; + avatar?: string; + desc?: string; +} + +interface HonorInitialState { + talkativeList?: RawHonorItem[]; + actorList?: RawHonorItem[]; +} + export enum WebHonorType { TALKATIVE = 'talkative', PERFORMER = 'performer', @@ -12,8 +32,8 @@ export enum WebHonorType { } -async function fetchHonorData(cookieObject: Record, groupCode: string, type: number) { - let resJson; +async function fetchHonorData(cookieObject: Record, groupCode: string, type: number): Promise { + let resJson: HonorInitialState | undefined; try { const res = await RequestUtil.HttpGetText( `https://qun.qq.com/interactive/honorlist?${new URLSearchParams({ @@ -26,7 +46,7 @@ async function fetchHonorData(cookieObject: Record, groupCode: s ); const match = /window\.__INITIAL_STATE__=(.*?);/.exec(res); if (match?.[1]) { - resJson = JSON.parse(match[1].trim()); + resJson = JSON.parse(match[1].trim()) as HonorInitialState; } return type === 1 ? resJson?.talkativeList : resJson?.actorList; } catch (e) { @@ -34,16 +54,16 @@ async function fetchHonorData(cookieObject: Record, groupCode: s } } -export async function getHonorListWebAPI(cookieObject: Record, groupCode: string, type: number) { +export async function getHonorListWebAPI(cookieObject: Record, groupCode: string, type: number): Promise { try { const data = await fetchHonorData(cookieObject, groupCode, type); if (!data) return []; - return data.map((item: any) => ({ - user_id: item?.uin, - nickname: item?.name, - avatar: item?.avatar, - description: item?.desc, + return data.map((item) => ({ + user_id: item?.uin ?? null, + nickname: item?.name ?? '', + avatar: item?.avatar ?? '', + description: item?.desc ?? '', })); } catch (e) { log.warn('getHonorListWebAPI failed (group=%s type=%d): %s', diff --git a/packages/protocol/src/web/group-notice.ts b/packages/protocol/src/web/group-notice.ts index 2fc3b8fa..1927d249 100644 --- a/packages/protocol/src/web/group-notice.ts +++ b/packages/protocol/src/web/group-notice.ts @@ -1,19 +1,20 @@ import { RequestUtil, cookieToString } from './request-util'; import https from 'node:https'; import { createLogger } from '@snowluma/common/logger'; +import type { JsonValue } from '@snowluma/common/json'; const log = createLogger('Bridge.Web'); export interface SetNoticeRetSuccess { ec?: number; em?: string; - [key: string]: any; + [key: string]: JsonValue | undefined; } export interface UploadImageRetSuccess { ec?: number; id?: string; - [key: string]: any; + [key: string]: JsonValue | undefined; } export interface WebApiGroupNoticeFeed { @@ -24,16 +25,16 @@ export interface WebApiGroupNoticeFeed { text: string; pics?: Array<{ id: string; w: number; h: number }>; }; - settings: any; + settings: JsonValue; read_num: number; - [key: string]: any; + [key: string]: JsonValue; } export interface WebApiGroupNoticeRet { ec: number; em?: string; feeds?: Record; - [key: string]: any; + [key: string]: JsonValue | Record | undefined; } export function calculateBkn(key: string): string { diff --git a/packages/protocol/src/web/request-util.ts b/packages/protocol/src/web/request-util.ts index 60f28d8f..ce726cbd 100644 --- a/packages/protocol/src/web/request-util.ts +++ b/packages/protocol/src/web/request-util.ts @@ -1,6 +1,8 @@ import https from 'node:https'; import http from 'node:http'; +type RequestBody = string | Buffer | Uint8Array | Record | undefined; + export class RequestUtil { static async HttpsGetCookies(url: string): Promise<{ [key: string]: string; }> { const client = url.startsWith('https') ? https : http; @@ -50,7 +52,7 @@ export class RequestUtil { }); } - static async HttpGetJson(url: string, method: string = 'GET', data?: any, headers: { + static async HttpGetJson(url: string, method: string = 'GET', data?: RequestBody, headers: { [key: string]: string; } = {}, isJsonRet: boolean = true, isArgJson: boolean = true, maxRedirects: number = 5): Promise { const option = new URL(url); @@ -91,8 +93,8 @@ export class RequestUtil { } else { reject(new Error(`Unexpected status code: ${res.statusCode}`)); } - } catch (parseError: any) { - reject(new Error(parseError.message)); + } catch (parseError: unknown) { + reject(new Error(parseError instanceof Error ? parseError.message : String(parseError))); } }); }); @@ -105,7 +107,7 @@ export class RequestUtil { }); } - static async HttpGetText(url: string, method: string = 'GET', data?: any, headers: { [key: string]: string; } = {}) { + static async HttpGetText(url: string, method: string = 'GET', data?: RequestBody, headers: { [key: string]: string; } = {}) { return this.HttpGetJson(url, method, data, headers, false, false); } } diff --git a/packages/proton/src/runtime.ts b/packages/proton/src/runtime.ts index b3b8b185..c48cc583 100644 --- a/packages/proton/src/runtime.ts +++ b/packages/proton/src/runtime.ts @@ -1,11 +1,5 @@ -/** - * Runtime fallback functions. - * If the Vite plugin is active, calls to protobuf_encode/decode are replaced - * at compile-time with generated type-specific functions. - * If NOT transformed, these fallbacks throw to alert the developer. - */ -import type { MessageRegistry } from './ast/types.js'; import { buildDependencyRegistry } from './ast/dependency-graph.js'; +import type { MessageRegistry } from './ast/types.js'; import { generateCode } from './codegen/generator.js'; import { normalizeRuntimeMapPath, @@ -19,14 +13,14 @@ type DynamicEncode = (params: unknown) => Uint8Array; type DynamicDecode = (data: Uint8Array) => unknown; interface DynamicTypeFns { - encode?: DynamicEncode; - decode?: DynamicDecode; + encode?: DynamicEncode; + decode?: DynamicDecode; } interface StackFrame { - file: string; - line: number; - column: number; + file: string; + line: number; + column: number; } const DYNAMIC_MISS = Symbol('snowluma-proton-dynamic-miss'); @@ -64,7 +58,7 @@ export function protobuf_encode(_params: T): Uint8Array { throw new Error( 'protobuf_encode() was not transformed by the @snowluma/proton Vite plugin. ' + - 'Make sure protobufVitePlugin() is added to your vite.config.ts plugins array.', + 'Make sure protobufVitePlugin() is added to your vite.config.ts plugins array.', ); } @@ -74,7 +68,7 @@ export function protobuf_decode(_data: Uint8Array): T { throw new Error( 'protobuf_decode() was not transformed by the @snowluma/proton Vite plugin. ' + - 'Make sure protobufVitePlugin() is added to your vite.config.ts plugins array.', + 'Make sure protobufVitePlugin() is added to your vite.config.ts plugins array.', ); } @@ -111,9 +105,9 @@ function ensureTypeFunctions(typeName: string): DynamicTypeFns | null { const factory = new Function( `${generated}\nreturn {\n` + - ` encode: typeof protobuf_encode_${typeName} === 'function' ? protobuf_encode_${typeName} : undefined,\n` + - ` decode: typeof protobuf_decode_${typeName} === 'function' ? protobuf_decode_${typeName} : undefined,\n` + - `};`, + ` encode: typeof protobuf_encode_${typeName} === 'function' ? protobuf_encode_${typeName} : undefined,\n` + + ` decode: typeof protobuf_decode_${typeName} === 'function' ? protobuf_decode_${typeName} : undefined,\n` + + `};`, ) as () => DynamicTypeFns; const compiled = factory(); @@ -126,8 +120,8 @@ function resolveCallSite(fnName: RuntimeMapFnName, caller: StackFrame): RuntimeM const candidates = runtimeMapCallSites.filter(cs => cs.fnName === fnName && - cs.line === caller.line && - pathsLikelySame(callerFile, cs.file), + cs.line === caller.line && + pathsLikelySame(callerFile, cs.file), ); if (candidates.length === 0) return null; diff --git a/packages/sdk/src/messages/chain.ts b/packages/sdk/src/messages/chain.ts index 6df4f8d6..607d4c41 100644 --- a/packages/sdk/src/messages/chain.ts +++ b/packages/sdk/src/messages/chain.ts @@ -24,7 +24,7 @@ export interface MessageChainSender { const SINGLE_USE_TYPES = new Set(['reply']); export class MessageChain implements MessageChainLike { - private readonly replyStateBrand?: THasReply; + declare private readonly replyStateBrand?: THasReply; private constructor( private readonly items: AnyMessageSegment[] = [], diff --git a/packages/sqlite/src/index.ts b/packages/sqlite/src/index.ts index 72d47db8..73f87eb6 100644 --- a/packages/sqlite/src/index.ts +++ b/packages/sqlite/src/index.ts @@ -73,16 +73,12 @@ function loadAddon(): unknown { type BetterSqlite3Type = typeof BetterSqlite3; type DatabaseInstance = InstanceType; type DatabaseOptions = ConstructorParameters[1]; +type BetterSqlite3Constructor = new (filename?: string | Buffer, options?: DatabaseOptions) => DatabaseInstance; const Database = function Database(filename?: string | Buffer, options?: DatabaseOptions): DatabaseInstance { const opts = { ...(options ?? {}), nativeBinding: loadAddon() } as DatabaseOptions; - // The `as any` shrug here is unavoidable — better-sqlite3's - // overload set declares `new Database(...)` returns the class type - // but its default export is a callable that *also* returns it via - // an internal `new.target == null` branch. TS can't reconcile the - // two without `as any`. - - return new (BetterSqlite3 as any)(filename, opts); + // better-sqlite3 的默认导出同时支持函数调用和 new 调用;这里只需要构造器形态。 + return new (BetterSqlite3 as unknown as BetterSqlite3Constructor)(filename, opts); } as unknown as BetterSqlite3Type; export default Database; diff --git a/tsconfig.base.json b/tsconfig.base.json index 0f4ad410..b9036bde 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -7,6 +7,12 @@ "ES2022" ], "strict": true, + "noImplicitAny": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true,