Skip to content

Commit bcd61f0

Browse files
committed
refactor: dedupe helpers and source seams
1 parent ebe18c0 commit bcd61f0

83 files changed

Lines changed: 2788 additions & 4488 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
2+
import { formatNormalizedAllowFromEntries } from "openclaw/plugin-sdk/allow-from";
3+
import {
4+
adaptScopedAccountAccessor,
5+
createScopedChannelConfigAdapter,
6+
} from "openclaw/plugin-sdk/channel-config-helpers";
7+
import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
8+
import {
9+
listBlueBubblesAccountIds,
10+
type ResolvedBlueBubblesAccount,
11+
resolveBlueBubblesAccount,
12+
resolveDefaultBlueBubblesAccountId,
13+
} from "./accounts.js";
14+
import { BlueBubblesConfigSchema } from "./config-schema.js";
15+
import type { ChannelPlugin } from "./runtime-api.js";
16+
import { normalizeBlueBubblesHandle } from "./targets.js";
17+
18+
export const bluebubblesMeta = {
19+
id: "bluebubbles",
20+
label: "BlueBubbles",
21+
selectionLabel: "BlueBubbles (macOS app)",
22+
detailLabel: "BlueBubbles",
23+
docsPath: "/channels/bluebubbles",
24+
docsLabel: "bluebubbles",
25+
blurb: "iMessage via the BlueBubbles mac app + REST API.",
26+
systemImage: "bubble.left.and.text.bubble.right",
27+
aliases: ["bb"],
28+
order: 75,
29+
preferOver: ["imessage"],
30+
};
31+
32+
export const bluebubblesCapabilities: ChannelPlugin<ResolvedBlueBubblesAccount>["capabilities"] = {
33+
chatTypes: ["direct", "group"],
34+
media: true,
35+
reactions: true,
36+
edit: true,
37+
unsend: true,
38+
reply: true,
39+
effects: true,
40+
groupManagement: true,
41+
};
42+
43+
export const bluebubblesReload = { configPrefixes: ["channels.bluebubbles"] };
44+
export const bluebubblesConfigSchema = buildChannelConfigSchema(BlueBubblesConfigSchema);
45+
46+
export const bluebubblesConfigAdapter =
47+
createScopedChannelConfigAdapter<ResolvedBlueBubblesAccount>({
48+
sectionKey: "bluebubbles",
49+
listAccountIds: listBlueBubblesAccountIds,
50+
resolveAccount: adaptScopedAccountAccessor(resolveBlueBubblesAccount),
51+
defaultAccountId: resolveDefaultBlueBubblesAccountId,
52+
clearBaseFields: ["serverUrl", "password", "name", "webhookPath"],
53+
resolveAllowFrom: (account: ResolvedBlueBubblesAccount) => account.config.allowFrom,
54+
formatAllowFrom: (allowFrom) =>
55+
formatNormalizedAllowFromEntries({
56+
allowFrom,
57+
normalizeEntry: (entry) => normalizeBlueBubblesHandle(entry.replace(/^bluebubbles:/i, "")),
58+
}),
59+
});
60+
61+
export function describeBlueBubblesAccount(account: ResolvedBlueBubblesAccount) {
62+
return describeAccountSnapshot({
63+
account,
64+
configured: account.configured,
65+
extra: {
66+
baseUrl: account.baseUrl,
67+
},
68+
});
69+
}
Lines changed: 15 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,31 @@
1-
import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
2-
import { formatNormalizedAllowFromEntries } from "openclaw/plugin-sdk/allow-from";
3-
import {
4-
adaptScopedAccountAccessor,
5-
createScopedChannelConfigAdapter,
6-
} from "openclaw/plugin-sdk/channel-config-helpers";
7-
import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
81
import type { ChannelPlugin } from "openclaw/plugin-sdk/core";
2+
import { type ResolvedBlueBubblesAccount } from "./accounts.js";
93
import {
10-
listBlueBubblesAccountIds,
11-
type ResolvedBlueBubblesAccount,
12-
resolveBlueBubblesAccount,
13-
resolveDefaultBlueBubblesAccountId,
14-
} from "./accounts.js";
15-
import { BlueBubblesConfigSchema } from "./config-schema.js";
4+
bluebubblesCapabilities,
5+
bluebubblesConfigAdapter,
6+
bluebubblesConfigSchema,
7+
bluebubblesMeta,
8+
bluebubblesReload,
9+
describeBlueBubblesAccount,
10+
} from "./channel-shared.js";
1611
import { blueBubblesSetupAdapter } from "./setup-core.js";
1712
import { blueBubblesSetupWizard } from "./setup-surface.js";
18-
import { normalizeBlueBubblesHandle } from "./targets.js";
19-
20-
const meta = {
21-
id: "bluebubbles",
22-
label: "BlueBubbles",
23-
selectionLabel: "BlueBubbles (macOS app)",
24-
detailLabel: "BlueBubbles",
25-
docsPath: "/channels/bluebubbles",
26-
docsLabel: "bluebubbles",
27-
blurb: "iMessage via the BlueBubbles mac app + REST API.",
28-
systemImage: "bubble.left.and.text.bubble.right",
29-
aliases: ["bb"],
30-
order: 75,
31-
preferOver: ["imessage"],
32-
} as const;
33-
34-
const bluebubblesConfigAdapter = createScopedChannelConfigAdapter<ResolvedBlueBubblesAccount>({
35-
sectionKey: "bluebubbles",
36-
listAccountIds: listBlueBubblesAccountIds,
37-
resolveAccount: adaptScopedAccountAccessor(resolveBlueBubblesAccount),
38-
defaultAccountId: resolveDefaultBlueBubblesAccountId,
39-
clearBaseFields: ["serverUrl", "password", "name", "webhookPath"],
40-
resolveAllowFrom: (account: ResolvedBlueBubblesAccount) => account.config.allowFrom,
41-
formatAllowFrom: (allowFrom) =>
42-
formatNormalizedAllowFromEntries({
43-
allowFrom,
44-
normalizeEntry: (entry) => normalizeBlueBubblesHandle(entry.replace(/^bluebubbles:/i, "")),
45-
}),
46-
});
4713

