whale-code 6.4.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/README.md +95 -0
- package/bin/swag-agent.js +9 -0
- package/bin/swagmanager-mcp.js +321 -0
- package/dist/cli/app.d.ts +26 -0
- package/dist/cli/app.js +64 -0
- package/dist/cli/chat/AgentSelector.d.ts +14 -0
- package/dist/cli/chat/AgentSelector.js +14 -0
- package/dist/cli/chat/ChatApp.d.ts +9 -0
- package/dist/cli/chat/ChatApp.js +267 -0
- package/dist/cli/chat/ChatInput.d.ts +39 -0
- package/dist/cli/chat/ChatInput.js +509 -0
- package/dist/cli/chat/MarkdownText.d.ts +10 -0
- package/dist/cli/chat/MarkdownText.js +20 -0
- package/dist/cli/chat/MessageList.d.ts +37 -0
- package/dist/cli/chat/MessageList.js +80 -0
- package/dist/cli/chat/ModelSelector.d.ts +20 -0
- package/dist/cli/chat/ModelSelector.js +73 -0
- package/dist/cli/chat/RewindViewer.d.ts +26 -0
- package/dist/cli/chat/RewindViewer.js +185 -0
- package/dist/cli/chat/StoreSelector.d.ts +14 -0
- package/dist/cli/chat/StoreSelector.js +24 -0
- package/dist/cli/chat/StreamingText.d.ts +12 -0
- package/dist/cli/chat/StreamingText.js +12 -0
- package/dist/cli/chat/SubagentPanel.d.ts +45 -0
- package/dist/cli/chat/SubagentPanel.js +110 -0
- package/dist/cli/chat/TeamPanel.d.ts +21 -0
- package/dist/cli/chat/TeamPanel.js +42 -0
- package/dist/cli/chat/ToolIndicator.d.ts +25 -0
- package/dist/cli/chat/ToolIndicator.js +436 -0
- package/dist/cli/chat/hooks/useAgentLoop.d.ts +39 -0
- package/dist/cli/chat/hooks/useAgentLoop.js +382 -0
- package/dist/cli/chat/hooks/useSlashCommands.d.ts +37 -0
- package/dist/cli/chat/hooks/useSlashCommands.js +387 -0
- package/dist/cli/commands/config-cmd.d.ts +10 -0
- package/dist/cli/commands/config-cmd.js +99 -0
- package/dist/cli/commands/doctor.d.ts +14 -0
- package/dist/cli/commands/doctor.js +172 -0
- package/dist/cli/commands/init.d.ts +16 -0
- package/dist/cli/commands/init.js +278 -0
- package/dist/cli/commands/mcp.d.ts +12 -0
- package/dist/cli/commands/mcp.js +162 -0
- package/dist/cli/login/LoginApp.d.ts +7 -0
- package/dist/cli/login/LoginApp.js +157 -0
- package/dist/cli/print-mode.d.ts +31 -0
- package/dist/cli/print-mode.js +202 -0
- package/dist/cli/serve-mode.d.ts +37 -0
- package/dist/cli/serve-mode.js +636 -0
- package/dist/cli/services/agent-definitions.d.ts +25 -0
- package/dist/cli/services/agent-definitions.js +91 -0
- package/dist/cli/services/agent-events.d.ts +178 -0
- package/dist/cli/services/agent-events.js +175 -0
- package/dist/cli/services/agent-loop.d.ts +90 -0
- package/dist/cli/services/agent-loop.js +762 -0
- package/dist/cli/services/agent-worker-base.d.ts +97 -0
- package/dist/cli/services/agent-worker-base.js +220 -0
- package/dist/cli/services/auth-service.d.ts +30 -0
- package/dist/cli/services/auth-service.js +160 -0
- package/dist/cli/services/background-processes.d.ts +126 -0
- package/dist/cli/services/background-processes.js +318 -0
- package/dist/cli/services/browser-auth.d.ts +24 -0
- package/dist/cli/services/browser-auth.js +180 -0
- package/dist/cli/services/claude-md-loader.d.ts +16 -0
- package/dist/cli/services/claude-md-loader.js +58 -0
- package/dist/cli/services/config-store.d.ts +47 -0
- package/dist/cli/services/config-store.js +79 -0
- package/dist/cli/services/debug-log.d.ts +10 -0
- package/dist/cli/services/debug-log.js +52 -0
- package/dist/cli/services/error-logger.d.ts +58 -0
- package/dist/cli/services/error-logger.js +269 -0
- package/dist/cli/services/file-history.d.ts +21 -0
- package/dist/cli/services/file-history.js +83 -0
- package/dist/cli/services/format-server-response.d.ts +16 -0
- package/dist/cli/services/format-server-response.js +440 -0
- package/dist/cli/services/git-context.d.ts +11 -0
- package/dist/cli/services/git-context.js +66 -0
- package/dist/cli/services/hooks.d.ts +85 -0
- package/dist/cli/services/hooks.js +258 -0
- package/dist/cli/services/interactive-tools.d.ts +125 -0
- package/dist/cli/services/interactive-tools.js +260 -0
- package/dist/cli/services/keybinding-manager.d.ts +52 -0
- package/dist/cli/services/keybinding-manager.js +115 -0
- package/dist/cli/services/local-tools.d.ts +22 -0
- package/dist/cli/services/local-tools.js +697 -0
- package/dist/cli/services/lsp-manager.d.ts +18 -0
- package/dist/cli/services/lsp-manager.js +717 -0
- package/dist/cli/services/mcp-client.d.ts +48 -0
- package/dist/cli/services/mcp-client.js +157 -0
- package/dist/cli/services/memory-manager.d.ts +16 -0
- package/dist/cli/services/memory-manager.js +57 -0
- package/dist/cli/services/model-manager.d.ts +18 -0
- package/dist/cli/services/model-manager.js +71 -0
- package/dist/cli/services/model-router.d.ts +26 -0
- package/dist/cli/services/model-router.js +149 -0
- package/dist/cli/services/permission-modes.d.ts +13 -0
- package/dist/cli/services/permission-modes.js +43 -0
- package/dist/cli/services/rewind.d.ts +84 -0
- package/dist/cli/services/rewind.js +194 -0
- package/dist/cli/services/ripgrep.d.ts +28 -0
- package/dist/cli/services/ripgrep.js +138 -0
- package/dist/cli/services/sandbox.d.ts +29 -0
- package/dist/cli/services/sandbox.js +97 -0
- package/dist/cli/services/server-tools.d.ts +61 -0
- package/dist/cli/services/server-tools.js +543 -0
- package/dist/cli/services/session-persistence.d.ts +23 -0
- package/dist/cli/services/session-persistence.js +99 -0
- package/dist/cli/services/subagent-worker.d.ts +19 -0
- package/dist/cli/services/subagent-worker.js +41 -0
- package/dist/cli/services/subagent.d.ts +47 -0
- package/dist/cli/services/subagent.js +647 -0
- package/dist/cli/services/system-prompt.d.ts +7 -0
- package/dist/cli/services/system-prompt.js +198 -0
- package/dist/cli/services/team-lead.d.ts +73 -0
- package/dist/cli/services/team-lead.js +512 -0
- package/dist/cli/services/team-state.d.ts +77 -0
- package/dist/cli/services/team-state.js +398 -0
- package/dist/cli/services/teammate.d.ts +31 -0
- package/dist/cli/services/teammate.js +689 -0
- package/dist/cli/services/telemetry.d.ts +61 -0
- package/dist/cli/services/telemetry.js +209 -0
- package/dist/cli/services/tools/agent-tools.d.ts +14 -0
- package/dist/cli/services/tools/agent-tools.js +347 -0
- package/dist/cli/services/tools/file-ops.d.ts +15 -0
- package/dist/cli/services/tools/file-ops.js +487 -0
- package/dist/cli/services/tools/search-tools.d.ts +8 -0
- package/dist/cli/services/tools/search-tools.js +186 -0
- package/dist/cli/services/tools/shell-exec.d.ts +10 -0
- package/dist/cli/services/tools/shell-exec.js +168 -0
- package/dist/cli/services/tools/task-manager.d.ts +28 -0
- package/dist/cli/services/tools/task-manager.js +209 -0
- package/dist/cli/services/tools/web-tools.d.ts +11 -0
- package/dist/cli/services/tools/web-tools.js +395 -0
- package/dist/cli/setup/SetupApp.d.ts +9 -0
- package/dist/cli/setup/SetupApp.js +191 -0
- package/dist/cli/shared/MatrixIntro.d.ts +4 -0
- package/dist/cli/shared/MatrixIntro.js +83 -0
- package/dist/cli/shared/Theme.d.ts +74 -0
- package/dist/cli/shared/Theme.js +127 -0
- package/dist/cli/shared/WhaleBanner.d.ts +10 -0
- package/dist/cli/shared/WhaleBanner.js +12 -0
- package/dist/cli/shared/markdown.d.ts +21 -0
- package/dist/cli/shared/markdown.js +756 -0
- package/dist/cli/status/StatusApp.d.ts +4 -0
- package/dist/cli/status/StatusApp.js +105 -0
- package/dist/cli/stores/StoreApp.d.ts +7 -0
- package/dist/cli/stores/StoreApp.js +81 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +538 -0
- package/dist/local-agent/connection.d.ts +48 -0
- package/dist/local-agent/connection.js +332 -0
- package/dist/local-agent/discovery.d.ts +18 -0
- package/dist/local-agent/discovery.js +146 -0
- package/dist/local-agent/executor.d.ts +34 -0
- package/dist/local-agent/executor.js +241 -0
- package/dist/local-agent/index.d.ts +14 -0
- package/dist/local-agent/index.js +198 -0
- package/dist/node/adapters/base.d.ts +35 -0
- package/dist/node/adapters/base.js +10 -0
- package/dist/node/adapters/discord.d.ts +29 -0
- package/dist/node/adapters/discord.js +299 -0
- package/dist/node/adapters/email.d.ts +23 -0
- package/dist/node/adapters/email.js +218 -0
- package/dist/node/adapters/imessage.d.ts +17 -0
- package/dist/node/adapters/imessage.js +118 -0
- package/dist/node/adapters/slack.d.ts +26 -0
- package/dist/node/adapters/slack.js +259 -0
- package/dist/node/adapters/sms.d.ts +23 -0
- package/dist/node/adapters/sms.js +161 -0
- package/dist/node/adapters/telegram.d.ts +17 -0
- package/dist/node/adapters/telegram.js +101 -0
- package/dist/node/adapters/webchat.d.ts +27 -0
- package/dist/node/adapters/webchat.js +160 -0
- package/dist/node/adapters/whatsapp.d.ts +28 -0
- package/dist/node/adapters/whatsapp.js +230 -0
- package/dist/node/cli.d.ts +2 -0
- package/dist/node/cli.js +325 -0
- package/dist/node/config.d.ts +17 -0
- package/dist/node/config.js +31 -0
- package/dist/node/runtime.d.ts +50 -0
- package/dist/node/runtime.js +351 -0
- package/dist/server/handlers/__test-utils__/mock-supabase.d.ts +11 -0
- package/dist/server/handlers/__test-utils__/mock-supabase.js +393 -0
- package/dist/server/handlers/analytics.d.ts +17 -0
- package/dist/server/handlers/analytics.js +266 -0
- package/dist/server/handlers/api-keys.d.ts +6 -0
- package/dist/server/handlers/api-keys.js +221 -0
- package/dist/server/handlers/billing.d.ts +33 -0
- package/dist/server/handlers/billing.js +272 -0
- package/dist/server/handlers/browser.d.ts +10 -0
- package/dist/server/handlers/browser.js +517 -0
- package/dist/server/handlers/catalog.d.ts +99 -0
- package/dist/server/handlers/catalog.js +976 -0
- package/dist/server/handlers/comms.d.ts +254 -0
- package/dist/server/handlers/comms.js +588 -0
- package/dist/server/handlers/creations.d.ts +6 -0
- package/dist/server/handlers/creations.js +479 -0
- package/dist/server/handlers/crm.d.ts +89 -0
- package/dist/server/handlers/crm.js +538 -0
- package/dist/server/handlers/discovery.d.ts +6 -0
- package/dist/server/handlers/discovery.js +288 -0
- package/dist/server/handlers/embeddings.d.ts +92 -0
- package/dist/server/handlers/embeddings.js +197 -0
- package/dist/server/handlers/enrichment.d.ts +8 -0
- package/dist/server/handlers/enrichment.js +768 -0
- package/dist/server/handlers/image-gen.d.ts +6 -0
- package/dist/server/handlers/image-gen.js +409 -0
- package/dist/server/handlers/inventory.d.ts +319 -0
- package/dist/server/handlers/inventory.js +447 -0
- package/dist/server/handlers/kali.d.ts +10 -0
- package/dist/server/handlers/kali.js +210 -0
- package/dist/server/handlers/llm-providers.d.ts +6 -0
- package/dist/server/handlers/llm-providers.js +673 -0
- package/dist/server/handlers/local-agent.d.ts +6 -0
- package/dist/server/handlers/local-agent.js +118 -0
- package/dist/server/handlers/meta-ads.d.ts +111 -0
- package/dist/server/handlers/meta-ads.js +2279 -0
- package/dist/server/handlers/nodes.d.ts +33 -0
- package/dist/server/handlers/nodes.js +699 -0
- package/dist/server/handlers/operations.d.ts +138 -0
- package/dist/server/handlers/operations.js +131 -0
- package/dist/server/handlers/platform.d.ts +23 -0
- package/dist/server/handlers/platform.js +227 -0
- package/dist/server/handlers/supply-chain.d.ts +19 -0
- package/dist/server/handlers/supply-chain.js +327 -0
- package/dist/server/handlers/transcription.d.ts +17 -0
- package/dist/server/handlers/transcription.js +121 -0
- package/dist/server/handlers/video-gen.d.ts +6 -0
- package/dist/server/handlers/video-gen.js +466 -0
- package/dist/server/handlers/voice.d.ts +8 -0
- package/dist/server/handlers/voice.js +1146 -0
- package/dist/server/handlers/workflow-steps.d.ts +86 -0
- package/dist/server/handlers/workflow-steps.js +2349 -0
- package/dist/server/handlers/workflows.d.ts +7 -0
- package/dist/server/handlers/workflows.js +989 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +2427 -0
- package/dist/server/lib/batch-client.d.ts +80 -0
- package/dist/server/lib/batch-client.js +467 -0
- package/dist/server/lib/code-worker-pool.d.ts +31 -0
- package/dist/server/lib/code-worker-pool.js +224 -0
- package/dist/server/lib/code-worker.d.ts +1 -0
- package/dist/server/lib/code-worker.js +188 -0
- package/dist/server/lib/compaction-service.d.ts +32 -0
- package/dist/server/lib/compaction-service.js +162 -0
- package/dist/server/lib/logger.d.ts +19 -0
- package/dist/server/lib/logger.js +46 -0
- package/dist/server/lib/otel.d.ts +38 -0
- package/dist/server/lib/otel.js +126 -0
- package/dist/server/lib/pg-rate-limiter.d.ts +21 -0
- package/dist/server/lib/pg-rate-limiter.js +86 -0
- package/dist/server/lib/prompt-sanitizer.d.ts +37 -0
- package/dist/server/lib/prompt-sanitizer.js +177 -0
- package/dist/server/lib/provider-capabilities.d.ts +85 -0
- package/dist/server/lib/provider-capabilities.js +190 -0
- package/dist/server/lib/provider-failover.d.ts +74 -0
- package/dist/server/lib/provider-failover.js +210 -0
- package/dist/server/lib/rate-limiter.d.ts +39 -0
- package/dist/server/lib/rate-limiter.js +147 -0
- package/dist/server/lib/server-agent-loop.d.ts +107 -0
- package/dist/server/lib/server-agent-loop.js +667 -0
- package/dist/server/lib/server-subagent.d.ts +78 -0
- package/dist/server/lib/server-subagent.js +203 -0
- package/dist/server/lib/session-checkpoint.d.ts +51 -0
- package/dist/server/lib/session-checkpoint.js +145 -0
- package/dist/server/lib/ssrf-guard.d.ts +13 -0
- package/dist/server/lib/ssrf-guard.js +240 -0
- package/dist/server/lib/supabase-client.d.ts +7 -0
- package/dist/server/lib/supabase-client.js +78 -0
- package/dist/server/lib/template-resolver.d.ts +31 -0
- package/dist/server/lib/template-resolver.js +215 -0
- package/dist/server/lib/utils.d.ts +16 -0
- package/dist/server/lib/utils.js +147 -0
- package/dist/server/local-agent-gateway.d.ts +82 -0
- package/dist/server/local-agent-gateway.js +426 -0
- package/dist/server/providers/anthropic.d.ts +20 -0
- package/dist/server/providers/anthropic.js +199 -0
- package/dist/server/providers/bedrock.d.ts +20 -0
- package/dist/server/providers/bedrock.js +194 -0
- package/dist/server/providers/gemini.d.ts +24 -0
- package/dist/server/providers/gemini.js +486 -0
- package/dist/server/providers/openai.d.ts +24 -0
- package/dist/server/providers/openai.js +522 -0
- package/dist/server/providers/registry.d.ts +32 -0
- package/dist/server/providers/registry.js +58 -0
- package/dist/server/providers/shared.d.ts +32 -0
- package/dist/server/providers/shared.js +124 -0
- package/dist/server/providers/types.d.ts +92 -0
- package/dist/server/providers/types.js +12 -0
- package/dist/server/proxy-handlers.d.ts +6 -0
- package/dist/server/proxy-handlers.js +89 -0
- package/dist/server/tool-router.d.ts +149 -0
- package/dist/server/tool-router.js +803 -0
- package/dist/server/validation.d.ts +24 -0
- package/dist/server/validation.js +301 -0
- package/dist/server/worker.d.ts +19 -0
- package/dist/server/worker.js +201 -0
- package/dist/setup.d.ts +8 -0
- package/dist/setup.js +181 -0
- package/dist/shared/agent-core.d.ts +157 -0
- package/dist/shared/agent-core.js +534 -0
- package/dist/shared/anthropic-types.d.ts +105 -0
- package/dist/shared/anthropic-types.js +7 -0
- package/dist/shared/api-client.d.ts +90 -0
- package/dist/shared/api-client.js +379 -0
- package/dist/shared/constants.d.ts +33 -0
- package/dist/shared/constants.js +80 -0
- package/dist/shared/sse-parser.d.ts +26 -0
- package/dist/shared/sse-parser.js +259 -0
- package/dist/shared/tool-dispatch.d.ts +52 -0
- package/dist/shared/tool-dispatch.js +191 -0
- package/dist/shared/types.d.ts +72 -0
- package/dist/shared/types.js +7 -0
- package/dist/updater.d.ts +25 -0
- package/dist/updater.js +140 -0
- package/dist/webchat/widget.d.ts +0 -0
- package/dist/webchat/widget.js +397 -0
- package/package.json +95 -0
- package/src/cli/services/builtin-skills/commit.md +19 -0
- package/src/cli/services/builtin-skills/review-pr.md +21 -0
- package/src/cli/services/builtin-skills/review.md +18 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { BaseAdapter } from "./base.js";
|
|
2
|
+
const DISCORD_API = "https://discord.com/api/v10";
|
|
3
|
+
const GATEWAY_URL = "wss://gateway.discord.gg/?v=10&encoding=json";
|
|
4
|
+
const MAX_MESSAGE_LENGTH = 2000;
|
|
5
|
+
// Discord Gateway opcodes
|
|
6
|
+
const OP = {
|
|
7
|
+
DISPATCH: 0,
|
|
8
|
+
HEARTBEAT: 1,
|
|
9
|
+
IDENTIFY: 2,
|
|
10
|
+
RESUME: 6,
|
|
11
|
+
RECONNECT: 7,
|
|
12
|
+
INVALID_SESSION: 9,
|
|
13
|
+
HELLO: 10,
|
|
14
|
+
HEARTBEAT_ACK: 11,
|
|
15
|
+
};
|
|
16
|
+
// Discord Gateway intents
|
|
17
|
+
const INTENTS = {
|
|
18
|
+
GUILDS: 1 << 0,
|
|
19
|
+
GUILD_MESSAGES: 1 << 9,
|
|
20
|
+
MESSAGE_CONTENT: 1 << 15,
|
|
21
|
+
DIRECT_MESSAGES: 1 << 12,
|
|
22
|
+
};
|
|
23
|
+
export class DiscordAdapter extends BaseAdapter {
|
|
24
|
+
type = "discord";
|
|
25
|
+
name;
|
|
26
|
+
config;
|
|
27
|
+
ws = null;
|
|
28
|
+
heartbeatTimer = null;
|
|
29
|
+
sequence = null;
|
|
30
|
+
sessionId = null;
|
|
31
|
+
resumeUrl = null;
|
|
32
|
+
botUserId = null;
|
|
33
|
+
reconnectAttempts = 0;
|
|
34
|
+
constructor(name, config) {
|
|
35
|
+
super();
|
|
36
|
+
this.name = name;
|
|
37
|
+
this.config = config;
|
|
38
|
+
}
|
|
39
|
+
async start() {
|
|
40
|
+
if (this.running)
|
|
41
|
+
return;
|
|
42
|
+
this.running = true;
|
|
43
|
+
this.reconnectAttempts = 0;
|
|
44
|
+
console.log(`[discord] Connecting to gateway...`);
|
|
45
|
+
this.connectGateway(GATEWAY_URL);
|
|
46
|
+
}
|
|
47
|
+
async stop() {
|
|
48
|
+
this.running = false;
|
|
49
|
+
if (this.heartbeatTimer) {
|
|
50
|
+
clearInterval(this.heartbeatTimer);
|
|
51
|
+
this.heartbeatTimer = null;
|
|
52
|
+
}
|
|
53
|
+
if (this.ws) {
|
|
54
|
+
this.ws.close(1000, "Shutting down");
|
|
55
|
+
this.ws = null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async sendMessage(msg) {
|
|
59
|
+
try {
|
|
60
|
+
const channelId = msg.metadata?.channel_id || msg.conversation_id;
|
|
61
|
+
if (!channelId) {
|
|
62
|
+
console.error("[discord] No channel_id to send to");
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
const maxLen = this.config.max_message_length || MAX_MESSAGE_LENGTH;
|
|
66
|
+
let content = msg.content;
|
|
67
|
+
// Split long messages into chunks (Discord 2000 char limit)
|
|
68
|
+
const chunks = [];
|
|
69
|
+
while (content.length > 0) {
|
|
70
|
+
chunks.push(content.slice(0, maxLen));
|
|
71
|
+
content = content.slice(maxLen);
|
|
72
|
+
}
|
|
73
|
+
for (const chunk of chunks) {
|
|
74
|
+
const res = await fetch(`${DISCORD_API}/channels/${channelId}/messages`, {
|
|
75
|
+
method: "POST",
|
|
76
|
+
headers: {
|
|
77
|
+
"Content-Type": "application/json",
|
|
78
|
+
Authorization: `Bot ${this.config.bot_token}`,
|
|
79
|
+
},
|
|
80
|
+
body: JSON.stringify({
|
|
81
|
+
content: chunk,
|
|
82
|
+
// Reply to the original message if we have a message_id
|
|
83
|
+
...(msg.metadata?.message_id ? {
|
|
84
|
+
message_reference: { message_id: msg.metadata.message_id },
|
|
85
|
+
} : {}),
|
|
86
|
+
}),
|
|
87
|
+
});
|
|
88
|
+
if (!res.ok) {
|
|
89
|
+
const err = await res.text();
|
|
90
|
+
console.error(`[discord] Send failed (${res.status}):`, err);
|
|
91
|
+
this.stats.errors++;
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
this.stats.messages_out++;
|
|
96
|
+
this.stats.last_message_at = new Date().toISOString();
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
console.error(`[discord] Send error:`, err.message);
|
|
101
|
+
this.stats.errors++;
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
connectGateway(url) {
|
|
106
|
+
if (!this.running)
|
|
107
|
+
return;
|
|
108
|
+
try {
|
|
109
|
+
this.ws = new WebSocket(url);
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
console.error(`[discord] WebSocket creation failed:`, err.message);
|
|
113
|
+
this.scheduleReconnect();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
this.ws.onopen = () => {
|
|
117
|
+
console.log(`[discord] Gateway connected`);
|
|
118
|
+
this.reconnectAttempts = 0;
|
|
119
|
+
};
|
|
120
|
+
this.ws.onmessage = (event) => {
|
|
121
|
+
try {
|
|
122
|
+
const data = JSON.parse(String(event.data));
|
|
123
|
+
this.handleGatewayMessage(data);
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
console.error(`[discord] Failed to parse gateway message:`, err.message);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
this.ws.onclose = (event) => {
|
|
130
|
+
console.log(`[discord] Gateway closed: ${event.code} ${event.reason}`);
|
|
131
|
+
if (this.heartbeatTimer) {
|
|
132
|
+
clearInterval(this.heartbeatTimer);
|
|
133
|
+
this.heartbeatTimer = null;
|
|
134
|
+
}
|
|
135
|
+
// Non-resumable close codes
|
|
136
|
+
const fatalCodes = [4004, 4010, 4011, 4012, 4013, 4014];
|
|
137
|
+
if (fatalCodes.includes(event.code)) {
|
|
138
|
+
console.error(`[discord] Fatal close code ${event.code}, not reconnecting`);
|
|
139
|
+
this.running = false;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (this.running)
|
|
143
|
+
this.scheduleReconnect();
|
|
144
|
+
};
|
|
145
|
+
this.ws.onerror = () => {
|
|
146
|
+
console.error(`[discord] Gateway error`);
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
handleGatewayMessage(data) {
|
|
150
|
+
const { op, d, s, t } = data;
|
|
151
|
+
// Track sequence for resume
|
|
152
|
+
if (s !== null && s !== undefined)
|
|
153
|
+
this.sequence = s;
|
|
154
|
+
switch (op) {
|
|
155
|
+
case OP.HELLO: {
|
|
156
|
+
// Start heartbeating
|
|
157
|
+
const interval = d.heartbeat_interval;
|
|
158
|
+
this.heartbeatTimer = setInterval(() => {
|
|
159
|
+
this.ws?.send(JSON.stringify({ op: OP.HEARTBEAT, d: this.sequence }));
|
|
160
|
+
}, interval);
|
|
161
|
+
// Send initial heartbeat after random jitter
|
|
162
|
+
setTimeout(() => {
|
|
163
|
+
this.ws?.send(JSON.stringify({ op: OP.HEARTBEAT, d: this.sequence }));
|
|
164
|
+
}, Math.random() * interval);
|
|
165
|
+
// Identify or resume
|
|
166
|
+
if (this.sessionId && this.sequence !== null) {
|
|
167
|
+
this.ws?.send(JSON.stringify({
|
|
168
|
+
op: OP.RESUME,
|
|
169
|
+
d: {
|
|
170
|
+
token: this.config.bot_token,
|
|
171
|
+
session_id: this.sessionId,
|
|
172
|
+
seq: this.sequence,
|
|
173
|
+
},
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
this.ws?.send(JSON.stringify({
|
|
178
|
+
op: OP.IDENTIFY,
|
|
179
|
+
d: {
|
|
180
|
+
token: this.config.bot_token,
|
|
181
|
+
intents: INTENTS.GUILDS | INTENTS.GUILD_MESSAGES | INTENTS.MESSAGE_CONTENT | INTENTS.DIRECT_MESSAGES,
|
|
182
|
+
properties: {
|
|
183
|
+
os: process.platform,
|
|
184
|
+
browser: "whalenode",
|
|
185
|
+
device: "whalenode",
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
case OP.HEARTBEAT_ACK:
|
|
193
|
+
// Server acknowledged heartbeat — connection is healthy
|
|
194
|
+
break;
|
|
195
|
+
case OP.HEARTBEAT:
|
|
196
|
+
// Server requesting immediate heartbeat
|
|
197
|
+
this.ws?.send(JSON.stringify({ op: OP.HEARTBEAT, d: this.sequence }));
|
|
198
|
+
break;
|
|
199
|
+
case OP.RECONNECT:
|
|
200
|
+
console.log(`[discord] Server requested reconnect`);
|
|
201
|
+
this.ws?.close(4000, "Reconnecting");
|
|
202
|
+
break;
|
|
203
|
+
case OP.INVALID_SESSION:
|
|
204
|
+
console.log(`[discord] Invalid session, re-identifying...`);
|
|
205
|
+
this.sessionId = null;
|
|
206
|
+
this.sequence = null;
|
|
207
|
+
// Wait 1-5s before re-identifying (per Discord docs)
|
|
208
|
+
setTimeout(() => {
|
|
209
|
+
this.ws?.close(4000, "Re-identifying");
|
|
210
|
+
}, 1000 + Math.random() * 4000);
|
|
211
|
+
break;
|
|
212
|
+
case OP.DISPATCH:
|
|
213
|
+
this.handleDispatch(t, d);
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
handleDispatch(eventName, data) {
|
|
218
|
+
switch (eventName) {
|
|
219
|
+
case "READY":
|
|
220
|
+
this.sessionId = data.session_id;
|
|
221
|
+
this.resumeUrl = data.resume_gateway_url;
|
|
222
|
+
this.botUserId = data.user?.id;
|
|
223
|
+
console.log(`[discord] Ready as ${data.user?.username}#${data.user?.discriminator} (${data.guilds?.length || 0} guilds)`);
|
|
224
|
+
break;
|
|
225
|
+
case "RESUMED":
|
|
226
|
+
console.log(`[discord] Session resumed`);
|
|
227
|
+
break;
|
|
228
|
+
case "MESSAGE_CREATE":
|
|
229
|
+
this.handleMessage(data).catch(err => {
|
|
230
|
+
console.error(`[discord] Error handling message:`, err.message);
|
|
231
|
+
this.stats.errors++;
|
|
232
|
+
});
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
async handleMessage(data) {
|
|
237
|
+
// Ignore bot's own messages
|
|
238
|
+
if (data.author?.id === this.botUserId)
|
|
239
|
+
return;
|
|
240
|
+
// Ignore other bots
|
|
241
|
+
if (data.author?.bot)
|
|
242
|
+
return;
|
|
243
|
+
const channelId = data.channel_id;
|
|
244
|
+
const guildId = data.guild_id;
|
|
245
|
+
// Filter by allowed channels
|
|
246
|
+
if (this.config.allowed_channels?.length) {
|
|
247
|
+
if (!this.config.allowed_channels.includes(channelId))
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
// Filter by allowed guilds
|
|
251
|
+
if (this.config.allowed_guilds?.length && guildId) {
|
|
252
|
+
if (!this.config.allowed_guilds.includes(guildId))
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
// Check mention requirement
|
|
256
|
+
if (this.config.mention_required && this.botUserId) {
|
|
257
|
+
const mentioned = data.mentions?.some((m) => m.id === this.botUserId);
|
|
258
|
+
if (!mentioned)
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
// Strip bot mention from content
|
|
262
|
+
let content = data.content || "";
|
|
263
|
+
if (this.botUserId) {
|
|
264
|
+
content = content.replace(new RegExp(`<@!?${this.botUserId}>`, "g"), "").trim();
|
|
265
|
+
}
|
|
266
|
+
if (!content)
|
|
267
|
+
return;
|
|
268
|
+
const payload = {
|
|
269
|
+
sender_id: data.author?.id || "unknown",
|
|
270
|
+
sender_name: data.member?.nick || data.author?.global_name || data.author?.username,
|
|
271
|
+
content,
|
|
272
|
+
metadata: {
|
|
273
|
+
channel_id: channelId,
|
|
274
|
+
guild_id: guildId,
|
|
275
|
+
message_id: data.id,
|
|
276
|
+
is_dm: !guildId,
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
this.stats.messages_in++;
|
|
280
|
+
this.stats.last_message_at = new Date().toISOString();
|
|
281
|
+
if (this.onInboundMessage) {
|
|
282
|
+
await this.onInboundMessage(payload);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
scheduleReconnect() {
|
|
286
|
+
if (!this.running)
|
|
287
|
+
return;
|
|
288
|
+
this.reconnectAttempts++;
|
|
289
|
+
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts - 1), 60_000);
|
|
290
|
+
console.log(`[discord] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})...`);
|
|
291
|
+
setTimeout(() => {
|
|
292
|
+
if (!this.running)
|
|
293
|
+
return;
|
|
294
|
+
// Use resume URL if available, otherwise default gateway
|
|
295
|
+
const url = (this.sessionId && this.resumeUrl) ? this.resumeUrl : GATEWAY_URL;
|
|
296
|
+
this.connectGateway(url);
|
|
297
|
+
}, delay);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BaseAdapter, type OutboundMessage } from "./base.js";
|
|
2
|
+
export interface EmailConfig {
|
|
3
|
+
api_key: string;
|
|
4
|
+
from_address: string;
|
|
5
|
+
webhook_port?: number;
|
|
6
|
+
webhook_secret?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class EmailAdapter extends BaseAdapter {
|
|
9
|
+
readonly type = "email";
|
|
10
|
+
readonly name: string;
|
|
11
|
+
private config;
|
|
12
|
+
private server;
|
|
13
|
+
private reconnectAttempts;
|
|
14
|
+
constructor(name: string, config: EmailConfig);
|
|
15
|
+
start(): Promise<void>;
|
|
16
|
+
stop(): Promise<void>;
|
|
17
|
+
sendMessage(msg: OutboundMessage): Promise<boolean>;
|
|
18
|
+
private startWebhookServer;
|
|
19
|
+
/** Handle inbound Resend webhook event (POST with JSON body) */
|
|
20
|
+
private handleIncoming;
|
|
21
|
+
private processWebhookPayload;
|
|
22
|
+
private scheduleRestart;
|
|
23
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { createHmac } from "node:crypto";
|
|
3
|
+
import { BaseAdapter } from "./base.js";
|
|
4
|
+
const RESEND_API = "https://api.resend.com";
|
|
5
|
+
const DEFAULT_WEBHOOK_PORT = 3102;
|
|
6
|
+
export class EmailAdapter extends BaseAdapter {
|
|
7
|
+
type = "email";
|
|
8
|
+
name;
|
|
9
|
+
config;
|
|
10
|
+
server = null;
|
|
11
|
+
reconnectAttempts = 0;
|
|
12
|
+
constructor(name, config) {
|
|
13
|
+
super();
|
|
14
|
+
this.name = name;
|
|
15
|
+
this.config = config;
|
|
16
|
+
}
|
|
17
|
+
async start() {
|
|
18
|
+
if (this.running)
|
|
19
|
+
return;
|
|
20
|
+
this.running = true;
|
|
21
|
+
this.reconnectAttempts = 0;
|
|
22
|
+
console.log(`[email] Starting Resend webhook server...`);
|
|
23
|
+
this.startWebhookServer();
|
|
24
|
+
}
|
|
25
|
+
async stop() {
|
|
26
|
+
this.running = false;
|
|
27
|
+
if (this.server) {
|
|
28
|
+
await new Promise((resolve) => {
|
|
29
|
+
this.server.close(() => resolve());
|
|
30
|
+
});
|
|
31
|
+
this.server = null;
|
|
32
|
+
}
|
|
33
|
+
console.log(`[email] Webhook server stopped`);
|
|
34
|
+
}
|
|
35
|
+
async sendMessage(msg) {
|
|
36
|
+
try {
|
|
37
|
+
const toAddress = msg.metadata?.from_address || msg.sender_id;
|
|
38
|
+
if (!toAddress) {
|
|
39
|
+
console.error("[email] No recipient email address");
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
const subject = msg.metadata?.subject || "Re: Your message";
|
|
43
|
+
const res = await fetch(`${RESEND_API}/emails`, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: {
|
|
46
|
+
"Content-Type": "application/json",
|
|
47
|
+
Authorization: `Bearer ${this.config.api_key}`,
|
|
48
|
+
},
|
|
49
|
+
body: JSON.stringify({
|
|
50
|
+
from: this.config.from_address,
|
|
51
|
+
to: [toAddress],
|
|
52
|
+
subject,
|
|
53
|
+
text: msg.content,
|
|
54
|
+
}),
|
|
55
|
+
});
|
|
56
|
+
if (res.ok) {
|
|
57
|
+
this.stats.messages_out++;
|
|
58
|
+
this.stats.last_message_at = new Date().toISOString();
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
const err = await res.text();
|
|
62
|
+
console.error(`[email] Send failed (${res.status}):`, err);
|
|
63
|
+
this.stats.errors++;
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
console.error(`[email] Send error:`, err.message);
|
|
68
|
+
this.stats.errors++;
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
startWebhookServer() {
|
|
73
|
+
const port = this.config.webhook_port || DEFAULT_WEBHOOK_PORT;
|
|
74
|
+
this.server = createServer((req, res) => {
|
|
75
|
+
if (req.method === "POST") {
|
|
76
|
+
this.handleIncoming(req, res);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// Health check endpoint
|
|
80
|
+
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
81
|
+
res.end("WhaleNode Email adapter running");
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
this.server.on("error", (err) => {
|
|
85
|
+
console.error(`[email] Server error:`, err.message);
|
|
86
|
+
this.stats.errors++;
|
|
87
|
+
if (this.running)
|
|
88
|
+
this.scheduleRestart();
|
|
89
|
+
});
|
|
90
|
+
this.server.listen(port, () => {
|
|
91
|
+
console.log(`[email] Resend webhook server listening on port ${port}`);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/** Handle inbound Resend webhook event (POST with JSON body) */
|
|
95
|
+
handleIncoming(req, res) {
|
|
96
|
+
let body = "";
|
|
97
|
+
req.on("data", (chunk) => {
|
|
98
|
+
body += chunk.toString();
|
|
99
|
+
});
|
|
100
|
+
req.on("end", () => {
|
|
101
|
+
// Verify webhook signature if secret is configured
|
|
102
|
+
if (this.config.webhook_secret) {
|
|
103
|
+
const signature = req.headers["svix-signature"];
|
|
104
|
+
const messageId = req.headers["svix-id"];
|
|
105
|
+
const timestamp = req.headers["svix-timestamp"];
|
|
106
|
+
if (!signature || !messageId || !timestamp) {
|
|
107
|
+
res.writeHead(401);
|
|
108
|
+
res.end("Missing signature headers");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const signedContent = `${messageId}.${timestamp}.${body}`;
|
|
112
|
+
const secretBytes = Buffer.from(this.config.webhook_secret.replace("whsec_", ""), "base64");
|
|
113
|
+
const expectedSignature = createHmac("sha256", secretBytes)
|
|
114
|
+
.update(signedContent)
|
|
115
|
+
.digest("base64");
|
|
116
|
+
// Svix sends multiple signatures separated by spaces, each prefixed with "v1,"
|
|
117
|
+
const signatures = signature.split(" ").map((s) => s.replace("v1,", ""));
|
|
118
|
+
const valid = signatures.some((s) => s === expectedSignature);
|
|
119
|
+
if (!valid) {
|
|
120
|
+
console.warn(`[email] Invalid webhook signature`);
|
|
121
|
+
res.writeHead(401);
|
|
122
|
+
res.end("Invalid signature");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
127
|
+
res.end(JSON.stringify({ status: "ok" }));
|
|
128
|
+
this.processWebhookPayload(body).catch((err) => {
|
|
129
|
+
console.error(`[email] Error processing webhook:`, err.message);
|
|
130
|
+
this.stats.errors++;
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
async processWebhookPayload(raw) {
|
|
135
|
+
let data;
|
|
136
|
+
try {
|
|
137
|
+
data = JSON.parse(raw);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
console.error(`[email] Invalid JSON in webhook payload`);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Resend inbound email webhook structure
|
|
144
|
+
// The event type for inbound emails is "email.received"
|
|
145
|
+
const eventType = data.type;
|
|
146
|
+
if (eventType !== "email.received") {
|
|
147
|
+
// Ignore non-inbound events (delivery status, bounces, etc.)
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const emailData = data.data;
|
|
151
|
+
if (!emailData)
|
|
152
|
+
return;
|
|
153
|
+
const fromAddress = emailData.from;
|
|
154
|
+
const toAddress = emailData.to;
|
|
155
|
+
const subject = emailData.subject || "";
|
|
156
|
+
// Extract text content from the email body
|
|
157
|
+
// Resend provides text and/or html fields
|
|
158
|
+
let content = emailData.text || "";
|
|
159
|
+
if (!content && emailData.html) {
|
|
160
|
+
// Basic HTML-to-text: strip tags and decode common entities
|
|
161
|
+
content = emailData.html
|
|
162
|
+
.replace(/<br\s*\/?>/gi, "\n")
|
|
163
|
+
.replace(/<\/p>/gi, "\n\n")
|
|
164
|
+
.replace(/<[^>]+>/g, "")
|
|
165
|
+
.replace(/&/g, "&")
|
|
166
|
+
.replace(/</g, "<")
|
|
167
|
+
.replace(/>/g, ">")
|
|
168
|
+
.replace(/"/g, "\"")
|
|
169
|
+
.replace(/'/g, "'")
|
|
170
|
+
.replace(/ /g, " ")
|
|
171
|
+
.trim();
|
|
172
|
+
}
|
|
173
|
+
if (!content && subject) {
|
|
174
|
+
content = subject;
|
|
175
|
+
}
|
|
176
|
+
if (!fromAddress || !content)
|
|
177
|
+
return;
|
|
178
|
+
const payload = {
|
|
179
|
+
sender_id: fromAddress,
|
|
180
|
+
content,
|
|
181
|
+
metadata: {
|
|
182
|
+
from_address: fromAddress,
|
|
183
|
+
to_address: toAddress,
|
|
184
|
+
subject,
|
|
185
|
+
email_id: emailData.id,
|
|
186
|
+
created_at: emailData.created_at,
|
|
187
|
+
attachments: emailData.attachments?.length || 0,
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
this.stats.messages_in++;
|
|
191
|
+
this.stats.last_message_at = new Date().toISOString();
|
|
192
|
+
if (this.onInboundMessage) {
|
|
193
|
+
await this.onInboundMessage(payload).catch((err) => {
|
|
194
|
+
console.error(`[email] Error handling message:`, err.message);
|
|
195
|
+
this.stats.errors++;
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
scheduleRestart() {
|
|
200
|
+
if (!this.running)
|
|
201
|
+
return;
|
|
202
|
+
this.reconnectAttempts++;
|
|
203
|
+
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts - 1), 60_000);
|
|
204
|
+
console.log(`[email] Restarting webhook server in ${delay}ms (attempt ${this.reconnectAttempts})...`);
|
|
205
|
+
setTimeout(() => {
|
|
206
|
+
if (!this.running)
|
|
207
|
+
return;
|
|
208
|
+
if (this.server) {
|
|
209
|
+
this.server.close(() => {
|
|
210
|
+
this.startWebhookServer();
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
this.startWebhookServer();
|
|
215
|
+
}
|
|
216
|
+
}, delay);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BaseAdapter, type OutboundMessage } from "./base.js";
|
|
2
|
+
export interface IMessageConfig {
|
|
3
|
+
watch_groups?: number[];
|
|
4
|
+
mention_required?: string;
|
|
5
|
+
max_message_length?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare class IMessageAdapter extends BaseAdapter {
|
|
8
|
+
readonly type = "imessage";
|
|
9
|
+
readonly name: string;
|
|
10
|
+
private process;
|
|
11
|
+
private config;
|
|
12
|
+
constructor(name: string, config?: IMessageConfig);
|
|
13
|
+
start(): Promise<void>;
|
|
14
|
+
stop(): Promise<void>;
|
|
15
|
+
sendMessage(msg: OutboundMessage): Promise<boolean>;
|
|
16
|
+
private handleLine;
|
|
17
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { BaseAdapter } from "./base.js";
|
|
3
|
+
export class IMessageAdapter extends BaseAdapter {
|
|
4
|
+
type = "imessage";
|
|
5
|
+
name;
|
|
6
|
+
process = null;
|
|
7
|
+
config;
|
|
8
|
+
constructor(name, config = {}) {
|
|
9
|
+
super();
|
|
10
|
+
this.name = name;
|
|
11
|
+
this.config = config;
|
|
12
|
+
}
|
|
13
|
+
async start() {
|
|
14
|
+
if (this.running)
|
|
15
|
+
return;
|
|
16
|
+
this.running = true;
|
|
17
|
+
// Spawn imsg watch as a child process
|
|
18
|
+
const args = ["watch", "--json"];
|
|
19
|
+
this.process = spawn("imsg", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
20
|
+
let buffer = "";
|
|
21
|
+
this.process.stdout?.on("data", (chunk) => {
|
|
22
|
+
buffer += chunk.toString();
|
|
23
|
+
const lines = buffer.split("\n");
|
|
24
|
+
buffer = lines.pop() || "";
|
|
25
|
+
for (const line of lines) {
|
|
26
|
+
if (!line.trim())
|
|
27
|
+
continue;
|
|
28
|
+
this.handleLine(line).catch(err => {
|
|
29
|
+
console.error(`[imessage] Error handling message:`, err.message);
|
|
30
|
+
this.stats.errors++;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
this.process.stderr?.on("data", (chunk) => {
|
|
35
|
+
console.error(`[imessage] stderr:`, chunk.toString().trim());
|
|
36
|
+
});
|
|
37
|
+
this.process.on("exit", (code) => {
|
|
38
|
+
console.log(`[imessage] Process exited with code ${code}`);
|
|
39
|
+
const wasRunning = this.running;
|
|
40
|
+
this.running = false;
|
|
41
|
+
// Auto-restart after 5s if unexpected exit while we were supposed to be running
|
|
42
|
+
if (code !== 0 && wasRunning) {
|
|
43
|
+
console.log(`[imessage] Restarting in 5s...`);
|
|
44
|
+
setTimeout(() => this.start(), 5000);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
console.log(`[imessage] Watching for messages...`);
|
|
48
|
+
}
|
|
49
|
+
async stop() {
|
|
50
|
+
this.running = false;
|
|
51
|
+
if (this.process) {
|
|
52
|
+
this.process.kill("SIGTERM");
|
|
53
|
+
this.process = null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async sendMessage(msg) {
|
|
57
|
+
try {
|
|
58
|
+
const text = msg.content.substring(0, this.config.max_message_length || 4000);
|
|
59
|
+
const chatId = msg.metadata?.chat_id || msg.conversation_id;
|
|
60
|
+
if (!chatId) {
|
|
61
|
+
console.error("[imessage] No chat_id to send to");
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
const proc = spawn("imsg", ["send", "--chat", String(chatId), text]);
|
|
65
|
+
return new Promise((resolve) => {
|
|
66
|
+
proc.on("exit", (code) => {
|
|
67
|
+
if (code === 0) {
|
|
68
|
+
this.stats.messages_out++;
|
|
69
|
+
this.stats.last_message_at = new Date().toISOString();
|
|
70
|
+
resolve(true);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
this.stats.errors++;
|
|
74
|
+
resolve(false);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
this.stats.errors++;
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async handleLine(line) {
|
|
85
|
+
let data;
|
|
86
|
+
try {
|
|
87
|
+
data = JSON.parse(line);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return; // Not JSON
|
|
91
|
+
}
|
|
92
|
+
// Filter by watched groups
|
|
93
|
+
if (this.config.watch_groups?.length) {
|
|
94
|
+
if (!this.config.watch_groups.includes(data.chat_id))
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// Check mention requirement
|
|
98
|
+
if (this.config.mention_required) {
|
|
99
|
+
if (!data.text?.includes(this.config.mention_required))
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const payload = {
|
|
103
|
+
sender_id: data.sender || data.handle || "unknown",
|
|
104
|
+
sender_name: data.sender_name,
|
|
105
|
+
content: data.text || "",
|
|
106
|
+
metadata: {
|
|
107
|
+
chat_id: data.chat_id,
|
|
108
|
+
group: data.group,
|
|
109
|
+
attachments: data.attachments,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
this.stats.messages_in++;
|
|
113
|
+
this.stats.last_message_at = new Date().toISOString();
|
|
114
|
+
if (this.onInboundMessage) {
|
|
115
|
+
await this.onInboundMessage(payload);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|