mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-04 20:45:40 +00:00
refactor(plugin-sdk): remove channel-specific sdk seams
This commit is contained in:
@@ -16,3 +16,4 @@ export * from "./src/resolve-channels.js";
|
||||
export * from "./src/resolve-users.js";
|
||||
export * from "./src/outbound-session-route.js";
|
||||
export * from "./src/send.js";
|
||||
export * from "./src/send.components.js";
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
withNormalizedTimestamp,
|
||||
readBooleanParam,
|
||||
} from "../runtime-api.js";
|
||||
import { sendDiscordComponentMessage } from "../send.components.js";
|
||||
import {
|
||||
createThreadDiscord,
|
||||
deleteMessageDiscord,
|
||||
@@ -29,7 +30,6 @@ import {
|
||||
removeOwnReactionsDiscord,
|
||||
removeReactionDiscord,
|
||||
searchMessagesDiscord,
|
||||
sendDiscordComponentMessage,
|
||||
sendMessageDiscord,
|
||||
sendPollDiscord,
|
||||
sendStickerDiscord,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { GatewayPlugin } from "@buape/carbon/gateway";
|
||||
import type { DiscordActionConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { ActionGate } from "openclaw/plugin-sdk/discord-core";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { clearGateways, registerGateway } from "../monitor/gateway-registry.js";
|
||||
import type { ActionGate } from "../runtime-api.js";
|
||||
import { handleDiscordPresenceAction } from "./runtime.presence.js";
|
||||
|
||||
const mockUpdatePresence = vi.fn();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-chunking";
|
||||
import { resolveAccountEntry } from "openclaw/plugin-sdk/routing";
|
||||
import { normalizeAccountId } from "openclaw/plugin-sdk/routing";
|
||||
import { DISCORD_TEXT_CHUNK_LIMIT } from "./outbound-adapter.js";
|
||||
|
||||
@@ -3,7 +3,7 @@ import { isChannelExecApprovalClientEnabledFromConfig } from "openclaw/plugin-sd
|
||||
import { resolveApprovalApprovers } from "openclaw/plugin-sdk/approval-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { DiscordExecApprovalConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-dispatch-runtime";
|
||||
import { resolveDiscordAccount } from "./accounts.js";
|
||||
import { parseDiscordTarget } from "./targets.js";
|
||||
|
||||
|
||||
@@ -25,25 +25,24 @@ import {
|
||||
} from "openclaw/plugin-sdk/channel-inbound";
|
||||
import { enqueueSystemEvent } from "openclaw/plugin-sdk/channel-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { DiscordAccountConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
|
||||
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
|
||||
import { resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy";
|
||||
import { readSessionUpdatedAt, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
||||
import type { DiscordAccountConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
|
||||
import {
|
||||
type PluginInteractiveDiscordHandlerContext,
|
||||
} from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import { type PluginInteractiveDiscordHandlerContext } from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { createNonExitingRuntime, type RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy";
|
||||
import { readSessionUpdatedAt, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
||||
import { logDebug, logError } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveDiscordMaxLinesPerMessage } from "../accounts.js";
|
||||
import {
|
||||
parseDiscordComponentCustomIdForCarbon,
|
||||
parseDiscordModalCustomIdForCarbon,
|
||||
} from "../component-custom-id.js";
|
||||
import { resolveDiscordMaxLinesPerMessage } from "../accounts.js";
|
||||
import { resolveDiscordComponentEntry, resolveDiscordModalEntry } from "../components-registry.js";
|
||||
import type { DiscordComponentEntry, DiscordModalEntry } from "../components.js";
|
||||
import { editDiscordComponentMessage } from "../send.components.js";
|
||||
import {
|
||||
AGENT_BUTTON_KEY,
|
||||
AGENT_SELECT_KEY,
|
||||
@@ -96,7 +95,6 @@ let replyRuntimePromise: Promise<typeof import("openclaw/plugin-sdk/reply-runtim
|
||||
let replyPipelineRuntimePromise:
|
||||
| Promise<typeof import("openclaw/plugin-sdk/channel-reply-pipeline")>
|
||||
| undefined;
|
||||
let sendComponentsRuntimePromise: Promise<typeof import("../send.components.js")> | undefined;
|
||||
let typingRuntimePromise: Promise<typeof import("./typing.js")> | undefined;
|
||||
|
||||
async function loadConversationRuntime() {
|
||||
@@ -124,11 +122,6 @@ async function loadReplyPipelineRuntime() {
|
||||
return await replyPipelineRuntimePromise;
|
||||
}
|
||||
|
||||
async function loadSendComponentsRuntime() {
|
||||
sendComponentsRuntimePromise ??= import("../send.components.js");
|
||||
return await sendComponentsRuntimePromise;
|
||||
}
|
||||
|
||||
async function loadTypingRuntime() {
|
||||
typingRuntimePromise ??= import("./typing.js");
|
||||
return await typingRuntimePromise;
|
||||
@@ -269,7 +262,6 @@ async function dispatchPluginDiscordInteractiveEvent(params: {
|
||||
const approvalMessageId = params.messageId?.trim() || params.interaction.message?.id?.trim();
|
||||
if (approvalMessageId) {
|
||||
try {
|
||||
const { editDiscordComponentMessage } = await loadSendComponentsRuntime();
|
||||
await editDiscordComponentMessage(
|
||||
normalizedConversationId,
|
||||
approvalMessageId,
|
||||
@@ -901,7 +893,9 @@ async function handleDiscordModalTrigger(params: {
|
||||
}
|
||||
|
||||
try {
|
||||
await params.interaction.showModal((await loadComponentsRuntime()).createDiscordFormModal(modalEntry));
|
||||
await params.interaction.showModal(
|
||||
(await loadComponentsRuntime()).createDiscordFormModal(modalEntry),
|
||||
);
|
||||
} catch (err) {
|
||||
logError(`${params.label}: failed to show modal: ${String(err)}`);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-dispatch-runtime";
|
||||
import { buildDiscordInboundAccessContext } from "./inbound-context.js";
|
||||
|
||||
export function buildFinalizedDiscordDirectInboundContext() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-dispatch-runtime";
|
||||
import { expectChannelInboundContextContract as expectInboundContextContract } from "openclaw/plugin-sdk/testing";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildDiscordInboundAccessContext } from "./inbound-context.js";
|
||||
|
||||
@@ -23,12 +23,12 @@ import { recordInboundSession } from "openclaw/plugin-sdk/conversation-runtime";
|
||||
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
|
||||
import { resolveChunkMode } from "openclaw/plugin-sdk/reply-chunking";
|
||||
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-dispatch-runtime";
|
||||
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-dispatch-runtime";
|
||||
import {
|
||||
buildPendingHistoryContextFromMap,
|
||||
clearHistoryEntriesIfEnabled,
|
||||
} from "openclaw/plugin-sdk/reply-history";
|
||||
import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
|
||||
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { buildAgentSessionKey } from "openclaw/plugin-sdk/routing";
|
||||
import { resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
|
||||
import { danger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { CommandArgs } from "openclaw/plugin-sdk/command-auth";
|
||||
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-dispatch-runtime";
|
||||
import { type DiscordChannelConfigResolved, type DiscordGuildEntryResolved } from "./allow-list.js";
|
||||
import { buildDiscordInboundAccessContext } from "./inbound-context.js";
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { NativeCommandSpec } from "openclaw/plugin-sdk/command-auth";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { DiscordAccountConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import * as pluginCommandsModule from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import * as dispatcherModule from "openclaw/plugin-sdk/reply-runtime";
|
||||
import * as dispatcherModule from "openclaw/plugin-sdk/reply-dispatch-runtime";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { __testing as nativeCommandTesting, createDiscordNativeCommand } from "./native-command.js";
|
||||
import {
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as commandRegistryModule from "openclaw/plugin-sdk/command-auth";
|
||||
import type { ChatCommandDefinition, CommandArgsParsing } from "openclaw/plugin-sdk/command-auth";
|
||||
import type { ModelsProviderData } from "openclaw/plugin-sdk/command-auth";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import * as dispatcherModule from "openclaw/plugin-sdk/reply-runtime";
|
||||
import * as dispatcherModule from "openclaw/plugin-sdk/reply-dispatch-runtime";
|
||||
import * as globalsModule from "openclaw/plugin-sdk/runtime-env";
|
||||
import * as commandTextModule from "openclaw/plugin-sdk/text-runtime";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
@@ -17,6 +17,10 @@ import {
|
||||
resolveCommandAuthorizedFromAuthorizers,
|
||||
resolveNativeCommandSessionTargets,
|
||||
} from "openclaw/plugin-sdk/command-auth-native";
|
||||
import type { OpenClawConfig, loadConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { buildPairingReply } from "openclaw/plugin-sdk/conversation-runtime";
|
||||
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
|
||||
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
|
||||
import {
|
||||
buildCommandTextFromArgs,
|
||||
findCommandByNativeName,
|
||||
@@ -31,21 +35,19 @@ import {
|
||||
type CommandArgs,
|
||||
type NativeCommandSpec,
|
||||
} from "openclaw/plugin-sdk/native-command-registry";
|
||||
import type { OpenClawConfig, loadConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
|
||||
import { resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy";
|
||||
import { buildPairingReply } from "openclaw/plugin-sdk/conversation-runtime";
|
||||
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
|
||||
import * as pluginRuntime from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import { resolveChunkMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-chunking";
|
||||
import {
|
||||
dispatchReplyWithDispatcher,
|
||||
type ReplyPayload,
|
||||
} from "openclaw/plugin-sdk/reply-dispatch-runtime";
|
||||
import {
|
||||
resolveSendableOutboundReplyParts,
|
||||
resolveTextChunksWithFallback,
|
||||
} from "openclaw/plugin-sdk/reply-payload";
|
||||
import * as replyRuntime from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { resolveChunkMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy";
|
||||
import { loadWebMedia } from "openclaw/plugin-sdk/web-media";
|
||||
import { resolveDiscordMaxLinesPerMessage } from "../accounts.js";
|
||||
import { chunkDiscordTextWithMode } from "../chunk.js";
|
||||
@@ -87,7 +89,7 @@ const log = createSubsystemLogger("discord/native-command");
|
||||
const DISCORD_COMMAND_DESCRIPTION_MAX = 100;
|
||||
let matchPluginCommandImpl = pluginRuntime.matchPluginCommand;
|
||||
let executePluginCommandImpl = pluginRuntime.executePluginCommand;
|
||||
let dispatchReplyWithDispatcherImpl = replyRuntime.dispatchReplyWithDispatcher;
|
||||
let dispatchReplyWithDispatcherImpl = dispatchReplyWithDispatcher;
|
||||
let resolveDiscordNativeInteractionRouteStateImpl = resolveDiscordNativeInteractionRouteState;
|
||||
|
||||
export const __testing = {
|
||||
@@ -106,8 +108,8 @@ export const __testing = {
|
||||
return previous;
|
||||
},
|
||||
setDispatchReplyWithDispatcher(
|
||||
next: typeof replyRuntime.dispatchReplyWithDispatcher,
|
||||
): typeof replyRuntime.dispatchReplyWithDispatcher {
|
||||
next: typeof dispatchReplyWithDispatcher,
|
||||
): typeof dispatchReplyWithDispatcher {
|
||||
const previous = dispatchReplyWithDispatcherImpl;
|
||||
dispatchReplyWithDispatcherImpl = next;
|
||||
return previous;
|
||||
|
||||
@@ -22,7 +22,7 @@ import type { OpenClawConfig, ReplyToMode } from "openclaw/plugin-sdk/config-run
|
||||
import { loadConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { createConnectedChannelStatusPatch } from "openclaw/plugin-sdk/gateway-runtime";
|
||||
import { getPluginCommandSpecs } from "openclaw/plugin-sdk/plugin-runtime";
|
||||
import { resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-chunking";
|
||||
import {
|
||||
danger,
|
||||
isVerbose,
|
||||
|
||||
@@ -2,13 +2,13 @@ import type { RequestClient } from "@buape/carbon";
|
||||
import { resolveAgentAvatar } from "openclaw/plugin-sdk/agent-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { MarkdownTableMode, ReplyToMode } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { ChunkMode } from "openclaw/plugin-sdk/reply-chunking";
|
||||
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-dispatch-runtime";
|
||||
import {
|
||||
resolveSendableOutboundReplyParts,
|
||||
resolveTextChunksWithFallback,
|
||||
sendMediaWithLeadingCaption,
|
||||
} from "openclaw/plugin-sdk/reply-payload";
|
||||
import type { ChunkMode } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import {
|
||||
resolveRetryConfig,
|
||||
retryAsync,
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
type OpenClawConfig,
|
||||
type ReplyToMode,
|
||||
} from "openclaw/plugin-sdk/config-runtime";
|
||||
import { createReplyReferencePlanner } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { createReplyReferencePlanner } from "openclaw/plugin-sdk/reply-reference";
|
||||
import { buildAgentSessionKey } from "openclaw/plugin-sdk/routing";
|
||||
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
@@ -16,6 +16,7 @@ export function createDiscordOutboundHoisted() {
|
||||
}
|
||||
|
||||
type DiscordSendModule = typeof import("./send.js");
|
||||
type DiscordSendComponentsModule = typeof import("./send.components.js");
|
||||
type DiscordThreadBindingsModule = typeof import("./monitor/thread-bindings.js");
|
||||
|
||||
export const DEFAULT_DISCORD_SEND_RESULT = {
|
||||
@@ -34,14 +35,24 @@ export async function createDiscordSendModuleMock(
|
||||
return {
|
||||
...actual,
|
||||
sendMessageDiscord: (...args: unknown[]) => hoisted.sendMessageDiscordMock(...args),
|
||||
sendDiscordComponentMessage: (...args: unknown[]) =>
|
||||
hoisted.sendDiscordComponentMessageMock(...args),
|
||||
sendPollDiscord: (...args: unknown[]) => hoisted.sendPollDiscordMock(...args),
|
||||
sendWebhookMessageDiscord: (...args: unknown[]) =>
|
||||
hoisted.sendWebhookMessageDiscordMock(...args),
|
||||
};
|
||||
}
|
||||
|
||||
export async function createDiscordSendComponentsModuleMock(
|
||||
hoisted: DiscordOutboundHoisted,
|
||||
importOriginal: () => Promise<DiscordSendComponentsModule>,
|
||||
) {
|
||||
const actual = await importOriginal();
|
||||
return {
|
||||
...actual,
|
||||
sendDiscordComponentMessage: (...args: unknown[]) =>
|
||||
hoisted.sendDiscordComponentMessageMock(...args),
|
||||
};
|
||||
}
|
||||
|
||||
export async function createDiscordThreadBindingsModuleMock(
|
||||
hoisted: DiscordOutboundHoisted,
|
||||
importOriginal: () => Promise<DiscordThreadBindingsModule>,
|
||||
@@ -59,14 +70,20 @@ export async function installDiscordOutboundModuleSpies(hoisted: DiscordOutbound
|
||||
vi.spyOn(sendModule, "sendMessageDiscord").mockImplementation(
|
||||
mockedSendModule.sendMessageDiscord,
|
||||
);
|
||||
vi.spyOn(sendModule, "sendDiscordComponentMessage").mockImplementation(
|
||||
mockedSendModule.sendDiscordComponentMessage,
|
||||
);
|
||||
vi.spyOn(sendModule, "sendPollDiscord").mockImplementation(mockedSendModule.sendPollDiscord);
|
||||
vi.spyOn(sendModule, "sendWebhookMessageDiscord").mockImplementation(
|
||||
mockedSendModule.sendWebhookMessageDiscord,
|
||||
);
|
||||
|
||||
const sendComponentsModule = await import("./send.components.js");
|
||||
const mockedSendComponentsModule = await createDiscordSendComponentsModuleMock(
|
||||
hoisted,
|
||||
async () => sendComponentsModule,
|
||||
);
|
||||
vi.spyOn(sendComponentsModule, "sendDiscordComponentMessage").mockImplementation(
|
||||
mockedSendComponentsModule.sendDiscordComponentMessage,
|
||||
);
|
||||
|
||||
const threadBindingsModule = await import("./monitor/thread-bindings.js");
|
||||
const mockedThreadBindingsModule = await createDiscordThreadBindingsModuleMock(
|
||||
hoisted,
|
||||
|
||||
@@ -16,12 +16,8 @@ import {
|
||||
import type { DiscordComponentMessageSpec } from "./components.js";
|
||||
import { getThreadBindingManager, type ThreadBindingRecord } from "./monitor/thread-bindings.js";
|
||||
import { normalizeDiscordOutboundTarget } from "./normalize.js";
|
||||
import {
|
||||
sendDiscordComponentMessage,
|
||||
sendMessageDiscord,
|
||||
sendPollDiscord,
|
||||
sendWebhookMessageDiscord,
|
||||
} from "./send.js";
|
||||
import { sendDiscordComponentMessage } from "./send.components.js";
|
||||
import { sendMessageDiscord, sendPollDiscord, sendWebhookMessageDiscord } from "./send.js";
|
||||
import { buildDiscordInteractiveComponents } from "./shared-interactive.js";
|
||||
|
||||
export const DISCORD_TEXT_CHUNK_LIMIT = 2000;
|
||||
|
||||
@@ -7,27 +7,35 @@ export {
|
||||
} from "openclaw/plugin-sdk/channel-status";
|
||||
export {
|
||||
buildChannelConfigSchema,
|
||||
getChatChannelMeta,
|
||||
DiscordConfigSchema,
|
||||
} from "openclaw/plugin-sdk/channel-config-schema";
|
||||
export type {
|
||||
ChannelMessageActionAdapter,
|
||||
ChannelMessageActionContext,
|
||||
ChannelMessageActionName,
|
||||
} from "openclaw/plugin-sdk/channel-contract";
|
||||
export type { ChannelPlugin, OpenClawPluginApi, PluginRuntime } from "openclaw/plugin-sdk/core";
|
||||
export type {
|
||||
DiscordAccountConfig,
|
||||
DiscordActionConfig,
|
||||
DiscordConfig,
|
||||
OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/config-runtime";
|
||||
export {
|
||||
jsonResult,
|
||||
readNumberParam,
|
||||
readStringArrayParam,
|
||||
readStringParam,
|
||||
resolvePollMaxSelections,
|
||||
type ActionGate,
|
||||
type ChannelPlugin,
|
||||
type DiscordAccountConfig,
|
||||
type DiscordActionConfig,
|
||||
type DiscordConfig,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/discord-core";
|
||||
export { DiscordConfigSchema } from "openclaw/plugin-sdk/discord-core";
|
||||
} from "openclaw/plugin-sdk/channel-actions";
|
||||
export type { ActionGate } from "openclaw/plugin-sdk/channel-actions";
|
||||
export { readBooleanParam } from "openclaw/plugin-sdk/boolean-param";
|
||||
export {
|
||||
assertMediaNotDataUrl,
|
||||
parseAvailableTags,
|
||||
readReactionParams,
|
||||
withNormalizedTimestamp,
|
||||
} from "openclaw/plugin-sdk/discord-core";
|
||||
} from "openclaw/plugin-sdk/channel-actions";
|
||||
export {
|
||||
createHybridChannelConfigAdapter,
|
||||
createScopedChannelConfigAdapter,
|
||||
@@ -40,12 +48,13 @@ export {
|
||||
createAccountListHelpers,
|
||||
} from "openclaw/plugin-sdk/account-helpers";
|
||||
export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
||||
export { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/discord";
|
||||
export {
|
||||
emptyPluginConfigSchema,
|
||||
formatPairingApproveHint,
|
||||
getChatChannelMeta,
|
||||
} from "openclaw/plugin-sdk/core";
|
||||
export { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
|
||||
export { resolveAccountEntry } from "openclaw/plugin-sdk/routing";
|
||||
export type {
|
||||
ChannelMessageActionAdapter,
|
||||
ChannelMessageActionName,
|
||||
} from "openclaw/plugin-sdk/channel-contract";
|
||||
export {
|
||||
hasConfiguredSecretInput,
|
||||
normalizeResolvedSecretInputString,
|
||||
|
||||
@@ -10,7 +10,7 @@ import { maxBytesForKind } from "openclaw/plugin-sdk/media-runtime";
|
||||
import { extensionForMime } from "openclaw/plugin-sdk/media-runtime";
|
||||
import { unlinkIfExists } from "openclaw/plugin-sdk/media-runtime";
|
||||
import type { PollInput } from "openclaw/plugin-sdk/media-runtime";
|
||||
import { resolveChunkMode } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import { resolveChunkMode } from "openclaw/plugin-sdk/reply-chunking";
|
||||
import type { RetryConfig } from "openclaw/plugin-sdk/retry-runtime";
|
||||
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
||||
import { convertMarkdownTables } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
@@ -17,8 +17,8 @@ import {
|
||||
normalizePollInput,
|
||||
type PollInput,
|
||||
} from "openclaw/plugin-sdk/media-runtime";
|
||||
import type { ChunkMode } from "openclaw/plugin-sdk/reply-chunking";
|
||||
import { resolveTextChunksWithFallback } from "openclaw/plugin-sdk/reply-payload";
|
||||
import type { ChunkMode } from "openclaw/plugin-sdk/reply-runtime";
|
||||
import type { RetryRunner } from "openclaw/plugin-sdk/retry-runtime";
|
||||
import { loadWebMedia } from "openclaw/plugin-sdk/web-media";
|
||||
import { resolveDiscordAccount } from "./accounts.js";
|
||||
|
||||
@@ -44,11 +44,6 @@ export {
|
||||
sendWebhookMessageDiscord,
|
||||
sendVoiceMessageDiscord,
|
||||
} from "./send.outbound.js";
|
||||
export {
|
||||
editDiscordComponentMessage,
|
||||
registerBuiltDiscordComponentMessage,
|
||||
sendDiscordComponentMessage,
|
||||
} from "./send.components.js";
|
||||
export { sendTypingDiscord } from "./send.typing.js";
|
||||
export {
|
||||
fetchChannelPermissionsDiscord,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
||||
import type { Mock } from "vitest";
|
||||
import { expect, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../../../../src/plugin-sdk/discord.js";
|
||||
|
||||
export type NativeCommandSpecMock = {
|
||||
name: string;
|
||||
|
||||
@@ -4,19 +4,32 @@ export {
|
||||
projectCredentialSnapshotFields,
|
||||
resolveConfiguredFromRequiredCredentialStatuses,
|
||||
} from "openclaw/plugin-sdk/channel-status";
|
||||
export { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
||||
export { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/slack";
|
||||
export { looksLikeSlackTargetId, normalizeSlackMessagingTarget } from "./targets.js";
|
||||
export type { ChannelPlugin, OpenClawConfig, SlackAccountConfig } from "openclaw/plugin-sdk/slack";
|
||||
export {
|
||||
buildChannelConfigSchema,
|
||||
SlackConfigSchema,
|
||||
} from "openclaw/plugin-sdk/channel-config-schema";
|
||||
export type { ChannelMessageActionContext } from "openclaw/plugin-sdk/channel-contract";
|
||||
export { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
||||
export type {
|
||||
ChannelPlugin,
|
||||
OpenClawConfig,
|
||||
OpenClawPluginApi,
|
||||
PluginRuntime,
|
||||
} from "openclaw/plugin-sdk/core";
|
||||
export type { SlackAccountConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
export {
|
||||
emptyPluginConfigSchema,
|
||||
formatPairingApproveHint,
|
||||
getChatChannelMeta,
|
||||
} from "openclaw/plugin-sdk/core";
|
||||
export { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
|
||||
export { looksLikeSlackTargetId, normalizeSlackMessagingTarget } from "./targets.js";
|
||||
export {
|
||||
createActionGate,
|
||||
imageResultFromFile,
|
||||
jsonResult,
|
||||
readNumberParam,
|
||||
readReactionParams,
|
||||
readStringParam,
|
||||
SlackConfigSchema,
|
||||
withNormalizedTimestamp,
|
||||
} from "openclaw/plugin-sdk/slack-core";
|
||||
} from "openclaw/plugin-sdk/channel-actions";
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
export type {
|
||||
ChannelMessageActionAdapter,
|
||||
ChannelPlugin,
|
||||
OpenClawConfig,
|
||||
OpenClawPluginApi,
|
||||
PluginRuntime,
|
||||
TelegramAccountConfig,
|
||||
TelegramActionConfig,
|
||||
TelegramNetworkConfig,
|
||||
} from "openclaw/plugin-sdk/telegram-core";
|
||||
export type { OpenClawPluginApi, PluginRuntime } from "openclaw/plugin-sdk/core";
|
||||
export type { ChannelMessageActionAdapter } from "openclaw/plugin-sdk/channel-contract";
|
||||
export type { TelegramApiOverride } from "./src/send.js";
|
||||
export type {
|
||||
OpenClawPluginService,
|
||||
OpenClawPluginServiceContext,
|
||||
PluginLogger,
|
||||
} from "openclaw/plugin-sdk/core";
|
||||
import type { OpenClawConfig as RuntimeOpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
export type {
|
||||
AcpRuntime,
|
||||
AcpRuntimeCapabilities,
|
||||
@@ -29,19 +22,23 @@ export type {
|
||||
export { AcpRuntimeError } from "openclaw/plugin-sdk/acp-runtime";
|
||||
|
||||
export {
|
||||
buildTokenChannelStatusSummary,
|
||||
clearAccountEntryFields,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAccountId,
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
parseTelegramTopicConversation,
|
||||
projectCredentialSnapshotFields,
|
||||
resolveConfiguredFromCredentialStatuses,
|
||||
resolveTelegramPollVisibility,
|
||||
} from "openclaw/plugin-sdk/telegram-core";
|
||||
emptyPluginConfigSchema,
|
||||
formatPairingApproveHint,
|
||||
getChatChannelMeta,
|
||||
} from "openclaw/plugin-sdk/core";
|
||||
export {
|
||||
buildChannelConfigSchema,
|
||||
getChatChannelMeta,
|
||||
TelegramConfigSchema,
|
||||
} from "openclaw/plugin-sdk/channel-config-schema";
|
||||
export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
||||
export {
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
buildTokenChannelStatusSummary,
|
||||
projectCredentialSnapshotFields,
|
||||
resolveConfiguredFromCredentialStatuses,
|
||||
} from "openclaw/plugin-sdk/channel-status";
|
||||
export {
|
||||
jsonResult,
|
||||
readNumberParam,
|
||||
readReactionParams,
|
||||
@@ -49,8 +46,7 @@ export {
|
||||
readStringOrNumberParam,
|
||||
readStringParam,
|
||||
resolvePollMaxSelections,
|
||||
TelegramConfigSchema,
|
||||
} from "openclaw/plugin-sdk/telegram-core";
|
||||
} from "openclaw/plugin-sdk/channel-actions";
|
||||
export type { TelegramProbe } from "./src/probe.js";
|
||||
export { auditTelegramGroupMembership, collectTelegramUnmentionedGroupIds } from "./src/audit.js";
|
||||
export { resolveTelegramRuntimeGroupPolicy } from "./src/group-access.js";
|
||||
@@ -90,3 +86,12 @@ export {
|
||||
setTelegramThreadBindingMaxAgeBySessionKey,
|
||||
} from "./src/thread-bindings.js";
|
||||
export { resolveTelegramToken } from "./src/token.js";
|
||||
export type { ChannelPlugin } from "openclaw/plugin-sdk/core";
|
||||
export type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
export type TelegramAccountConfig = NonNullable<
|
||||
NonNullable<RuntimeOpenClawConfig["channels"]>["telegram"]
|
||||
>;
|
||||
export type TelegramActionConfig = NonNullable<TelegramAccountConfig["actions"]>;
|
||||
export type TelegramNetworkConfig = NonNullable<TelegramAccountConfig["network"]>;
|
||||
export { parseTelegramTopicConversation } from "./src/topic-conversation.js";
|
||||
export { resolveTelegramPollVisibility } from "./src/poll-visibility.js";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { resolveAccountWithDefaultFallback } from "openclaw/plugin-sdk/account-core";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { coerceSecretRef } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { TelegramAccountConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { tryReadSecretFileSync } from "openclaw/plugin-sdk/core";
|
||||
import { resolveDefaultSecretProviderAlias } from "openclaw/plugin-sdk/provider-auth";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/routing";
|
||||
@@ -8,7 +9,6 @@ import {
|
||||
hasConfiguredSecretInput,
|
||||
normalizeSecretInputString,
|
||||
} from "openclaw/plugin-sdk/secret-input";
|
||||
import type { TelegramAccountConfig } from "openclaw/plugin-sdk/telegram-core";
|
||||
import {
|
||||
mergeTelegramAccountConfig,
|
||||
resolveDefaultTelegramAccountId,
|
||||
|
||||
@@ -10,16 +10,16 @@ import {
|
||||
resolveAccountWithDefaultFallback,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/account-core";
|
||||
import type {
|
||||
TelegramAccountConfig,
|
||||
TelegramActionConfig,
|
||||
} from "openclaw/plugin-sdk/config-runtime";
|
||||
import {
|
||||
listBoundAccountIds,
|
||||
resolveDefaultAgentBoundAccountId,
|
||||
} from "openclaw/plugin-sdk/routing";
|
||||
import { formatSetExplicitDefaultInstruction } from "openclaw/plugin-sdk/routing";
|
||||
import { createSubsystemLogger, isTruthyEnvValue } from "openclaw/plugin-sdk/runtime-env";
|
||||
import type {
|
||||
TelegramAccountConfig,
|
||||
TelegramActionConfig,
|
||||
} from "openclaw/plugin-sdk/telegram-core";
|
||||
import { resolveTelegramToken } from "./token.js";
|
||||
|
||||
let log: ReturnType<typeof createSubsystemLogger> | null = null;
|
||||
|
||||
@@ -9,9 +9,8 @@ import {
|
||||
readStringOrNumberParam,
|
||||
readStringParam,
|
||||
resolvePollMaxSelections,
|
||||
type OpenClawConfig,
|
||||
type TelegramActionConfig,
|
||||
} from "openclaw/plugin-sdk/telegram-core";
|
||||
} from "openclaw/plugin-sdk/channel-actions";
|
||||
import type { OpenClawConfig, TelegramActionConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { createTelegramActionGate, resolveTelegramPollActionGateState } from "./accounts.js";
|
||||
import {
|
||||
fitsTelegramCallbackData,
|
||||
@@ -23,6 +22,7 @@ import {
|
||||
resolveTelegramInlineButtonsScope,
|
||||
resolveTelegramTargetChatType,
|
||||
} from "./inline-buttons.js";
|
||||
import { resolveTelegramPollVisibility } from "./poll-visibility.js";
|
||||
import { resolveTelegramReactionLevel } from "./reaction-level.js";
|
||||
import {
|
||||
createForumTopicTelegram,
|
||||
@@ -94,22 +94,6 @@ function readTelegramForumTopicIconColor(
|
||||
}
|
||||
return iconColor as TelegramForumTopicIconColor;
|
||||
}
|
||||
function resolveTelegramPollVisibility(params: {
|
||||
pollAnonymous?: boolean;
|
||||
pollPublic?: boolean;
|
||||
}): boolean | undefined {
|
||||
if (params.pollAnonymous && params.pollPublic) {
|
||||
throw new Error("pollAnonymous and pollPublic are mutually exclusive");
|
||||
}
|
||||
if (params.pollAnonymous) {
|
||||
return true;
|
||||
}
|
||||
if (params.pollPublic) {
|
||||
return false;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function readTelegramButtons(
|
||||
params: Record<string, unknown>,
|
||||
): TelegramInlineButtons | undefined {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { TelegramNetworkConfig } from "openclaw/plugin-sdk/telegram-core";
|
||||
import type { TelegramNetworkConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { resolveTelegramApiBase, resolveTelegramFetch } from "./fetch.js";
|
||||
import { makeProxyFetch } from "./proxy.js";
|
||||
|
||||
|
||||
@@ -29,11 +29,6 @@ import { getRuntimeConfigSnapshot } from "openclaw/plugin-sdk/runtime-config-sna
|
||||
import { danger, logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { getChildLogger } from "openclaw/plugin-sdk/runtime-env";
|
||||
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
||||
import {
|
||||
normalizeTelegramCommandName,
|
||||
resolveTelegramCustomCommands,
|
||||
TELEGRAM_COMMAND_NAME_PATTERN,
|
||||
} from "openclaw/plugin-sdk/telegram-command-config";
|
||||
import { resolveTelegramAccount } from "./accounts.js";
|
||||
import { withTelegramApiErrorLogging } from "./api-logging.js";
|
||||
import { isSenderAllowed, normalizeDmAllowFromWithStore } from "./bot-access.js";
|
||||
@@ -59,6 +54,11 @@ import {
|
||||
} from "./bot/helpers.js";
|
||||
import type { TelegramContext, TelegramGetChat } from "./bot/types.js";
|
||||
import type { TelegramInlineButtons } from "./button-types.js";
|
||||
import {
|
||||
normalizeTelegramCommandName,
|
||||
resolveTelegramCustomCommands,
|
||||
TELEGRAM_COMMAND_NAME_PATTERN,
|
||||
} from "./command-config.js";
|
||||
import {
|
||||
resolveTelegramConversationBaseSessionKey,
|
||||
resolveTelegramConversationRoute,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ChannelPlugin } from "openclaw/plugin-sdk/telegram-core";
|
||||
import type { ChannelPlugin } from "openclaw/plugin-sdk/core";
|
||||
import { type ResolvedTelegramAccount } from "./accounts.js";
|
||||
import type { TelegramProbe } from "./probe.js";
|
||||
import { telegramSetupAdapter } from "./setup-core.js";
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
||||
import {
|
||||
buildDmGroupAccountAllowlistAdapter,
|
||||
createNestedAllowlistOverrideResolver,
|
||||
} from "openclaw/plugin-sdk/allowlist-config-edit";
|
||||
import type { ChannelMessageActionAdapter } from "openclaw/plugin-sdk/channel-contract";
|
||||
import { createPairingPrefixStripper } from "openclaw/plugin-sdk/channel-pairing";
|
||||
import { createAllowlistProviderRouteAllowlistWarningCollector } from "openclaw/plugin-sdk/channel-policy";
|
||||
import { attachChannelToResult } from "openclaw/plugin-sdk/channel-send-result";
|
||||
import {
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
buildTokenChannelStatusSummary,
|
||||
projectCredentialSnapshotFields,
|
||||
resolveConfiguredFromCredentialStatuses,
|
||||
} from "openclaw/plugin-sdk/channel-status";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { createChatChannelPlugin } from "openclaw/plugin-sdk/core";
|
||||
import { clearAccountEntryFields } from "openclaw/plugin-sdk/core";
|
||||
import { createChannelDirectoryAdapter } from "openclaw/plugin-sdk/directory-runtime";
|
||||
import {
|
||||
resolveOutboundSendDep,
|
||||
@@ -22,17 +32,6 @@ import {
|
||||
createComputedAccountStatusAdapter,
|
||||
createDefaultChannelRuntimeState,
|
||||
} from "openclaw/plugin-sdk/status-helpers";
|
||||
import {
|
||||
buildTokenChannelStatusSummary,
|
||||
clearAccountEntryFields,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
parseTelegramTopicConversation,
|
||||
projectCredentialSnapshotFields,
|
||||
resolveConfiguredFromCredentialStatuses,
|
||||
type ChannelMessageActionAdapter,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/telegram-core";
|
||||
import {
|
||||
listTelegramAccountIds,
|
||||
resolveTelegramAccount,
|
||||
@@ -89,6 +88,7 @@ import {
|
||||
} from "./thread-bindings.js";
|
||||
import { buildTelegramThreadingToolContext } from "./threading-tool-context.js";
|
||||
import { resolveTelegramToken } from "./token.js";
|
||||
import { parseTelegramTopicConversation } from "./topic-conversation.js";
|
||||
|
||||
type TelegramSendFn = typeof sendMessageTelegram;
|
||||
|
||||
@@ -824,8 +824,7 @@ export const telegramPlugin = createChatChannelPlugin({
|
||||
threading: {
|
||||
topLevelReplyToMode: "telegram",
|
||||
buildToolContext: (params) => buildTelegramThreadingToolContext(params),
|
||||
resolveAutoThreadId: ({ to, toolContext }) =>
|
||||
resolveTelegramAutoThreadId({ to, toolContext }),
|
||||
resolveAutoThreadId: ({ to, toolContext }) => resolveTelegramAutoThreadId({ to, toolContext }),
|
||||
},
|
||||
outbound: {
|
||||
base: {
|
||||
|
||||
95
extensions/telegram/src/command-config.ts
Normal file
95
extensions/telegram/src/command-config.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
export const TELEGRAM_COMMAND_NAME_PATTERN = /^[a-z0-9_]{1,32}$/;
|
||||
|
||||
export type TelegramCustomCommandInput = {
|
||||
command?: string | null;
|
||||
description?: string | null;
|
||||
};
|
||||
|
||||
export type TelegramCustomCommandIssue = {
|
||||
index: number;
|
||||
field: "command" | "description";
|
||||
message: string;
|
||||
};
|
||||
|
||||
export function normalizeTelegramCommandName(value: string): string {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
return "";
|
||||
}
|
||||
const withoutSlash = trimmed.startsWith("/") ? trimmed.slice(1) : trimmed;
|
||||
return withoutSlash.trim().toLowerCase().replace(/-/g, "_");
|
||||
}
|
||||
|
||||
function normalizeTelegramCommandDescription(value: string): string {
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
export function resolveTelegramCustomCommands(params: {
|
||||
commands?: TelegramCustomCommandInput[] | null;
|
||||
reservedCommands?: Set<string>;
|
||||
checkReserved?: boolean;
|
||||
checkDuplicates?: boolean;
|
||||
}): {
|
||||
commands: Array<{ command: string; description: string }>;
|
||||
issues: TelegramCustomCommandIssue[];
|
||||
} {
|
||||
const entries = Array.isArray(params.commands) ? params.commands : [];
|
||||
const reserved = params.reservedCommands ?? new Set<string>();
|
||||
const checkReserved = params.checkReserved !== false;
|
||||
const checkDuplicates = params.checkDuplicates !== false;
|
||||
const seen = new Set<string>();
|
||||
const resolved: Array<{ command: string; description: string }> = [];
|
||||
const issues: TelegramCustomCommandIssue[] = [];
|
||||
|
||||
for (let index = 0; index < entries.length; index += 1) {
|
||||
const entry = entries[index];
|
||||
const normalized = normalizeTelegramCommandName(String(entry?.command ?? ""));
|
||||
if (!normalized) {
|
||||
issues.push({
|
||||
index,
|
||||
field: "command",
|
||||
message: "Telegram custom command is missing a command name.",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (!TELEGRAM_COMMAND_NAME_PATTERN.test(normalized)) {
|
||||
issues.push({
|
||||
index,
|
||||
field: "command",
|
||||
message: `Telegram custom command "/${normalized}" is invalid (use a-z, 0-9, underscore; max 32 chars).`,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (checkReserved && reserved.has(normalized)) {
|
||||
issues.push({
|
||||
index,
|
||||
field: "command",
|
||||
message: `Telegram custom command "/${normalized}" conflicts with a native command.`,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (checkDuplicates && seen.has(normalized)) {
|
||||
issues.push({
|
||||
index,
|
||||
field: "command",
|
||||
message: `Telegram custom command "/${normalized}" is duplicated.`,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
const description = normalizeTelegramCommandDescription(String(entry?.description ?? ""));
|
||||
if (!description) {
|
||||
issues.push({
|
||||
index,
|
||||
field: "description",
|
||||
message: `Telegram custom command "/${normalized}" is missing a description.`,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (checkDuplicates) {
|
||||
seen.add(normalized);
|
||||
}
|
||||
resolved.push({ command: normalized, description });
|
||||
}
|
||||
|
||||
return { commands: resolved, issues };
|
||||
}
|
||||
15
extensions/telegram/src/poll-visibility.ts
Normal file
15
extensions/telegram/src/poll-visibility.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export function resolveTelegramPollVisibility(params: {
|
||||
pollAnonymous?: boolean;
|
||||
pollPublic?: boolean;
|
||||
}): boolean | undefined {
|
||||
if (params.pollAnonymous && params.pollPublic) {
|
||||
throw new Error("pollAnonymous and pollPublic are mutually exclusive");
|
||||
}
|
||||
if (params.pollAnonymous) {
|
||||
return true;
|
||||
}
|
||||
if (params.pollPublic) {
|
||||
return false;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { BaseProbeResult } from "openclaw/plugin-sdk/channel-contract";
|
||||
import type { TelegramNetworkConfig } from "openclaw/plugin-sdk/telegram-core";
|
||||
import type { TelegramNetworkConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { fetchWithTimeout } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveTelegramApiBase, resolveTelegramFetch } from "./fetch.js";
|
||||
import { makeProxyFetch } from "./proxy.js";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { parseTelegramTopicConversation } from "openclaw/plugin-sdk/telegram-core";
|
||||
import { parseTelegramTopicConversation } from "./topic-conversation.js";
|
||||
|
||||
export function resolveTelegramSessionConversation(params: {
|
||||
kind: "group" | "channel";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { TelegramNetworkConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import {
|
||||
createEnvPatchedAccountSetupAdapter,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import type { ChannelSetupAdapter, ChannelSetupDmPolicy } from "openclaw/plugin-sdk/setup";
|
||||
import { formatCliCommand, formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
|
||||
import type { TelegramNetworkConfig } from "openclaw/plugin-sdk/telegram-core";
|
||||
import { resolveDefaultTelegramAccountId, resolveTelegramAccount } from "./accounts.js";
|
||||
import { lookupTelegramChatId } from "./api-fetch.js";
|
||||
|
||||
|
||||
@@ -4,14 +4,14 @@ import {
|
||||
adaptScopedAccountAccessor,
|
||||
createScopedChannelConfigAdapter,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { createChannelPluginBase } from "openclaw/plugin-sdk/core";
|
||||
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/routing";
|
||||
import {
|
||||
getChatChannelMeta,
|
||||
normalizeAccountId,
|
||||
type ChannelPlugin,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/telegram-core";
|
||||
} from "openclaw/plugin-sdk/core";
|
||||
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/routing";
|
||||
import { inspectTelegramAccount } from "./account-inspect.js";
|
||||
import {
|
||||
listTelegramAccountIds,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { resolveNormalizedAccountEntry } from "openclaw/plugin-sdk/account-core";
|
||||
import type { BaseTokenResolution } from "openclaw/plugin-sdk/channel-contract";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { TelegramAccountConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { tryReadSecretFileSync } from "openclaw/plugin-sdk/core";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/routing";
|
||||
import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
||||
import type { TelegramAccountConfig } from "openclaw/plugin-sdk/telegram-core";
|
||||
|
||||
export type TelegramTokenSource = "env" | "tokenFile" | "config" | "none";
|
||||
|
||||
|
||||
58
extensions/telegram/src/topic-conversation.ts
Normal file
58
extensions/telegram/src/topic-conversation.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
export type ParsedTelegramTopicConversation = {
|
||||
chatId: string;
|
||||
topicId: string;
|
||||
canonicalConversationId: string;
|
||||
};
|
||||
|
||||
function buildTelegramTopicConversationId(params: {
|
||||
chatId: string;
|
||||
topicId: string;
|
||||
}): string | null {
|
||||
const chatId = params.chatId.trim();
|
||||
const topicId = params.topicId.trim();
|
||||
if (!/^-?\d+$/.test(chatId) || !/^\d+$/.test(topicId)) {
|
||||
return null;
|
||||
}
|
||||
return `${chatId}:topic:${topicId}`;
|
||||
}
|
||||
|
||||
export function parseTelegramTopicConversation(params: {
|
||||
conversationId: string;
|
||||
parentConversationId?: string;
|
||||
}): ParsedTelegramTopicConversation | null {
|
||||
const conversation = params.conversationId.trim();
|
||||
const directMatch = conversation.match(/^(-?\d+):topic:(\d+)$/i);
|
||||
if (directMatch?.[1] && directMatch[2]) {
|
||||
const canonicalConversationId = buildTelegramTopicConversationId({
|
||||
chatId: directMatch[1],
|
||||
topicId: directMatch[2],
|
||||
});
|
||||
if (!canonicalConversationId) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
chatId: directMatch[1],
|
||||
topicId: directMatch[2],
|
||||
canonicalConversationId,
|
||||
};
|
||||
}
|
||||
if (!/^\d+$/.test(conversation)) {
|
||||
return null;
|
||||
}
|
||||
const parent = params.parentConversationId?.trim();
|
||||
if (!parent || !/^-?\d+$/.test(parent)) {
|
||||
return null;
|
||||
}
|
||||
const canonicalConversationId = buildTelegramTopicConversationId({
|
||||
chatId: parent,
|
||||
topicId: conversation,
|
||||
});
|
||||
if (!canonicalConversationId) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
chatId: parent,
|
||||
topicId: conversation,
|
||||
canonicalConversationId,
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { buildDmGroupAccountAllowlistAdapter } from "openclaw/plugin-sdk/allowlist-config-edit";
|
||||
import { splitChannelApprovalCapability } from "openclaw/plugin-sdk/approval-runtime";
|
||||
import { getChatChannelMeta, type ChannelPlugin } from "openclaw/plugin-sdk/telegram-core";
|
||||
import { getChatChannelMeta, type ChannelPlugin } from "openclaw/plugin-sdk/core";
|
||||
import type { ResolvedTelegramAccount } from "./src/accounts.js";
|
||||
import { resolveTelegramAccount } from "./src/accounts.js";
|
||||
import { telegramApprovalCapability } from "./src/approval-native.js";
|
||||
|
||||
@@ -1 +1,70 @@
|
||||
export { hasAnyWhatsAppAuth } from "./src/accounts.js";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
||||
import { resolveUserPath } from "openclaw/plugin-sdk/account-resolution";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { resolveOAuthDir } from "openclaw/plugin-sdk/state-paths";
|
||||
import { hasWebCredsSync } from "./src/creds-files.js";
|
||||
|
||||
function addAccountAuthDirs(
|
||||
authDirs: Set<string>,
|
||||
accountId: string,
|
||||
authDir: string | undefined,
|
||||
accountsRoot: string,
|
||||
env: NodeJS.ProcessEnv,
|
||||
): void {
|
||||
authDirs.add(path.join(accountsRoot, normalizeAccountId(accountId)));
|
||||
const configuredAuthDir = authDir?.trim();
|
||||
if (configuredAuthDir) {
|
||||
authDirs.add(resolveUserPath(configuredAuthDir, env));
|
||||
}
|
||||
}
|
||||
|
||||
function listWhatsAppAuthDirs(
|
||||
cfg: OpenClawConfig,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): readonly string[] {
|
||||
const oauthDir = resolveOAuthDir(env);
|
||||
const accountsRoot = path.join(oauthDir, "whatsapp");
|
||||
const channel = cfg.channels?.whatsapp;
|
||||
const authDirs = new Set<string>([oauthDir, path.join(accountsRoot, DEFAULT_ACCOUNT_ID)]);
|
||||
|
||||
addAccountAuthDirs(authDirs, DEFAULT_ACCOUNT_ID, undefined, accountsRoot, env);
|
||||
|
||||
if (channel?.defaultAccount?.trim()) {
|
||||
addAccountAuthDirs(
|
||||
authDirs,
|
||||
channel.defaultAccount,
|
||||
channel.accounts?.[channel.defaultAccount]?.authDir,
|
||||
accountsRoot,
|
||||
env,
|
||||
);
|
||||
}
|
||||
|
||||
const accounts = channel?.accounts;
|
||||
if (accounts) {
|
||||
for (const [accountId, account] of Object.entries(accounts)) {
|
||||
addAccountAuthDirs(authDirs, accountId, account?.authDir, accountsRoot, env);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const entries = fs.readdirSync(accountsRoot, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory()) {
|
||||
authDirs.add(path.join(accountsRoot, entry.name));
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Missing directories mean no auth state.
|
||||
}
|
||||
|
||||
return [...authDirs];
|
||||
}
|
||||
|
||||
export function hasAnyWhatsAppAuth(
|
||||
cfg: OpenClawConfig,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): boolean {
|
||||
return listWhatsAppAuthDirs(cfg, env).some((authDir) => hasWebCredsSync(authDir));
|
||||
}
|
||||
|
||||
@@ -1,35 +1,39 @@
|
||||
export { getChatChannelMeta, type ChannelPlugin } from "openclaw/plugin-sdk/core";
|
||||
export {
|
||||
buildChannelConfigSchema,
|
||||
createActionGate,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
WhatsAppConfigSchema,
|
||||
} from "openclaw/plugin-sdk/channel-config-schema";
|
||||
export { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
||||
export {
|
||||
formatWhatsAppConfigAllowFromEntries,
|
||||
getChatChannelMeta,
|
||||
resolveWhatsAppConfigAllowFrom,
|
||||
resolveWhatsAppConfigDefaultTo,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
export {
|
||||
createActionGate,
|
||||
jsonResult,
|
||||
normalizeE164,
|
||||
readReactionParams,
|
||||
readStringParam,
|
||||
resolveWhatsAppGroupIntroHint,
|
||||
resolveWhatsAppGroupRequireMention,
|
||||
resolveWhatsAppGroupToolPolicy,
|
||||
ToolAuthorizationError,
|
||||
WhatsAppConfigSchema,
|
||||
type ChannelPlugin,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/whatsapp-core";
|
||||
} from "openclaw/plugin-sdk/channel-actions";
|
||||
export { normalizeE164 } from "openclaw/plugin-sdk/account-resolution";
|
||||
export type { DmPolicy, GroupPolicy } from "openclaw/plugin-sdk/config-runtime";
|
||||
import type { OpenClawConfig as RuntimeOpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
|
||||
export {
|
||||
type ChannelMessageActionName,
|
||||
createWhatsAppOutboundBase,
|
||||
looksLikeWhatsAppTargetId,
|
||||
normalizeWhatsAppAllowFromEntries,
|
||||
normalizeWhatsAppMessagingTarget,
|
||||
resolveWhatsAppGroupIntroHint,
|
||||
resolveWhatsAppHeartbeatRecipients,
|
||||
resolveWhatsAppMentionStripRegexes,
|
||||
type ChannelMessageActionName,
|
||||
type DmPolicy,
|
||||
type GroupPolicy,
|
||||
type WhatsAppAccountConfig,
|
||||
} from "openclaw/plugin-sdk/whatsapp-shared";
|
||||
} from "openclaw/plugin-sdk/channel-runtime";
|
||||
import { loadWebMedia } from "openclaw/plugin-sdk/web-media";
|
||||
export {
|
||||
resolveWhatsAppGroupRequireMention,
|
||||
resolveWhatsAppGroupToolPolicy,
|
||||
} from "./group-policy.js";
|
||||
export {
|
||||
isWhatsAppGroupJid,
|
||||
isWhatsAppUserTarget,
|
||||
@@ -37,6 +41,19 @@ export {
|
||||
} from "./normalize-target.js";
|
||||
export { resolveWhatsAppOutboundTarget } from "./resolve-outbound-target.js";
|
||||
export { resolveWhatsAppReactionLevel } from "./reaction-level.js";
|
||||
|
||||
export type OpenClawConfig = RuntimeOpenClawConfig;
|
||||
export type WhatsAppAccountConfig = NonNullable<
|
||||
NonNullable<NonNullable<RuntimeOpenClawConfig["channels"]>["whatsapp"]>["accounts"]
|
||||
>[string];
|
||||
|
||||
export class ToolAuthorizationError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "ToolAuthorizationError";
|
||||
}
|
||||
}
|
||||
|
||||
type MonitorWebChannel = typeof import("./channel.runtime.js").monitorWebChannel;
|
||||
|
||||
let channelRuntimePromise: Promise<typeof import("./channel.runtime.js")> | null = null;
|
||||
|
||||
Reference in New Issue
Block a user