4814
export const bluebubblesSetupPlugin: ChannelPlugin<ResolvedBlueBubblesAccount> = {
4915
id: "bluebubbles",
5016
meta: {
51-
...meta,
52-
aliases: [...meta.aliases],
53-
preferOver: [...meta.preferOver],
54-
},
55-
capabilities: {
56-
chatTypes: ["direct", "group"],
57-
media: true,
58-
reactions: true,
59-
edit: true,
60-
unsend: true,
61-
reply: true,
62-
effects: true,
63-
groupManagement: true,
17+
...bluebubblesMeta,
18+
aliases: [...bluebubblesMeta.aliases],
19+
preferOver: [...bluebubblesMeta.preferOver],
6420
},
65-
reload: { configPrefixes: ["channels.bluebubbles"] },
66-
configSchema: buildChannelConfigSchema(BlueBubblesConfigSchema),
21+
capabilities: bluebubblesCapabilities,
22+
reload: bluebubblesReload,
23+
configSchema: bluebubblesConfigSchema,
6724
setupWizard: blueBubblesSetupWizard,
6825
config: {
6926
...bluebubblesConfigAdapter,
7027
isConfigured: (account) => account.configured,
71-
describeAccount: (account) =>
72-
describeAccountSnapshot({
73-
account,
74-
configured: account.configured,
75-
extra: {
76-
baseUrl: account.baseUrl,
77-
},
78-
}),
28+
describeAccount: (account) => describeBlueBubblesAccount(account),
7929
},
8030
setup: blueBubblesSetupAdapter,
8131
};

extensions/bluebubbles/src/channel.ts

