flingit 0.0.18 → 0.0.20
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/cli/commands/plugin.d.ts +3 -10
- package/dist/cli/commands/plugin.d.ts.map +1 -1
- package/dist/cli/commands/plugin.js +103 -209
- package/dist/cli/commands/plugin.js.map +1 -1
- package/dist/cli/commands/push.d.ts +5 -0
- package/dist/cli/commands/push.d.ts.map +1 -1
- package/dist/cli/commands/push.js +21 -33
- package/dist/cli/commands/push.js.map +1 -1
- package/dist/cli/deploy/bundler.d.ts +14 -10
- package/dist/cli/deploy/bundler.d.ts.map +1 -1
- package/dist/cli/deploy/bundler.js +44 -70
- package/dist/cli/deploy/bundler.js.map +1 -1
- package/dist/cli/utils/config.d.ts +12 -4
- package/dist/cli/utils/config.d.ts.map +1 -1
- package/dist/cli/utils/config.js +49 -6
- package/dist/cli/utils/config.js.map +1 -1
- package/dist/runtime/discord.d.ts +18 -59
- package/dist/runtime/discord.d.ts.map +1 -1
- package/dist/runtime/discord.js +20 -60
- package/dist/runtime/discord.js.map +1 -1
- package/dist/runtime/slack.d.ts +83 -0
- package/dist/runtime/slack.d.ts.map +1 -0
- package/dist/runtime/slack.js +87 -0
- package/dist/runtime/slack.js.map +1 -0
- package/dist/shared/discord-types.d.ts +0 -24
- package/dist/shared/discord-types.d.ts.map +1 -1
- package/dist/shared/plugin-metadata.d.ts +28 -0
- package/dist/shared/plugin-metadata.d.ts.map +1 -0
- package/dist/shared/plugin-metadata.js +65 -0
- package/dist/shared/plugin-metadata.js.map +1 -0
- package/dist/shared/slack-types.d.ts +61 -0
- package/dist/shared/slack-types.d.ts.map +1 -0
- package/dist/shared/slack-types.js +8 -0
- package/dist/shared/slack-types.js.map +1 -0
- package/dist/worker-runtime/discord.d.ts +75 -27
- package/dist/worker-runtime/discord.d.ts.map +1 -1
- package/dist/worker-runtime/discord.js +156 -108
- package/dist/worker-runtime/discord.js.map +1 -1
- package/dist/worker-runtime/index.d.ts +1 -1
- package/dist/worker-runtime/index.d.ts.map +1 -1
- package/dist/worker-runtime/index.js +0 -4
- package/dist/worker-runtime/index.js.map +1 -1
- package/dist/worker-runtime/plugin-common.d.ts +27 -0
- package/dist/worker-runtime/plugin-common.d.ts.map +1 -0
- package/dist/worker-runtime/plugin-common.js +91 -0
- package/dist/worker-runtime/plugin-common.js.map +1 -0
- package/dist/worker-runtime/slack.d.ts +67 -0
- package/dist/worker-runtime/slack.d.ts.map +1 -0
- package/dist/worker-runtime/slack.js +135 -0
- package/dist/worker-runtime/slack.js.map +1 -0
- package/package.json +10 -2
- package/templates/default/skills/fling/.hash +1 -1
- package/templates/default/skills/fling/DISCORD.md +69 -126
- package/templates/default/skills/fling/SKILL.md +13 -0
- package/templates/default/skills/fling/SLACK.md +214 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for plugin worker-runtime modules.
|
|
3
|
+
*
|
|
4
|
+
* Extracts the common plumbing (service binding fetch, identity tokens,
|
|
5
|
+
* HMAC signing, error parsing) so each plugin module only contains
|
|
6
|
+
* plugin-specific logic.
|
|
7
|
+
*/
|
|
8
|
+
import { __getEnv } from "./index.js";
|
|
9
|
+
/**
|
|
10
|
+
* Create a pluginFetch function bound to a specific service binding name.
|
|
11
|
+
*
|
|
12
|
+
* @param bindingName - The env binding name (e.g., "DISCORD_PLUGIN")
|
|
13
|
+
*/
|
|
14
|
+
export function createPluginFetch(bindingName) {
|
|
15
|
+
return function pluginFetch(path, init) {
|
|
16
|
+
const env = __getEnv();
|
|
17
|
+
const binding = env[bindingName];
|
|
18
|
+
if (binding === null || binding === undefined || typeof binding["fetch"] !== "function") {
|
|
19
|
+
throw new Error(`${bindingName} service binding not available. ` +
|
|
20
|
+
"Re-deploy your project with 'fling push' to get the latest bindings.");
|
|
21
|
+
}
|
|
22
|
+
return binding.fetch(`https://plugin${path}`, init);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
// ═══════════════════════════════════════════════════════
|
|
26
|
+
// HMAC + IDENTITY TOKENS
|
|
27
|
+
// ═══════════════════════════════════════════════════════
|
|
28
|
+
export async function hmacSha256(secret, message) {
|
|
29
|
+
const encoder = new TextEncoder();
|
|
30
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
31
|
+
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(message));
|
|
32
|
+
return Array.from(new Uint8Array(signature))
|
|
33
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
34
|
+
.join("");
|
|
35
|
+
}
|
|
36
|
+
export async function generateIdentityToken() {
|
|
37
|
+
const env = __getEnv();
|
|
38
|
+
const projectId = env["FLING_PROJECT_ID"];
|
|
39
|
+
const workerSecret = env["FLING_WORKER_SECRET"];
|
|
40
|
+
const projectIdStr = typeof projectId === "string" ? projectId :
|
|
41
|
+
typeof projectId === "number" ? String(projectId) : "";
|
|
42
|
+
const secretStr = typeof workerSecret === "string" ? workerSecret : "";
|
|
43
|
+
if (!secretStr) {
|
|
44
|
+
throw new Error("FLING_WORKER_SECRET not available. Re-deploy your project with 'fling push' to get the latest configuration.");
|
|
45
|
+
}
|
|
46
|
+
const timestamp = Date.now();
|
|
47
|
+
const payload = `${projectIdStr}.${timestamp}`;
|
|
48
|
+
const signature = await hmacSha256(secretStr, payload);
|
|
49
|
+
return `${payload}.${signature}`;
|
|
50
|
+
}
|
|
51
|
+
let cachedIdentityToken = null;
|
|
52
|
+
const TOKEN_CACHE_TTL_MS = 30_000;
|
|
53
|
+
export async function getProjectHeaders() {
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
if (!cachedIdentityToken || cachedIdentityToken.expiresAt <= now) {
|
|
56
|
+
const token = await generateIdentityToken();
|
|
57
|
+
cachedIdentityToken = { token, expiresAt: now + TOKEN_CACHE_TTL_MS };
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
"X-Fling-Identity": cachedIdentityToken.token,
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// ═══════════════════════════════════════════════════════
|
|
65
|
+
// ERROR RESPONSE PARSING
|
|
66
|
+
// ═══════════════════════════════════════════════════════
|
|
67
|
+
/**
|
|
68
|
+
* Parse an error response and throw with a descriptive message.
|
|
69
|
+
* Handles both JSON and non-JSON error bodies gracefully.
|
|
70
|
+
*/
|
|
71
|
+
export async function throwResponseError(response, defaultMessage) {
|
|
72
|
+
const text = await response.text();
|
|
73
|
+
try {
|
|
74
|
+
const json = JSON.parse(text);
|
|
75
|
+
throw new Error(json.error ?? defaultMessage);
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
if (e instanceof SyntaxError) {
|
|
79
|
+
throw new Error(`${defaultMessage} (HTTP ${response.status}): ${text}`);
|
|
80
|
+
}
|
|
81
|
+
throw e;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Reset the identity token cache. Exposed for testing only.
|
|
86
|
+
* @internal
|
|
87
|
+
*/
|
|
88
|
+
export function __resetTokenCache() {
|
|
89
|
+
cachedIdentityToken = null;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=plugin-common.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-common.js","sourceRoot":"","sources":["../../src/worker-runtime/plugin-common.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAUtC;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,WAAmB;IACnD,OAAO,SAAS,WAAW,CAAC,IAAY,EAAE,IAAkB;QAC1D,MAAM,GAAG,GAAG,QAAQ,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,GAAG,CAAC,WAAW,CAAC,CAAC;QAEjC,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,SAAS,IAAI,OAAQ,OAAmC,CAAC,OAAO,CAAC,KAAK,UAAU,EAAE,CAAC;YACrH,MAAM,IAAI,KAAK,CACb,GAAG,WAAW,kCAAkC;gBAChD,sEAAsE,CACvE,CAAC;QACJ,CAAC;QAED,OAAQ,OAA0B,CAAC,KAAK,CAAC,iBAAiB,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;IAC1E,CAAC,CAAC;AACJ,CAAC;AAED,0DAA0D;AAC1D,yBAAyB;AACzB,0DAA0D;AAE1D,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAc,EAAE,OAAe;IAC9D,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CACvC,KAAK,EACL,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EACtB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EACjC,KAAK,EACL,CAAC,MAAM,CAAC,CACT,CAAC;IACF,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IACjF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,SAAS,CAAC,CAAC;SACzC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;SAC3C,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,MAAM,GAAG,GAAG,QAAQ,EAAE,CAAC;IACvB,MAAM,SAAS,GAAG,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAC1C,MAAM,YAAY,GAAG,GAAG,CAAC,qBAAqB,CAAC,CAAC;IAEhD,MAAM,YAAY,GAAG,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAC9D,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACzD,MAAM,SAAS,GAAG,OAAO,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IAEvE,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,8GAA8G,CAC/G,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAG,GAAG,YAAY,IAAI,SAAS,EAAE,CAAC;IAC/C,MAAM,SAAS,GAAG,MAAM,UAAU,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAEvD,OAAO,GAAG,OAAO,IAAI,SAAS,EAAE,CAAC;AACnC,CAAC;AAED,IAAI,mBAAmB,GAAgD,IAAI,CAAC;AAC5E,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,IAAI,CAAC,mBAAmB,IAAI,mBAAmB,CAAC,SAAS,IAAI,GAAG,EAAE,CAAC;QACjE,MAAM,KAAK,GAAG,MAAM,qBAAqB,EAAE,CAAC;QAC5C,mBAAmB,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,GAAG,kBAAkB,EAAE,CAAC;IACvE,CAAC;IAED,OAAO;QACL,kBAAkB,EAAE,mBAAmB,CAAC,KAAK;QAC7C,cAAc,EAAE,kBAAkB;KACnC,CAAC;AACJ,CAAC;AAED,0DAA0D;AAC1D,yBAAyB;AACzB,0DAA0D;AAE1D;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,QAAkB,EAAE,cAAsB;IACjF,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAuB,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,cAAc,CAAC,CAAC;IAChD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,YAAY,WAAW,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,GAAG,cAAc,UAAU,QAAQ,CAAC,MAAM,MAAM,IAAI,EAAE,CAAC,CAAC;QAC1E,CAAC;QACD,MAAM,CAAC,CAAC;IACV,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB;IAC/B,mBAAmB,GAAG,IAAI,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fling Worker Runtime - Slack Integration
|
|
3
|
+
*
|
|
4
|
+
* Worker-compatible Slack runtime for Cloudflare Workers deployment.
|
|
5
|
+
* This module provides Slack-specific exports for the worker environment.
|
|
6
|
+
*
|
|
7
|
+
* Used via the "flingit/plugin/slack" subpath export.
|
|
8
|
+
*/
|
|
9
|
+
export type { SlackEvent, SlackBlock, SlackMessage, SendMessageOptions, EditMessageOptions, } from "../shared/slack-types.js";
|
|
10
|
+
import type { SlackEvent, SlackMessage, SendMessageOptions, EditMessageOptions } from "../shared/slack-types.js";
|
|
11
|
+
/**
|
|
12
|
+
* An app mention event from Slack.
|
|
13
|
+
*/
|
|
14
|
+
export interface MentionEvent {
|
|
15
|
+
type: "app_mention";
|
|
16
|
+
channel: string;
|
|
17
|
+
user: string;
|
|
18
|
+
text: string;
|
|
19
|
+
ts: string;
|
|
20
|
+
thread_ts?: string;
|
|
21
|
+
team?: string;
|
|
22
|
+
}
|
|
23
|
+
type MentionHandler = (event: MentionEvent) => Promise<void> | void;
|
|
24
|
+
type GenericEventHandler = (event: SlackEvent) => Promise<void> | void;
|
|
25
|
+
/**
|
|
26
|
+
* Internal: Route an event to the appropriate handler.
|
|
27
|
+
*/
|
|
28
|
+
export declare function __handleSlackEvent(event: SlackEvent): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Common entry point for the bundler-generated worker.
|
|
31
|
+
*/
|
|
32
|
+
export declare function __handlePluginRequest(request: Request): Promise<Response>;
|
|
33
|
+
export declare const slack: {
|
|
34
|
+
/**
|
|
35
|
+
* Register a handler for @mentions of your bot.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* slack.onMention(async (event) => {
|
|
40
|
+
* await slack.sendMessage({
|
|
41
|
+
* channelId: event.channel,
|
|
42
|
+
* threadTs: event.ts,
|
|
43
|
+
* text: "Hey! How can I help?",
|
|
44
|
+
* });
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
onMention(handler: MentionHandler): void;
|
|
49
|
+
/**
|
|
50
|
+
* Register a fallback handler for events that aren't handled by other handlers.
|
|
51
|
+
* Use this for advanced cases where you need to handle raw events.
|
|
52
|
+
*/
|
|
53
|
+
onEvent(handler: GenericEventHandler): void;
|
|
54
|
+
/**
|
|
55
|
+
* Send a message to a Slack channel.
|
|
56
|
+
*/
|
|
57
|
+
sendMessage(options: SendMessageOptions): Promise<SlackMessage>;
|
|
58
|
+
/**
|
|
59
|
+
* Edit a message.
|
|
60
|
+
*/
|
|
61
|
+
editMessage(channelId: string, ts: string, options: EditMessageOptions): Promise<SlackMessage>;
|
|
62
|
+
/**
|
|
63
|
+
* Add a reaction to a message.
|
|
64
|
+
*/
|
|
65
|
+
addReaction(channelId: string, ts: string, emoji: string): Promise<void>;
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=slack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../src/worker-runtime/slack.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,YAAY,EACV,UAAU,EACV,UAAU,EACV,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EACV,UAAU,EACV,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,0BAA0B,CAAC;AAMlC;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,aAAa,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAgBD,KAAK,cAAc,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AACpE,KAAK,mBAAmB,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAMvE;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAczE;AAMD;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAa/E;AAED,eAAO,MAAM,KAAK;IAChB;;;;;;;;;;;;;OAaG;uBACgB,cAAc,GAAG,IAAI;IAIxC;;;OAGG;qBACc,mBAAmB,GAAG,IAAI;IAI3C;;OAEG;yBACwB,kBAAkB,GAAG,OAAO,CAAC,YAAY,CAAC;IAmBrE;;OAEG;2BAEU,MAAM,MACb,MAAM,WACD,kBAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC;IAiBxB;;OAEG;2BAC0B,MAAM,MAAM,MAAM,SAAS,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAa/E,CAAC"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fling Worker Runtime - Slack Integration
|
|
3
|
+
*
|
|
4
|
+
* Worker-compatible Slack runtime for Cloudflare Workers deployment.
|
|
5
|
+
* This module provides Slack-specific exports for the worker environment.
|
|
6
|
+
*
|
|
7
|
+
* Used via the "flingit/plugin/slack" subpath export.
|
|
8
|
+
*/
|
|
9
|
+
import { getPluginMeta } from "../shared/plugin-metadata.js";
|
|
10
|
+
import { createPluginFetch, getProjectHeaders, throwResponseError } from "./plugin-common.js";
|
|
11
|
+
// ═══════════════════════════════════════════════════════
|
|
12
|
+
// INTERNAL HELPERS
|
|
13
|
+
// ═══════════════════════════════════════════════════════
|
|
14
|
+
// Service binding name from plugin metadata (single source of truth)
|
|
15
|
+
const meta = getPluginMeta("slack");
|
|
16
|
+
if (!meta)
|
|
17
|
+
throw new Error("slack plugin not found in metadata registry");
|
|
18
|
+
const pluginFetch = createPluginFetch(meta.serviceBinding);
|
|
19
|
+
// Registered handlers
|
|
20
|
+
let _mentionHandler = null;
|
|
21
|
+
let _fallbackHandler = null;
|
|
22
|
+
/**
|
|
23
|
+
* Internal: Route an event to the appropriate handler.
|
|
24
|
+
*/
|
|
25
|
+
export async function __handleSlackEvent(event) {
|
|
26
|
+
// Handle app mentions
|
|
27
|
+
if (event.type === "event_callback") {
|
|
28
|
+
const innerEvent = event.event;
|
|
29
|
+
if (innerEvent?.type === "app_mention" && _mentionHandler) {
|
|
30
|
+
await _mentionHandler(innerEvent);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Fallback handler
|
|
35
|
+
if (_fallbackHandler) {
|
|
36
|
+
await _fallbackHandler(event);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// ═══════════════════════════════════════════════════════
|
|
40
|
+
// PUBLIC API
|
|
41
|
+
// ═══════════════════════════════════════════════════════
|
|
42
|
+
/**
|
|
43
|
+
* Common entry point for the bundler-generated worker.
|
|
44
|
+
*/
|
|
45
|
+
export async function __handlePluginRequest(request) {
|
|
46
|
+
const event = await request.json();
|
|
47
|
+
try {
|
|
48
|
+
await __handleSlackEvent(event);
|
|
49
|
+
return new Response(JSON.stringify({ ok: true }), {
|
|
50
|
+
headers: { "Content-Type": "application/json" },
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error("Slack event handler error:", error);
|
|
55
|
+
return new Response(JSON.stringify({ ok: true }), {
|
|
56
|
+
headers: { "Content-Type": "application/json" },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export const slack = {
|
|
61
|
+
/**
|
|
62
|
+
* Register a handler for @mentions of your bot.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* slack.onMention(async (event) => {
|
|
67
|
+
* await slack.sendMessage({
|
|
68
|
+
* channelId: event.channel,
|
|
69
|
+
* threadTs: event.ts,
|
|
70
|
+
* text: "Hey! How can I help?",
|
|
71
|
+
* });
|
|
72
|
+
* });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
onMention(handler) {
|
|
76
|
+
_mentionHandler = handler;
|
|
77
|
+
},
|
|
78
|
+
/**
|
|
79
|
+
* Register a fallback handler for events that aren't handled by other handlers.
|
|
80
|
+
* Use this for advanced cases where you need to handle raw events.
|
|
81
|
+
*/
|
|
82
|
+
onEvent(handler) {
|
|
83
|
+
_fallbackHandler = handler;
|
|
84
|
+
},
|
|
85
|
+
/**
|
|
86
|
+
* Send a message to a Slack channel.
|
|
87
|
+
*/
|
|
88
|
+
async sendMessage(options) {
|
|
89
|
+
const response = await pluginFetch("/proxy/messages", {
|
|
90
|
+
method: "POST",
|
|
91
|
+
headers: await getProjectHeaders(),
|
|
92
|
+
body: JSON.stringify({
|
|
93
|
+
channelId: options.channelId,
|
|
94
|
+
text: options.text,
|
|
95
|
+
blocks: options.blocks,
|
|
96
|
+
threadTs: options.threadTs,
|
|
97
|
+
}),
|
|
98
|
+
});
|
|
99
|
+
if (!response.ok) {
|
|
100
|
+
await throwResponseError(response, "Failed to send message");
|
|
101
|
+
}
|
|
102
|
+
return response.json();
|
|
103
|
+
},
|
|
104
|
+
/**
|
|
105
|
+
* Edit a message.
|
|
106
|
+
*/
|
|
107
|
+
async editMessage(channelId, ts, options) {
|
|
108
|
+
const response = await pluginFetch(`/proxy/messages/${channelId}/${ts}`, {
|
|
109
|
+
method: "PATCH",
|
|
110
|
+
headers: await getProjectHeaders(),
|
|
111
|
+
body: JSON.stringify({
|
|
112
|
+
text: options.text,
|
|
113
|
+
blocks: options.blocks,
|
|
114
|
+
}),
|
|
115
|
+
});
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
await throwResponseError(response, "Failed to edit message");
|
|
118
|
+
}
|
|
119
|
+
return response.json();
|
|
120
|
+
},
|
|
121
|
+
/**
|
|
122
|
+
* Add a reaction to a message.
|
|
123
|
+
*/
|
|
124
|
+
async addReaction(channelId, ts, emoji) {
|
|
125
|
+
const encodedEmoji = encodeURIComponent(emoji);
|
|
126
|
+
const response = await pluginFetch(`/proxy/reactions/${channelId}/${ts}/${encodedEmoji}`, {
|
|
127
|
+
method: "PUT",
|
|
128
|
+
headers: await getProjectHeaders(),
|
|
129
|
+
});
|
|
130
|
+
if (!response.ok) {
|
|
131
|
+
await throwResponseError(response, "Failed to add reaction");
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
//# sourceMappingURL=slack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack.js","sourceRoot":"","sources":["../../src/worker-runtime/slack.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flingit",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.20",
|
|
4
4
|
"description": "Personal Software Platform - Build and deploy personal tools through conversation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -24,6 +24,12 @@
|
|
|
24
24
|
"import": "./dist/runtime/discord.js",
|
|
25
25
|
"default": "./dist/runtime/discord.js"
|
|
26
26
|
},
|
|
27
|
+
"./plugin/slack": {
|
|
28
|
+
"workerd": "./dist/worker-runtime/slack.js",
|
|
29
|
+
"types": "./dist/runtime/slack.d.ts",
|
|
30
|
+
"import": "./dist/runtime/slack.js",
|
|
31
|
+
"default": "./dist/runtime/slack.js"
|
|
32
|
+
},
|
|
27
33
|
"./worker-runtime": {
|
|
28
34
|
"types": "./dist/worker-runtime/index.d.ts",
|
|
29
35
|
"import": "./dist/worker-runtime/index.js"
|
|
@@ -67,9 +73,11 @@
|
|
|
67
73
|
"dotenv": "^17.2.3",
|
|
68
74
|
"esbuild": "^0.27.2",
|
|
69
75
|
"hono": "^4.11.7",
|
|
70
|
-
"tsx": "^4.19.0"
|
|
76
|
+
"tsx": "^4.19.0",
|
|
77
|
+
"zod": "^3.25.76"
|
|
71
78
|
},
|
|
72
79
|
"devDependencies": {
|
|
80
|
+
"@cloudflare/workers-types": "^4.0.0",
|
|
73
81
|
"@eslint/js": "^9.39.2",
|
|
74
82
|
"@types/better-sqlite3": "^7.6.12",
|
|
75
83
|
"@types/node": "^24.10.9",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
0862078e056a789ecb1426fcebd51516
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
# Discord Skill
|
|
2
2
|
|
|
3
|
-
Build Discord chatops bots with Fling.
|
|
3
|
+
Build Discord chatops bots with Fling. Handle slash commands, send messages, handle interactions, and react to events — all from your Fling project.
|
|
4
4
|
|
|
5
5
|
## Setup
|
|
6
6
|
|
|
7
7
|
Discord requires a one-time setup before use:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm exec fling plugin install discord # Connect Discord account (opens browser)
|
|
11
|
-
npm exec fling
|
|
12
|
-
npm exec fling push # Deploy code + register slash commands
|
|
10
|
+
npm exec fling plugin install discord # Connect Discord account + add bot to a server (opens browser)
|
|
11
|
+
npm exec fling push # Deploy code
|
|
13
12
|
```
|
|
14
13
|
|
|
15
14
|
Run these commands yourself — you have bash access.
|
|
@@ -20,48 +19,31 @@ Run these commands yourself — you have bash access.
|
|
|
20
19
|
import { app } from "flingit";
|
|
21
20
|
import { discord } from "flingit/plugin/discord";
|
|
22
21
|
|
|
23
|
-
// 1.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (interaction.data?.name === "deploy") {
|
|
47
|
-
const branch = interaction.data.options?.[0]?.value as string;
|
|
48
|
-
await discord.replyToInteraction(interaction, {
|
|
49
|
-
content: `Deploying ${branch}...`
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// Do work after the initial reply...
|
|
53
|
-
const result = await runDeploy(branch);
|
|
54
|
-
|
|
55
|
-
// Send a followup message (no 3-second limit)
|
|
56
|
-
await discord.sendFollowup(interaction, {
|
|
57
|
-
content: `Deployed ${branch}: ${result}`
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return c.json({ ok: true });
|
|
22
|
+
// 1. Handle slash commands using onCommand(name, description, handler)
|
|
23
|
+
// Commands are auto-registered with Discord on `fling push`
|
|
24
|
+
discord.onCommand("ping", "Check if the bot is alive", async (interaction) => {
|
|
25
|
+
await discord.reply(interaction, {
|
|
26
|
+
content: "Pong!",
|
|
27
|
+
ephemeral: true // Only visible to the user who ran the command
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
discord.onCommand("deploy", "Deploy a branch", async (interaction, options) => {
|
|
32
|
+
const branch = options.getString("branch") ?? "main";
|
|
33
|
+
await discord.reply(interaction, {
|
|
34
|
+
content: `Deploying ${branch}...`
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Do work after the initial reply...
|
|
38
|
+
const result = await runDeploy(branch);
|
|
39
|
+
|
|
40
|
+
// Send a followup message (no 3-second limit)
|
|
41
|
+
await discord.followup(interaction, {
|
|
42
|
+
content: `Deployed ${branch}: ${result}`
|
|
43
|
+
});
|
|
62
44
|
});
|
|
63
45
|
|
|
64
|
-
//
|
|
46
|
+
// 2. Send messages proactively (e.g., from webhooks or cron)
|
|
65
47
|
app.post("/api/notify", async (c) => {
|
|
66
48
|
const { channelId, text } = await c.req.json();
|
|
67
49
|
|
|
@@ -82,67 +64,41 @@ All methods are imported from `"flingit/plugin/discord"`:
|
|
|
82
64
|
import { discord } from "flingit/plugin/discord";
|
|
83
65
|
```
|
|
84
66
|
|
|
85
|
-
### discord.
|
|
86
|
-
|
|
87
|
-
Register slash commands. **Must be exported** as a top-level `export const commands` for `fling push` to detect them.
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
90
|
-
export const commands = discord.defineCommands([
|
|
91
|
-
{ name: "status", description: "Show system status" },
|
|
92
|
-
{
|
|
93
|
-
name: "lookup",
|
|
94
|
-
description: "Look up a user",
|
|
95
|
-
options: [
|
|
96
|
-
{ name: "email", type: "string", description: "User email", required: true },
|
|
97
|
-
{ name: "verbose", type: "boolean", description: "Show full details" }
|
|
98
|
-
]
|
|
99
|
-
}
|
|
100
|
-
]);
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
**Command names:** 1-32 chars, lowercase letters, numbers, and hyphens only.
|
|
104
|
-
|
|
105
|
-
**Option types:** `"string"`, `"integer"`, `"boolean"`, `"user"`, `"channel"`, `"role"`
|
|
106
|
-
|
|
107
|
-
Options can have predefined choices:
|
|
108
|
-
|
|
109
|
-
```typescript
|
|
110
|
-
{
|
|
111
|
-
name: "env",
|
|
112
|
-
type: "string",
|
|
113
|
-
description: "Target environment",
|
|
114
|
-
required: true,
|
|
115
|
-
choices: [
|
|
116
|
-
{ name: "Production", value: "prod" },
|
|
117
|
-
{ name: "Staging", value: "staging" }
|
|
118
|
-
]
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### discord.verifyInteraction(c)
|
|
67
|
+
### discord.onCommand(name, description, handler)
|
|
123
68
|
|
|
124
|
-
|
|
69
|
+
Register a handler for a specific slash command. Commands are **automatically registered** with Discord when you run `fling push` — no need to use the Discord Developer Portal.
|
|
125
70
|
|
|
126
71
|
```typescript
|
|
127
|
-
|
|
128
|
-
const interaction = await discord.verifyInteraction(c);
|
|
129
|
-
|
|
72
|
+
discord.onCommand("ping", "Check if the bot is alive", async (interaction, options) => {
|
|
130
73
|
// interaction.data.name = command name
|
|
131
|
-
//
|
|
74
|
+
// options.getString("param") = get a string option
|
|
75
|
+
// options.getNumber("count") = get a number option
|
|
76
|
+
// options.getBoolean("force") = get a boolean option
|
|
132
77
|
// interaction.member.user = who ran the command (in servers)
|
|
133
78
|
// interaction.guild_id = which server
|
|
134
79
|
// interaction.channel_id = which channel
|
|
80
|
+
await discord.reply(interaction, "Pong!");
|
|
135
81
|
});
|
|
136
82
|
```
|
|
137
83
|
|
|
138
84
|
The platform verifies Discord's cryptographic signature before forwarding to your code — you don't need to handle that.
|
|
139
85
|
|
|
140
|
-
### discord.
|
|
86
|
+
### discord.onEvent(handler)
|
|
87
|
+
|
|
88
|
+
Register a fallback handler for interactions that don't match any `onCommand`. Use for advanced cases.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
discord.onEvent(async (interaction) => {
|
|
92
|
+
// Called for any interaction not handled by onCommand
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### discord.reply(interaction, options)
|
|
141
97
|
|
|
142
98
|
Reply to a slash command. **Must be called within 3 seconds** of receiving the interaction.
|
|
143
99
|
|
|
144
100
|
```typescript
|
|
145
|
-
|
|
101
|
+
discord.reply(interaction, {
|
|
146
102
|
content: "Hello!", // Text content (max 2000 chars)
|
|
147
103
|
ephemeral: true, // Only visible to command user (optional)
|
|
148
104
|
embeds: [{ // Rich embeds (optional, max 10)
|
|
@@ -153,16 +109,16 @@ await discord.replyToInteraction(interaction, {
|
|
|
153
109
|
});
|
|
154
110
|
```
|
|
155
111
|
|
|
156
|
-
### discord.
|
|
112
|
+
### discord.followup(interaction, options)
|
|
157
113
|
|
|
158
114
|
Send additional messages after the initial reply. No 3-second time limit.
|
|
159
115
|
|
|
160
116
|
```typescript
|
|
161
|
-
|
|
117
|
+
discord.reply(interaction, { content: "Working on it..." });
|
|
162
118
|
|
|
163
119
|
// ... do async work ...
|
|
164
120
|
|
|
165
|
-
const msg = await discord.
|
|
121
|
+
const msg = await discord.followup(interaction, {
|
|
166
122
|
content: "Done! Here are the results.",
|
|
167
123
|
embeds: [{ title: "Results", description: resultText }]
|
|
168
124
|
});
|
|
@@ -243,23 +199,17 @@ Content max: 2000 characters. Embeds max: 10 per message.
|
|
|
243
199
|
|
|
244
200
|
## Reading Command Options
|
|
245
201
|
|
|
246
|
-
|
|
202
|
+
The `options` helper provides typed accessors for command options:
|
|
247
203
|
|
|
248
204
|
```typescript
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const options = interaction.data.options ?? [];
|
|
254
|
-
const branch = options.find(o => o.name === "branch")?.value as string;
|
|
255
|
-
const force = options.find(o => o.name === "force")?.value as boolean ?? false;
|
|
256
|
-
|
|
257
|
-
await discord.replyToInteraction(interaction, {
|
|
258
|
-
content: `Deploying ${branch}${force ? " (force)" : ""}...`
|
|
259
|
-
});
|
|
260
|
-
}
|
|
205
|
+
discord.onCommand("deploy", "Deploy a branch", async (interaction, options) => {
|
|
206
|
+
const branch = options.getString("branch") ?? "main";
|
|
207
|
+
const force = options.getBoolean("force") ?? false;
|
|
208
|
+
const count = options.getNumber("count") ?? 1;
|
|
261
209
|
|
|
262
|
-
|
|
210
|
+
await discord.reply(interaction, {
|
|
211
|
+
content: `Deploying ${branch}${force ? " (force)" : ""}...`
|
|
212
|
+
});
|
|
263
213
|
});
|
|
264
214
|
```
|
|
265
215
|
|
|
@@ -268,9 +218,7 @@ app.post("/discord", async (c) => {
|
|
|
268
218
|
Access who ran the command and where:
|
|
269
219
|
|
|
270
220
|
```typescript
|
|
271
|
-
|
|
272
|
-
const interaction = await discord.verifyInteraction(c);
|
|
273
|
-
|
|
221
|
+
discord.onCommand("info", "Show context info", async (interaction) => {
|
|
274
222
|
// Who ran it
|
|
275
223
|
const user = interaction.member?.user; // In servers
|
|
276
224
|
// user.id, user.username
|
|
@@ -289,14 +237,12 @@ app.post("/discord", async (c) => {
|
|
|
289
237
|
|
|
290
238
|
```bash
|
|
291
239
|
# Setup
|
|
292
|
-
npm exec fling plugin install discord # Connect Discord (OAuth)
|
|
293
|
-
npm exec fling plugin discord add-server # Claim a server
|
|
294
|
-
npm exec fling plugin discord remove-server # Release a server
|
|
240
|
+
npm exec fling plugin install discord # Connect Discord (OAuth), auto-claims server
|
|
295
241
|
|
|
296
242
|
# Status
|
|
297
243
|
npm exec fling plugin permissions discord # Show connection status + servers
|
|
298
244
|
|
|
299
|
-
# Deployment
|
|
245
|
+
# Deployment
|
|
300
246
|
npm exec fling push
|
|
301
247
|
|
|
302
248
|
# Teardown
|
|
@@ -305,24 +251,21 @@ npm exec fling plugin remove discord # Disconnect, release all servers
|
|
|
305
251
|
|
|
306
252
|
## How It Works
|
|
307
253
|
|
|
308
|
-
1. **`fling plugin install discord`** opens a browser for Discord OAuth. You authorize Fling to
|
|
309
|
-
2. **`
|
|
310
|
-
3. **`
|
|
311
|
-
4. When a user runs a slash command, Discord sends it to the platform, which routes it to your worker
|
|
312
|
-
5. Your
|
|
254
|
+
1. **`fling plugin install discord`** opens a browser for Discord OAuth. You authorize Fling, select a server to add the bot to, and that server is automatically claimed for your project.
|
|
255
|
+
2. **`discord.onCommand(name, description, handler)`** registers a named command handler with a description.
|
|
256
|
+
3. **`fling push`** bundles your code, deploys it, then automatically registers your slash commands with Discord's API for each claimed server. Commands appear instantly in Discord's slash command picker.
|
|
257
|
+
4. When a user runs a slash command, Discord sends it to the platform, which routes it to your worker.
|
|
258
|
+
5. Your handler receives the interaction and calls `discord.reply()` to respond.
|
|
259
|
+
6. To add the bot to additional servers, re-run `fling plugin install discord` (remove and reinstall to re-OAuth), then `fling push` again to register commands in the new server.
|
|
313
260
|
|
|
314
261
|
## Important Constraints
|
|
315
262
|
|
|
316
263
|
1. **Discord features only work in deployed workers** — They throw errors locally. Use `fling push` to deploy, then test in Discord.
|
|
317
264
|
|
|
318
|
-
2. **Reply within 3 seconds** — `
|
|
319
|
-
|
|
320
|
-
3. **Commands must be exported** — `fling push` looks for `export const commands = discord.defineCommands(...)`. If the export is missing, commands won't be registered.
|
|
321
|
-
|
|
322
|
-
4. **Handle interactions at POST /discord** — The platform routes all interactions to this exact path on your worker.
|
|
265
|
+
2. **Reply within 3 seconds** — `discord.reply()` must be called within 3 seconds. For slow operations, reply immediately with "Working..." then use `discord.followup()` for the result.
|
|
323
266
|
|
|
324
|
-
|
|
267
|
+
3. **Channels must be in claimed servers** — `sendMessage()`, `editMessage()`, and `addReaction()` only work in channels belonging to servers claimed by your project.
|
|
325
268
|
|
|
326
|
-
|
|
269
|
+
4. **One project per server** — A Discord server can only be claimed by one Fling project at a time.
|
|
327
270
|
|
|
328
|
-
|
|
271
|
+
5. **Plugin must be installed first** — Run `fling plugin install discord` before using any Discord features. Check with `fling plugin permissions discord`.
|