@ebowwa/channel-types 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +13 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +15 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/plugins/index.d.ts +4 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +5 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/registry.d.ts +100 -0
- package/dist/plugins/registry.d.ts.map +1 -0
- package/dist/plugins/registry.js +150 -0
- package/dist/plugins/registry.js.map +1 -0
- package/dist/plugins/types.adapters.d.ts +126 -0
- package/dist/plugins/types.adapters.d.ts.map +1 -0
- package/dist/plugins/types.adapters.js +3 -0
- package/dist/plugins/types.adapters.js.map +1 -0
- package/dist/plugins/types.core.d.ts +82 -0
- package/dist/plugins/types.core.d.ts.map +1 -0
- package/dist/plugins/types.core.js +51 -0
- package/dist/plugins/types.core.js.map +1 -0
- package/dist/plugins/types.plugin.d.ts +141 -0
- package/dist/plugins/types.plugin.d.ts.map +1 -0
- package/dist/plugins/types.plugin.js +3 -0
- package/dist/plugins/types.plugin.js.map +1 -0
- package/package.json +5 -1
- package/src/config.js +270 -0
- package/src/config.ts +32 -1
- package/src/example.js +431 -0
- package/src/index.js +76 -0
- package/src/index.ts +5 -0
- package/src/plugins/index.ts +5 -0
- package/src/plugins/registry.ts +232 -0
- package/src/plugins/types.adapters.ts +183 -0
- package/src/plugins/types.core.ts +161 -0
- package/src/plugins/types.plugin.ts +227 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// src/plugins/registry.ts
|
|
2
|
+
|
|
3
|
+
import type { ChannelId } from "../index.js";
|
|
4
|
+
import type { ChannelPlugin } from "./types.plugin.js";
|
|
5
|
+
|
|
6
|
+
// ============================================================
|
|
7
|
+
// REGISTRATION TYPES
|
|
8
|
+
// ============================================================
|
|
9
|
+
|
|
10
|
+
export interface PluginChannelRegistration {
|
|
11
|
+
/** Plugin ID (usually matches channel ID) */
|
|
12
|
+
pluginId: string;
|
|
13
|
+
|
|
14
|
+
/** The channel plugin */
|
|
15
|
+
plugin: ChannelPlugin;
|
|
16
|
+
|
|
17
|
+
/** Optional dock metadata (lightweight channel info) */
|
|
18
|
+
dock?: ChannelDock;
|
|
19
|
+
|
|
20
|
+
/** Source (e.g., "core", "extension", "custom") */
|
|
21
|
+
source: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ChannelDock {
|
|
25
|
+
/** Channel ID */
|
|
26
|
+
id: ChannelId;
|
|
27
|
+
|
|
28
|
+
/** Human-readable label */
|
|
29
|
+
label: string;
|
|
30
|
+
|
|
31
|
+
/** Capabilities summary */
|
|
32
|
+
capabilities: {
|
|
33
|
+
chatTypes: string[];
|
|
34
|
+
hasMedia: boolean;
|
|
35
|
+
hasThreads: boolean;
|
|
36
|
+
hasReactions: boolean;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Outbound limits */
|
|
40
|
+
outboundLimits: {
|
|
41
|
+
textChunkLimit: number;
|
|
42
|
+
maxFileSize?: number;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ============================================================
|
|
47
|
+
// PLUGIN REGISTRY
|
|
48
|
+
// ============================================================
|
|
49
|
+
|
|
50
|
+
class PluginRegistryImpl {
|
|
51
|
+
private channels: Map<string, PluginChannelRegistration> = new Map();
|
|
52
|
+
private channelOrder: string[] = [];
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Register a channel plugin
|
|
56
|
+
*/
|
|
57
|
+
registerChannel(registration: PluginChannelRegistration): void {
|
|
58
|
+
const id = registration.plugin.id;
|
|
59
|
+
|
|
60
|
+
if (typeof id !== "string") {
|
|
61
|
+
throw new Error("Channel plugin id must be a string");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const normalizedId = id.trim().toLowerCase();
|
|
65
|
+
|
|
66
|
+
if (this.channels.has(normalizedId)) {
|
|
67
|
+
console.warn(`[PluginRegistry] Channel "${normalizedId}" already registered, replacing`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.channels.set(normalizedId, registration);
|
|
71
|
+
|
|
72
|
+
// Add to order if not present
|
|
73
|
+
if (!this.channelOrder.includes(normalizedId)) {
|
|
74
|
+
this.channelOrder.push(normalizedId);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log(`[PluginRegistry] Registered channel: ${normalizedId} (source: ${registration.source})`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Unregister a channel plugin
|
|
82
|
+
*/
|
|
83
|
+
unregisterChannel(channelId: string): boolean {
|
|
84
|
+
const normalizedId = channelId.trim().toLowerCase();
|
|
85
|
+
const existed = this.channels.delete(normalizedId);
|
|
86
|
+
|
|
87
|
+
if (existed) {
|
|
88
|
+
this.channelOrder = this.channelOrder.filter(id => id !== normalizedId);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return existed;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get a channel plugin by ID
|
|
96
|
+
*/
|
|
97
|
+
getChannel(channelId: string): ChannelPlugin | undefined {
|
|
98
|
+
const normalizedId = this.normalizeChannelId(channelId);
|
|
99
|
+
const registration = this.channels.get(normalizedId);
|
|
100
|
+
return registration?.plugin;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get channel registration (includes dock and source)
|
|
105
|
+
*/
|
|
106
|
+
getChannelRegistration(channelId: string): PluginChannelRegistration | undefined {
|
|
107
|
+
const normalizedId = this.normalizeChannelId(channelId);
|
|
108
|
+
return this.channels.get(normalizedId);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get all registered channels
|
|
113
|
+
*/
|
|
114
|
+
getChannels(): ChannelPlugin[] {
|
|
115
|
+
return this.channelOrder
|
|
116
|
+
.map(id => this.channels.get(id)?.plugin)
|
|
117
|
+
.filter((p): p is ChannelPlugin => p !== undefined);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get all channel registrations
|
|
122
|
+
*/
|
|
123
|
+
getChannelRegistrations(): PluginChannelRegistration[] {
|
|
124
|
+
return this.channelOrder
|
|
125
|
+
.map(id => this.channels.get(id))
|
|
126
|
+
.filter((r): r is PluginChannelRegistration => r !== undefined);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get channels ordered by registration
|
|
131
|
+
*/
|
|
132
|
+
getChannelOrder(): string[] {
|
|
133
|
+
return [...this.channelOrder];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Set channel order
|
|
138
|
+
*/
|
|
139
|
+
setChannelOrder(order: string[]): void {
|
|
140
|
+
this.channelOrder = order.map(id => id.toLowerCase().trim());
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get channels that support a specific capability
|
|
145
|
+
*/
|
|
146
|
+
getChannelsByCapability(capability: keyof ChannelPlugin["capabilities"]["supports"]): ChannelPlugin[] {
|
|
147
|
+
return this.getChannels().filter(plugin =>
|
|
148
|
+
plugin.capabilities.supports[capability] === true
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get channels by chat type
|
|
154
|
+
*/
|
|
155
|
+
getChannelsByChatType(chatType: string): ChannelPlugin[] {
|
|
156
|
+
return this.getChannels().filter(plugin =>
|
|
157
|
+
plugin.capabilities.chatTypes?.includes(chatType as any)
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Check if a channel is registered
|
|
163
|
+
*/
|
|
164
|
+
hasChannel(channelId: string): boolean {
|
|
165
|
+
return this.channels.has(this.normalizeChannelId(channelId));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Normalize channel ID (handle aliases)
|
|
170
|
+
*/
|
|
171
|
+
normalizeChannelId(channelId: string): string {
|
|
172
|
+
const id = channelId.trim().toLowerCase();
|
|
173
|
+
|
|
174
|
+
// Common aliases
|
|
175
|
+
const aliases: Record<string, string> = {
|
|
176
|
+
"tg": "telegram",
|
|
177
|
+
"imsg": "imessage",
|
|
178
|
+
"wa": "whatsapp",
|
|
179
|
+
"dc": "discord",
|
|
180
|
+
"sl": "slack",
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
return aliases[id] || id;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Clear all registrations (for testing)
|
|
188
|
+
*/
|
|
189
|
+
clear(): void {
|
|
190
|
+
this.channels.clear();
|
|
191
|
+
this.channelOrder = [];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get registry stats
|
|
196
|
+
*/
|
|
197
|
+
getStats(): {
|
|
198
|
+
channelCount: number;
|
|
199
|
+
channels: string[];
|
|
200
|
+
} {
|
|
201
|
+
return {
|
|
202
|
+
channelCount: this.channels.size,
|
|
203
|
+
channels: this.getChannelOrder(),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ============================================================
|
|
209
|
+
// SINGLETON INSTANCE
|
|
210
|
+
// ============================================================
|
|
211
|
+
|
|
212
|
+
export const pluginRegistry = new PluginRegistryImpl();
|
|
213
|
+
|
|
214
|
+
// ============================================================
|
|
215
|
+
// CONVENIENCE FUNCTIONS
|
|
216
|
+
// ============================================================
|
|
217
|
+
|
|
218
|
+
export function registerChannel(registration: PluginChannelRegistration): void {
|
|
219
|
+
pluginRegistry.registerChannel(registration);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function getChannel(channelId: string): ChannelPlugin | undefined {
|
|
223
|
+
return pluginRegistry.getChannel(channelId);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function getChannels(): ChannelPlugin[] {
|
|
227
|
+
return pluginRegistry.getChannels();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function hasChannel(channelId: string): boolean {
|
|
231
|
+
return pluginRegistry.hasChannel(channelId);
|
|
232
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// src/plugins/types.adapters.ts
|
|
2
|
+
|
|
3
|
+
import type { ChannelId, ChannelMessage } from "../index.js";
|
|
4
|
+
|
|
5
|
+
// ============================================================
|
|
6
|
+
// CONFIG ADAPTER
|
|
7
|
+
// ============================================================
|
|
8
|
+
|
|
9
|
+
export interface ChannelConfigAdapter<ResolvedAccount = unknown> {
|
|
10
|
+
/** List available account IDs for this channel */
|
|
11
|
+
listAccountIds: (config: Record<string, unknown>) => Promise<string[]>;
|
|
12
|
+
|
|
13
|
+
/** Resolve account config to a normalized account object */
|
|
14
|
+
resolveAccount: (config: Record<string, unknown>, accountId: string) => Promise<ResolvedAccount>;
|
|
15
|
+
|
|
16
|
+
/** Validate account credentials */
|
|
17
|
+
validateAccount?: (account: ResolvedAccount) => Promise<boolean>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ============================================================
|
|
21
|
+
// OUTBOUND ADAPTER (Message Delivery)
|
|
22
|
+
// ============================================================
|
|
23
|
+
|
|
24
|
+
export type DeliveryMode = "direct" | "queue" | "webhook";
|
|
25
|
+
|
|
26
|
+
export interface ChunkResult {
|
|
27
|
+
chunks: string[];
|
|
28
|
+
totalChunks: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ChannelOutboundAdapter<ResolvedAccount = unknown> {
|
|
32
|
+
/** How messages are delivered */
|
|
33
|
+
deliveryMode: DeliveryMode;
|
|
34
|
+
|
|
35
|
+
/** Max characters per text message */
|
|
36
|
+
textChunkLimit?: number;
|
|
37
|
+
|
|
38
|
+
/** Chunk text for this channel (markdown-aware) */
|
|
39
|
+
chunker?: (text: string, limit: number) => ChunkResult;
|
|
40
|
+
|
|
41
|
+
/** Send text message */
|
|
42
|
+
sendText: (params: {
|
|
43
|
+
to: string;
|
|
44
|
+
text: string;
|
|
45
|
+
accountId: string;
|
|
46
|
+
account: ResolvedAccount;
|
|
47
|
+
replyToId?: string;
|
|
48
|
+
threadId?: string;
|
|
49
|
+
}) => Promise<OutboundResult>;
|
|
50
|
+
|
|
51
|
+
/** Send media message */
|
|
52
|
+
sendMedia?: (params: {
|
|
53
|
+
to: string;
|
|
54
|
+
text?: string;
|
|
55
|
+
mediaUrl: string;
|
|
56
|
+
mediaType: "image" | "video" | "audio" | "file";
|
|
57
|
+
accountId: string;
|
|
58
|
+
account: ResolvedAccount;
|
|
59
|
+
}) => Promise<OutboundResult>;
|
|
60
|
+
|
|
61
|
+
/** Send poll (if supported) */
|
|
62
|
+
sendPoll?: (params: {
|
|
63
|
+
to: string;
|
|
64
|
+
question: string;
|
|
65
|
+
options: string[];
|
|
66
|
+
accountId: string;
|
|
67
|
+
account: ResolvedAccount;
|
|
68
|
+
}) => Promise<OutboundResult>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface OutboundResult {
|
|
72
|
+
success: boolean;
|
|
73
|
+
messageId?: string;
|
|
74
|
+
error?: string;
|
|
75
|
+
timestamp: Date;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ============================================================
|
|
79
|
+
// GATEWAY ADAPTER (Account Lifecycle)
|
|
80
|
+
// ============================================================
|
|
81
|
+
|
|
82
|
+
export interface ChannelGatewayAdapter<ResolvedAccount = unknown> {
|
|
83
|
+
/** Start monitoring an account for incoming messages */
|
|
84
|
+
startAccount: (params: {
|
|
85
|
+
accountId: string;
|
|
86
|
+
account: ResolvedAccount;
|
|
87
|
+
onMessage: (message: ChannelMessage) => void;
|
|
88
|
+
}) => Promise<void>;
|
|
89
|
+
|
|
90
|
+
/** Stop monitoring an account */
|
|
91
|
+
stopAccount: (params: {
|
|
92
|
+
accountId: string;
|
|
93
|
+
account: ResolvedAccount;
|
|
94
|
+
}) => Promise<void>;
|
|
95
|
+
|
|
96
|
+
/** Check if account is active */
|
|
97
|
+
isAccountActive?: (accountId: string) => boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ============================================================
|
|
101
|
+
// STATUS ADAPTER (Health & Audits)
|
|
102
|
+
// ============================================================
|
|
103
|
+
|
|
104
|
+
export interface ChannelStatusAdapter<ResolvedAccount = unknown> {
|
|
105
|
+
/** Health probe for account */
|
|
106
|
+
healthProbe: (params: {
|
|
107
|
+
accountId: string;
|
|
108
|
+
account: ResolvedAccount;
|
|
109
|
+
}) => Promise<HealthStatus>;
|
|
110
|
+
|
|
111
|
+
/** Audit account for issues */
|
|
112
|
+
audit?: (params: {
|
|
113
|
+
accountId: string;
|
|
114
|
+
account: ResolvedAccount;
|
|
115
|
+
}) => Promise<ChannelAudit[]>;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface HealthStatus {
|
|
119
|
+
healthy: boolean;
|
|
120
|
+
latency?: number;
|
|
121
|
+
error?: string;
|
|
122
|
+
lastCheck: Date;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface ChannelAudit {
|
|
126
|
+
level: "info" | "warning" | "error";
|
|
127
|
+
message: string;
|
|
128
|
+
timestamp: Date;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ============================================================
|
|
132
|
+
// THREADING ADAPTER
|
|
133
|
+
// ============================================================
|
|
134
|
+
|
|
135
|
+
export interface ChannelThreadingAdapter {
|
|
136
|
+
/** Supported reply modes */
|
|
137
|
+
replyModes: ("quote" | "thread" | "reference")[];
|
|
138
|
+
|
|
139
|
+
/** Default reply mode */
|
|
140
|
+
defaultReplyMode: "quote" | "thread" | "reference";
|
|
141
|
+
|
|
142
|
+
/** Get thread context for a message */
|
|
143
|
+
getThreadContext?: (messageId: string) => Promise<ChannelMessage[]>;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ============================================================
|
|
147
|
+
// MESSAGING ADAPTER (Target Normalization)
|
|
148
|
+
// ============================================================
|
|
149
|
+
|
|
150
|
+
export interface ChannelMessagingAdapter {
|
|
151
|
+
/** Normalize a target string (e.g., "@user", "tg:user", URL) to canonical format */
|
|
152
|
+
normalizeTarget: (target: string) => Promise<NormalizedTarget>;
|
|
153
|
+
|
|
154
|
+
/** Parse target from URL */
|
|
155
|
+
parseUrl?: (url: string) => Promise<NormalizedTarget | null>;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface NormalizedTarget {
|
|
159
|
+
channelId: ChannelId;
|
|
160
|
+
targetId: string;
|
|
161
|
+
targetType: "user" | "group" | "channel";
|
|
162
|
+
original: string;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ============================================================
|
|
166
|
+
// ONBOARDING ADAPTER (Setup Wizard)
|
|
167
|
+
// ============================================================
|
|
168
|
+
|
|
169
|
+
export interface ChannelOnboardingAdapter {
|
|
170
|
+
/** CLI setup wizard steps */
|
|
171
|
+
wizardSteps: OnboardingStep[];
|
|
172
|
+
|
|
173
|
+
/** Validate setup input */
|
|
174
|
+
validateInput?: (step: string, input: string) => Promise<boolean>;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface OnboardingStep {
|
|
178
|
+
id: string;
|
|
179
|
+
prompt: string;
|
|
180
|
+
type: "text" | "password" | "select" | "confirm";
|
|
181
|
+
options?: string[];
|
|
182
|
+
required: boolean;
|
|
183
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// src/plugins/types.core.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Core types that support the plugin system.
|
|
5
|
+
* These are the foundational types used across the channel plugin architecture.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ChannelId } from "../index.js";
|
|
9
|
+
|
|
10
|
+
// ============================================================
|
|
11
|
+
// ACCOUNT RESOLUTION
|
|
12
|
+
// ============================================================
|
|
13
|
+
|
|
14
|
+
/** Resolved account interface - minimal guaranteed shape */
|
|
15
|
+
export interface ResolvedAccount {
|
|
16
|
+
/** Unique account identifier */
|
|
17
|
+
id: string;
|
|
18
|
+
|
|
19
|
+
/** Human-readable label */
|
|
20
|
+
label?: string;
|
|
21
|
+
|
|
22
|
+
/** Platform-specific data */
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ============================================================
|
|
27
|
+
// CHANNEL IDENTITY
|
|
28
|
+
// ============================================================
|
|
29
|
+
|
|
30
|
+
/** String-based channel ID (for serialization) */
|
|
31
|
+
export type ChannelIdString = string;
|
|
32
|
+
|
|
33
|
+
/** Convert ChannelId to string representation */
|
|
34
|
+
export function channelIdToString(id: ChannelId): ChannelIdString {
|
|
35
|
+
const base = `${id.platform}:${id.accountId}`;
|
|
36
|
+
return id.instanceId ? `${base}:${id.instanceId}` : base;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Parse string back to ChannelId */
|
|
40
|
+
export function parseChannelIdString(str: ChannelIdString): ChannelId {
|
|
41
|
+
const parts = str.split(":");
|
|
42
|
+
if (parts.length < 2) {
|
|
43
|
+
throw new Error(`Invalid channel ID string: ${str}`);
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
platform: parts[0] as ChannelId["platform"],
|
|
47
|
+
accountId: parts[1],
|
|
48
|
+
instanceId: parts[2],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ============================================================
|
|
53
|
+
// PLUGIN LIFECYCLE
|
|
54
|
+
// ============================================================
|
|
55
|
+
|
|
56
|
+
export interface PluginLifecycle {
|
|
57
|
+
/** Called when plugin is registered */
|
|
58
|
+
onRegister?: () => Promise<void> | void;
|
|
59
|
+
|
|
60
|
+
/** Called when plugin is unregistered */
|
|
61
|
+
onUnregister?: () => Promise<void> | void;
|
|
62
|
+
|
|
63
|
+
/** Called before channel starts */
|
|
64
|
+
beforeStart?: () => Promise<void> | void;
|
|
65
|
+
|
|
66
|
+
/** Called after channel stops */
|
|
67
|
+
afterStop?: () => Promise<void> | void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ============================================================
|
|
71
|
+
// PLUGIN CONTEXT
|
|
72
|
+
// ============================================================
|
|
73
|
+
|
|
74
|
+
export interface PluginContext {
|
|
75
|
+
/** Plugin ID */
|
|
76
|
+
pluginId: string;
|
|
77
|
+
|
|
78
|
+
/** Working directory for plugin */
|
|
79
|
+
workDir: string;
|
|
80
|
+
|
|
81
|
+
/** Data directory for persistent storage */
|
|
82
|
+
dataDir: string;
|
|
83
|
+
|
|
84
|
+
/** Temporary directory */
|
|
85
|
+
tempDir: string;
|
|
86
|
+
|
|
87
|
+
/** Logger instance */
|
|
88
|
+
logger: PluginLogger;
|
|
89
|
+
|
|
90
|
+
/** Get a value from plugin config */
|
|
91
|
+
getConfig: (key: string, defaultValue?: unknown) => unknown;
|
|
92
|
+
|
|
93
|
+
/** Set a value in plugin config */
|
|
94
|
+
setConfig: (key: string, value: unknown) => Promise<void>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface PluginLogger {
|
|
98
|
+
debug: (message: string, ...args: unknown[]) => void;
|
|
99
|
+
info: (message: string, ...args: unknown[]) => void;
|
|
100
|
+
warn: (message: string, ...args: unknown[]) => void;
|
|
101
|
+
error: (message: string, ...args: unknown[]) => void;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ============================================================
|
|
105
|
+
// PLUGIN ERROR
|
|
106
|
+
// ============================================================
|
|
107
|
+
|
|
108
|
+
export class PluginError extends Error {
|
|
109
|
+
constructor(
|
|
110
|
+
public readonly pluginId: string,
|
|
111
|
+
public readonly code: string,
|
|
112
|
+
message: string,
|
|
113
|
+
public readonly cause?: Error
|
|
114
|
+
) {
|
|
115
|
+
super(`[${pluginId}] ${code}: ${message}`);
|
|
116
|
+
this.name = "PluginError";
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export enum PluginErrorCode {
|
|
121
|
+
/** Invalid configuration */
|
|
122
|
+
INVALID_CONFIG = "INVALID_CONFIG",
|
|
123
|
+
|
|
124
|
+
/** Authentication failed */
|
|
125
|
+
AUTH_FAILED = "AUTH_FAILED",
|
|
126
|
+
|
|
127
|
+
/** Rate limit exceeded */
|
|
128
|
+
RATE_LIMITED = "RATE_LIMITED",
|
|
129
|
+
|
|
130
|
+
/** Network error */
|
|
131
|
+
NETWORK_ERROR = "NETWORK_ERROR",
|
|
132
|
+
|
|
133
|
+
/** Account not found */
|
|
134
|
+
ACCOUNT_NOT_FOUND = "ACCOUNT_NOT_FOUND",
|
|
135
|
+
|
|
136
|
+
/** Feature not supported */
|
|
137
|
+
NOT_SUPPORTED = "NOT_SUPPORTED",
|
|
138
|
+
|
|
139
|
+
/** Internal plugin error */
|
|
140
|
+
INTERNAL_ERROR = "INTERNAL_ERROR",
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ============================================================
|
|
144
|
+
// CHANNEL EVENTS
|
|
145
|
+
// ============================================================
|
|
146
|
+
|
|
147
|
+
export type ChannelEventType =
|
|
148
|
+
| "message"
|
|
149
|
+
| "message_edit"
|
|
150
|
+
| "message_delete"
|
|
151
|
+
| "reaction"
|
|
152
|
+
| "typing"
|
|
153
|
+
| "presence"
|
|
154
|
+
| "error";
|
|
155
|
+
|
|
156
|
+
export interface ChannelEvent {
|
|
157
|
+
type: ChannelEventType;
|
|
158
|
+
channelId: ChannelId;
|
|
159
|
+
timestamp: Date;
|
|
160
|
+
data: unknown;
|
|
161
|
+
}
|