@yaebal/panel 0.0.2 → 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 +156 -96
- package/lib/index.d.ts +43 -12
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +253 -20
- package/lib/index.js.map +1 -1
- package/lib/index.test.js +81 -3
- package/lib/index.test.js.map +1 -1
- package/lib/panel-html.d.ts +2 -2
- package/lib/panel-html.d.ts.map +1 -1
- package/lib/panel-html.js +412 -115
- package/lib/panel-html.js.map +1 -1
- package/lib/serve.d.ts.map +1 -1
- package/lib/serve.js.map +1 -1
- package/lib/sqlite.d.ts +2 -5
- package/lib/sqlite.d.ts.map +1 -1
- package/lib/sqlite.js +76 -18
- package/lib/sqlite.js.map +1 -1
- package/lib/sqlite.test.js +41 -8
- package/lib/sqlite.test.js.map +1 -1
- package/package.json +2 -2
- package/src/index.test.ts +104 -4
- package/src/index.ts +327 -30
- package/src/panel-html.ts +412 -115
- package/src/serve.ts +1 -1
- package/src/sqlite.test.ts +47 -9
- package/src/sqlite.ts +94 -21
package/lib/panel-html.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"panel-html.js","sourceRoot":"","sources":["../src/panel-html.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,MAAM,CAAC,MAAM,UAAU,GAAG
|
|
1
|
+
{"version":3,"file":"panel-html.js","sourceRoot":"","sources":["../src/panel-html.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAqhBlB,CAAC"}
|
package/lib/serve.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,MAAM,EAAuB,MAAM,WAAW,CAAC;AAEjG,iDAAiD;AACjD,MAAM,WAAW,YAAY;IAC5B,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC3D;AA0BD;;;;;;;;;GASG;AACH,wBAAgB,KAAK,CACpB,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,EAC3D,OAAO,EAAE,YAAY,GACnB,MAAM,CAeR"}
|
package/lib/serve.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve.js","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"serve.js","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA0D,MAAM,WAAW,CAAC;AAYjG,yFAAyF;AACzF,SAAS,SAAS,CAAC,GAAoB;IACtC,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,WAAW,CAAC;IAC7C,MAAM,GAAG,GAAG,UAAU,IAAI,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,CAAC;IAE9C,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,KAAK,CAAC;IACnC,MAAM,OAAO,GAAG,MAAM,KAAK,KAAK,IAAI,MAAM,KAAK,MAAM,CAAC;IAEtD,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE;QACvB,MAAM;QACN,OAAO,EAAE,GAAG,CAAC,OAAiC;QAC9C,4EAA4E;QAC5E,IAAI,EAAE,OAAO,CAAC,CAAC,CAAE,GAAiC,CAAC,CAAC,CAAC,SAAS;QAC9D,mDAAmD;QACnD,MAAM,EAAE,MAAM;KACC,CAAC,CAAC;AACnB,CAAC;AAED,yEAAyE;AACzE,KAAK,UAAU,aAAa,CAAC,GAAmB,EAAE,QAAkB;IACnE,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;IACrE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;AACpD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,KAAK,CACpB,OAA2D,EAC3D,OAAqB;IAErB,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACxC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;aACtC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;aAChD,KAAK,CAAC,GAAG,EAAE;YACX,IAAI,CAAC,GAAG,CAAC,WAAW;gBAAE,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YACzC,GAAG,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE;QAC9C,OAAO,CAAC,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AACf,CAAC"}
|
package/lib/sqlite.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DatabaseSync } from "node:sqlite";
|
|
2
|
-
import type { HistoryOptions, PanelChat, PanelEvent, PanelMessage, PanelStore } from "./index.js";
|
|
2
|
+
import type { HistoryOptions, PanelChat, PanelChatRecord, PanelEvent, PanelMessage, PanelStore } from "./index.js";
|
|
3
3
|
/** options for {@link SqlitePanelStore}. */
|
|
4
4
|
export interface SqlitePanelStoreOptions {
|
|
5
5
|
/** sqlite file path, or `":memory:"` (default). ignored when `db` is provided. */
|
|
@@ -19,10 +19,7 @@ export interface SqlitePanelStoreOptions {
|
|
|
19
19
|
export declare class SqlitePanelStore implements PanelStore {
|
|
20
20
|
#private;
|
|
21
21
|
constructor(options?: SqlitePanelStoreOptions);
|
|
22
|
-
record(chat:
|
|
23
|
-
id: number;
|
|
24
|
-
name?: string;
|
|
25
|
-
}, message: PanelMessage): void;
|
|
22
|
+
record(chat: PanelChatRecord, message: PanelMessage): void;
|
|
26
23
|
chats(): PanelChat[];
|
|
27
24
|
history(chatId: number, options?: HistoryOptions): PanelMessage[];
|
|
28
25
|
subscribe(listener: (event: PanelEvent) => void): () => void;
|
package/lib/sqlite.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../src/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EACX,cAAc,EAEd,SAAS,EACT,UAAU,
|
|
1
|
+
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../src/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EACX,cAAc,EAEd,SAAS,EACT,eAAe,EACf,UAAU,EAEV,YAAY,EAEZ,UAAU,EACV,MAAM,YAAY,CAAC;AAUpB,4CAA4C;AAC5C,MAAM,WAAW,uBAAuB;IACvC,kFAAkF;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,EAAE,CAAC,EAAE,YAAY,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,qBAAa,gBAAiB,YAAW,UAAU;;gBAItC,OAAO,GAAE,uBAA4B;IAqCjD,MAAM,CAAC,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,YAAY,GAAG,IAAI;IAwD1D,KAAK,IAAI,SAAS,EAAE;IAmCpB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,YAAY,EAAE;IAiCjE,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GAAG,MAAM,IAAI;IAK5D,yEAAyE;IACzE,KAAK,IAAI,IAAI;CAGb"}
|
package/lib/sqlite.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { DatabaseSync } from "node:sqlite";
|
|
2
|
+
function addColumn(db, table, definition) {
|
|
3
|
+
try {
|
|
4
|
+
db.exec(`ALTER TABLE ${table} ADD COLUMN ${definition}`);
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
// Existing sqlite stores from older panel versions simply already have the column.
|
|
8
|
+
}
|
|
9
|
+
}
|
|
2
10
|
/**
|
|
3
11
|
* a persistent {@link PanelStore} backed by node's built-in `node:sqlite` (node 22.5+).
|
|
4
12
|
* zero third-party deps. import from `@yaebal/panel/sqlite`.
|
|
@@ -15,10 +23,15 @@ export class SqlitePanelStore {
|
|
|
15
23
|
this.#db = options.db ?? new DatabaseSync(options.path ?? ":memory:");
|
|
16
24
|
this.#db.exec(`
|
|
17
25
|
CREATE TABLE IF NOT EXISTS panel_chats (
|
|
18
|
-
id
|
|
19
|
-
name
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
id INTEGER PRIMARY KEY,
|
|
27
|
+
name TEXT,
|
|
28
|
+
first_name TEXT,
|
|
29
|
+
last_name TEXT,
|
|
30
|
+
username TEXT,
|
|
31
|
+
last_text TEXT NOT NULL,
|
|
32
|
+
last_date INTEGER NOT NULL,
|
|
33
|
+
last_attachment TEXT,
|
|
34
|
+
last_event TEXT
|
|
22
35
|
);
|
|
23
36
|
CREATE TABLE IF NOT EXISTS panel_messages (
|
|
24
37
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -27,25 +40,53 @@ export class SqlitePanelStore {
|
|
|
27
40
|
text TEXT NOT NULL,
|
|
28
41
|
date INTEGER NOT NULL,
|
|
29
42
|
attachments TEXT,
|
|
30
|
-
media_group TEXT
|
|
43
|
+
media_group TEXT,
|
|
44
|
+
keyboard TEXT,
|
|
45
|
+
event TEXT
|
|
31
46
|
);
|
|
32
47
|
CREATE INDEX IF NOT EXISTS panel_messages_chat ON panel_messages (chat_id, date);
|
|
33
48
|
`);
|
|
49
|
+
addColumn(this.#db, "panel_chats", "first_name TEXT");
|
|
50
|
+
addColumn(this.#db, "panel_chats", "last_name TEXT");
|
|
51
|
+
addColumn(this.#db, "panel_chats", "username TEXT");
|
|
52
|
+
addColumn(this.#db, "panel_chats", "last_attachment TEXT");
|
|
53
|
+
addColumn(this.#db, "panel_chats", "last_event TEXT");
|
|
54
|
+
addColumn(this.#db, "panel_messages", "keyboard TEXT");
|
|
55
|
+
addColumn(this.#db, "panel_messages", "event TEXT");
|
|
34
56
|
}
|
|
35
57
|
record(chat, message) {
|
|
36
58
|
this.#db
|
|
37
|
-
.prepare("INSERT INTO panel_messages (chat_id, direction, text, date, attachments, media_group) VALUES (?, ?, ?, ?, ?, ?)")
|
|
38
|
-
.run(chat.id, message.direction, message.text, message.date, message.attachments ? JSON.stringify(message.attachments) : null, message.mediaGroupId ?? null);
|
|
59
|
+
.prepare("INSERT INTO panel_messages (chat_id, direction, text, date, attachments, media_group, keyboard, event) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
|
|
60
|
+
.run(chat.id, message.direction, message.text, message.date, message.attachments ? JSON.stringify(message.attachments) : null, message.mediaGroupId ?? null, message.keyboard ? JSON.stringify(message.keyboard) : null, message.event ? JSON.stringify(message.event) : null);
|
|
39
61
|
// keep the name when an outgoing message omits it; COALESCE the existing row's value
|
|
40
62
|
this.#db
|
|
41
63
|
.prepare(`
|
|
42
|
-
INSERT INTO panel_chats (
|
|
64
|
+
INSERT INTO panel_chats (
|
|
65
|
+
id, name, first_name, last_name, username, last_text, last_date, last_attachment, last_event
|
|
66
|
+
) VALUES (
|
|
67
|
+
:id, :name, :firstName, :lastName, :username, :text, :date, :lastAttachment, :lastEvent
|
|
68
|
+
)
|
|
43
69
|
ON CONFLICT(id) DO UPDATE SET
|
|
44
70
|
name = COALESCE(:name, panel_chats.name),
|
|
71
|
+
first_name = COALESCE(:firstName, panel_chats.first_name),
|
|
72
|
+
last_name = COALESCE(:lastName, panel_chats.last_name),
|
|
73
|
+
username = COALESCE(:username, panel_chats.username),
|
|
45
74
|
last_text = :text,
|
|
46
|
-
last_date = :date
|
|
75
|
+
last_date = :date,
|
|
76
|
+
last_attachment = :lastAttachment,
|
|
77
|
+
last_event = :lastEvent
|
|
47
78
|
`)
|
|
48
|
-
.run({
|
|
79
|
+
.run({
|
|
80
|
+
id: chat.id,
|
|
81
|
+
name: chat.name ?? null,
|
|
82
|
+
firstName: chat.firstName ?? null,
|
|
83
|
+
lastName: chat.lastName ?? null,
|
|
84
|
+
username: chat.username ?? null,
|
|
85
|
+
text: message.text,
|
|
86
|
+
date: message.date,
|
|
87
|
+
lastAttachment: message.attachments?.[0]?.type ?? null,
|
|
88
|
+
lastEvent: message.event?.type ?? null,
|
|
89
|
+
});
|
|
49
90
|
// brand-new chat with no name → give it a stable fallback label
|
|
50
91
|
this.#db
|
|
51
92
|
.prepare("UPDATE panel_chats SET name = ? WHERE id = ? AND name IS NULL")
|
|
@@ -56,14 +97,27 @@ export class SqlitePanelStore {
|
|
|
56
97
|
}
|
|
57
98
|
chats() {
|
|
58
99
|
const rows = this.#db
|
|
59
|
-
.prepare("SELECT id, name, last_text, last_date FROM panel_chats ORDER BY last_date DESC")
|
|
100
|
+
.prepare("SELECT id, name, first_name, last_name, username, last_text, last_date, last_attachment, last_event FROM panel_chats ORDER BY last_date DESC")
|
|
60
101
|
.all();
|
|
61
|
-
return rows.map((r) =>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
102
|
+
return rows.map((r) => {
|
|
103
|
+
const chat = {
|
|
104
|
+
id: r.id,
|
|
105
|
+
name: r.name ?? `chat ${r.id}`,
|
|
106
|
+
lastText: r.last_text,
|
|
107
|
+
lastDate: r.last_date,
|
|
108
|
+
};
|
|
109
|
+
if (r.first_name)
|
|
110
|
+
chat.firstName = r.first_name;
|
|
111
|
+
if (r.last_name)
|
|
112
|
+
chat.lastName = r.last_name;
|
|
113
|
+
if (r.username)
|
|
114
|
+
chat.username = r.username;
|
|
115
|
+
if (r.last_attachment)
|
|
116
|
+
chat.lastAttachmentType = r.last_attachment;
|
|
117
|
+
if (r.last_event)
|
|
118
|
+
chat.lastEventType = r.last_event;
|
|
119
|
+
return chat;
|
|
120
|
+
});
|
|
67
121
|
}
|
|
68
122
|
history(chatId, options) {
|
|
69
123
|
const before = options?.before ?? Number.MAX_SAFE_INTEGER;
|
|
@@ -71,7 +125,7 @@ export class SqlitePanelStore {
|
|
|
71
125
|
// grab the most recent `limit` rows older than `before`, then return ascending
|
|
72
126
|
const rows = this.#db
|
|
73
127
|
.prepare(`
|
|
74
|
-
SELECT direction, text, date, attachments, media_group FROM panel_messages
|
|
128
|
+
SELECT direction, text, date, attachments, media_group, keyboard, event FROM panel_messages
|
|
75
129
|
WHERE chat_id = ? AND date < ?
|
|
76
130
|
ORDER BY date DESC, id DESC LIMIT ?
|
|
77
131
|
`)
|
|
@@ -82,6 +136,10 @@ export class SqlitePanelStore {
|
|
|
82
136
|
msg.attachments = JSON.parse(r.attachments);
|
|
83
137
|
if (r.media_group)
|
|
84
138
|
msg.mediaGroupId = r.media_group;
|
|
139
|
+
if (r.keyboard)
|
|
140
|
+
msg.keyboard = JSON.parse(r.keyboard);
|
|
141
|
+
if (r.event)
|
|
142
|
+
msg.event = JSON.parse(r.event);
|
|
85
143
|
return msg;
|
|
86
144
|
});
|
|
87
145
|
}
|
package/lib/sqlite.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../src/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../src/sqlite.ts"],"names":[],"mappings":"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"}
|
package/lib/sqlite.test.js
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
// `node:sqlite` (and therefore `SqlitePanelStore`) is only available on node 22.5+.
|
|
4
|
+
// on older runtimes a static import of "./sqlite.js" throws ERR_UNKNOWN_BUILTIN_MODULE
|
|
5
|
+
// at module-evaluation time, which would fail the whole file. import it lazily and
|
|
6
|
+
// skip the suite when the builtin is missing instead.
|
|
7
|
+
let SqlitePanelStore;
|
|
8
|
+
try {
|
|
9
|
+
({ SqlitePanelStore } = await import("./sqlite.js"));
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
SqlitePanelStore = undefined;
|
|
13
|
+
}
|
|
14
|
+
const skip = SqlitePanelStore ? false : "node:sqlite unavailable (requires node 22.5+)";
|
|
15
|
+
// non-undefined alias for the test bodies (only reached when not skipped)
|
|
16
|
+
const Store = SqlitePanelStore;
|
|
17
|
+
test("SqlitePanelStore: records, lists newest-first, paginates and emits events", { skip }, () => {
|
|
18
|
+
const store = new Store(); // :memory:
|
|
6
19
|
const events = [];
|
|
7
20
|
store.subscribe((e) => events.push(e.chatId));
|
|
8
21
|
store.record({ id: 1, name: "@sam" }, { direction: "in", text: "hi", date: 10 });
|
|
@@ -21,22 +34,42 @@ test("SqlitePanelStore: records, lists newest-first, paginates and emits events"
|
|
|
21
34
|
assert.ok(events.length >= 2);
|
|
22
35
|
store.close();
|
|
23
36
|
});
|
|
24
|
-
test("SqlitePanelStore persists attachments and media_group_id round-trip", () => {
|
|
25
|
-
const store = new
|
|
26
|
-
store.record({ id: 1, name: "@u" }, {
|
|
37
|
+
test("SqlitePanelStore persists attachments and media_group_id round-trip", { skip }, () => {
|
|
38
|
+
const store = new Store();
|
|
39
|
+
store.record({ id: 1, name: "@u", firstName: "Uma", lastName: "Ray", username: "u" }, {
|
|
27
40
|
direction: "in",
|
|
28
41
|
text: "[photo]",
|
|
29
42
|
date: 1,
|
|
30
43
|
attachments: [{ type: "photo", fileId: "f1" }],
|
|
31
44
|
mediaGroupId: "G1",
|
|
45
|
+
keyboard: {
|
|
46
|
+
type: "inline",
|
|
47
|
+
rows: [[{ text: "Open", kind: "callback", callbackData: "open" }]],
|
|
48
|
+
},
|
|
32
49
|
});
|
|
33
|
-
store.record({ id: 1, name: "@u" }, {
|
|
50
|
+
store.record({ id: 1, name: "@u" }, {
|
|
51
|
+
direction: "in",
|
|
52
|
+
text: "button clicked: open",
|
|
53
|
+
date: 2,
|
|
54
|
+
event: { type: "callback", title: "button clicked", detail: "open", data: "open" },
|
|
55
|
+
});
|
|
56
|
+
const chat = store.chats()[0];
|
|
57
|
+
assert.equal(chat?.firstName, "Uma");
|
|
58
|
+
assert.equal(chat?.lastName, "Ray");
|
|
59
|
+
assert.equal(chat?.username, "u");
|
|
60
|
+
assert.equal(chat?.lastAttachmentType, undefined);
|
|
61
|
+
assert.equal(chat?.lastEventType, "callback");
|
|
34
62
|
const hist = store.history(1);
|
|
35
63
|
assert.deepEqual(hist[0]?.attachments, [{ type: "photo", fileId: "f1" }]);
|
|
36
64
|
assert.equal(hist[0]?.mediaGroupId, "G1");
|
|
37
|
-
|
|
65
|
+
assert.deepEqual(hist[0]?.keyboard, {
|
|
66
|
+
type: "inline",
|
|
67
|
+
rows: [[{ text: "Open", kind: "callback", callbackData: "open" }]],
|
|
68
|
+
});
|
|
69
|
+
// event message has no attachments/group keys
|
|
38
70
|
assert.equal(hist[1]?.attachments, undefined);
|
|
39
71
|
assert.equal(hist[1]?.mediaGroupId, undefined);
|
|
72
|
+
assert.equal(hist[1]?.event?.type, "callback");
|
|
40
73
|
store.close();
|
|
41
74
|
});
|
|
42
75
|
//# sourceMappingURL=sqlite.test.js.map
|
package/lib/sqlite.test.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite.test.js","sourceRoot":"","sources":["../src/sqlite.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,IAAI,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"sqlite.test.js","sourceRoot":"","sources":["../src/sqlite.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yaebal/panel",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "yaebal panel — an operator panel: view chats and reply from the browser.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"src"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@yaebal/core": "0.0.
|
|
27
|
+
"@yaebal/core": "0.0.5"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/node": "latest"
|
package/src/index.test.ts
CHANGED
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import { Composer, Context, type Middleware } from "@yaebal/core";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
MemoryPanelStore,
|
|
6
|
+
panelHandler,
|
|
7
|
+
recorder,
|
|
8
|
+
recordOutgoing,
|
|
9
|
+
recordTelegramUpdate,
|
|
10
|
+
} from "./index.js";
|
|
11
|
+
import { PANEL_HTML } from "./panel-html.js";
|
|
5
12
|
|
|
6
13
|
const noop = async () => {};
|
|
7
14
|
const entry = <C extends Context>(c: Composer<C>) =>
|
|
8
15
|
c.toMiddleware() as unknown as Middleware<Context>;
|
|
9
16
|
|
|
17
|
+
test("PANEL_HTML inline script parses", () => {
|
|
18
|
+
const script = PANEL_HTML.match(/<script>([\s\S]*?)<\/script>/)?.[1];
|
|
19
|
+
assert.ok(script);
|
|
20
|
+
assert.doesNotThrow(() => new Function(script));
|
|
21
|
+
});
|
|
22
|
+
|
|
10
23
|
test("MemoryPanelStore records, preserves names, sorts and reads history", () => {
|
|
11
24
|
const s = new MemoryPanelStore();
|
|
12
25
|
|
|
@@ -49,6 +62,8 @@ test("recorder logs incoming private text into the store", async () => {
|
|
|
49
62
|
assert.equal(hist[0]?.text, "hello");
|
|
50
63
|
assert.equal(hist[0]?.direction, "in");
|
|
51
64
|
assert.equal(store.chats()[0]?.name, "@sam");
|
|
65
|
+
assert.equal(store.chats()[0]?.firstName, "Sam");
|
|
66
|
+
assert.equal(store.chats()[0]?.username, "sam");
|
|
52
67
|
});
|
|
53
68
|
|
|
54
69
|
function fakePanel() {
|
|
@@ -108,7 +123,7 @@ test("panel send posts via the api and logs an outgoing message", async () => {
|
|
|
108
123
|
);
|
|
109
124
|
assert.equal(res.status, 200);
|
|
110
125
|
assert.deepEqual(sent, [{ chat_id: 1, text: "yo" }]);
|
|
111
|
-
|
|
126
|
+
|
|
112
127
|
const last = store.history(1).at(-1);
|
|
113
128
|
assert.equal(last?.direction, "out");
|
|
114
129
|
assert.equal(last?.text, "yo");
|
|
@@ -165,6 +180,62 @@ test("recorder captures a caption as the message text", async () => {
|
|
|
165
180
|
assert.equal(store.history(8)[0]?.text, "the report");
|
|
166
181
|
});
|
|
167
182
|
|
|
183
|
+
test("recordTelegramUpdate records framework-agnostic callback events", async () => {
|
|
184
|
+
const store = new MemoryPanelStore();
|
|
185
|
+
|
|
186
|
+
await recordTelegramUpdate(store, {
|
|
187
|
+
update_id: 1,
|
|
188
|
+
callback_query: {
|
|
189
|
+
id: "cb1",
|
|
190
|
+
from: { id: 7, is_bot: false, first_name: "Pat", last_name: "Lee", username: "pat" },
|
|
191
|
+
message: { message_id: 2, chat: { id: 7, type: "private" } },
|
|
192
|
+
data: "demo:open",
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const chat = store.chats()[0];
|
|
197
|
+
const msg = store.history(7)[0];
|
|
198
|
+
|
|
199
|
+
assert.equal(chat?.firstName, "Pat");
|
|
200
|
+
assert.equal(chat?.lastName, "Lee");
|
|
201
|
+
assert.equal(chat?.lastEventType, "callback");
|
|
202
|
+
assert.equal(msg?.event?.type, "callback");
|
|
203
|
+
assert.equal(msg?.event?.data, "demo:open");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("recorder captures inline keyboards for timeline previews", async () => {
|
|
207
|
+
const store = new MemoryPanelStore();
|
|
208
|
+
|
|
209
|
+
await recordTelegramUpdate(store, {
|
|
210
|
+
update_id: 1,
|
|
211
|
+
message: {
|
|
212
|
+
message_id: 1,
|
|
213
|
+
date: 0,
|
|
214
|
+
chat: { id: 9, type: "private" },
|
|
215
|
+
from: { id: 9, is_bot: false, first_name: "Key" },
|
|
216
|
+
text: "choose",
|
|
217
|
+
reply_markup: {
|
|
218
|
+
inline_keyboard: [
|
|
219
|
+
[
|
|
220
|
+
{ text: "Open", callback_data: "open" },
|
|
221
|
+
{ text: "Docs", url: "https://yaeb.al" },
|
|
222
|
+
],
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
assert.deepEqual(store.history(9)[0]?.keyboard, {
|
|
229
|
+
type: "inline",
|
|
230
|
+
rows: [
|
|
231
|
+
[
|
|
232
|
+
{ text: "Open", kind: "callback", callbackData: "open" },
|
|
233
|
+
{ text: "Docs", kind: "url", url: "https://yaeb.al" },
|
|
234
|
+
],
|
|
235
|
+
],
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
168
239
|
test("panel send rejects an empty/missing text with 400", async () => {
|
|
169
240
|
const { handler, sent } = fakePanel();
|
|
170
241
|
|
|
@@ -194,6 +265,25 @@ test("panel send forwards whitelisted extras (parse_mode) to sendMessage", async
|
|
|
194
265
|
assert.deepEqual(sent, [{ chat_id: 1, text: "*hi*", parse_mode: "MarkdownV2" }]);
|
|
195
266
|
});
|
|
196
267
|
|
|
268
|
+
test("panel send forwards and self-records reply_markup previews", async () => {
|
|
269
|
+
const { handler, sent, store } = fakePanel();
|
|
270
|
+
const replyMarkup = { inline_keyboard: [[{ text: "Open", callback_data: "open" }]] };
|
|
271
|
+
|
|
272
|
+
await handler(
|
|
273
|
+
new Request("http://x/api/chats/1/send?token=secret", {
|
|
274
|
+
method: "POST",
|
|
275
|
+
headers: { "content-type": "application/json" },
|
|
276
|
+
body: JSON.stringify({ text: "choose", reply_markup: replyMarkup }),
|
|
277
|
+
}),
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
assert.deepEqual(sent.at(-1), { chat_id: 1, text: "choose", reply_markup: replyMarkup });
|
|
281
|
+
assert.deepEqual(store.history(1).at(-1)?.keyboard, {
|
|
282
|
+
type: "inline",
|
|
283
|
+
rows: [[{ text: "Open", kind: "callback", callbackData: "open" }]],
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
197
287
|
test("panel API returns 404 for an unknown path", async () => {
|
|
198
288
|
const { handler } = fakePanel();
|
|
199
289
|
|
|
@@ -320,11 +410,16 @@ test("SSE stream advertises text/event-stream and forwards a record event", asyn
|
|
|
320
410
|
const res = await handler(new Request("http://x/api/stream?token=secret"));
|
|
321
411
|
assert.match(res.headers.get("content-type") ?? "", /text\/event-stream/);
|
|
322
412
|
|
|
323
|
-
const reader = res.body
|
|
413
|
+
const reader = res.body?.getReader();
|
|
414
|
+
assert.ok(reader);
|
|
324
415
|
await reader.read(); // ": connected"
|
|
416
|
+
|
|
325
417
|
store.record({ id: 1, name: "@u" }, { direction: "in", text: "ping", date: 1 });
|
|
418
|
+
|
|
326
419
|
const { value } = await reader.read();
|
|
420
|
+
assert.ok(value);
|
|
327
421
|
assert.match(new TextDecoder().decode(value), /event: record/);
|
|
422
|
+
|
|
328
423
|
await reader.cancel();
|
|
329
424
|
});
|
|
330
425
|
|
|
@@ -420,7 +515,12 @@ test("recorder extracts a document with name + mime", async () => {
|
|
|
420
515
|
date: 0,
|
|
421
516
|
chat: { id: 8, type: "private" },
|
|
422
517
|
from: { id: 8, is_bot: false, first_name: "Lee" },
|
|
423
|
-
document: {
|
|
518
|
+
document: {
|
|
519
|
+
file_id: "doc1",
|
|
520
|
+
file_unique_id: "d",
|
|
521
|
+
file_name: "report.pdf",
|
|
522
|
+
mime_type: "application/pdf",
|
|
523
|
+
},
|
|
424
524
|
},
|
|
425
525
|
} as never,
|
|
426
526
|
updateType: "message",
|