paperkite 0.1.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.
Files changed (121) hide show
  1. package/README.md +81 -0
  2. package/data/flows.example.yml +65 -0
  3. package/data/settings.example.yml +13 -0
  4. package/dist/data/hooks/weekend-slot.d.ts +12 -0
  5. package/dist/data/hooks/weekend-slot.js +107 -0
  6. package/dist/data/hooks/weekend-slot.js.map +1 -0
  7. package/dist/packages/account-health/src/index.d.ts +8 -0
  8. package/dist/packages/account-health/src/index.js +37 -0
  9. package/dist/packages/account-health/src/index.js.map +1 -0
  10. package/dist/packages/archive/src/archiver.d.ts +98 -0
  11. package/dist/packages/archive/src/archiver.js +399 -0
  12. package/dist/packages/archive/src/archiver.js.map +1 -0
  13. package/dist/packages/archive/src/config.d.ts +22 -0
  14. package/dist/packages/archive/src/config.js +62 -0
  15. package/dist/packages/archive/src/config.js.map +1 -0
  16. package/dist/packages/archive/src/console/live.d.ts +24 -0
  17. package/dist/packages/archive/src/console/live.js +144 -0
  18. package/dist/packages/archive/src/console/live.js.map +1 -0
  19. package/dist/packages/archive/src/console/media.d.ts +66 -0
  20. package/dist/packages/archive/src/console/media.js +368 -0
  21. package/dist/packages/archive/src/console/media.js.map +1 -0
  22. package/dist/packages/archive/src/console/server.d.ts +12 -0
  23. package/dist/packages/archive/src/console/server.js +583 -0
  24. package/dist/packages/archive/src/console/server.js.map +1 -0
  25. package/dist/packages/archive/src/console/service.d.ts +13 -0
  26. package/dist/packages/archive/src/console/service.js +86 -0
  27. package/dist/packages/archive/src/console/service.js.map +1 -0
  28. package/dist/packages/archive/src/storage/index.d.ts +6 -0
  29. package/dist/packages/archive/src/storage/index.js +23 -0
  30. package/dist/packages/archive/src/storage/index.js.map +1 -0
  31. package/dist/packages/archive/src/storage/model.d.ts +296 -0
  32. package/dist/packages/archive/src/storage/model.js +151 -0
  33. package/dist/packages/archive/src/storage/model.js.map +1 -0
  34. package/dist/packages/archive/src/storage/postgres.d.ts +67 -0
  35. package/dist/packages/archive/src/storage/postgres.js +1021 -0
  36. package/dist/packages/archive/src/storage/postgres.js.map +1 -0
  37. package/dist/packages/archive/src/storage/sqlite.d.ts +59 -0
  38. package/dist/packages/archive/src/storage/sqlite.js +981 -0
  39. package/dist/packages/archive/src/storage/sqlite.js.map +1 -0
  40. package/dist/packages/notify-bark/src/index.d.ts +33 -0
  41. package/dist/packages/notify-bark/src/index.js +96 -0
  42. package/dist/packages/notify-bark/src/index.js.map +1 -0
  43. package/dist/packages/runtime-console/src/console/errors.d.ts +5 -0
  44. package/dist/packages/runtime-console/src/console/errors.js +11 -0
  45. package/dist/packages/runtime-console/src/console/errors.js.map +1 -0
  46. package/dist/packages/runtime-console/src/console/logs.d.ts +1 -0
  47. package/dist/packages/runtime-console/src/console/logs.js +27 -0
  48. package/dist/packages/runtime-console/src/console/logs.js.map +1 -0
  49. package/dist/packages/runtime-console/src/console/server.d.ts +6 -0
  50. package/dist/packages/runtime-console/src/console/server.js +182 -0
  51. package/dist/packages/runtime-console/src/console/server.js.map +1 -0
  52. package/dist/packages/runtime-console/src/index.d.ts +10 -0
  53. package/dist/packages/runtime-console/src/index.js +83 -0
  54. package/dist/packages/runtime-console/src/index.js.map +1 -0
  55. package/dist/src/app.d.ts +15 -0
  56. package/dist/src/app.js +34 -0
  57. package/dist/src/app.js.map +1 -0
  58. package/dist/src/cli.d.ts +2 -0
  59. package/dist/src/cli.js +250 -0
  60. package/dist/src/cli.js.map +1 -0
  61. package/dist/src/config/editor.d.ts +2 -0
  62. package/dist/src/config/editor.js +19 -0
  63. package/dist/src/config/editor.js.map +1 -0
  64. package/dist/src/config/loader.d.ts +6 -0
  65. package/dist/src/config/loader.js +188 -0
  66. package/dist/src/config/loader.js.map +1 -0
  67. package/dist/src/config/model.d.ts +71 -0
  68. package/dist/src/config/model.js +97 -0
  69. package/dist/src/config/model.js.map +1 -0
  70. package/dist/src/config/paths.d.ts +3 -0
  71. package/dist/src/config/paths.js +12 -0
  72. package/dist/src/config/paths.js.map +1 -0
  73. package/dist/src/config/settings.d.ts +19 -0
  74. package/dist/src/config/settings.js +44 -0
  75. package/dist/src/config/settings.js.map +1 -0
  76. package/dist/src/control/process-lock.d.ts +4 -0
  77. package/dist/src/control/process-lock.js +57 -0
  78. package/dist/src/control/process-lock.js.map +1 -0
  79. package/dist/src/control/socket.d.ts +14 -0
  80. package/dist/src/control/socket.js +131 -0
  81. package/dist/src/control/socket.js.map +1 -0
  82. package/dist/src/engine/hooks.d.ts +4 -0
  83. package/dist/src/engine/hooks.js +38 -0
  84. package/dist/src/engine/hooks.js.map +1 -0
  85. package/dist/src/engine/logger.d.ts +17 -0
  86. package/dist/src/engine/logger.js +78 -0
  87. package/dist/src/engine/logger.js.map +1 -0
  88. package/dist/src/engine/runtime.d.ts +78 -0
  89. package/dist/src/engine/runtime.js +799 -0
  90. package/dist/src/engine/runtime.js.map +1 -0
  91. package/dist/src/engine/scheduler.d.ts +9 -0
  92. package/dist/src/engine/scheduler.js +58 -0
  93. package/dist/src/engine/scheduler.js.map +1 -0
  94. package/dist/src/extensions/loader.d.ts +11 -0
  95. package/dist/src/extensions/loader.js +202 -0
  96. package/dist/src/extensions/loader.js.map +1 -0
  97. package/dist/src/extensions/manager.d.ts +1 -0
  98. package/dist/src/extensions/manager.js +121 -0
  99. package/dist/src/extensions/manager.js.map +1 -0
  100. package/dist/src/extensions/profile.d.ts +14 -0
  101. package/dist/src/extensions/profile.js +57 -0
  102. package/dist/src/extensions/profile.js.map +1 -0
  103. package/dist/src/extensions/registry.d.ts +19 -0
  104. package/dist/src/extensions/registry.js +62 -0
  105. package/dist/src/extensions/registry.js.map +1 -0
  106. package/dist/src/telegram/client.d.ts +20 -0
  107. package/dist/src/telegram/client.js +54 -0
  108. package/dist/src/telegram/client.js.map +1 -0
  109. package/dist/src/telegram/failure.d.ts +2 -0
  110. package/dist/src/telegram/failure.js +98 -0
  111. package/dist/src/telegram/failure.js.map +1 -0
  112. package/dist/src/telegram/login.d.ts +1 -0
  113. package/dist/src/telegram/login.js +31 -0
  114. package/dist/src/telegram/login.js.map +1 -0
  115. package/dist/src/telegram/pool.d.ts +47 -0
  116. package/dist/src/telegram/pool.js +300 -0
  117. package/dist/src/telegram/pool.js.map +1 -0
  118. package/dist/src/telegram/session-files.d.ts +4 -0
  119. package/dist/src/telegram/session-files.js +34 -0
  120. package/dist/src/telegram/session-files.js.map +1 -0
  121. package/package.json +62 -0
