@adriangalilea/utils 2.6.0 → 2.7.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.
@@ -18,6 +18,7 @@ export * from "./coalesce.js";
18
18
  export * from "./ctx.js";
19
19
  export * from "./draft.js";
20
20
  export * from "./groups.js";
21
+ export * from "./inline-feedback.js";
21
22
  export * from "./keys.js";
22
23
  export * from "./kit.js";
23
24
  export * from "./language.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bot/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bot/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC"}
package/dist/bot/index.js CHANGED
@@ -18,6 +18,7 @@ export * from "./coalesce.js";
18
18
  export * from "./ctx.js";
19
19
  export * from "./draft.js";
20
20
  export * from "./groups.js";
21
+ export * from "./inline-feedback.js";
21
22
  export * from "./keys.js";
22
23
  export * from "./kit.js";
23
24
  export * from "./language.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/bot/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/bot/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,cAAc,qBAAqB,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Inline-feedback tripwire — behavioral detection for a BotFather setting the
3
+ * Bot API cannot see.
4
+ *
5
+ * `chosen_inline_result` delivery is gated by BotFather's inline FEEDBACK
6
+ * probability, which defaults to 0% and silently RESETS on bot transfer and
7
+ * on inline-mode toggling. At 0%, taps post the placeholder client-side but
8
+ * the bot never hears about them, so nothing ever fills in — and `getMe`
9
+ * reports nothing (the profile `expects` drift-check is blind here). The
10
+ * asymmetry IS measurable though: results keep getting served while a chosen
11
+ * event never arrives.
12
+ *
13
+ * Storage is injected as three atomic-ish ops (wire to D1/Redis/anything);
14
+ * this module owns the thresholds, the throttle, and the operator message.
15
+ * Thresholds are deliberately lazy — this is a drift alarm, not a metric.
16
+ *
17
+ * @example
18
+ * const probe = inlineFeedbackProbe({
19
+ * bump: () => store.bumpInlineServed(ctx),
20
+ * reset: () => store.recordInlineChosen(ctx, Date.now()),
21
+ * markAlerted: () => store.markInlineAlerted(ctx, Date.now()),
22
+ * adminIds: () => deps.adminIds(),
23
+ * })
24
+ * bot.inlineQuery(async (ctx) => { …answer with results…; await probe.onServed(ctx.bot) })
25
+ * bot.chosenInlineResult(() => true, async (ctx) => { await probe.onChosen(); … })
26
+ */
27
+ import type { Bot } from "gramio";
28
+ export interface InlineFeedbackProbeOptions {
29
+ /** Increment the served counter, returning the new count + when the last alert fired. */
30
+ bump: () => Promise<{
31
+ served: number;
32
+ lastAlertAt: number | null;
33
+ }>;
34
+ /** A chosen event arrived: feedback works, the counter starts over. */
35
+ reset: () => Promise<void>;
36
+ /** Persist that the alert fired (also zeroes the counter downstream). */
37
+ markAlerted: () => Promise<void>;
38
+ adminIds: () => Promise<readonly number[]> | readonly number[];
39
+ /** Served results (WITH content — empty answers prove nothing) before the alarm. Default 25. */
40
+ threshold?: number;
41
+ /** Minimum ms between alerts. Default 24h. */
42
+ alertEveryMs?: number;
43
+ /** Called with the alarm line before the DM goes out (wire your logger). */
44
+ onTrip?: (message: string) => void;
45
+ }
46
+ export interface InlineFeedbackProbe {
47
+ /** Call after answering an inline query WITH results. Never throws. */
48
+ onServed(bot: Bot): Promise<void>;
49
+ /** Call from the chosen_inline_result handler. Never throws. */
50
+ onChosen(): Promise<void>;
51
+ }
52
+ export declare function inlineFeedbackProbe(opts: InlineFeedbackProbeOptions): InlineFeedbackProbe;
53
+ //# sourceMappingURL=inline-feedback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inline-feedback.d.ts","sourceRoot":"","sources":["../../src/bot/inline-feedback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAGlC,MAAM,WAAW,0BAA0B;IAC1C,yFAAyF;IACzF,IAAI,EAAE,MAAM,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IACpE,uEAAuE;IACvE,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,yEAAyE;IACzE,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,QAAQ,EAAE,MAAM,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,GAAG,SAAS,MAAM,EAAE,CAAC;IAC/D,gGAAgG;IAChG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,mBAAmB;IACnC,uEAAuE;IACvE,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,gEAAgE;IAChE,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAKD,wBAAgB,mBAAmB,CAClC,IAAI,EAAE,0BAA0B,GAC9B,mBAAmB,CA6BrB"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Inline-feedback tripwire — behavioral detection for a BotFather setting the
3
+ * Bot API cannot see.
4
+ *
5
+ * `chosen_inline_result` delivery is gated by BotFather's inline FEEDBACK
6
+ * probability, which defaults to 0% and silently RESETS on bot transfer and
7
+ * on inline-mode toggling. At 0%, taps post the placeholder client-side but
8
+ * the bot never hears about them, so nothing ever fills in — and `getMe`
9
+ * reports nothing (the profile `expects` drift-check is blind here). The
10
+ * asymmetry IS measurable though: results keep getting served while a chosen
11
+ * event never arrives.
12
+ *
13
+ * Storage is injected as three atomic-ish ops (wire to D1/Redis/anything);
14
+ * this module owns the thresholds, the throttle, and the operator message.
15
+ * Thresholds are deliberately lazy — this is a drift alarm, not a metric.
16
+ *
17
+ * @example
18
+ * const probe = inlineFeedbackProbe({
19
+ * bump: () => store.bumpInlineServed(ctx),
20
+ * reset: () => store.recordInlineChosen(ctx, Date.now()),
21
+ * markAlerted: () => store.markInlineAlerted(ctx, Date.now()),
22
+ * adminIds: () => deps.adminIds(),
23
+ * })
24
+ * bot.inlineQuery(async (ctx) => { …answer with results…; await probe.onServed(ctx.bot) })
25
+ * bot.chosenInlineResult(() => true, async (ctx) => { await probe.onChosen(); … })
26
+ */
27
+ import { notifyAdmins } from "./notify.js";
28
+ const DEFAULT_THRESHOLD = 25;
29
+ const DEFAULT_ALERT_EVERY_MS = 24 * 60 * 60 * 1000;
30
+ export function inlineFeedbackProbe(opts) {
31
+ const threshold = opts.threshold ?? DEFAULT_THRESHOLD;
32
+ const alertEveryMs = opts.alertEveryMs ?? DEFAULT_ALERT_EVERY_MS;
33
+ return {
34
+ async onServed(bot) {
35
+ try {
36
+ const { served, lastAlertAt } = await opts.bump();
37
+ if (served < threshold)
38
+ return;
39
+ const now = Date.now();
40
+ if (lastAlertAt !== null && now - lastAlertAt < alertEveryMs)
41
+ return;
42
+ await opts.markAlerted();
43
+ const line = `inline feedback looks DISABLED: ${served} inline results served since the last chosen_inline_result`;
44
+ opts.onTrip?.(line);
45
+ const ids = await opts.adminIds();
46
+ await notifyAdmins(bot, ids, `⚠️ ${line}.\n` +
47
+ "Taps post the placeholder but the bot never hears about them, so nothing ever fills in.\n" +
48
+ "@BotFather → /setinlinefeedback → this bot → 100%. (It resets to 0% on bot TRANSFER and on inline-mode toggling.)");
49
+ }
50
+ catch {
51
+ // best-effort: the probe must never break the inline answer path
52
+ }
53
+ },
54
+ async onChosen() {
55
+ await opts.reset().catch(() => { });
56
+ },
57
+ };
58
+ }
59
+ //# sourceMappingURL=inline-feedback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inline-feedback.js","sourceRoot":"","sources":["../../src/bot/inline-feedback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAyB3C,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAC7B,MAAM,sBAAsB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEnD,MAAM,UAAU,mBAAmB,CAClC,IAAgC;IAEhC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,iBAAiB,CAAC;IACtD,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,sBAAsB,CAAC;IACjE,OAAO;QACN,KAAK,CAAC,QAAQ,CAAC,GAAG;YACjB,IAAI,CAAC;gBACJ,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;gBAClD,IAAI,MAAM,GAAG,SAAS;oBAAE,OAAO;gBAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBACvB,IAAI,WAAW,KAAK,IAAI,IAAI,GAAG,GAAG,WAAW,GAAG,YAAY;oBAAE,OAAO;gBACrE,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;gBACzB,MAAM,IAAI,GAAG,mCAAmC,MAAM,4DAA4D,CAAC;gBACnH,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC;gBACpB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAClC,MAAM,YAAY,CACjB,GAAyC,EACzC,GAAG,EACH,MAAM,IAAI,KAAK;oBACd,2FAA2F;oBAC3F,mHAAmH,CACpH,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACR,iEAAiE;YAClE,CAAC;QACF,CAAC;QACD,KAAK,CAAC,QAAQ;YACb,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACpC,CAAC;KACD,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adriangalilea/utils",
3
- "version": "2.6.0",
3
+ "version": "2.7.0",
4
4
  "description": "TypeScript utilities - logger, currency, formatter, GramIO bot plugins, and more",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -147,6 +147,10 @@
147
147
  "./llm": {
148
148
  "types": "./dist/llm/index.d.ts",
149
149
  "default": "./dist/llm/index.js"
150
+ },
151
+ "./bot/inline-feedback": {
152
+ "types": "./dist/bot/inline-feedback.d.ts",
153
+ "default": "./dist/bot/inline-feedback.js"
150
154
  }
151
155
  },
152
156
  "files": [