@yaebal/panel 0.0.1 → 0.0.4

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 CHANGED
@@ -1,8 +1,19 @@
1
1
  # @yaebal/panel
2
2
 
3
- an operator panel for [yaebal](https://github.com/neverlane/yaebal) bots: view
4
- incoming private-chat messages and reply from the browser. ships as a
5
- self-contained `fetch` handler mount it on any HTTP framework.
3
+ framework-agnostic operator panel for Telegram bots. view incoming private chats, inspect
4
+ media, callbacks and UI events, and reply from the browser. ships as a self-contained
5
+ `fetch` handler, so it mounts on Node, Bun, Deno, edge runtimes, Hono, Fastify, Express
6
+ adapters, grammY, GramIO, puregram, or any other bot stack.
7
+
8
+ ## 0.0.3 highlights
9
+
10
+ - framework-neutral recording with `recordTelegramUpdate(store, update)`
11
+ - `createPanelApi(token)` for text replies, file proxying and operator media uploads
12
+ - redesigned SVG-only UI: avatars, identity sidebar, rich text/media previews
13
+ - media viewer dialog, polished voice cards, video cards, albums and documents
14
+ - inline/reply keyboard previews plus callback, reaction, poll and member-event rows
15
+ - persistence via pluggable `PanelStore` (in-memory + sqlite included)
16
+ - CORS, `basePath` mounting and failed-auth rate limiting
6
17
 
7
18
  ## install
8
19
 
@@ -10,25 +21,205 @@ self-contained `fetch` handler — mount it on any HTTP framework.
10
21
  pnpm add @yaebal/panel
11
22
  ```
12
23
 
13
- ## usage
24
+ ## quick start: yaebal
14
25
 
15
26
  ```ts
16
27
  import { Bot } from "@yaebal/core";
17
- import { MemoryPanelStore, recorder, panelHandler } from "@yaebal/panel";
28
+ import { MemoryPanelStore, panelHandler, recordOutgoing, recorder } from "@yaebal/panel";
29
+ import { serve } from "@yaebal/panel/serve";
18
30
 
19
- const bot = new Bot(token);
31
+ const bot = new Bot(process.env.BOT_TOKEN!);
20
32
  const store = new MemoryPanelStore();
21
33
 
22
- bot.install(recorder(store)); // log incoming private messages
34
+ bot.install(recorder(store));
35
+ recordOutgoing(bot.api, store);
36
+
37
+ const handler = panelHandler(bot.api, store, {
38
+ token: process.env.PANEL_TOKEN!,
39
+ recordSends: false,
40
+ });
41
+
42
+ serve(handler, { port: 8080 });
23
43
  bot.start();
44
+ ```
45
+
46
+ open `http://localhost:8080` and paste `PANEL_TOKEN`. the token is kept in
47
+ `sessionStorage` and sent as `Authorization: Bearer ...`; it is not placed in page URLs
48
+ except for the EventSource stream, where browsers cannot set headers.
49
+
50
+ ## quick start: any framework
51
+
52
+ use your existing framework for updates. use `@yaebal/panel` for the store, panel HTTP
53
+ handler, and a Bot API client that understands panel uploads.
54
+
55
+ ```ts
56
+ import {
57
+ MemoryPanelStore,
58
+ createPanelApi,
59
+ panelHandler,
60
+ recordTelegramUpdate,
61
+ } from "@yaebal/panel";
62
+ import { serve } from "@yaebal/panel/serve";
63
+
64
+ const store = new MemoryPanelStore();
65
+ const panelApi = createPanelApi(process.env.BOT_TOKEN!);
66
+
67
+ // call this from your framework middleware for every raw Telegram update
68
+ await recordTelegramUpdate(store, rawTelegramUpdate);
69
+
70
+ const handler = panelHandler(panelApi, store, { token: process.env.PANEL_TOKEN! });
71
+ serve(handler, { port: 8080 });
72
+ ```
73
+
74
+ ### grammY
75
+
76
+ ```ts
77
+ import { Bot } from "grammy";
78
+ import { MemoryPanelStore, createPanelApi, panelHandler, recordTelegramUpdate } from "@yaebal/panel";
79
+
80
+ const bot = new Bot(process.env.BOT_TOKEN!);
81
+ const store = new MemoryPanelStore();
82
+
83
+ bot.use(async (ctx, next) => {
84
+ await recordTelegramUpdate(store, ctx.update);
85
+ await next();
86
+ });
87
+
88
+ const handler = panelHandler(createPanelApi(process.env.BOT_TOKEN!), store, {
89
+ token: process.env.PANEL_TOKEN!,
90
+ });
91
+ ```
92
+
93
+ ### GramIO
94
+
95
+ ```ts
96
+ import { Bot } from "gramio";
97
+ import { MemoryPanelStore, createPanelApi, panelHandler, recordTelegramUpdate } from "@yaebal/panel";
98
+
99
+ const bot = new Bot(process.env.BOT_TOKEN!);
100
+ const store = new MemoryPanelStore();
101
+
102
+ bot.use(async (ctx, next) => {
103
+ await recordTelegramUpdate(store, ctx.update);
104
+ return next();
105
+ });
106
+
107
+ const handler = panelHandler(createPanelApi(process.env.BOT_TOKEN!), store, {
108
+ token: process.env.PANEL_TOKEN!,
109
+ });
110
+ ```
111
+
112
+ ### puregram
113
+
114
+ ```ts
115
+ import { Telegram } from "puregram";
116
+ import { MemoryPanelStore, createPanelApi, panelHandler, recordTelegramUpdate } from "@yaebal/panel";
24
117
 
25
- // serve the panel (auth via a required token)
26
- const handler = panelHandler(bot.api, store, { token: process.env.PANEL_TOKEN! });
27
- // handler: (Request) => Promise<Response> — open /?token=<PANEL_TOKEN>
118
+ const telegram = new Telegram({ token: process.env.BOT_TOKEN! });
119
+ const store = new MemoryPanelStore();
120
+
121
+ telegram.updates.use(async (ctx, next) => {
122
+ await recordTelegramUpdate(store, ctx.update);
123
+ return next();
124
+ });
125
+
126
+ const handler = panelHandler(createPanelApi(process.env.BOT_TOKEN!), store, {
127
+ token: process.env.PANEL_TOKEN!,
128
+ });
129
+ ```
130
+
131
+ ### other frameworks
132
+
133
+ if your framework exposes raw Telegram updates, pass them to `recordTelegramUpdate`. if it
134
+ does not, record manually with `store.record({ id, firstName, lastName, username }, message)`.
135
+ the panel only needs `PanelMessage` objects and a `PanelApi` implementation.
136
+
137
+ ## mounting
138
+
139
+ `panelHandler` returns `(Request) => Promise<Response>` and binds no port of its own.
140
+
141
+ ```ts
142
+ // node 20+, native node:http helper
143
+ import { serve } from "@yaebal/panel/serve";
144
+ serve(handler, { port: 8080 });
145
+
146
+ // bun
147
+ Bun.serve({ port: 8080, fetch: handler });
148
+
149
+ // deno
150
+ Deno.serve({ port: 8080 }, handler);
151
+
152
+ // hono / any fetch framework, pair with basePath: "/panel"
153
+ app.all("/panel/*", (c) => handler(c.req.raw));
154
+
155
+ // cloudflare workers / deno deploy / vercel edge
156
+ export default { fetch: handler };
157
+ ```
158
+
159
+ ## options
160
+
161
+ ```ts
162
+ panelHandler(api, store, {
163
+ token: process.env.PANEL_TOKEN!,
164
+ basePath: "/panel",
165
+ cors: "https://ops.example",
166
+ rateLimit: { max: 10, windowMs: 60_000 },
167
+ clientKey: (req) => req.headers.get("x-real-ip") ?? "shared",
168
+ recordSends: true,
169
+ });
28
170
  ```
29
171
 
30
- implement `PanelStore` (`record` / `chats` / `history`) to persist conversations
31
- in redis, postgres, etc. instead of the in-memory default.
172
+ ## persistence
173
+
174
+ ```ts
175
+ import { SqlitePanelStore } from "@yaebal/panel/sqlite";
176
+
177
+ const store = new SqlitePanelStore({ path: "./panel.db" });
178
+ ```
179
+
180
+ implement `PanelStore` (`record`, `chats`, `history`, optional `subscribe`) for Redis,
181
+ Postgres or any other persistence layer. `subscribe` powers SSE; without it the UI still
182
+ works through polling.
183
+
184
+ ## what gets recorded
185
+
186
+ - private message text and captions
187
+ - photos, videos, animations, audio, voice, video notes, documents, stickers and albums
188
+ - inline and reply keyboards attached to messages
189
+ - callback queries as timeline event rows
190
+ - message reactions, reaction counts, poll answers and private member-status changes
191
+ - outgoing `send*` results when you install `recordOutgoing(api, store)`
192
+
193
+ `recordTelegramUpdate` and `recorder` ignore group chat messages by design. The panel is an
194
+ operator inbox for private support-style conversations.
195
+
196
+ ## media
197
+
198
+ incoming media is stored by `file_id`; the browser loads bytes through
199
+ `GET /api/file?id=<file_id>`, so the bot token never reaches the browser. Operator uploads
200
+ use multipart `POST /api/chats/:id/send`; the panel infers `sendPhoto`, `sendVideo`,
201
+ `sendVoice`, `sendAudio` or `sendDocument` from the file MIME type.
202
+
203
+ `createPanelApi(token)` uses the yaebal Bot API encoder internally, so media proxying and
204
+ operator uploads work even when the bot itself runs on grammY, GramIO, puregram or another
205
+ framework.
206
+
207
+ ## api routes
208
+
209
+ ```text
210
+ GET / -> login + chat SPA (public)
211
+ GET /api/chats -> PanelChat[]
212
+ GET /api/chats/:id -> PanelMessage[] (?before=&limit=)
213
+ GET /api/stream -> text/event-stream of record events
214
+ GET /api/file?id=<file_id> -> proxied file bytes
215
+ POST /api/chats/:id/send -> json { text, reply_markup?, ... } or multipart { file, caption?, type? }
216
+ ```
217
+
218
+ ## example
219
+
220
+ run [`examples/panel`](https://github.com/neverlane/yaebal/tree/master/examples/panel) for
221
+ the complete demo: keyboards, callbacks, event rows, media viewer, styled voice/video
222
+ messages, albums, outgoing logging, login and SSE.
32
223
 
33
224
  ---
34
225
 
package/lib/index.d.ts CHANGED
@@ -1,48 +1,162 @@
1
- import type { Context, Plugin } from "@yaebal/core";
1
+ import type { ApiOptions, Context, Plugin } from "@yaebal/core";
2
2
  export { PANEL_HTML } from "./panel-html.js";
3
+ /** downloadable file kinds carried by a message (each maps to one `file_id`). */
4
+ export type AttachmentType = "photo" | "video" | "animation" | "audio" | "voice" | "video_note" | "document" | "sticker";
5
+ /** a downloadable attachment, referenced by telegram `file_id`. */
6
+ export interface PanelAttachment {
7
+ type: AttachmentType;
8
+ fileId: string;
9
+ fileName?: string;
10
+ mimeType?: string;
11
+ }
12
+ export interface PanelKeyboardButton {
13
+ text: string;
14
+ kind?: "callback" | "url" | "web_app" | "login_url" | "switch_inline" | "pay" | "unknown";
15
+ callbackData?: string;
16
+ url?: string;
17
+ }
18
+ export interface PanelKeyboard {
19
+ type: "inline" | "reply";
20
+ rows: PanelKeyboardButton[][];
21
+ }
22
+ export type PanelMessageEventType = "callback" | "reaction" | "reaction_count" | "poll_answer" | "chat_member";
23
+ export interface PanelMessageEvent {
24
+ type: PanelMessageEventType;
25
+ title: string;
26
+ detail?: string;
27
+ data?: string;
28
+ }
29
+ export interface PanelChatRecord {
30
+ id: number;
31
+ name?: string;
32
+ firstName?: string;
33
+ lastName?: string;
34
+ username?: string;
35
+ }
3
36
  export interface PanelMessage {
4
37
  direction: "in" | "out";
38
+ /** caption / text, or a `[kind]` placeholder when the message is media-only. */
5
39
  text: string;
6
40
  date: number;
41
+ /** downloadable attachments, fetched lazily through `GET /api/file?id=...`. */
42
+ attachments?: PanelAttachment[];
43
+ /** telegram album id — consecutive messages sharing it are one media group. */
44
+ mediaGroupId?: string;
45
+ /** inline/reply keyboard attached to the telegram message, rendered as a compact preview. */
46
+ keyboard?: PanelKeyboard;
47
+ /** non-message update rendered in the timeline (callback, reaction, poll answer, member event). */
48
+ event?: PanelMessageEvent;
7
49
  }
8
50
  export interface PanelChat {
9
51
  id: number;
10
52
  name: string;
53
+ firstName?: string;
54
+ lastName?: string;
55
+ username?: string;
11
56
  lastText: string;
12
57
  lastDate: number;
58
+ lastAttachmentType?: AttachmentType;
59
+ lastEventType?: PanelMessageEventType;
60
+ }
61
+ /** options for reading a slice of a conversation. */
62
+ export interface HistoryOptions {
63
+ /** return only messages strictly older than this unix timestamp (for "load earlier"). */
64
+ before?: number;
65
+ /** cap the result to the most recent N messages within the window. */
66
+ limit?: number;
67
+ }
68
+ /** a change the panel may want to react to in real time. */
69
+ export interface PanelEvent {
70
+ type: "record";
71
+ chatId: number;
72
+ direction: "in" | "out";
13
73
  }
14
74
  /** where conversations are kept for the panel to read. implement for persistence. */
15
75
  export interface PanelStore {
16
- record(chat: {
17
- id: number;
18
- name?: string;
19
- }, message: PanelMessage): void | Promise<void>;
76
+ record(chat: PanelChatRecord, message: PanelMessage): void | Promise<void>;
20
77
  chats(): PanelChat[] | Promise<PanelChat[]>;
21
- history(chatId: number): PanelMessage[] | Promise<PanelMessage[]>;
78
+ history(chatId: number, options?: HistoryOptions): PanelMessage[] | Promise<PanelMessage[]>;
79
+ /** optional realtime hook — return an unsubscribe fn. enables the panel's SSE stream. */
80
+ subscribe?(listener: (event: PanelEvent) => void): () => void;
22
81
  }
23
82
  /** defaults to in-memory store. Lost on restart — swap for a persistent one in production. */
24
83
  export declare class MemoryPanelStore implements PanelStore {
25
84
  #private;
26
- record(chat: {
27
- id: number;
28
- name?: string;
29
- }, message: PanelMessage): void;
85
+ record(chat: PanelChatRecord, message: PanelMessage): void;
30
86
  chats(): PanelChat[];
31
- history(chatId: number): PanelMessage[];
87
+ history(chatId: number, options?: HistoryOptions): PanelMessage[];
88
+ subscribe(listener: (event: PanelEvent) => void): () => void;
32
89
  }
33
- /** records incoming private-chat text into the store so the panel can show it. */
90
+ /** records a raw Telegram update into the store; useful from any bot framework. */
91
+ export declare function recordTelegramUpdate(store: PanelStore, update: unknown): Promise<boolean>;
92
+ /** records incoming private-chat updates into the store so the panel can show them. */
34
93
  export declare function recorder(store: PanelStore): Plugin<Context, Record<never, never>>;
35
- interface SendApi {
94
+ /**
95
+ * what {@link panelHandler} needs from the api. `sendMessage` is required; `call` and
96
+ * `fileUrl` unlock media (file proxying + operator uploads) and are present on the real
97
+ * `@yaebal/core` `Api`. without them, media routes answer `501`.
98
+ */
99
+ export interface PanelApi {
36
100
  sendMessage(params: Record<string, unknown>): Promise<unknown>;
101
+ call?<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T>;
102
+ fileUrl?(filePath: string): string;
37
103
  }
104
+ /** create a small Bot API client that satisfies {@link PanelApi}; useful with any framework. */
105
+ export declare function createPanelApi(token: string, options?: ApiOptions): PanelApi;
106
+ /** the slice of `@yaebal/core`'s `Api` that {@link recordOutgoing} needs. */
107
+ interface AfterHookApi {
108
+ after(hook: (method: string, result: unknown) => unknown): unknown;
109
+ }
110
+ /**
111
+ * record replies the bot sends *outside* the panel (e.g. `ctx.reply(...)` or `ctx.replyWithPhoto(...)`
112
+ * in your handlers) so they show up in the conversation too. hooks the api's `after` stage and logs
113
+ * every successful `send*` call (including `sendMediaGroup`, which returns an array) to a private chat.
114
+ *
115
+ * pairs with `panelHandler(..., { recordSends: false })` — the panel records its own sends,
116
+ * so disable that to avoid double entries when this is installed.
117
+ *
118
+ * ```ts
119
+ * recordOutgoing(bot.api, store);
120
+ * const handler = panelHandler(bot.api, store, { token, recordSends: false });
121
+ * ```
122
+ */
123
+ export declare function recordOutgoing<A extends AfterHookApi>(api: A, store: PanelStore): A;
38
124
  export interface PanelOptions {
39
125
  /** shared secret required to open the panel and call its api. */
40
126
  token: string;
127
+ /**
128
+ * allowed CORS origin(s) for the panel api. omit for same-origin only (default).
129
+ * pass `"*"` to allow any origin, or an explicit list to echo a matching `Origin` back.
130
+ */
131
+ cors?: string | string[];
132
+ /**
133
+ * mount prefix when the handler does not live at the server root, e.g. `"/panel"`.
134
+ * the UI builds its api urls from this, so no extra rewriting is needed. default `""`.
135
+ */
136
+ basePath?: string;
137
+ /**
138
+ * throttle failed auth attempts per client. defaults to 10 failures / 60s, then `429`
139
+ * until the window passes. pass `false` to disable.
140
+ */
141
+ rateLimit?: {
142
+ max?: number;
143
+ windowMs?: number;
144
+ } | false;
145
+ /**
146
+ * derive a client key for rate limiting. defaults to `x-forwarded-for` / `x-real-ip`,
147
+ * falling back to a single shared bucket when no proxy header is present.
148
+ */
149
+ clientKey?: (request: Request) => string;
150
+ /**
151
+ * record replies sent from the panel into the store. default `true`. set to `false`
152
+ * when you use {@link recordOutgoing}, which already captures every outgoing message.
153
+ */
154
+ recordSends?: boolean;
41
155
  }
42
156
  /**
43
- * a fetch-style handler for the operator panel: serves the ui at `/`, and a small
44
- * api to list chats, read a conversation, and send a reply. mount it on any
45
- * fetch-compatible server. open it at `/?token=<your token>`.
157
+ * a fetch-style handler for the operator panel: serves the login + chat UI at the mount
158
+ * root, and a small api to list chats, read a conversation, stream updates, and send a
159
+ * reply. mount it on any fetch-compatible server.
46
160
  */
47
- export declare function panelHandler(api: SendApi, store: PanelStore, options: PanelOptions): (request: Request) => Promise<Response>;
161
+ export declare function panelHandler(api: PanelApi, store: PanelStore, options: PanelOptions): (request: Request) => Promise<Response>;
48
162
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAgBpD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,MAAM,WAAW,YAAY;IAC5B,SAAS,EAAE,IAAI,GAAG,KAAK,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,SAAS;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,qFAAqF;AACrF,MAAM,WAAW,UAAU;IAC1B,MAAM,CAAC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,OAAO,EAAE,YAAY,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzF,KAAK,IAAI,SAAS,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IAC5C,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;CAClE;AAED,8FAA8F;AAC9F,qBAAa,gBAAiB,YAAW,UAAU;;IAIlD,MAAM,CAAC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,OAAO,EAAE,YAAY,GAAG,IAAI;IAiBxE,KAAK,IAAI,SAAS,EAAE;IAIpB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,EAAE;CAGvC;AAED,kFAAkF;AAClF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAqBjF;AAED,UAAU,OAAO;IAChB,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,YAAY;IAC5B,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;CACd;AAQD;;;;GAIG;AACH,wBAAgB,YAAY,CAC3B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,YAAY,GACnB,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAuDzC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}