@@ -0,0 +1,86 @@
1
+ import { access } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import fastifyStatic from "@fastify/static";
5
+ import { Service } from "@paperkite/sdk";
6
+ import { createArchiveConsoleServer } from "./server.js";
7
+ import { createArchiveStore, resolveBackend } from "../storage/index.js";
8
+ export class ArchiveConsoleWebService extends Service {
9
+ async run() {
10
+ const config = this.config ?? {};
11
+ const store = createArchiveStore({
12
+ url: config.url,
13
+ schema: config.schema
14
+ });
15
+ const server = createArchiveConsoleServer({
16
+ store,
17
+ backend: resolveBackend(config.url),
18
+ mediaDir: config.mediaDir,
19
+ session: this.session,
20
+ sessions: this.sessions,
21
+ logger: this.context.logger
22
+ });
23
+ try {
24
+ await store.init();
25
+ await server.register(fastifyStatic, {
26
+ root: await publicDirectory(config.publicDir),
27
+ index: "index.html"
28
+ });
29
+ const host = config.host ?? "127.0.0.1";
30
+ const port = normalizeConsolePort(config.port);
31
+ await listenRetrying(server, host, port, this.context.logger);
32
+ this.context.logger.info("archive console listening", { host, port });
33
+ await waitForAbort(this.signal);
34
+ }
35
+ finally {
36
+ await server.close().catch(() => undefined);
37
+ await store.close();
38
+ }
39
+ }
40
+ }
41
+ function normalizeConsolePort(value) {
42
+ const port = Number(value ?? 3379);
43
+ if (!Number.isInteger(port) || port < 1 || port > 65_535) {
44
+ throw new Error("archive console port must be an integer between 1 and 65535");
45
+ }
46
+ return port;
47
+ }
48
+ async function listenRetrying(server, host, port, logger) {
49
+ for (let attempt = 0;; attempt += 1) {
50
+ try {
51
+ await server.listen({ host, port });
52
+ return;
53
+ }
54
+ catch (error) {
55
+ if (attempt < 6 && isEaddrinuse(error)) {
56
+ logger.warn("archive console port is still held by a stopping instance, retrying", { host, port });
57
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, 500));
58
+ continue;
59
+ }
60
+ throw error;
61
+ }
62
+ }
63
+ }
64
+ function isEaddrinuse(error) {
65
+ return error instanceof Error && "code" in error && error.code === "EADDRINUSE";
66
+ }
67
+ function waitForAbort(signal) {
68
+ if (signal.aborted)
69
+ return Promise.resolve();
70
+ return new Promise((resolvePromise) => {
71
+ signal.addEventListener("abort", () => resolvePromise(), { once: true });
72
+ });
73
+ }
74
+ async function publicDirectory(configured) {
75
+ if (configured)
76
+ return resolve(configured);
77
+ const local = fileURLToPath(new URL("../public", import.meta.url));
78
+ try {
79
+ await access(local);
80
+ return local;
81
+ }
82
+ catch {
83
+ return resolve(process.cwd(), "packages/archive/public");
84
+ }
85
+ }
86
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../../../../../packages/archive/src/console/service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { ArchiveStore, ArchiveStoreOptions } from "./model.js";
2
+ export * from "./model.js";
3
+ export { PostgresArchiveStore } from "./postgres.js";
4
+ export { SqliteArchiveStore } from "./sqlite.js";
5
+ export declare function createArchiveStore(options?: ArchiveStoreOptions): ArchiveStore;
6
+ export declare function resolveBackend(url: string | undefined): "sqlite" | "postgres";
@@ -0,0 +1,23 @@
1
+ import { resolve } from "node:path";
2
+ import { PostgresArchiveStore } from "./postgres.js";
3
+ import { SqliteArchiveStore } from "./sqlite.js";
4
+ export * from "./model.js";
5
+ export { PostgresArchiveStore } from "./postgres.js";
6
+ export { SqliteArchiveStore } from "./sqlite.js";
7
+ export function createArchiveStore(options = {}) {
8
+ const url = options.url ?? "sqlite:data/archive.db";
9
+ if (resolveBackend(url) === "sqlite") {
10
+ const file = url.slice("sqlite:".length) || "data/archive.db";
11
+ return new SqliteArchiveStore(resolve(file));
12
+ }
13
+ return new PostgresArchiveStore(url, options.schema ?? "public");
14
+ }
15
+ export function resolveBackend(url) {
16
+ const value = (url ?? "sqlite:data/archive.db").trim().toLowerCase();
17
+ if (value.startsWith("postgres:") || value.startsWith("postgresql:"))
18
+ return "postgres";
19
+ if (value.startsWith("sqlite:"))
20
+ return "sqlite";
21
+ throw new Error(`unknown archive backend scheme in url: ${String(url)} (use sqlite: or postgresql:)`);
22
+ }
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/archive/src/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGjD,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,UAAU,kBAAkB,CAAC,OAAO,GAAwB,EAAE;IAClE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,wBAAwB,CAAC;IACpD,IAAI,cAAc,CAAC,GAAG,CAAC,KAAK,QAAQ,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,iBAAiB,CAAC;QAC9D,OAAO,IAAI,kBAAkB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,IAAI,oBAAoB,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,IAAI,QAAQ,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,GAAuB;IACpD,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,wBAAwB,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrE,IAAI,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,aAAa,CAAC;QAAE,OAAO,UAAU,CAAC;IACxF,IAAI,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,QAAQ,CAAC;IACjD,MAAM,IAAI,KAAK,CAAC,0CAA0C,MAAM,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;AACxG,CAAC"}
@@ -0,0 +1,296 @@
1
+ export type TimeMode = "include" | "exclude" | "off";
2
+ /** 一次归档抓取后落库的聊天元数据。 */
3
+ export interface ChatRecord {
4
+ readonly chatId: string;
5
+ readonly title?: string;
6
+ readonly username?: string;
7
+ readonly type?: string;
8
+ readonly description?: string;
9
+ readonly membersCount?: number;
10
+ }
11
+ /** 增量锚点:该群最新已归档消息。 */
12
+ export interface LastMessageInfo {
13
+ readonly messageId: number;
14
+ readonly date: string;
15
+ }
16
+ /** 消息实体(对齐 Telegram 原生 entities):offset/length 为 UTF-16 码元,链接实体带 url。 */
17
+ export interface MessageEntity {
18
+ readonly className: string;
19
+ readonly offset: number;
20
+ readonly length: number;
21
+ readonly url?: string;
22
+ }
23
+ /** 写入后端的一条消息行(字段与 messages 表一一对应)。 */
24
+ export interface MessageRow {
25
+ readonly messageId: number;
26
+ readonly chatId: string;
27
+ readonly groupedId?: string;
28
+ readonly chatTitle?: string;
29
+ readonly senderId?: string;
30
+ readonly senderUsername?: string;
31
+ readonly senderFirstName?: string;
32
+ readonly senderLastName?: string;
33
+ readonly date: string;
34
+ readonly text: string;
35
+ readonly entities?: readonly MessageEntity[];
36
+ readonly messageType: string;
37
+ readonly replyToMessageId?: number;
38
+ readonly forwardFromId?: string;
39
+ readonly forwardFromName?: string;
40
+ readonly hasMedia: boolean;
41
+ readonly mediaType?: string;
42
+ readonly mediaFilePath?: string;
43
+ }
44
+ /** 写入后端的一条媒体记录行。 */
45
+ export interface MediaRow {
46
+ readonly messageId: number;
47
+ readonly chatId: string;
48
+ readonly mediaType: string;
49
+ readonly fileName: string;
50
+ readonly filePath: string;
51
+ readonly fileSize?: number;
52
+ readonly mimeType?: string;
53
+ }
54
+ export interface BatchResult {
55
+ readonly messages: number;
56
+ readonly media: number;
57
+ }
58
+ /** 已落库的媒体文件完整记录(媒体文件路由用)。 */
59
+ export interface StoredMediaFile {
60
+ readonly id: string;
61
+ readonly messageId: number;
62
+ readonly chatId: string;
63
+ readonly mediaType: string;
64
+ readonly fileName?: string;
65
+ readonly filePath?: string;
66
+ readonly fileSize?: number;
67
+ readonly mimeType?: string;
68
+ }
69
+ /** 同相册内的一条可预览消息(来自 messages 表)。 */
70
+ export interface AlbumRow {
71
+ readonly recordId: string;
72
+ readonly messageId: number;
73
+ readonly chatId: string;
74
+ readonly groupedId: string;
75
+ readonly date: string;
76
+ readonly hasMedia: boolean;
77
+ readonly mediaType?: string;
78
+ readonly messageType?: string;
79
+ readonly mimeType?: string;
80
+ }
81
+ /** 检索返回的完整消息行(附媒体文件与相册行)。 */
82
+ export interface MessageRecord {
83
+ readonly recordId: string;
84
+ readonly messageId: number;
85
+ readonly chatId: string;
86
+ readonly groupedId?: string;
87
+ readonly chatTitle?: string;
88
+ readonly date: string;
89
+ readonly senderId?: string;
90
+ readonly senderUsername?: string;
91
+ readonly senderFirstName?: string;
92
+ readonly senderLastName?: string;
93
+ readonly hasMedia: boolean;
94
+ readonly mediaType?: string;
95
+ readonly messageType?: string;
96
+ readonly mimeType?: string;
97
+ readonly replyToMessageId?: number;
98
+ /** 被回复消息的文本摘录(同会话内按消息 ID 定位,blocked = 0)。 */
99
+ readonly replyToText?: string;
100
+ readonly forwardFromId?: string;
101
+ readonly forwardFromName?: string;
102
+ /** 原始消息实体(对齐 Telegram 原生 entities,链接渲染用)。 */
103
+ readonly entities?: readonly MessageEntity[];
104
+ readonly text: string;
105
+ readonly mediaFiles: readonly StoredMediaFile[];
106
+ readonly albumRows: readonly AlbumRow[];
107
+ }
108
+ export interface ArchiveQuery {
109
+ readonly keyword?: string;
110
+ readonly excludeKeyword?: string;
111
+ /** 多会话检索:与其余条件 AND,会话之间 OR。 */
112
+ readonly chatIds?: readonly string[];
113
+ readonly chatTitle?: string;
114
+ readonly dateFrom?: string;
115
+ readonly dateTo?: string;
116
+ readonly timeMode?: TimeMode;
117
+ /** 多用户检索:与其余条件 AND,用户之间 OR。 */
118
+ readonly senderIds?: readonly string[];
119
+ readonly forwardFromId?: string;
120
+ readonly forwardFromName?: string;
121
+ readonly limit?: number;
122
+ readonly offset?: number;
123
+ }
124
+ export interface ArchiveSearchResult {
125
+ /** 条目视图:相册成员折叠成单个相册条目,分页按条目推进。 */
126
+ readonly items: readonly ContextEntry[];
127
+ readonly total: number;
128
+ /** 过滤条件命中的消息总数(相册按成员条数计,首页统计用)。 */
129
+ readonly totalMessages: number;
130
+ readonly limit: number;
131
+ readonly offset: number;
132
+ }
133
+ export interface MessageContextEntry {
134
+ readonly kind: "message";
135
+ readonly record: MessageRecord;
136
+ }
137
+ export interface AlbumContextEntry {
138
+ readonly kind: "album";
139
+ readonly rows: readonly MessageRecord[];
140
+ readonly captionText: string;
141
+ readonly recordId: string;
142
+ readonly focusRecordId?: string;
143
+ }
144
+ export type ContextEntry = MessageContextEntry | AlbumContextEntry;
145
+ /** 检索用户信息:按发送者聚合后的展示字段。 */
146
+ export interface SenderInfo {
147
+ readonly senderId: string;
148
+ readonly username?: string;
149
+ readonly firstName?: string;
150
+ readonly lastName?: string;
151
+ }
152
+ export interface SenderQuery {
153
+ readonly q?: string;
154
+ readonly chatId?: string;
155
+ readonly limit?: number;
156
+ }
157
+ export interface SenderSearchResult {
158
+ readonly items: readonly SenderInfo[];
159
+ readonly total: number;
160
+ }
161
+ /** 用户聚合概要中的单会话条目。 */
162
+ export interface SenderSummaryChat {
163
+ readonly chatId: string;
164
+ readonly chatTitle?: string;
165
+ readonly count: number;
166
+ readonly lastDate?: string;
167
+ readonly lastText?: string;
168
+ }
169
+ /** 用户聚合概要:总计 + 按会话拆分(含每会话最新文本)。 */
170
+ export interface SenderSummary {
171
+ readonly sender: SenderInfo;
172
+ readonly total: number;
173
+ readonly firstDate?: string;
174
+ readonly lastDate?: string;
175
+ readonly chats: readonly SenderSummaryChat[];
176
+ }
177
+ /** 回复链:锚点的回复对象与回复者;parent/children 均为上下文条目。 */
178
+ export interface ReplyChainResult {
179
+ readonly parent?: ContextEntry;
180
+ readonly children: readonly ContextEntry[];
181
+ readonly replyToMessageId?: number;
182
+ }
183
+ /** 锚点条目及两侧上下文;beforeN/afterN 为该侧条目总数(不含锚点)。 */
184
+ export interface ArchiveContextResult {
185
+ readonly anchor: ContextEntry | undefined;
186
+ readonly before: readonly ContextEntry[];
187
+ readonly after: readonly ContextEntry[];
188
+ readonly beforeN: number;
189
+ readonly afterN: number;
190
+ }
191
+ export declare function captionOf(rows: readonly MessageRecord[]): string;
192
+ export declare function uniqueGroupKeys(rows: readonly Record<string, unknown>[]): (readonly [string, string])[];
193
+ export declare function albumEntryOf(rows: readonly MessageRecord[], focusRecordId?: string): ContextEntry;
194
+ export declare function buildContextEntries(baselines: readonly MessageRecord[], groupRows: ReadonlyMap<string, readonly MessageRecord[]>): ContextEntry[];
195
+ /** 会话清单行:chats 元数据 + 消息表的按会话聚合。 */
196
+ export interface ChatLedgerRow {
197
+ readonly chatId: string;
198
+ readonly title: string;
199
+ readonly username?: string;
200
+ readonly type?: string;
201
+ readonly description?: string;
202
+ readonly membersCount?: number;
203
+ readonly count: number;
204
+ readonly lastDate?: string;
205
+ readonly lastText?: string;
206
+ }
207
+ export interface ArchiveStoreOptions {
208
+ readonly url?: string;
209
+ readonly schema?: string;
210
+ }
211
+ /** 屏蔽词快照:词表(已归一化小写)与内存缓存版本号。 */
212
+ export interface BlockwordState {
213
+ readonly words: readonly string[];
214
+ readonly version: number;
215
+ }
216
+ export type BlockwordAddResult = "added" | "exists" | "invalid";
217
+ /** 被屏蔽用户:以 sender_id 精确匹配消息行,附展示信息供管理界面使用。 */
218
+ export interface BlockedUserInfo {
219
+ readonly userId: string;
220
+ readonly name?: string;
221
+ readonly username?: string;
222
+ }
223
+ export interface BlockedUserInput {
224
+ readonly userId: string;
225
+ readonly name?: string;
226
+ readonly username?: string;
227
+ }
228
+ /** 屏蔽用户快照:名单与内存缓存版本号。 */
229
+ export interface BlockedUserState {
230
+ readonly users: readonly BlockedUserInfo[];
231
+ readonly version: number;
232
+ }
233
+ export type BlockedUserAddResult = "added" | "exists" | "invalid";
234
+ export interface ArchiveStore {
235
+ init(): Promise<void>;
236
+ close(): Promise<void>;
237
+ saveChat(chat: ChatRecord): Promise<boolean>;
238
+ startSyncSession(chatId: string, startDate: string, endDate: string): Promise<number>;
239
+ completeSyncSession(sessionId: number, messagesCount: number, mediaCount: number): Promise<void>;
240
+ getLastMessageInfo(chatId: string): Promise<LastMessageInfo | undefined>;
241
+ getChatUsername(chatId: string): Promise<string | undefined>;
242
+ messageIdsExist(chatId: string, messageIds: readonly number[]): Promise<ReadonlySet<number>>;
243
+ saveBatch(messages: readonly MessageRow[], media: readonly MediaRow[]): Promise<BatchResult>;
244
+ searchStructured(query: ArchiveQuery): Promise<ArchiveSearchResult>;
245
+ listChatLedger(limit: number): Promise<ChatLedgerRow[]>;
246
+ getMessageContext(recordId: string, beforeN: number, afterN: number, beforeOffset?: number, afterOffset?: number): Promise<ArchiveContextResult>;
247
+ getMessageByRecordId(recordId: string): Promise<MessageRecord | undefined>;
248
+ /** 用户维度检索:按发送者聚合(blocked = 0),支持名字/用户名/ID 匹配。 */
249
+ searchSenders(query: SenderQuery): Promise<SenderSearchResult>;
250
+ /** 用户聚合概要:不存在该用户(不看屏蔽)返回 undefined,路由映射 404。 */
251
+ getSenderSummary(senderId: string, chatId?: string): Promise<SenderSummary | undefined>;
252
+ /** 回复链:锚点不存在(含被屏蔽)返回 undefined,路由映射 404。 */
253
+ getReplyChain(recordId: string): Promise<ReplyChainResult | undefined>;
254
+ /** 按聊天引用解析消息:chatId、username 或私有频道 code 之一定位聊天,返回存档行 ID;不存在返回 undefined。 */
255
+ resolveMessageRef(ref: {
256
+ chatId?: string;
257
+ username?: string;
258
+ code?: string;
259
+ messageId: number;
260
+ }): Promise<string | undefined>;
261
+ getMediaFileById(id: string): Promise<StoredMediaFile | undefined>;
262
+ /** 屏蔽词内存缓存快照,读路径不触表。 */
263
+ listBlockwords(): Promise<BlockwordState>;
264
+ /** 写透缓存:先落库再更新内存缓存,并同步三条消息的 blocked 标志。 */
265
+ addBlockword(word: string): Promise<BlockwordAddResult>;
266
+ /** 写透缓存;删除后仅重算命中被删词的行。 */
267
+ removeBlockword(word: string): Promise<boolean>;
268
+ /** 屏蔽用户内存缓存快照,读路径不触表。 */
269
+ listBlockedUsers(): Promise<BlockedUserState>;
270
+ /** 写透缓存:先落库再更新内存缓存,并同步 sender 命中行的 blocked 标志。 */
271
+ addBlockedUser(input: BlockedUserInput): Promise<BlockedUserAddResult>;
272
+ /** 写透缓存;删除后仅重算发送者为被删用户的行。 */
273
+ removeBlockedUser(userId: string): Promise<boolean>;
274
+ }
275
+ export declare function normalizeLimit(value: number | undefined): number;
276
+ export declare function normalizeOffset(value: number | undefined): number;
277
+ export declare function normalizeContextLimit(value: number | undefined): number;
278
+ export declare function normalizeTimeMode(value: TimeMode | undefined): TimeMode;
279
+ export declare function splitTerms(value: string | undefined): string[];
280
+ /** 解析时间输入为 UTC ISO 字符串;空值返回 undefined,非法输入抛错。 */
281
+ export declare function normalizeDate(value: string | undefined): string | undefined;
282
+ export declare function normalizeText(value: string | undefined): string | undefined;
283
+ export declare const BLOCKWORD_MAX_LENGTH = 64;
284
+ /** 屏蔽词归一化:裁剪、小写;空词或超长返回 undefined。 */
285
+ export declare function normalizeBlockword(value: string): string | undefined;
286
+ export declare const BLOCKED_USER_MAX_LENGTH = 64;
287
+ /** 屏蔽用户 ID 归一化:裁剪;空串或超长返回 undefined。 */
288
+ export declare function normalizeUserId(value: string): string | undefined;
289
+ export declare function normalizeRecordId(value: string): string;
290
+ export declare function mediaKey(chatId: string, messageId: number): string;
291
+ export declare function groupKey(chatId: string, groupedId: string): string;
292
+ export declare function paramPlaceholders(start: number, count: number): string;
293
+ /** 实体列:JSON 串 ↔ 实体数组(空数组与空值同样视为无实体)。 */
294
+ export declare function entitiesJson(entities: readonly MessageEntity[] | undefined): string | null;
295
+ export declare function parseEntities(value: unknown): readonly MessageEntity[] | undefined;
296
+ export declare function toIsoDate(value: unknown): string;
@@ -0,0 +1,151 @@
1
+ export function captionOf(rows) {
2
+ return rows.find((row) => row.text.trim().length > 0)?.text ?? "";
3
+ }
4
+ export function uniqueGroupKeys(rows) {
5
+ const seen = new Set();
6
+ const keys = [];
7
+ for (const row of rows) {
8
+ const value = row.grouped_id;
9
+ if (value === undefined || value === null || value === "")
10
+ continue;
11
+ const grouped = String(value);
12
+ const key = groupKey(String(row.chat_id), grouped);
13
+ if (seen.has(key))
14
+ continue;
15
+ seen.add(key);
16
+ keys.push([String(row.chat_id), grouped]);
17
+ }
18
+ return keys;
19
+ }
20
+ export function albumEntryOf(rows, focusRecordId) {
21
+ if (rows.length < 2)
22
+ return { kind: "message", record: rows[0] };
23
+ return {
24
+ kind: "album",
25
+ rows,
26
+ captionText: captionOf(rows),
27
+ recordId: rows[0].recordId,
28
+ ...(focusRecordId !== undefined ? { focusRecordId } : {})
29
+ };
30
+ }
31
+ export function buildContextEntries(baselines, groupRows) {
32
+ return baselines.map((row) => {
33
+ if (row.groupedId === undefined)
34
+ return { kind: "message", record: row };
35
+ const rows = groupRows.get(groupKey(row.chatId, row.groupedId));
36
+ if (rows === undefined || rows.length < 2)
37
+ return { kind: "message", record: row };
38
+ return { kind: "album", rows, captionText: captionOf(rows), recordId: row.recordId };
39
+ });
40
+ }
41
+ export function normalizeLimit(value) {
42
+ return clampInteger(value, 1, 200, 50);
43
+ }
44
+ export function normalizeOffset(value) {
45
+ const number = Number(value);
46
+ return Number.isInteger(number) && number > 0 ? number : 0;
47
+ }
48
+ export function normalizeContextLimit(value) {
49
+ return clampInteger(value, 0, 999, 10);
50
+ }
51
+ export function normalizeTimeMode(value) {
52
+ const mode = value ?? "include";
53
+ if (mode !== "include" && mode !== "exclude" && mode !== "off") {
54
+ throw new Error(`invalid time mode: ${String(value)}`);
55
+ }
56
+ return mode;
57
+ }
58
+ export function splitTerms(value) {
59
+ return (value ?? "").trim().split(/\s+/).filter(Boolean);
60
+ }
61
+ /** 解析时间输入为 UTC ISO 字符串;空值返回 undefined,非法输入抛错。 */
62
+ export function normalizeDate(value) {
63
+ const text = (value ?? "").trim();
64
+ if (!text)
65
+ return undefined;
66
+ const time = Date.parse(text);
67
+ if (Number.isNaN(time))
68
+ throw new Error(`invalid date: ${value}`);
69
+ return new Date(time).toISOString();
70
+ }
71
+ export function normalizeText(value) {
72
+ const text = (value ?? "").trim();
73
+ return text || undefined;
74
+ }
75
+ export const BLOCKWORD_MAX_LENGTH = 64;
76
+ /** 屏蔽词归一化:裁剪、小写;空词或超长返回 undefined。 */
77
+ export function normalizeBlockword(value) {
78
+ const text = value.trim().toLowerCase();
79
+ if (!text || text.length > BLOCKWORD_MAX_LENGTH)
80
+ return undefined;
81
+ return text;
82
+ }
83
+ export const BLOCKED_USER_MAX_LENGTH = 64;
84
+ /** 屏蔽用户 ID 归一化:裁剪;空串或超长返回 undefined。 */
85
+ export function normalizeUserId(value) {
86
+ const text = value.trim();
87
+ if (!text || text.length > BLOCKED_USER_MAX_LENGTH)
88
+ return undefined;
89
+ return text;
90
+ }
91
+ export function normalizeRecordId(value) {
92
+ const result = value.trim();
93
+ if (!/^\d+$/.test(result))
94
+ throw new Error(`invalid message record id: ${value}`);
95
+ return result;
96
+ }
97
+ export function mediaKey(chatId, messageId) {
98
+ return `${chatId}:${messageId}`;
99
+ }
100
+ export function groupKey(chatId, groupedId) {
101
+ return `${chatId}:${groupedId}`;
102
+ }
103
+ export function paramPlaceholders(start, count) {
104
+ return Array.from({ length: count }, (_, index) => `$${start + index}`).join(", ");
105
+ }
106
+ /** 实体列:JSON 串 ↔ 实体数组(空数组与空值同样视为无实体)。 */
107
+ export function entitiesJson(entities) {
108
+ return entities !== undefined && entities.length > 0 ? JSON.stringify(entities) : null;
109
+ }
110
+ export function parseEntities(value) {
111
+ if (typeof value !== "string" || value === "")
112
+ return undefined;
113
+ try {
114
+ const parsed = JSON.parse(value);
115
+ if (!Array.isArray(parsed))
116
+ return undefined;
117
+ const entities = parsed
118
+ .filter((item) => typeof item === "object" && item !== null)
119
+ .map((item) => ({
120
+ className: String(item.className ?? ""),
121
+ offset: Number(item.offset ?? 0),
122
+ length: Number(item.length ?? 0),
123
+ ...(typeof item.url === "string" && item.url ? { url: item.url } : {})
124
+ }))
125
+ .filter((entity) => entity.className !== "" && entity.length > 0);
126
+ return entities.length > 0 ? entities : undefined;
127
+ }
128
+ catch {
129
+ return undefined;
130
+ }
131
+ }
132
+ export function toIsoDate(value) {
133
+ if (value instanceof Date)
134
+ return value.toISOString();
135
+ const text = String(value).trim();
136
+ if (/^\d+$/.test(text)) {
137
+ const timestamp = Number(text);
138
+ return new Date(timestamp < 1e12 ? timestamp * 1_000 : timestamp).toISOString();
139
+ }
140
+ const time = Date.parse(text);
141
+ if (Number.isNaN(time))
142
+ throw new Error(`invalid date value: ${text}`);
143
+ return new Date(time).toISOString();
144
+ }
145
+ function clampInteger(value, lower, upper, fallback) {
146
+ const number = Number(value);
147
+ if (!Number.isInteger(number))
148
+ return fallback;
149
+ return Math.min(upper, Math.max(lower, number));
150
+ }
151
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../../../packages/archive/src/storage/model.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ import { type ArchiveContextResult, type ArchiveQuery, type ArchiveSearchResult, type ArchiveStore, type BatchResult, type BlockedUserAddResult, type BlockedUserInput, type BlockedUserState, type BlockwordAddResult, type BlockwordState, type ChatLedgerRow, type ChatRecord, type LastMessageInfo, type MediaRow, type MessageRecord, type MessageRow, type ReplyChainResult, type SenderQuery, type SenderSearchResult, type SenderSummary, type StoredMediaFile } from "./model.js";
2
+ export declare class PostgresArchiveStore implements ArchiveStore {
3
+ readonly url: string;
4
+ private readonly pool;
5
+ private readonly schemaName;
6
+ private readonly schema;
7
+ /** 屏蔽词内存缓存(小写归一);读路径条件固定为 blocked = FALSE,与词数无关。 */
8
+ private blockwords;
9
+ private blockwordsVersion;
10
+ /** 屏蔽用户内存缓存(sender_id 精确匹配)。 */
11
+ private blockedUsers;
12
+ private blockedUsersVersion;
13
+ constructor(url: string, schema?: string);
14
+ init(): Promise<void>;
15
+ /** 老库迁移:messages 补 blocked 标志列与 entities 实体列。 */
16
+ private ensureLegacyColumns;
17
+ /** blocked 标志维护的部分索引:置位只扫未屏蔽行,解锁只扫已屏蔽行。 */
18
+ private ensureBlockedIndexes;
19
+ close(): Promise<void>;
20
+ saveChat(chat: ChatRecord): Promise<boolean>;
21
+ startSyncSession(chatId: string, startDate: string, endDate: string): Promise<number>;
22
+ completeSyncSession(sessionId: number, messagesCount: number, mediaCount: number): Promise<void>;
23
+ getLastMessageInfo(chatId: string): Promise<LastMessageInfo | undefined>;
24
+ getChatUsername(chatId: string): Promise<string | undefined>;
25
+ messageIdsExist(chatId: string, messageIds: readonly number[]): Promise<ReadonlySet<number>>;
26
+ saveBatch(messages: readonly MessageRow[], media: readonly MediaRow[]): Promise<BatchResult>;
27
+ listBlockwords(): Promise<BlockwordState>;
28
+ addBlockword(word: string): Promise<BlockwordAddResult>;
29
+ removeBlockword(word: string): Promise<boolean>;
30
+ listBlockedUsers(): Promise<BlockedUserState>;
31
+ addBlockedUser(input: BlockedUserInput): Promise<BlockedUserAddResult>;
32
+ removeBlockedUser(userId: string): Promise<boolean>;
33
+ /** 增量置位:只置未屏蔽行中命中新词的行的标志。 */
34
+ private markBlockwordMatches;
35
+ /** 增量解锁:只重算已屏蔽且命中被删词的行,其余行不受影响。 */
36
+ private unmarkBlockwordMatches;
37
+ /** 增量置位:只置未屏蔽行中发送者为新用户的行的标志。 */
38
+ private markBlockedUserMatches;
39
+ /** 增量解锁:只重算已屏蔽且发送者为被删用户的行。 */
40
+ private unmarkBlockedUserMatches;
41
+ /**
42
+ * 增量解锁执行:match 命中已屏蔽行,剩余规则(holds)全不命中时解锁。
43
+ * 命中行先经子查询物化,避免 UPDATE 边扫索引边改 blocked 时漏行。
44
+ */
45
+ private runUnblock;
46
+ searchStructured(query: ArchiveQuery): Promise<ArchiveSearchResult>;
47
+ listChatLedger(limit: number): Promise<ChatLedgerRow[]>;
48
+ getMessageByRecordId(recordId: string): Promise<MessageRecord | undefined>;
49
+ searchSenders(query: SenderQuery): Promise<SenderSearchResult>;
50
+ getSenderSummary(senderId: string, chatId?: string): Promise<SenderSummary | undefined>;
51
+ getReplyChain(recordId: string): Promise<ReplyChainResult | undefined>;
52
+ resolveMessageRef(ref: {
53
+ chatId?: string;
54
+ username?: string;
55
+ code?: string;
56
+ messageId: number;
57
+ }): Promise<string | undefined>;
58
+ getMessageContext(recordId: string, beforeN: number, afterN: number, beforeOffset?: number, afterOffset?: number): Promise<ArchiveContextResult>;
59
+ private fetchGroupAttached;
60
+ private fetchGroupsAttached;
61
+ private groupRows;
62
+ getMediaFileById(id: string): Promise<StoredMediaFile | undefined>;
63
+ private attachMedia;
64
+ private fetchMediaFiles;
65
+ private fetchAlbumRows;
66
+ private table;
67
+ }