mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-09 02:44:28 +02:00
fix(check): repair latest type drift batch
This commit is contained in:
@@ -15,13 +15,12 @@ import type {
|
||||
ResolvedFeishuAccount,
|
||||
} from "./types.js";
|
||||
|
||||
const {
|
||||
_listConfiguredAccountIds,
|
||||
listAccountIds: listFeishuAccountIds,
|
||||
resolveDefaultAccountId,
|
||||
} = createAccountListHelpers("feishu", {
|
||||
allowUnlistedDefaultAccount: true,
|
||||
});
|
||||
const { listAccountIds: listFeishuAccountIds, resolveDefaultAccountId } = createAccountListHelpers(
|
||||
"feishu",
|
||||
{
|
||||
allowUnlistedDefaultAccount: true,
|
||||
},
|
||||
);
|
||||
|
||||
export { listFeishuAccountIds };
|
||||
|
||||
|
||||
@@ -10,6 +10,12 @@ const resolveLegacyWebhookNameToChatUserId = vi
|
||||
const { clearSynologyWebhookRateLimiterStateForTest, createWebhookHandler } =
|
||||
await import("./webhook-handler.js");
|
||||
|
||||
type TestLog = {
|
||||
info: (...args: unknown[]) => void;
|
||||
warn: (...args: unknown[]) => void;
|
||||
error: (...args: unknown[]) => void;
|
||||
};
|
||||
|
||||
function makeAccount(
|
||||
overrides: Partial<ResolvedSynologyChatAccount> = {},
|
||||
): ResolvedSynologyChatAccount {
|
||||
@@ -40,7 +46,7 @@ const validBody = makeFormBody({
|
||||
});
|
||||
|
||||
async function runDangerousNameMatchReply(
|
||||
log: { info: unknown; warn: unknown; error: unknown },
|
||||
log: TestLog,
|
||||
options: {
|
||||
resolvedChatUserId?: number;
|
||||
accountIdSuffix: string;
|
||||
@@ -73,7 +79,7 @@ async function runDangerousNameMatchReply(
|
||||
}
|
||||
|
||||
describe("createWebhookHandler", () => {
|
||||
let log: { info: unknown; warn: unknown; error: unknown };
|
||||
let log: TestLog;
|
||||
|
||||
beforeEach(() => {
|
||||
clearSynologyWebhookRateLimiterStateForTest();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawPluginApi } from "./api.js";
|
||||
import register from "./index.js";
|
||||
|
||||
describe("thread-ownership plugin", () => {
|
||||
@@ -40,7 +41,7 @@ describe("thread-ownership plugin", () => {
|
||||
|
||||
describe("message_sending", () => {
|
||||
beforeEach(() => {
|
||||
register.register(api as unknown);
|
||||
register.register(api as unknown as OpenClawPluginApi);
|
||||
});
|
||||
|
||||
async function sendSlackThreadMessage() {
|
||||
@@ -112,7 +113,7 @@ describe("thread-ownership plugin", () => {
|
||||
|
||||
describe("message_received @-mention tracking", () => {
|
||||
beforeEach(() => {
|
||||
register.register(api as unknown);
|
||||
register.register(api as unknown as OpenClawPluginApi);
|
||||
});
|
||||
|
||||
it("tracks @-mentions and skips ownership check for mentioned threads", async () => {
|
||||
|
||||
@@ -5,6 +5,10 @@ type TlonScryApi = {
|
||||
scry: (path: string) => Promise<unknown>;
|
||||
};
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | null {
|
||||
return value && typeof value === "object" ? (value as Record<string, unknown>) : null;
|
||||
}
|
||||
|
||||
export function createTlonCitationResolver(params: { api: TlonScryApi; runtime: RuntimeEnv }) {
|
||||
const { api, runtime } = params;
|
||||
|
||||
@@ -17,9 +21,10 @@ export function createTlonCitationResolver(params: { api: TlonScryApi; runtime:
|
||||
const scryPath = `/channels/v4/${cite.nest}/posts/post/${cite.postId}.json`;
|
||||
runtime.log?.(`[tlon] Fetching cited post: ${scryPath}`);
|
||||
|
||||
const data: unknown = await api.scry(scryPath);
|
||||
if (data?.essay?.content) {
|
||||
return extractMessageText(data.essay.content) || null;
|
||||
const data = asRecord(await api.scry(scryPath));
|
||||
const essay = asRecord(data?.essay);
|
||||
if (essay?.content) {
|
||||
return extractMessageText(essay.content) || null;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -2,6 +2,14 @@ import type { RuntimeEnv } from "../../api.js";
|
||||
import type { Foreigns } from "../urbit/foreigns.js";
|
||||
import { formatChangesDate } from "./utils.js";
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | null {
|
||||
return value && typeof value === "object" ? (value as Record<string, unknown>) : null;
|
||||
}
|
||||
|
||||
function formatErrorMessage(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
|
||||
export async function fetchGroupChanges(
|
||||
api: { scry: (path: string) => Promise<unknown> },
|
||||
runtime: RuntimeEnv,
|
||||
@@ -18,7 +26,7 @@ export async function fetchGroupChanges(
|
||||
return null;
|
||||
} catch (error: unknown) {
|
||||
runtime.log?.(
|
||||
`[tlon] Failed to fetch changes (falling back to full init): ${error?.message ?? String(error)}`,
|
||||
`[tlon] Failed to fetch changes (falling back to full init): ${formatErrorMessage(error)}`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
@@ -39,13 +47,16 @@ export async function fetchInitData(
|
||||
): Promise<InitData> {
|
||||
try {
|
||||
runtime.log?.("[tlon] Fetching groups-ui init data...");
|
||||
const initData = await api.scry("/groups-ui/v6/init.json");
|
||||
const initData = asRecord(await api.scry("/groups-ui/v6/init.json"));
|
||||
|
||||
const channels: string[] = [];
|
||||
if (initData?.groups) {
|
||||
for (const groupData of Object.values(initData.groups as Record<string, unknown>)) {
|
||||
if (groupData && typeof groupData === "object" && groupData.channels) {
|
||||
for (const channelNest of Object.keys(groupData.channels)) {
|
||||
const groups = asRecord(initData?.groups);
|
||||
if (groups) {
|
||||
for (const groupData of Object.values(groups)) {
|
||||
const typedGroupData = asRecord(groupData);
|
||||
const groupChannels = asRecord(typedGroupData?.channels);
|
||||
if (groupChannels) {
|
||||
for (const channelNest of Object.keys(groupChannels)) {
|
||||
if (channelNest.startsWith("chat/")) {
|
||||
channels.push(channelNest);
|
||||
}
|
||||
@@ -60,7 +71,8 @@ export async function fetchInitData(
|
||||
runtime.log?.("[tlon] No chat channels found via auto-discovery");
|
||||
}
|
||||
|
||||
const foreigns = (initData?.foreigns as Foreigns) || null;
|
||||
const foreignsValue = asRecord(initData?.foreigns);
|
||||
const foreigns = foreignsValue ? (foreignsValue as Foreigns) : null;
|
||||
if (foreigns) {
|
||||
const pendingCount = Object.values(foreigns).filter((f) =>
|
||||
f.invites?.some((i) => i.valid),
|
||||
@@ -72,7 +84,7 @@ export async function fetchInitData(
|
||||
|
||||
return { channels, foreigns };
|
||||
} catch (error: unknown) {
|
||||
runtime.log?.(`[tlon] Init data fetch failed: ${error?.message ?? String(error)}`);
|
||||
runtime.log?.(`[tlon] Init data fetch failed: ${formatErrorMessage(error)}`);
|
||||
return { channels: [], foreigns: null };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import type { RuntimeEnv } from "../../api.js";
|
||||
import { extractMessageText } from "./utils.js";
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | null {
|
||||
return value && typeof value === "object" ? (value as Record<string, unknown>) : null;
|
||||
}
|
||||
|
||||
function formatErrorMessage(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a number as @ud (with dots every 3 digits from the right)
|
||||
* e.g., 170141184507799509469114119040828178432 -> 170.141.184.507.799.509.469.114.119.040.828.178.432
|
||||
@@ -62,22 +70,29 @@ export async function fetchChannelHistory(
|
||||
let posts: unknown[] = [];
|
||||
if (Array.isArray(data)) {
|
||||
posts = data;
|
||||
} else if (data.posts && typeof data.posts === "object") {
|
||||
posts = Object.values(data.posts);
|
||||
} else if (typeof data === "object") {
|
||||
posts = Object.values(data);
|
||||
} else {
|
||||
const dataRecord = asRecord(data);
|
||||
const postMap = asRecord(dataRecord?.posts);
|
||||
if (postMap) {
|
||||
posts = Object.values(postMap);
|
||||
} else if (dataRecord) {
|
||||
posts = Object.values(dataRecord);
|
||||
}
|
||||
}
|
||||
|
||||
const messages = posts
|
||||
.map((item) => {
|
||||
const essay = item.essay || item["r-post"]?.set?.essay;
|
||||
const seal = item.seal || item["r-post"]?.set?.seal;
|
||||
const itemRecord = asRecord(item);
|
||||
const replyPost = asRecord(itemRecord?.["r-post"]);
|
||||
const replyPostSet = asRecord(replyPost?.set);
|
||||
const essay = asRecord(itemRecord?.essay) ?? asRecord(replyPostSet?.essay);
|
||||
const seal = asRecord(itemRecord?.seal) ?? asRecord(replyPostSet?.seal);
|
||||
|
||||
return {
|
||||
author: essay?.author || "unknown",
|
||||
author: typeof essay?.author === "string" ? essay.author : "unknown",
|
||||
content: extractMessageText(essay?.content || []),
|
||||
timestamp: essay?.sent || Date.now(),
|
||||
id: seal?.id,
|
||||
timestamp: typeof essay?.sent === "number" ? essay.sent : Date.now(),
|
||||
id: typeof seal?.id === "string" ? seal.id : undefined,
|
||||
} as TlonHistoryEntry;
|
||||
})
|
||||
.filter((msg) => msg.content);
|
||||
@@ -85,7 +100,7 @@ export async function fetchChannelHistory(
|
||||
runtime?.log?.(`[tlon] Extracted ${messages.length} messages from history`);
|
||||
return messages;
|
||||
} catch (error: unknown) {
|
||||
runtime?.log?.(`[tlon] Error fetching channel history: ${error?.message ?? String(error)}`);
|
||||
runtime?.log?.(`[tlon] Error fetching channel history: ${formatErrorMessage(error)}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -138,23 +153,37 @@ export async function fetchThreadHistory(
|
||||
let replies: unknown[] = [];
|
||||
if (Array.isArray(data)) {
|
||||
replies = data;
|
||||
} else if (data.replies && Array.isArray(data.replies)) {
|
||||
replies = data.replies;
|
||||
} else if (typeof data === "object") {
|
||||
replies = Object.values(data);
|
||||
} else {
|
||||
const dataRecord = asRecord(data);
|
||||
const replyValue = dataRecord?.replies;
|
||||
if (Array.isArray(replyValue)) {
|
||||
replies = replyValue;
|
||||
} else if (typeof replyValue === "object" && replyValue) {
|
||||
replies = Object.values(replyValue as Record<string, unknown>);
|
||||
} else if (dataRecord) {
|
||||
replies = Object.values(dataRecord);
|
||||
}
|
||||
}
|
||||
|
||||
const messages = replies
|
||||
.map((item) => {
|
||||
// Thread replies use 'memo' structure
|
||||
const memo = item.memo || item["r-reply"]?.set?.memo || item;
|
||||
const seal = item.seal || item["r-reply"]?.set?.seal;
|
||||
const itemRecord = asRecord(item);
|
||||
const replyRecord = asRecord(itemRecord?.["r-reply"]);
|
||||
const replySet = asRecord(replyRecord?.set);
|
||||
const memo = asRecord(itemRecord?.memo) ?? asRecord(replySet?.memo) ?? itemRecord;
|
||||
const seal = asRecord(itemRecord?.seal) ?? asRecord(replySet?.seal);
|
||||
|
||||
return {
|
||||
author: memo?.author || "unknown",
|
||||
author: typeof memo?.author === "string" ? memo.author : "unknown",
|
||||
content: extractMessageText(memo?.content || []),
|
||||
timestamp: memo?.sent || Date.now(),
|
||||
id: seal?.id || item.id,
|
||||
timestamp: typeof memo?.sent === "number" ? memo.sent : Date.now(),
|
||||
id:
|
||||
typeof seal?.id === "string"
|
||||
? seal.id
|
||||
: typeof itemRecord?.id === "string"
|
||||
? itemRecord.id
|
||||
: undefined,
|
||||
} as TlonHistoryEntry;
|
||||
})
|
||||
.filter((msg) => msg.content);
|
||||
@@ -162,29 +191,39 @@ export async function fetchThreadHistory(
|
||||
runtime?.log?.(`[tlon] Extracted ${messages.length} thread replies from history`);
|
||||
return messages;
|
||||
} catch (error: unknown) {
|
||||
runtime?.log?.(`[tlon] Error fetching thread history: ${error?.message ?? String(error)}`);
|
||||
runtime?.log?.(`[tlon] Error fetching thread history: ${formatErrorMessage(error)}`);
|
||||
// Fall back to trying alternate path structure
|
||||
try {
|
||||
const altPath = `/channels/v4/${channelNest}/posts/post/id/${formatUd(parentId)}.json`;
|
||||
runtime?.log?.(`[tlon] Trying alternate path: ${altPath}`);
|
||||
const data: unknown = await api.scry(altPath);
|
||||
const data = asRecord(await api.scry(altPath));
|
||||
const dataSeal = asRecord(data?.seal);
|
||||
const dataMeta = asRecord(dataSeal?.meta);
|
||||
const repliesValue = data?.replies;
|
||||
|
||||
if (data?.seal?.meta?.replyCount > 0 && data?.replies) {
|
||||
const replies = Array.isArray(data.replies) ? data.replies : Object.values(data.replies);
|
||||
if (typeof dataMeta?.replyCount === "number" && dataMeta.replyCount > 0 && repliesValue) {
|
||||
const replies = Array.isArray(repliesValue)
|
||||
? repliesValue
|
||||
: Object.values(repliesValue as Record<string, unknown>);
|
||||
const messages = replies
|
||||
.map((reply: unknown) => ({
|
||||
author: reply.memo?.author || "unknown",
|
||||
content: extractMessageText(reply.memo?.content || []),
|
||||
timestamp: reply.memo?.sent || Date.now(),
|
||||
id: reply.seal?.id,
|
||||
}))
|
||||
.map((reply: unknown) => {
|
||||
const replyRecord = asRecord(reply);
|
||||
const memo = asRecord(replyRecord?.memo);
|
||||
const seal = asRecord(replyRecord?.seal);
|
||||
return {
|
||||
author: typeof memo?.author === "string" ? memo.author : "unknown",
|
||||
content: extractMessageText(memo?.content || []),
|
||||
timestamp: typeof memo?.sent === "number" ? memo.sent : Date.now(),
|
||||
id: typeof seal?.id === "string" ? seal.id : undefined,
|
||||
};
|
||||
})
|
||||
.filter((msg: TlonHistoryEntry) => msg.content);
|
||||
|
||||
runtime?.log?.(`[tlon] Extracted ${messages.length} replies from post data`);
|
||||
return messages;
|
||||
}
|
||||
} catch (altError: unknown) {
|
||||
runtime?.log?.(`[tlon] Alternate path also failed: ${altError?.message ?? String(altError)}`);
|
||||
runtime?.log?.(`[tlon] Alternate path also failed: ${formatErrorMessage(altError)}`);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -45,6 +45,10 @@ export type MonitorTlonOpts = {
|
||||
accountId?: string | null;
|
||||
};
|
||||
|
||||
function formatErrorMessage(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
|
||||
export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<void> {
|
||||
const core = getTlonRuntime();
|
||||
const cfg = core.config.loadConfig();
|
||||
@@ -89,7 +93,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
return await authenticate(accountUrl, accountCode, { ssrfPolicy });
|
||||
} catch (error: unknown) {
|
||||
runtime.error?.(
|
||||
`[tlon] Failed to authenticate (attempt ${attempt}): ${error?.message ?? String(error)}`,
|
||||
`[tlon] Failed to authenticate (attempt ${attempt}): ${formatErrorMessage(error)}`,
|
||||
);
|
||||
if (attempt >= maxAttempts) {
|
||||
throw error;
|
||||
@@ -169,7 +173,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
}
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
runtime.log?.(`[tlon] Could not fetch nickname: ${error?.message ?? String(error)}`);
|
||||
runtime.log?.(`[tlon] Could not fetch nickname: ${formatErrorMessage(error)}`);
|
||||
}
|
||||
|
||||
// Store init foreigns for processing after settings are loaded
|
||||
@@ -236,7 +240,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
}
|
||||
initForeigns = initData.foreigns;
|
||||
} catch (error: unknown) {
|
||||
runtime.error?.(`[tlon] Auto-discovery failed: ${error?.message ?? String(error)}`);
|
||||
runtime.error?.(`[tlon] Auto-discovery failed: ${formatErrorMessage(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,8 +306,8 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
senderShip,
|
||||
isGroup,
|
||||
channelNest,
|
||||
_hostShip,
|
||||
_channelName,
|
||||
hostShip: _hostShip,
|
||||
channelName: _channelName,
|
||||
timestamp,
|
||||
parentId,
|
||||
isThreadReply,
|
||||
@@ -321,7 +325,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
runtime.log?.(`[tlon] Downloaded ${attachments.length} image(s) from message`);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
runtime.log?.(`[tlon] Failed to download images: ${error?.message ?? String(error)}`);
|
||||
runtime.log?.(`[tlon] Failed to download images: ${formatErrorMessage(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,7 +348,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
runtime?.log?.(`[tlon] Could not fetch thread context: ${error?.message ?? String(error)}`);
|
||||
runtime?.log?.(`[tlon] Could not fetch thread context: ${formatErrorMessage(error)}`);
|
||||
// Continue without thread context - not critical
|
||||
}
|
||||
}
|
||||
@@ -391,7 +395,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
"3. Action items if any\n" +
|
||||
"4. Notable participants";
|
||||
} catch (error: unknown) {
|
||||
const errorMsg = `Sorry, I encountered an error while fetching the channel history: ${error?.message ?? String(error)}`;
|
||||
const errorMsg = `Sorry, I encountered an error while fetching the channel history: ${formatErrorMessage(error)}`;
|
||||
if (isGroup && groupChannel) {
|
||||
const parsed = parseChannelNest(groupChannel);
|
||||
if (parsed) {
|
||||
@@ -807,9 +811,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
isThreadReply,
|
||||
});
|
||||
} catch (error: any) {
|
||||
runtime.error?.(
|
||||
`[tlon] Error handling channel firehose event: ${error?.message ?? String(error)}`,
|
||||
);
|
||||
runtime.error?.(`[tlon] Error handling channel firehose event: ${formatErrorMessage(error)}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -989,9 +991,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
timestamp: essay.sent || Date.now(),
|
||||
});
|
||||
} catch (error: any) {
|
||||
runtime.error?.(
|
||||
`[tlon] Error handling chat firehose event: ${error?.message ?? String(error)}`,
|
||||
);
|
||||
runtime.error?.(`[tlon] Error handling chat firehose event: ${formatErrorMessage(error)}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1044,9 +1044,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
runtime.error?.(
|
||||
`[tlon] Error handling contacts event: ${error?.message ?? String(error)}`,
|
||||
);
|
||||
runtime.error?.(`[tlon] Error handling contacts event: ${formatErrorMessage(error)}`);
|
||||
}
|
||||
},
|
||||
err: (error) => {
|
||||
@@ -1201,9 +1199,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
runtime.error?.(
|
||||
`[tlon] Error handling groups-ui event: ${error?.message ?? String(error)}`,
|
||||
);
|
||||
runtime.error?.(`[tlon] Error handling groups-ui event: ${formatErrorMessage(error)}`);
|
||||
}
|
||||
},
|
||||
err: (error) => {
|
||||
@@ -1335,7 +1331,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
await processPendingInvites(data as Foreigns);
|
||||
} catch (error: any) {
|
||||
runtime.error?.(
|
||||
`[tlon] Error handling foreigns event: ${error?.message ?? String(error)}`,
|
||||
`[tlon] Error handling foreigns event: ${formatErrorMessage(error)}`,
|
||||
);
|
||||
}
|
||||
})();
|
||||
@@ -1388,7 +1384,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
runtime.error?.(`[tlon] Channel refresh error: ${error?.message ?? String(error)}`);
|
||||
runtime.error?.(`[tlon] Channel refresh error: ${formatErrorMessage(error)}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1414,7 +1410,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
try {
|
||||
await api?.close();
|
||||
} catch (error: any) {
|
||||
runtime.error?.(`[tlon] Cleanup error: ${error?.message ?? String(error)}`);
|
||||
runtime.error?.(`[tlon] Cleanup error: ${formatErrorMessage(error)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,7 +242,7 @@ export function markdownToStory(markdown: string): Story {
|
||||
const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
|
||||
if (headerMatch) {
|
||||
const level = headerMatch[1].length as 1 | 2 | 3 | 4 | 5 | 6;
|
||||
const tag = `h${level}`;
|
||||
const tag = `h${level}` as const;
|
||||
story.push({
|
||||
block: {
|
||||
header: {
|
||||
|
||||
@@ -432,7 +432,6 @@ async function processMessageWithPipeline(params: ZaloMessagePipelineParams): Pr
|
||||
config,
|
||||
runtime,
|
||||
core,
|
||||
_text,
|
||||
mediaPath,
|
||||
mediaType,
|
||||
statusSink,
|
||||
|
||||
@@ -84,6 +84,11 @@ export function defineImportedCommandGroupSpecs<TRegisterArgs, TModule>(
|
||||
}
|
||||
|
||||
type ProgramCommandRegistrar = (program: Command) => Promise<void> | void;
|
||||
type AnyImportedProgramCommandGroupDefinition = {
|
||||
commandNames: readonly string[];
|
||||
loadModule: () => Promise<Record<string, unknown>>;
|
||||
exportName: string;
|
||||
};
|
||||
|
||||
export type ImportedProgramCommandGroupDefinition<
|
||||
TModule extends Record<TKey, ProgramCommandRegistrar>,
|
||||
@@ -108,10 +113,17 @@ export function defineImportedProgramCommandGroupSpec<
|
||||
}
|
||||
|
||||
export function defineImportedProgramCommandGroupSpecs<
|
||||
TModule extends Record<TKey, ProgramCommandRegistrar>,
|
||||
TKey extends keyof TModule & string,
|
||||
>(
|
||||
definitions: readonly ImportedProgramCommandGroupDefinition<TModule, TKey>[],
|
||||
): CommandGroupDescriptorSpec<(program: Command) => Promise<void>>[] {
|
||||
return definitions.map((definition) => defineImportedProgramCommandGroupSpec(definition));
|
||||
const TDefinitions extends readonly AnyImportedProgramCommandGroupDefinition[],
|
||||
>(definitions: TDefinitions): CommandGroupDescriptorSpec<(program: Command) => Promise<void>>[] {
|
||||
return definitions.map((definition) => ({
|
||||
commandNames: definition.commandNames,
|
||||
register: async (program: Command) => {
|
||||
const module = await definition.loadModule();
|
||||
const register = module[definition.exportName];
|
||||
if (typeof register !== "function") {
|
||||
throw new Error(`Missing program command registrar: ${definition.exportName}`);
|
||||
}
|
||||
await register(program);
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user