Lines changed: 13 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
2-
import { formatNormalizedAllowFromEntries } from "openclaw/plugin-sdk/allow-from";
3-
import {
4-
adaptScopedAccountAccessor,
5-
createScopedChannelConfigAdapter,
6-
createScopedDmSecurityResolver,
7-
} from "openclaw/plugin-sdk/channel-config-helpers";
1+
import { createScopedDmSecurityResolver } from "openclaw/plugin-sdk/channel-config-helpers";
82
import { createAccountStatusSink } from "openclaw/plugin-sdk/channel-lifecycle";
93
import { createPairingPrefixStripper } from "openclaw/plugin-sdk/channel-pairing";
104
import {
@@ -25,15 +19,21 @@ import {
2519
resolveDefaultBlueBubblesAccountId,
2620
} from "./accounts.js";
2721
import { bluebubblesMessageActions } from "./actions.js";
22+
import {
23+
bluebubblesCapabilities,
24+
bluebubblesConfigAdapter,
25+
bluebubblesConfigSchema,
26+
bluebubblesMeta as meta,
27+
bluebubblesReload,
28+
describeBlueBubblesAccount,
29+
} from "./channel-shared.js";
2830
import type { BlueBubblesProbe } from "./channel.runtime.js";
29-
import { BlueBubblesConfigSchema } from "./config-schema.js";
3031
import {
3132
resolveBlueBubblesGroupRequireMention,
3233
resolveBlueBubblesGroupToolPolicy,
3334
} from "./group-policy.js";
3435
import type { ChannelAccountSnapshot, ChannelPlugin } from "./runtime-api.js";
3536
import {
36-
buildChannelConfigSchema,
3737
buildProbeChannelStatusSummary,
3838
collectBlueBubblesStatusIssues,
3939
DEFAULT_ACCOUNT_ID,
@@ -57,20 +57,6 @@ const loadBlueBubblesChannelRuntime = createLazyRuntimeNamedExport(
5757
"blueBubblesChannelRuntime",
5858
);
5959

60-
const bluebubblesConfigAdapter = createScopedChannelConfigAdapter<ResolvedBlueBubblesAccount>({
61-
sectionKey: "bluebubbles",
62-
listAccountIds: listBlueBubblesAccountIds,
63-
resolveAccount: adaptScopedAccountAccessor(resolveBlueBubblesAccount),
64-
defaultAccountId: resolveDefaultBlueBubblesAccountId,
65-
clearBaseFields: ["serverUrl", "password", "name", "webhookPath"],
66-
resolveAllowFrom: (account: ResolvedBlueBubblesAccount) => account.config.allowFrom,
67-
formatAllowFrom: (allowFrom) =>
68-
formatNormalizedAllowFromEntries({
69-
allowFrom,
70-
normalizeEntry: (entry) => normalizeBlueBubblesHandle(entry.replace(/^bluebubbles:/i, "")),
71-
}),
72-
});
73-
7460
const resolveBlueBubblesDmPolicy = createScopedDmSecurityResolver<ResolvedBlueBubblesAccount>({
7561
channelKey: "bluebubbles",
7662
resolvePolicy: (account) => account.config.dmPolicy,
@@ -90,53 +76,23 @@ const collectBlueBubblesSecurityWarnings =
9076
mentionGated: false,
9177
});
9278

93-
const meta = {
94-
id: "bluebubbles",
95-
label: "BlueBubbles",
96-
selectionLabel: "BlueBubbles (macOS app)",
97-
detailLabel: "BlueBubbles",
98-
docsPath: "/channels/bluebubbles",
99-
docsLabel: "bluebubbles",
100-
blurb: "iMessage via the BlueBubbles mac app + REST API.",
101-
systemImage: "bubble.left.and.text.bubble.right",
102-
aliases: ["bb"],
103-
order: 75,
104-
preferOver: ["imessage"],
105-
};
106-
10779
export const bluebubblesPlugin: ChannelPlugin<ResolvedBlueBubblesAccount, BlueBubblesProbe> =
10880
createChatChannelPlugin<ResolvedBlueBubblesAccount, BlueBubblesProbe>({
10981
base: {
11082
id: "bluebubbles",
11183
meta,
112-
capabilities: {
113-
chatTypes: ["direct", "group"],
114-
media: true,
115-
reactions: true,
116-
edit: true,
117-
unsend: true,
118-
reply: true,
119-
effects: true,
120-
groupManagement: true,
121-
},
84+
capabilities: bluebubblesCapabilities,
12285
groups: {
12386
resolveRequireMention: resolveBlueBubblesGroupRequireMention,
12487
resolveToolPolicy: resolveBlueBubblesGroupToolPolicy,
12588
},
126-
reload: { configPrefixes: ["channels.bluebubbles"] },
127-
configSchema: buildChannelConfigSchema(BlueBubblesConfigSchema),
89+
reload: bluebubblesReload,
90+
configSchema: bluebubblesConfigSchema,
12891
setupWizard: blueBubblesSetupWizard,
12992
config: {
13093
...bluebubblesConfigAdapter,
13194
isConfigured: (account) => account.configured,
132-
describeAccount: (account): ChannelAccountSnapshot =>
133-
describeAccountSnapshot({
134-
account,
135-
configured: account.configured,
136-
extra: {
137-
baseUrl: account.baseUrl,
138-
},
139-
}),
95+
describeAccount: (account): ChannelAccountSnapshot => describeBlueBubblesAccount(account),
14096
},
14197
actions: bluebubblesMessageActions,
14298
messaging: {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
type LineCredentialAccount = {
2+
channelAccessToken?: string;
3+
channelSecret?: string;
4+
};
5+
6+
export function hasLineCredentials(account: LineCredentialAccount): boolean {
7+
return Boolean(account.channelAccessToken?.trim() && account.channelSecret?.trim());
8+
}
9+
10+
export function parseLineAllowFromId(raw: string): string | null {
11+
const trimmed = raw.trim().replace(/^line:(?:user:)?/i, "");
12+
if (!/^U[a-f0-9]{32}$/i.test(trimmed)) {
13+
return null;
14+
}
15+
return trimmed;
16+
}

extensions/line/src/channel-shared.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
type OpenClawConfig,
55
type ResolvedLineAccount,
66
} from "../runtime-api.js";
7+
import { hasLineCredentials, parseLineAllowFromId } from "./account-helpers.js";
78
import { lineConfigAdapter } from "./config-adapter.js";
89
import { LineChannelConfigSchema } from "./config-schema.js";
910

@@ -35,13 +36,12 @@ export const lineChannelPluginCommon = {
3536
configSchema: LineChannelConfigSchema,
3637
config: {
3738
...lineConfigAdapter,
38-
isConfigured: (account: ResolvedLineAccount) =>
39-
Boolean(account.channelAccessToken?.trim() && account.channelSecret?.trim()),
39+
isConfigured: (account: ResolvedLineAccount) => hasLineCredentials(account),
4040
describeAccount: (account: ResolvedLineAccount) => ({
4141
accountId: account.accountId,
4242
name: account.name,
4343
enabled: account.enabled,
44-
configured: Boolean(account.channelAccessToken?.trim() && account.channelSecret?.trim()),
44+
configured: hasLineCredentials(account),
4545
tokenSource: account.tokenSource ?? undefined,
4646
}),
4747
},
@@ -51,16 +51,8 @@ export const lineChannelPluginCommon = {
5151
>;
5252

5353
export function isLineConfigured(cfg: OpenClawConfig, accountId: string): boolean {
54-
const resolved = resolveLineAccount({ cfg, accountId });
55-
return Boolean(resolved.channelAccessToken.trim() && resolved.channelSecret.trim());
56-
}
57-
58-
export function parseLineAllowFromId(raw: string): string | null {
59-
const trimmed = raw.trim().replace(/^line:(?:user:)?/i, "");
60-
if (!/^U[a-f0-9]{32}$/i.test(trimmed)) {
61-
return null;
62-
}
63-
return trimmed;
54+
return hasLineCredentials(resolveLineAccount({ cfg, accountId }));
6455
}
6556

6657
export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../runtime-api.js";
58+
export { parseLineAllowFromId };

extensions/line/src/setup-core.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { ChannelSetupAdapter, OpenClawConfig } from "openclaw/plugin-sdk/setup";
2+
import { hasLineCredentials, parseLineAllowFromId } from "./account-helpers.js";
23
import {
34
DEFAULT_ACCOUNT_ID,
45
listLineAccountIds,
@@ -66,17 +67,10 @@ export function patchLineAccountConfig(params: {
6667
}
6768

6869
export function isLineConfigured(cfg: OpenClawConfig, accountId: string): boolean {
69-
const resolved = resolveLineAccount({ cfg, accountId });
70-
return Boolean(resolved.channelAccessToken.trim() && resolved.channelSecret.trim());
70+
return hasLineCredentials(resolveLineAccount({ cfg, accountId }));
7171
}
7272

73-
export function parseLineAllowFromId(raw: string): string | null {
74-
const trimmed = raw.trim().replace(/^line:(?:user:)?/i, "");
75-
if (!/^U[a-f0-9]{32}$/i.test(trimmed)) {
76-
return null;
77-
}
78-
return trimmed;
79-
}
73+
export { parseLineAllowFromId };
8074

8175
export const lineSetupAdapter: ChannelSetupAdapter = {
8276
resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),

0 commit comments

Comments
 (0)