@diegoaltoworks/chatter 0.33.0 → 0.34.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/channels/whatsapp/images.d.ts +85 -0
- package/dist/channels/whatsapp/images.d.ts.map +1 -0
- package/dist/channels/whatsapp/images.test.d.ts +2 -0
- package/dist/channels/whatsapp/images.test.d.ts.map +1 -0
- package/dist/channels/whatsapp/inbound.d.ts +9 -0
- package/dist/channels/whatsapp/inbound.d.ts.map +1 -1
- package/dist/channels/whatsapp/index.d.ts +1 -0
- package/dist/channels/whatsapp/index.d.ts.map +1 -1
- package/dist/channels/whatsapp/index.js +82 -0
- package/dist/channels/whatsapp/index.mjs +79 -0
- package/package.json +1 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional image requests in the WhatsApp channel: detect a drawing/picture
|
|
3
|
+
* request (with or without an attached photo), route it through `../../images`,
|
|
4
|
+
* and reply with the generated picture. Inert unless `./images` is
|
|
5
|
+
* configured — `createWhatsAppImageHandler` returns `false` for every
|
|
6
|
+
* message when it isn't, so callers fall through to normal chat.
|
|
7
|
+
*
|
|
8
|
+
* This module ships no bot personality: the detector and subject-cleaning
|
|
9
|
+
* patterns are generic drawing verbs (no hardcoded bot names), and every
|
|
10
|
+
* reply string (ack/limit/moderation/error) is caller-configured with no
|
|
11
|
+
* default text.
|
|
12
|
+
*
|
|
13
|
+
* Downloading the attached photo needs Baileys' `downloadMediaMessage`,
|
|
14
|
+
* which — per this subpath's rule that only `./baileys` touches the
|
|
15
|
+
* optional peer dependency at runtime — is injected as `downloadPhoto`
|
|
16
|
+
* rather than imported here. The host wires it via `./whatsapp`'s
|
|
17
|
+
* `loadBaileys` (see docs/channels.md), not this module.
|
|
18
|
+
*
|
|
19
|
+
* Everything pulled in from `../../images` is type-only: each subpath is
|
|
20
|
+
* built as its own bundle, so a value import here (even just for an
|
|
21
|
+
* `instanceof` check) would inline the whole `./images` module — OpenAI SDK
|
|
22
|
+
* included — into `./whatsapp`, and importing from the `../../images`
|
|
23
|
+
* barrel rather than its leaf files pulls in every sibling module's value
|
|
24
|
+
* imports too. Error mapping below checks `code`/`name` structurally
|
|
25
|
+
* instead, which also means it works when the error crossed a bundle
|
|
26
|
+
* boundary (the two subpaths' `ImageGenerationError` classes are not
|
|
27
|
+
* `instanceof`-compatible with each other).
|
|
28
|
+
*/
|
|
29
|
+
import type { WAMessage, WASocket } from "@whiskeysockets/baileys";
|
|
30
|
+
import type { CaptionComposer } from "../../images/captionComposer";
|
|
31
|
+
import type { ImageUploader } from "../../images/uploader";
|
|
32
|
+
export interface DrawRequestDetectorConfig {
|
|
33
|
+
/** Tested against the raw message text to decide whether it's an image request. Defaults to a generic set of drawing verbs — no bot-specific names. */
|
|
34
|
+
detectPattern?: RegExp;
|
|
35
|
+
/** Applied in order to derive the subject from the raw text (mention tokens, greetings/politeness, the draw verb itself). */
|
|
36
|
+
stripPatterns?: readonly RegExp[];
|
|
37
|
+
}
|
|
38
|
+
export declare function isDrawRequest(text: string, config?: DrawRequestDetectorConfig): boolean;
|
|
39
|
+
/** Cleans a draw request into a scene subject: "@1234 please draw me a cat" -> "cat". Falls back to the trimmed original when the patterns strip everything (or nothing). */
|
|
40
|
+
export declare function drawSubject(text: string, config?: DrawRequestDetectorConfig): string;
|
|
41
|
+
/** Neutral, overridable reply strings — this module ships no bot personality. Unset = silent for that outcome. */
|
|
42
|
+
export interface WhatsAppImageStrings {
|
|
43
|
+
/** Sent immediately on accepting the request, before generation starts. */
|
|
44
|
+
ack?: string;
|
|
45
|
+
/** Sent when the per-sender/day cap (`checkAndReserve`) blocks the request. */
|
|
46
|
+
limitReached?: string;
|
|
47
|
+
/** Sent when the image API blocks the request on moderation grounds. */
|
|
48
|
+
moderationBlocked?: string;
|
|
49
|
+
/** Sent for any other generation/upload/download failure. */
|
|
50
|
+
error?: string;
|
|
51
|
+
}
|
|
52
|
+
export interface WhatsAppImageHandlerDeps {
|
|
53
|
+
/** The slice of `./images`' `ImageUploader` this module needs. */
|
|
54
|
+
images: Pick<ImageUploader, "isConfigured" | "peekCached" | "getOrCreateImage">;
|
|
55
|
+
/**
|
|
56
|
+
* Per-sender/day reservation (e.g. `./usage`'s `DailyLimiter.checkAndReserve`).
|
|
57
|
+
* Omit to skip the cap entirely. Checked AFTER the cache, so a cache hit
|
|
58
|
+
* never burns a unit.
|
|
59
|
+
*/
|
|
60
|
+
checkAndReserve?: (senderId: string) => Promise<{
|
|
61
|
+
allowed: boolean;
|
|
62
|
+
}>;
|
|
63
|
+
/**
|
|
64
|
+
* Downloads the attached photo as bytes. Omit to ignore attached photos —
|
|
65
|
+
* text-only requests still work. See the module docstring for why this is
|
|
66
|
+
* injected rather than imported.
|
|
67
|
+
*/
|
|
68
|
+
downloadPhoto?: (message: WAMessage) => Promise<Uint8Array>;
|
|
69
|
+
/** Best-effort tailored caption for the reply. Omit to send the image with no caption. */
|
|
70
|
+
captionComposer?: Pick<CaptionComposer, "composeCaption">;
|
|
71
|
+
detector?: DrawRequestDetectorConfig;
|
|
72
|
+
strings?: WhatsAppImageStrings;
|
|
73
|
+
}
|
|
74
|
+
export interface WhatsAppImageRequestContext {
|
|
75
|
+
sock: WASocket;
|
|
76
|
+
message: WAMessage;
|
|
77
|
+
chatId: string;
|
|
78
|
+
senderId: string;
|
|
79
|
+
text: string;
|
|
80
|
+
hasPhoto: boolean;
|
|
81
|
+
}
|
|
82
|
+
/** `true` = the message was an image request and this module replied to it (successfully or with a mapped error) — the caller must not also route it through normal chat. `false` = not an image request, or `./images` isn't configured. */
|
|
83
|
+
export type WhatsAppImageHandler = (ctx: WhatsAppImageRequestContext) => Promise<boolean>;
|
|
84
|
+
export declare function createWhatsAppImageHandler(deps: WhatsAppImageHandlerDeps): WhatsAppImageHandler;
|
|
85
|
+
//# sourceMappingURL=images.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/images.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAkB3D,MAAM,WAAW,yBAAyB;IACxC,uJAAuJ;IACvJ,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6HAA6H;IAC7H,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAOD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,yBAA8B,GAAG,OAAO,CAE3F;AAED,6KAA6K;AAC7K,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,yBAA8B,GAAG,MAAM,CAUxF;AAED,kHAAkH;AAClH,MAAM,WAAW,oBAAoB;IACnC,2EAA2E;IAC3E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,wBAAwB;IACvC,kEAAkE;IAClE,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,cAAc,GAAG,YAAY,GAAG,kBAAkB,CAAC,CAAC;IAChF;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACtE;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IAC5D,0FAA0F;IAC1F,eAAe,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,gBAAgB,CAAC,CAAC;IAC1D,QAAQ,CAAC,EAAE,yBAAyB,CAAC;IACrC,OAAO,CAAC,EAAE,oBAAoB,CAAC;CAChC;AAED,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,SAAS,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,6OAA6O;AAC7O,MAAM,MAAM,oBAAoB,GAAG,CAAC,GAAG,EAAE,2BAA2B,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAsB1F,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,wBAAwB,GAAG,oBAAoB,CA4C/F"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"images.test.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/images.test.ts"],"names":[],"mappings":""}
|
|
@@ -50,6 +50,7 @@ import type { PromptLoader } from "../../core/prompts";
|
|
|
50
50
|
import type { VectorStore } from "../../core/retrieval";
|
|
51
51
|
import { type SessionIdentityRegistry } from "../gates";
|
|
52
52
|
import type { WhatsAppMessageEvent } from "./channel";
|
|
53
|
+
import type { WhatsAppImageHandler } from "./images";
|
|
53
54
|
export declare function isGroupJid(jid: string): boolean;
|
|
54
55
|
/** "447700900123:17@s.whatsapp.net" (device-suffixed) and "447700900123@s.whatsapp.net" match. */
|
|
55
56
|
export declare function jidsMatch(a: string | undefined, b: string | undefined): boolean;
|
|
@@ -124,6 +125,14 @@ export interface WhatsAppInboundConfig {
|
|
|
124
125
|
senderPhone: string;
|
|
125
126
|
text: string;
|
|
126
127
|
}) => string | undefined | Promise<string | undefined>;
|
|
128
|
+
/**
|
|
129
|
+
* Optional image-request routing (see `./images`). Consulted right before
|
|
130
|
+
* the normal chat pipeline; a `true` result means the message was handled
|
|
131
|
+
* as an image request (successfully or with a mapped error string) and
|
|
132
|
+
* this handler returns without answering through chat. Inert unless
|
|
133
|
+
* `./images` is configured.
|
|
134
|
+
*/
|
|
135
|
+
images?: WhatsAppImageHandler;
|
|
127
136
|
now?: () => number;
|
|
128
137
|
}
|
|
129
138
|
/** Builds the `onMessage` handler for `createWhatsAppChannel` (see `./channel`) — see the module docstring for how to wire `ServerDependencies` into it. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/inbound.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,KAAK,QAAQ,EAAc,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,KAAK,UAAU,EAAkB,MAAM,oBAAoB,CAAC;AAErE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAKL,KAAK,uBAAuB,EAE7B,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/inbound.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,KAAK,QAAQ,EAAc,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,KAAK,UAAU,EAAkB,MAAM,oBAAoB,CAAC;AAErE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAKL,KAAK,uBAAuB,EAE7B,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAIrD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE/C;AAED,kGAAkG;AAClG,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAI/E;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGpD;AAeD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,SAAS,EAClB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CA+BjB;AAID,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,GAAG,gBAAgB,CAWnE;AAED,mHAAmH;AACnH,wBAAgB,WAAW,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,CAWtD;AAID,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,QAAQ,EAAE,uBAAuB,CAAC;IAClC,4HAA4H;IAC5H,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gHAAgH;IAChH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,cAAc,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,oGAAoG;IACpG,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;KACd,KAAK,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACvD;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,oBAAoB,CAAC;IAC9B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAqBD,4JAA4J;AAC5J,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,qBAAqB,GAC5B,CAAC,KAAK,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAuHhD"}
|
|
@@ -29,6 +29,7 @@ export type { Baileys } from "./baileys";
|
|
|
29
29
|
export { loadBaileys } from "./baileys";
|
|
30
30
|
export { acquireSessionLease, createWhatsAppChannel, type LeaseGatedConnectDeps, reconnectDelayMs, senderNameFor, shutdownWaSessions, type WaSessionHandle, type WhatsAppChannelConfig, type WhatsAppMessageEvent, } from "./channel";
|
|
31
31
|
export { decrypt, encrypt } from "./crypto";
|
|
32
|
+
export { createWhatsAppImageHandler, type DrawRequestDetectorConfig, drawSubject, isDrawRequest, type WhatsAppImageHandler, type WhatsAppImageHandlerDeps, type WhatsAppImageRequestContext, type WhatsAppImageStrings, } from "./images";
|
|
32
33
|
export { createWhatsAppInboundHandler, extractText, isGroupJid, jidsMatch, jidToPhoneNumber, messageContext, senderPhoneFor, type WaMessageContext, type WhatsAppInboundConfig, } from "./inbound";
|
|
33
34
|
export { canAcquireLease, createTursoWaLeaseStore, LEASE_HEARTBEAT_MS, LEASE_STALE_MS, LEASE_WAIT_MS, type WaLeaseRow, type WaLeaseStore, } from "./lease";
|
|
34
35
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,qBAAqB,EAC1B,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EACL,4BAA4B,EAC5B,WAAW,EACX,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,GAC3B,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,KAAK,UAAU,EACf,KAAK,YAAY,GAClB,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,qBAAqB,EAC1B,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EACL,0BAA0B,EAC1B,KAAK,yBAAyB,EAC9B,WAAW,EACX,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EAChC,KAAK,oBAAoB,GAC1B,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,4BAA4B,EAC5B,WAAW,EACX,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,GAC3B,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,KAAK,UAAU,EACf,KAAK,YAAY,GAClB,MAAM,SAAS,CAAC"}
|
|
@@ -37,10 +37,13 @@ __export(index_exports, {
|
|
|
37
37
|
canAcquireLease: () => canAcquireLease,
|
|
38
38
|
createTursoWaLeaseStore: () => createTursoWaLeaseStore,
|
|
39
39
|
createWhatsAppChannel: () => createWhatsAppChannel,
|
|
40
|
+
createWhatsAppImageHandler: () => createWhatsAppImageHandler,
|
|
40
41
|
createWhatsAppInboundHandler: () => createWhatsAppInboundHandler,
|
|
41
42
|
decrypt: () => decrypt,
|
|
43
|
+
drawSubject: () => drawSubject,
|
|
42
44
|
encrypt: () => encrypt,
|
|
43
45
|
extractText: () => extractText,
|
|
46
|
+
isDrawRequest: () => isDrawRequest,
|
|
44
47
|
isGroupJid: () => isGroupJid,
|
|
45
48
|
jidToPhoneNumber: () => jidToPhoneNumber,
|
|
46
49
|
jidsMatch: () => jidsMatch,
|
|
@@ -467,6 +470,71 @@ function createWhatsAppChannel(config) {
|
|
|
467
470
|
};
|
|
468
471
|
}
|
|
469
472
|
|
|
473
|
+
// src/channels/whatsapp/images.ts
|
|
474
|
+
var DEFAULT_DETECT_PATTERN = /\b(draw|paint|sketch|illustrate)\b|\b(make|generate|create)\b[^.!?]{0,20}\b(picture|image|drawing|photo|cartoon)\b/i;
|
|
475
|
+
var DEFAULT_STRIP_PATTERNS = [
|
|
476
|
+
/@\d+/g,
|
|
477
|
+
/^(hey|hi|please|ok)[,!\s]+/i,
|
|
478
|
+
/^(can|could|will)\s+you\s+/i,
|
|
479
|
+
// Longest-alternative-first ((an|a), not (a|an)): a first-match-wins
|
|
480
|
+
// alternation would otherwise take "a" out of "an" and leave a stray "n".
|
|
481
|
+
/^(please\s+)?(draw|paint|sketch|illustrate|make|generate|create)\s+(me|us)?\s*(an|a)?\s*(picture|image|drawing|sketch|photo|cartoon)?\s*(of\s+)?/i
|
|
482
|
+
];
|
|
483
|
+
function testPattern(pattern, text) {
|
|
484
|
+
return new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, "")).test(text);
|
|
485
|
+
}
|
|
486
|
+
function isDrawRequest(text, config = {}) {
|
|
487
|
+
return testPattern(config.detectPattern ?? DEFAULT_DETECT_PATTERN, text);
|
|
488
|
+
}
|
|
489
|
+
function drawSubject(text, config = {}) {
|
|
490
|
+
let subject = text;
|
|
491
|
+
for (const pattern of config.stripPatterns ?? DEFAULT_STRIP_PATTERNS) {
|
|
492
|
+
subject = subject.replace(pattern, " ").replace(/\s+/g, " ").trim();
|
|
493
|
+
}
|
|
494
|
+
return subject.length > 0 ? subject : text.trim();
|
|
495
|
+
}
|
|
496
|
+
function isModerationError(error) {
|
|
497
|
+
return typeof error === "object" && error !== null && error.name === "ImageGenerationError" && error.code === "moderation";
|
|
498
|
+
}
|
|
499
|
+
async function replyText(sock, chatId, message, text) {
|
|
500
|
+
if (!text) return;
|
|
501
|
+
await sock.sendMessage(chatId, { text }, { quoted: message }).catch(() => void 0);
|
|
502
|
+
}
|
|
503
|
+
function createWhatsAppImageHandler(deps) {
|
|
504
|
+
return async function handleWhatsAppImageRequest(ctx) {
|
|
505
|
+
if (!deps.images.isConfigured()) return false;
|
|
506
|
+
if (!isDrawRequest(ctx.text, deps.detector)) return false;
|
|
507
|
+
const subject = drawSubject(ctx.text, deps.detector);
|
|
508
|
+
try {
|
|
509
|
+
const photoBytes = ctx.hasPhoto ? await deps.downloadPhoto?.(ctx.message) : void 0;
|
|
510
|
+
const inputImages = photoBytes ? [{ bytes: photoBytes }] : void 0;
|
|
511
|
+
const request = { prompt: subject, inputImages };
|
|
512
|
+
if (deps.checkAndReserve) {
|
|
513
|
+
const cached2 = await deps.images.peekCached(request);
|
|
514
|
+
if (!cached2) {
|
|
515
|
+
const reserved = await deps.checkAndReserve(ctx.senderId);
|
|
516
|
+
if (!reserved.allowed) {
|
|
517
|
+
await replyText(ctx.sock, ctx.chatId, ctx.message, deps.strings?.limitReached);
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
await replyText(ctx.sock, ctx.chatId, ctx.message, deps.strings?.ack);
|
|
523
|
+
const image = await deps.images.getOrCreateImage(request);
|
|
524
|
+
const caption = deps.captionComposer ? await deps.captionComposer.composeCaption({ subject }) : void 0;
|
|
525
|
+
await ctx.sock.sendMessage(
|
|
526
|
+
ctx.chatId,
|
|
527
|
+
caption ? { image: { url: image.url }, caption } : { image: { url: image.url } },
|
|
528
|
+
{ quoted: ctx.message }
|
|
529
|
+
);
|
|
530
|
+
} catch (error) {
|
|
531
|
+
const text = isModerationError(error) ? deps.strings?.moderationBlocked : deps.strings?.error;
|
|
532
|
+
await replyText(ctx.sock, ctx.chatId, ctx.message, text);
|
|
533
|
+
}
|
|
534
|
+
return true;
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
|
|
470
538
|
// src/core/guardrails.ts
|
|
471
539
|
function detectLeakage(text) {
|
|
472
540
|
const flags = [
|
|
@@ -751,6 +819,17 @@ function createWhatsAppInboundHandler(config) {
|
|
|
751
819
|
return;
|
|
752
820
|
}
|
|
753
821
|
const senderPhone = await senderPhoneFor(sock, message, chatId);
|
|
822
|
+
if (config.images) {
|
|
823
|
+
const handled = await config.images({
|
|
824
|
+
sock,
|
|
825
|
+
message,
|
|
826
|
+
chatId,
|
|
827
|
+
senderId: senderPhone,
|
|
828
|
+
text,
|
|
829
|
+
hasPhoto: Boolean(message.message?.imageMessage)
|
|
830
|
+
});
|
|
831
|
+
if (handled) return;
|
|
832
|
+
}
|
|
754
833
|
const personaLayer = await resolvePersonaLayer(config, { senderPhone, text });
|
|
755
834
|
const buckets = await resolveBuckets({
|
|
756
835
|
mode: "public",
|
|
@@ -793,10 +872,13 @@ function createWhatsAppInboundHandler(config) {
|
|
|
793
872
|
canAcquireLease,
|
|
794
873
|
createTursoWaLeaseStore,
|
|
795
874
|
createWhatsAppChannel,
|
|
875
|
+
createWhatsAppImageHandler,
|
|
796
876
|
createWhatsAppInboundHandler,
|
|
797
877
|
decrypt,
|
|
878
|
+
drawSubject,
|
|
798
879
|
encrypt,
|
|
799
880
|
extractText,
|
|
881
|
+
isDrawRequest,
|
|
800
882
|
isGroupJid,
|
|
801
883
|
jidToPhoneNumber,
|
|
802
884
|
jidsMatch,
|
|
@@ -411,6 +411,71 @@ function createWhatsAppChannel(config) {
|
|
|
411
411
|
};
|
|
412
412
|
}
|
|
413
413
|
|
|
414
|
+
// src/channels/whatsapp/images.ts
|
|
415
|
+
var DEFAULT_DETECT_PATTERN = /\b(draw|paint|sketch|illustrate)\b|\b(make|generate|create)\b[^.!?]{0,20}\b(picture|image|drawing|photo|cartoon)\b/i;
|
|
416
|
+
var DEFAULT_STRIP_PATTERNS = [
|
|
417
|
+
/@\d+/g,
|
|
418
|
+
/^(hey|hi|please|ok)[,!\s]+/i,
|
|
419
|
+
/^(can|could|will)\s+you\s+/i,
|
|
420
|
+
// Longest-alternative-first ((an|a), not (a|an)): a first-match-wins
|
|
421
|
+
// alternation would otherwise take "a" out of "an" and leave a stray "n".
|
|
422
|
+
/^(please\s+)?(draw|paint|sketch|illustrate|make|generate|create)\s+(me|us)?\s*(an|a)?\s*(picture|image|drawing|sketch|photo|cartoon)?\s*(of\s+)?/i
|
|
423
|
+
];
|
|
424
|
+
function testPattern(pattern, text) {
|
|
425
|
+
return new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, "")).test(text);
|
|
426
|
+
}
|
|
427
|
+
function isDrawRequest(text, config = {}) {
|
|
428
|
+
return testPattern(config.detectPattern ?? DEFAULT_DETECT_PATTERN, text);
|
|
429
|
+
}
|
|
430
|
+
function drawSubject(text, config = {}) {
|
|
431
|
+
let subject = text;
|
|
432
|
+
for (const pattern of config.stripPatterns ?? DEFAULT_STRIP_PATTERNS) {
|
|
433
|
+
subject = subject.replace(pattern, " ").replace(/\s+/g, " ").trim();
|
|
434
|
+
}
|
|
435
|
+
return subject.length > 0 ? subject : text.trim();
|
|
436
|
+
}
|
|
437
|
+
function isModerationError(error) {
|
|
438
|
+
return typeof error === "object" && error !== null && error.name === "ImageGenerationError" && error.code === "moderation";
|
|
439
|
+
}
|
|
440
|
+
async function replyText(sock, chatId, message, text) {
|
|
441
|
+
if (!text) return;
|
|
442
|
+
await sock.sendMessage(chatId, { text }, { quoted: message }).catch(() => void 0);
|
|
443
|
+
}
|
|
444
|
+
function createWhatsAppImageHandler(deps) {
|
|
445
|
+
return async function handleWhatsAppImageRequest(ctx) {
|
|
446
|
+
if (!deps.images.isConfigured()) return false;
|
|
447
|
+
if (!isDrawRequest(ctx.text, deps.detector)) return false;
|
|
448
|
+
const subject = drawSubject(ctx.text, deps.detector);
|
|
449
|
+
try {
|
|
450
|
+
const photoBytes = ctx.hasPhoto ? await deps.downloadPhoto?.(ctx.message) : void 0;
|
|
451
|
+
const inputImages = photoBytes ? [{ bytes: photoBytes }] : void 0;
|
|
452
|
+
const request = { prompt: subject, inputImages };
|
|
453
|
+
if (deps.checkAndReserve) {
|
|
454
|
+
const cached2 = await deps.images.peekCached(request);
|
|
455
|
+
if (!cached2) {
|
|
456
|
+
const reserved = await deps.checkAndReserve(ctx.senderId);
|
|
457
|
+
if (!reserved.allowed) {
|
|
458
|
+
await replyText(ctx.sock, ctx.chatId, ctx.message, deps.strings?.limitReached);
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
await replyText(ctx.sock, ctx.chatId, ctx.message, deps.strings?.ack);
|
|
464
|
+
const image = await deps.images.getOrCreateImage(request);
|
|
465
|
+
const caption = deps.captionComposer ? await deps.captionComposer.composeCaption({ subject }) : void 0;
|
|
466
|
+
await ctx.sock.sendMessage(
|
|
467
|
+
ctx.chatId,
|
|
468
|
+
caption ? { image: { url: image.url }, caption } : { image: { url: image.url } },
|
|
469
|
+
{ quoted: ctx.message }
|
|
470
|
+
);
|
|
471
|
+
} catch (error) {
|
|
472
|
+
const text = isModerationError(error) ? deps.strings?.moderationBlocked : deps.strings?.error;
|
|
473
|
+
await replyText(ctx.sock, ctx.chatId, ctx.message, text);
|
|
474
|
+
}
|
|
475
|
+
return true;
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
414
479
|
// src/core/guardrails.ts
|
|
415
480
|
function detectLeakage(text) {
|
|
416
481
|
const flags = [
|
|
@@ -695,6 +760,17 @@ function createWhatsAppInboundHandler(config) {
|
|
|
695
760
|
return;
|
|
696
761
|
}
|
|
697
762
|
const senderPhone = await senderPhoneFor(sock, message, chatId);
|
|
763
|
+
if (config.images) {
|
|
764
|
+
const handled = await config.images({
|
|
765
|
+
sock,
|
|
766
|
+
message,
|
|
767
|
+
chatId,
|
|
768
|
+
senderId: senderPhone,
|
|
769
|
+
text,
|
|
770
|
+
hasPhoto: Boolean(message.message?.imageMessage)
|
|
771
|
+
});
|
|
772
|
+
if (handled) return;
|
|
773
|
+
}
|
|
698
774
|
const personaLayer = await resolvePersonaLayer(config, { senderPhone, text });
|
|
699
775
|
const buckets = await resolveBuckets({
|
|
700
776
|
mode: "public",
|
|
@@ -736,10 +812,13 @@ export {
|
|
|
736
812
|
canAcquireLease,
|
|
737
813
|
createTursoWaLeaseStore,
|
|
738
814
|
createWhatsAppChannel,
|
|
815
|
+
createWhatsAppImageHandler,
|
|
739
816
|
createWhatsAppInboundHandler,
|
|
740
817
|
decrypt,
|
|
818
|
+
drawSubject,
|
|
741
819
|
encrypt,
|
|
742
820
|
extractText,
|
|
821
|
+
isDrawRequest,
|
|
743
822
|
isGroupJid,
|
|
744
823
|
jidToPhoneNumber,
|
|
745
824
|
jidsMatch,
|
package/package.json
CHANGED