@will-17173/telegram-cli 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.
- package/LICENSE +675 -0
- package/README.md +264 -0
- package/README.zh-CN.md +264 -0
- package/dist/account/account-context.js +22 -0
- package/dist/account/account-context.js.map +1 -0
- package/dist/account/account-presets.js +22 -0
- package/dist/account/account-presets.js.map +1 -0
- package/dist/account/account-store.js +225 -0
- package/dist/account/account-store.js.map +1 -0
- package/dist/cli/app.js +21 -0
- package/dist/cli/app.js.map +1 -0
- package/dist/cli/output.js +39 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/commands/account-options.js +55 -0
- package/dist/commands/account-options.js.map +1 -0
- package/dist/commands/account.js +449 -0
- package/dist/commands/account.js.map +1 -0
- package/dist/commands/config.js +63 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/data.js +52 -0
- package/dist/commands/data.js.map +1 -0
- package/dist/commands/query.js +116 -0
- package/dist/commands/query.js.map +1 -0
- package/dist/commands/telegram.js +423 -0
- package/dist/commands/telegram.js.map +1 -0
- package/dist/commands/types.js +12 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/config/credential-store.js +82 -0
- package/dist/config/credential-store.js.map +1 -0
- package/dist/config/env.js +78 -0
- package/dist/config/env.js.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/presenters/human.js +218 -0
- package/dist/presenters/human.js.map +1 -0
- package/dist/presenters/ink/DetailView.js +14 -0
- package/dist/presenters/ink/DetailView.js.map +1 -0
- package/dist/presenters/ink/TableView.js +9 -0
- package/dist/presenters/ink/TableView.js.map +1 -0
- package/dist/presenters/ink/TimelineView.js +34 -0
- package/dist/presenters/ink/TimelineView.js.map +1 -0
- package/dist/presenters/ink/display-width.js +149 -0
- package/dist/presenters/ink/display-width.js.map +1 -0
- package/dist/presenters/ink/image-preview.js +47 -0
- package/dist/presenters/ink/image-preview.js.map +1 -0
- package/dist/presenters/ink/listen-scroll.js +37 -0
- package/dist/presenters/ink/listen-scroll.js.map +1 -0
- package/dist/presenters/ink/listen-scrollbar.js +49 -0
- package/dist/presenters/ink/listen-scrollbar.js.map +1 -0
- package/dist/presenters/ink/listen.js +542 -0
- package/dist/presenters/ink/listen.js.map +1 -0
- package/dist/presenters/ink/mouse-scroll.js +55 -0
- package/dist/presenters/ink/mouse-scroll.js.map +1 -0
- package/dist/presenters/ink/render.js +45 -0
- package/dist/presenters/ink/render.js.map +1 -0
- package/dist/presenters/listen-message.js +267 -0
- package/dist/presenters/listen-message.js.map +1 -0
- package/dist/presenters/structured.js +30 -0
- package/dist/presenters/structured.js.map +1 -0
- package/dist/services/attachment-download.js +21 -0
- package/dist/services/attachment-download.js.map +1 -0
- package/dist/services/data-service.js +76 -0
- package/dist/services/data-service.js.map +1 -0
- package/dist/services/listen-album-aggregator.js +72 -0
- package/dist/services/listen-album-aggregator.js.map +1 -0
- package/dist/services/message-service.js +102 -0
- package/dist/services/message-service.js.map +1 -0
- package/dist/services/query-service.js +116 -0
- package/dist/services/query-service.js.map +1 -0
- package/dist/services/sync-service.js +134 -0
- package/dist/services/sync-service.js.map +1 -0
- package/dist/storage/chat-resolver.js +9 -0
- package/dist/storage/chat-resolver.js.map +1 -0
- package/dist/storage/message-db.js +246 -0
- package/dist/storage/message-db.js.map +1 -0
- package/dist/telegram/client-factory.js +27 -0
- package/dist/telegram/client-factory.js.map +1 -0
- package/dist/telegram/mtcute-client.js +324 -0
- package/dist/telegram/mtcute-client.js.map +1 -0
- package/dist/telegram/types.js +2 -0
- package/dist/telegram/types.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { actionDetail } from '../presenters/human.js';
|
|
2
|
+
export class MessageService {
|
|
3
|
+
tg;
|
|
4
|
+
constructor(tg) {
|
|
5
|
+
this.tg = tg;
|
|
6
|
+
}
|
|
7
|
+
async send(options) {
|
|
8
|
+
const invalid = validateSend(options);
|
|
9
|
+
if (invalid)
|
|
10
|
+
return invalid;
|
|
11
|
+
try {
|
|
12
|
+
const result = await this.tg.sendMessage(options);
|
|
13
|
+
const payload = {
|
|
14
|
+
sent: true,
|
|
15
|
+
msg_id: result.msg_id,
|
|
16
|
+
chat: options.chat,
|
|
17
|
+
};
|
|
18
|
+
if (options.reply != null)
|
|
19
|
+
payload.reply_to = options.reply;
|
|
20
|
+
return { ok: true, data: payload, human: actionDetail('Message Sent', payload) };
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return telegramFailure(error);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async edit(options) {
|
|
27
|
+
const invalid = validateEdit(options);
|
|
28
|
+
if (invalid)
|
|
29
|
+
return invalid;
|
|
30
|
+
try {
|
|
31
|
+
await this.tg.editMessage({ chat: options.chat, msgId: options.msgId, text: options.text, linkPreview: options.linkPreview });
|
|
32
|
+
const data = { edited: true, msg_id: options.msgId, chat: options.chat };
|
|
33
|
+
return { ok: true, data, human: actionDetail('Message Edited', data) };
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
return telegramFailure(error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async delete(options) {
|
|
40
|
+
const invalid = validateDelete(options);
|
|
41
|
+
if (invalid)
|
|
42
|
+
return invalid;
|
|
43
|
+
try {
|
|
44
|
+
await this.tg.deleteMessages({ chat: options.chat, msgIds: options.msgIds });
|
|
45
|
+
const data = { deleted: true, msg_ids: options.msgIds, chat: options.chat };
|
|
46
|
+
return { ok: true, data, human: actionDetail('Messages Deleted', data) };
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
return telegramFailure(error);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function validateSend(options) {
|
|
54
|
+
if (!options.chat.trim())
|
|
55
|
+
return invalidOption('chat must be a non-empty string.');
|
|
56
|
+
if (!options.message.trim())
|
|
57
|
+
return invalidOption('message must not be empty.');
|
|
58
|
+
if (options.reply != null && !isPositiveInteger(options.reply))
|
|
59
|
+
return invalidOption('reply must be a positive integer.');
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
function validateEdit(options) {
|
|
63
|
+
if (!options.chat.trim())
|
|
64
|
+
return invalidOption('chat must be a non-empty string.');
|
|
65
|
+
if (!isPositiveInteger(options.msgId))
|
|
66
|
+
return invalidOption('msg_id must be a positive integer.');
|
|
67
|
+
if (!options.text.trim())
|
|
68
|
+
return invalidOption('text must not be empty.');
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
function validateDelete(options) {
|
|
72
|
+
if (!options.chat.trim())
|
|
73
|
+
return invalidOption('chat must be a non-empty string.');
|
|
74
|
+
if (!Array.isArray(options.msgIds) || options.msgIds.length === 0) {
|
|
75
|
+
return invalidOption('msg_ids must be a non-empty array.');
|
|
76
|
+
}
|
|
77
|
+
if (!options.msgIds.every(isPositiveInteger))
|
|
78
|
+
return invalidOption('All msg_ids must be positive integers.');
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
function isPositiveInteger(value) {
|
|
82
|
+
return Number.isInteger(value) && value > 0;
|
|
83
|
+
}
|
|
84
|
+
function invalidOption(message) {
|
|
85
|
+
return { ok: false, error: { code: 'invalid_option', message } };
|
|
86
|
+
}
|
|
87
|
+
function telegramFailure(error) {
|
|
88
|
+
const details = errorDetails(error);
|
|
89
|
+
return {
|
|
90
|
+
ok: false,
|
|
91
|
+
error: details == null
|
|
92
|
+
? { code: 'telegram_error', message: errorMessage(error) }
|
|
93
|
+
: { code: 'telegram_error', message: errorMessage(error), details },
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function errorMessage(error) {
|
|
97
|
+
return error instanceof Error ? error.message : String(error);
|
|
98
|
+
}
|
|
99
|
+
function errorDetails(error) {
|
|
100
|
+
return error instanceof Error ? { name: error.name } : undefined;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=message-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-service.js","sourceRoot":"","sources":["../../src/services/message-service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAsBrD,MAAM,OAAO,cAAc;IACI;IAA7B,YAA6B,EAAyB;QAAzB,OAAE,GAAF,EAAE,CAAuB;IAAG,CAAC;IAE1D,KAAK,CAAC,IAAI,CAAC,OAAoB;QAC7B,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,CAAA;QACrC,IAAI,OAAO;YAAE,OAAO,OAAO,CAAA;QAE3B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,CAAA;YACjD,MAAM,OAAO,GAAoE;gBAC/E,IAAI,EAAE,IAAI;gBACV,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,IAAI,EAAE,OAAO,CAAC,IAAI;aACnB,CAAA;YACD,IAAI,OAAO,CAAC,KAAK,IAAI,IAAI;gBAAE,OAAO,CAAC,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAA;YAC3D,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,CAAC,cAAc,EAAE,OAAO,CAAC,EAAE,CAAA;QAClF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,eAAe,CAAC,KAAK,CAAC,CAAA;QAC/B,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAoB;QAC7B,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,CAAC,CAAA;QACrC,IAAI,OAAO;YAAE,OAAO,OAAO,CAAA;QAE3B,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAA;YAC7H,MAAM,IAAI,GAAG,EAAE,MAAM,EAAE,IAAa,EAAE,MAAM,EAAE,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAA;YACjF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY,CAAC,gBAAgB,EAAE,IAAI,CAAC,EAAE,CAAA;QACxE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,eAAe,CAAC,KAAK,CAAC,CAAA;QAC/B,CAAC;IACH,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,OAAsB;QACjC,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;QACvC,IAAI,OAAO;YAAE,OAAO,OAAO,CAAA;QAE3B,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,EAAE,CAAC,cAAc,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;YAC5E,MAAM,IAAI,GAAG,EAAE,OAAO,EAAE,IAAa,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAA;YACpF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY,CAAC,kBAAkB,EAAE,IAAI,CAAC,EAAE,CAAA;QAC1E,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,eAAe,CAAC,KAAK,CAAC,CAAA;QAC/B,CAAC;IACH,CAAC;CACF;AAED,SAAS,YAAY,CAAC,OAAoB;IACxC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE;QAAE,OAAO,aAAa,CAAC,kCAAkC,CAAC,CAAA;IAClF,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE;QAAE,OAAO,aAAa,CAAC,4BAA4B,CAAC,CAAA;IAC/E,IAAI,OAAO,CAAC,KAAK,IAAI,IAAI,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,aAAa,CAAC,mCAAmC,CAAC,CAAA;IACzH,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,SAAS,YAAY,CAAC,OAAoB;IACxC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE;QAAE,OAAO,aAAa,CAAC,kCAAkC,CAAC,CAAA;IAClF,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,aAAa,CAAC,oCAAoC,CAAC,CAAA;IACjG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE;QAAE,OAAO,aAAa,CAAC,yBAAyB,CAAC,CAAA;IACzE,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,SAAS,cAAc,CAAC,OAAsB;IAC5C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE;QAAE,OAAO,aAAa,CAAC,kCAAkC,CAAC,CAAA;IAClF,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClE,OAAO,aAAa,CAAC,oCAAoC,CAAC,CAAA;IAC5D,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC;QAAE,OAAO,aAAa,CAAC,wCAAwC,CAAC,CAAA;IAC5G,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAa;IACtC,OAAO,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAA;AAC7C,CAAC;AAED,SAAS,aAAa,CAAC,OAAe;IACpC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,EAAE,CAAA;AAClE,CAAC;AAED,SAAS,eAAe,CAAC,KAAc;IACrC,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,CAAA;IACnC,OAAO;QACL,EAAE,EAAE,KAAK;QACT,KAAK,EAAE,OAAO,IAAI,IAAI;YACpB,CAAC,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE;YAC1D,CAAC,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE;KACtE,CAAA;AACH,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IAClC,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAC/D,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IAClC,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;AAClE,CAAC"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { messageTable, statsSummary, timelineView, topTable } from '../presenters/human.js';
|
|
2
|
+
import { MessageDB } from '../storage/message-db.js';
|
|
3
|
+
export class QueryService {
|
|
4
|
+
db;
|
|
5
|
+
constructor(db = new MessageDB()) {
|
|
6
|
+
this.db = db;
|
|
7
|
+
}
|
|
8
|
+
close() {
|
|
9
|
+
this.db.close();
|
|
10
|
+
}
|
|
11
|
+
search(options) {
|
|
12
|
+
const valid = validateQueryOptions(options);
|
|
13
|
+
if (!valid.ok)
|
|
14
|
+
return valid;
|
|
15
|
+
const chatId = this.resolveChat(options.chat);
|
|
16
|
+
if (!chatId.ok)
|
|
17
|
+
return chatId;
|
|
18
|
+
try {
|
|
19
|
+
const data = options.regex
|
|
20
|
+
? this.db.searchRegex(options.keyword, { chatId: chatId.data, sender: options.sender, hours: options.hours, limit: options.limit })
|
|
21
|
+
: this.db.search(options.keyword, { chatId: chatId.data, sender: options.sender, hours: options.hours, limit: options.limit });
|
|
22
|
+
return { ok: true, data, human: messageTable(data, 'Search Results') };
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (error instanceof SyntaxError) {
|
|
26
|
+
return { ok: false, error: { code: 'invalid_regex', message: error.message } };
|
|
27
|
+
}
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
recent(options) {
|
|
32
|
+
const normalized = { ...options, hours: options.hours ?? 24, limit: options.limit ?? 50 };
|
|
33
|
+
const valid = validateQueryOptions(normalized);
|
|
34
|
+
if (!valid.ok)
|
|
35
|
+
return valid;
|
|
36
|
+
const chatId = this.resolveChat(options.chat);
|
|
37
|
+
if (!chatId.ok)
|
|
38
|
+
return chatId;
|
|
39
|
+
const data = this.db.getRecent({ chatId: chatId.data, sender: options.sender, hours: normalized.hours, limit: normalized.limit });
|
|
40
|
+
return { ok: true, data, human: messageTable(data, 'Recent Messages', 'No recent messages found.') };
|
|
41
|
+
}
|
|
42
|
+
stats() {
|
|
43
|
+
const data = { total: this.db.count(), chats: this.db.getChats() };
|
|
44
|
+
return { ok: true, data, human: statsSummary({ total: data.total }, 'Stats', data.chats) };
|
|
45
|
+
}
|
|
46
|
+
top(options) {
|
|
47
|
+
const valid = validateQueryOptions(options);
|
|
48
|
+
if (!valid.ok)
|
|
49
|
+
return valid;
|
|
50
|
+
const chatId = this.resolveChat(options.chat);
|
|
51
|
+
if (!chatId.ok)
|
|
52
|
+
return chatId;
|
|
53
|
+
const data = this.db.topSenders({ chatId: chatId.data, hours: options.hours, limit: options.limit });
|
|
54
|
+
return { ok: true, data, human: topTable('Top Senders', data) };
|
|
55
|
+
}
|
|
56
|
+
timeline(options) {
|
|
57
|
+
const valid = validateQueryOptions(options);
|
|
58
|
+
if (!valid.ok)
|
|
59
|
+
return valid;
|
|
60
|
+
if (options.granularity != null && options.granularity !== 'day' && options.granularity !== 'hour') {
|
|
61
|
+
return invalidOption('granularity', 'Use day or hour for --by.');
|
|
62
|
+
}
|
|
63
|
+
const chatId = this.resolveChat(options.chat);
|
|
64
|
+
if (!chatId.ok)
|
|
65
|
+
return chatId;
|
|
66
|
+
const data = this.db.timeline({ chatId: chatId.data, hours: options.hours, granularity: options.granularity ?? 'day' });
|
|
67
|
+
return { ok: true, data, human: timelineView('Timeline', data) };
|
|
68
|
+
}
|
|
69
|
+
today(options) {
|
|
70
|
+
const chatId = this.resolveChat(options.chat);
|
|
71
|
+
if (!chatId.ok)
|
|
72
|
+
return chatId;
|
|
73
|
+
const data = this.db.getToday({ chatId: chatId.data });
|
|
74
|
+
return { ok: true, data, human: messageTable(data, 'Today', 'No messages found today.') };
|
|
75
|
+
}
|
|
76
|
+
filter(options) {
|
|
77
|
+
const valid = validateQueryOptions(options);
|
|
78
|
+
if (!valid.ok)
|
|
79
|
+
return valid;
|
|
80
|
+
const words = options.keywords.split(',').map((word) => word.trim()).filter(Boolean);
|
|
81
|
+
if (words.length === 0)
|
|
82
|
+
return { ok: false, error: { code: 'invalid_keywords', message: 'Please provide at least one keyword.' } };
|
|
83
|
+
const source = options.hours == null ? this.today({ chat: options.chat }) : this.recent({ chat: options.chat, hours: options.hours, limit: 100000 });
|
|
84
|
+
if (!source.ok)
|
|
85
|
+
return source;
|
|
86
|
+
const regex = new RegExp(words.map((word) => escapeRegex(word)).join('|'), 'i');
|
|
87
|
+
const data = source.data.filter((row) => row.content && regex.test(row.content));
|
|
88
|
+
return { ok: true, data, human: messageTable(data, 'Filtered Messages', 'No filtered messages found.') };
|
|
89
|
+
}
|
|
90
|
+
resolveChat(chat) {
|
|
91
|
+
if (!chat)
|
|
92
|
+
return { ok: true, data: undefined };
|
|
93
|
+
const matches = this.db.findChats(chat);
|
|
94
|
+
if (matches.length === 1)
|
|
95
|
+
return { ok: true, data: matches[0].chat_id };
|
|
96
|
+
if (matches.length === 0)
|
|
97
|
+
return { ok: false, error: { code: 'chat_not_found', message: `Chat '${chat}' not found in database.` } };
|
|
98
|
+
return { ok: false, error: { code: 'ambiguous_chat', message: `Chat '${chat}' is ambiguous. Matches: ${matches.map((m) => m.chat_name ?? m.chat_id).join(', ')}` } };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function validateQueryOptions(options) {
|
|
102
|
+
if (options.limit != null && (!Number.isInteger(options.limit) || options.limit <= 0)) {
|
|
103
|
+
return invalidOption('limit', 'Limit must be a positive integer.');
|
|
104
|
+
}
|
|
105
|
+
if (options.hours != null && (!Number.isFinite(options.hours) || options.hours <= 0)) {
|
|
106
|
+
return invalidOption('hours', 'Hours must be a positive number.');
|
|
107
|
+
}
|
|
108
|
+
return { ok: true, data: undefined };
|
|
109
|
+
}
|
|
110
|
+
function invalidOption(option, message) {
|
|
111
|
+
return { ok: false, error: { code: 'invalid_option', message, details: { option } } };
|
|
112
|
+
}
|
|
113
|
+
function escapeRegex(value) {
|
|
114
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=query-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-service.js","sourceRoot":"","sources":["../../src/services/query-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { setTimeout as delayMs } from 'node:timers/promises';
|
|
2
|
+
import { actionDetail, syncSummary } from '../presenters/human.js';
|
|
3
|
+
import { MessageDB } from '../storage/message-db.js';
|
|
4
|
+
const FIRST_SYNC_LIMIT = 500;
|
|
5
|
+
export class SyncService {
|
|
6
|
+
tg;
|
|
7
|
+
db;
|
|
8
|
+
constructor(tg, db) {
|
|
9
|
+
this.tg = tg;
|
|
10
|
+
this.db = db ?? new MessageDB();
|
|
11
|
+
}
|
|
12
|
+
async history(options) {
|
|
13
|
+
const invalid = validateLimit(options.limit);
|
|
14
|
+
if (invalid)
|
|
15
|
+
return invalid;
|
|
16
|
+
try {
|
|
17
|
+
const messages = await this.tg.fetchHistory({ chat: parseChat(options.chat), limit: options.limit });
|
|
18
|
+
const stored = this.db.insertBatch(messages);
|
|
19
|
+
const data = { stored, chat: options.chat };
|
|
20
|
+
return { ok: true, data, human: actionDetail('History Synced', { chat: data.chat, stored: data.stored }) };
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return telegramFailure(error);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async sync(options) {
|
|
27
|
+
const invalid = validateLimit(options.limit);
|
|
28
|
+
if (invalid)
|
|
29
|
+
return invalid;
|
|
30
|
+
const chatId = this.db.resolveChatId(options.chat);
|
|
31
|
+
const minId = chatId == null ? 0 : this.db.getLastMsgId(chatId) ?? 0;
|
|
32
|
+
const limit = minId === 0 && options.limit > FIRST_SYNC_LIMIT ? FIRST_SYNC_LIMIT : options.limit;
|
|
33
|
+
try {
|
|
34
|
+
const messages = await this.tg.fetchHistory({ chat: parseChat(options.chat), limit, minId });
|
|
35
|
+
const stored = this.db.insertBatch(messages);
|
|
36
|
+
const data = { synced: stored, chat: options.chat };
|
|
37
|
+
return { ok: true, data, human: actionDetail('Sync Complete', { chat: data.chat, synced: data.synced }) };
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
return telegramFailure(error);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async refresh(options) {
|
|
44
|
+
const invalid = validateRefreshOptions(options);
|
|
45
|
+
if (invalid)
|
|
46
|
+
return invalid;
|
|
47
|
+
let dialogs;
|
|
48
|
+
try {
|
|
49
|
+
dialogs = await this.tg.listChats();
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
return telegramFailure(error);
|
|
53
|
+
}
|
|
54
|
+
const selected = options.maxChats == null ? dialogs : dialogs.slice(0, options.maxChats);
|
|
55
|
+
const results = {};
|
|
56
|
+
const failures = {};
|
|
57
|
+
for (let index = 0; index < selected.length; index += 1) {
|
|
58
|
+
const dialog = selected[index];
|
|
59
|
+
const lastId = this.db.getLastMsgId(dialog.id) ?? 0;
|
|
60
|
+
const limit = lastId === 0 && options.limit > FIRST_SYNC_LIMIT ? FIRST_SYNC_LIMIT : options.limit;
|
|
61
|
+
try {
|
|
62
|
+
const messages = await this.tg.fetchHistory({ chat: dialog.id, limit, minId: lastId });
|
|
63
|
+
results[dialog.name] = this.db.insertBatch(messages);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
results[dialog.name] = 0;
|
|
67
|
+
failures[dialog.name] = errorMessage(error);
|
|
68
|
+
}
|
|
69
|
+
if (options.delay > 0 && index < selected.length - 1) {
|
|
70
|
+
const jitter = options.delay * (Math.random() * 0.4 - 0.2);
|
|
71
|
+
await delayMs((options.delay + jitter) * 1000);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const updated_chats = Object.entries(results)
|
|
75
|
+
.filter(([, count]) => count > 0)
|
|
76
|
+
.sort((a, b) => b[1] - a[1])
|
|
77
|
+
.map(([name]) => name);
|
|
78
|
+
const data = {
|
|
79
|
+
new_messages: Object.values(results).reduce((sum, count) => sum + count, 0),
|
|
80
|
+
chats: selected.length,
|
|
81
|
+
updated_chats,
|
|
82
|
+
results,
|
|
83
|
+
failures,
|
|
84
|
+
};
|
|
85
|
+
return {
|
|
86
|
+
ok: true,
|
|
87
|
+
data,
|
|
88
|
+
human: syncSummary(data),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
close() {
|
|
92
|
+
this.db.close();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function validateRefreshOptions(options) {
|
|
96
|
+
const invalidLimit = validateLimit(options.limit);
|
|
97
|
+
if (invalidLimit)
|
|
98
|
+
return invalidLimit;
|
|
99
|
+
if (!Number.isFinite(options.delay) || options.delay < 0)
|
|
100
|
+
return invalidOption('delay must be a non-negative number.');
|
|
101
|
+
if (options.maxChats != null && (!isPositiveInteger(options.maxChats))) {
|
|
102
|
+
return invalidOption('maxChats must be a positive integer.');
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
function validateLimit(limit) {
|
|
107
|
+
return isPositiveInteger(limit) ? undefined : invalidOption('limit must be a positive integer.');
|
|
108
|
+
}
|
|
109
|
+
function invalidOption(message) {
|
|
110
|
+
return { ok: false, error: { code: 'invalid_option', message } };
|
|
111
|
+
}
|
|
112
|
+
function telegramFailure(error) {
|
|
113
|
+
const details = errorDetails(error);
|
|
114
|
+
return {
|
|
115
|
+
ok: false,
|
|
116
|
+
error: details == null
|
|
117
|
+
? { code: 'telegram_error', message: errorMessage(error) }
|
|
118
|
+
: { code: 'telegram_error', message: errorMessage(error), details },
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function errorMessage(error) {
|
|
122
|
+
return error instanceof Error ? error.message : String(error);
|
|
123
|
+
}
|
|
124
|
+
function errorDetails(error) {
|
|
125
|
+
return error instanceof Error ? { name: error.name } : undefined;
|
|
126
|
+
}
|
|
127
|
+
function isPositiveInteger(value) {
|
|
128
|
+
return Number.isInteger(value) && value > 0;
|
|
129
|
+
}
|
|
130
|
+
function parseChat(chat) {
|
|
131
|
+
const parsed = Number.parseInt(chat, 10);
|
|
132
|
+
return !Number.isNaN(parsed) && String(parsed) === chat.trim() ? parsed : chat;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=sync-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-service.js","sourceRoot":"","sources":["../../src/services/sync-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function canonicalChatId(chatId) {
|
|
2
|
+
if (chatId < 0) {
|
|
3
|
+
const digits = String(Math.abs(chatId));
|
|
4
|
+
if (digits.startsWith('100') && digits.length > 3)
|
|
5
|
+
return Number.parseInt(digits.slice(3), 10);
|
|
6
|
+
}
|
|
7
|
+
return chatId;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=chat-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-resolver.js","sourceRoot":"","sources":["../../src/storage/chat-resolver.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAA;QACvC,IAAI,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;IAChG,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC"}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { mkdirSync } from 'node:fs';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
import { getDbPath } from '../config/env.js';
|
|
5
|
+
import { canonicalChatId } from './chat-resolver.js';
|
|
6
|
+
export class MessageDB {
|
|
7
|
+
db;
|
|
8
|
+
constructor(path = getDbPath()) {
|
|
9
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
10
|
+
this.db = new Database(path);
|
|
11
|
+
this.db.pragma('journal_mode = WAL');
|
|
12
|
+
this.db.exec(`
|
|
13
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
14
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
15
|
+
platform TEXT NOT NULL DEFAULT 'telegram',
|
|
16
|
+
chat_id INTEGER NOT NULL,
|
|
17
|
+
chat_name TEXT,
|
|
18
|
+
msg_id INTEGER NOT NULL,
|
|
19
|
+
sender_id INTEGER,
|
|
20
|
+
sender_name TEXT,
|
|
21
|
+
content TEXT,
|
|
22
|
+
timestamp TEXT NOT NULL,
|
|
23
|
+
raw_json TEXT,
|
|
24
|
+
UNIQUE(platform, chat_id, msg_id)
|
|
25
|
+
);
|
|
26
|
+
CREATE INDEX IF NOT EXISTS idx_messages_chat_ts ON messages(chat_id, timestamp);
|
|
27
|
+
CREATE INDEX IF NOT EXISTS idx_messages_content ON messages(content);
|
|
28
|
+
CREATE INDEX IF NOT EXISTS idx_messages_sender ON messages(sender_name);
|
|
29
|
+
`);
|
|
30
|
+
}
|
|
31
|
+
insertBatch(messages) {
|
|
32
|
+
if (messages.length === 0)
|
|
33
|
+
return 0;
|
|
34
|
+
const stmt = this.db.prepare(`
|
|
35
|
+
INSERT OR IGNORE INTO messages
|
|
36
|
+
(platform, chat_id, chat_name, msg_id, sender_id, sender_name, content, timestamp, raw_json)
|
|
37
|
+
VALUES (@platform, @chat_id, @chat_name, @msg_id, @sender_id, @sender_name, @content, @timestamp, @raw_json)
|
|
38
|
+
`);
|
|
39
|
+
let inserted = 0;
|
|
40
|
+
const tx = this.db.transaction((rows) => {
|
|
41
|
+
for (const row of rows) {
|
|
42
|
+
inserted += this.insertPrepared(stmt, row);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
tx(messages);
|
|
46
|
+
return inserted;
|
|
47
|
+
}
|
|
48
|
+
insertMessage(message) {
|
|
49
|
+
const stmt = this.db.prepare(`
|
|
50
|
+
INSERT OR IGNORE INTO messages
|
|
51
|
+
(platform, chat_id, chat_name, msg_id, sender_id, sender_name, content, timestamp, raw_json)
|
|
52
|
+
VALUES (@platform, @chat_id, @chat_name, @msg_id, @sender_id, @sender_name, @content, @timestamp, @raw_json)
|
|
53
|
+
`);
|
|
54
|
+
return this.insertPrepared(stmt, message) === 1;
|
|
55
|
+
}
|
|
56
|
+
search(keyword, options = {}) {
|
|
57
|
+
const query = this.filteredQuery('content LIKE ?', [`%${keyword}%`], options);
|
|
58
|
+
return this.db.prepare(`${query.sql} ORDER BY timestamp DESC LIMIT ?`).all(...query.params, options.limit ?? 50);
|
|
59
|
+
}
|
|
60
|
+
searchRegex(pattern, options = {}) {
|
|
61
|
+
const regex = new RegExp(pattern, 'i');
|
|
62
|
+
const limit = options.limit ?? 50;
|
|
63
|
+
const query = this.filteredQuery('content IS NOT NULL', [], options);
|
|
64
|
+
const matches = [];
|
|
65
|
+
const rows = this.db.prepare(`${query.sql} ORDER BY timestamp DESC`).iterate(...query.params);
|
|
66
|
+
for (const row of rows) {
|
|
67
|
+
if (regex.test(row.content ?? ''))
|
|
68
|
+
matches.push(row);
|
|
69
|
+
if (matches.length >= limit)
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
return matches;
|
|
73
|
+
}
|
|
74
|
+
getRecent(options = {}) {
|
|
75
|
+
const params = [];
|
|
76
|
+
const conditions = ['1=1'];
|
|
77
|
+
this.addFilters(conditions, params, options);
|
|
78
|
+
const limit = options.limit ?? 500;
|
|
79
|
+
return this.db.prepare(`
|
|
80
|
+
SELECT * FROM (
|
|
81
|
+
SELECT * FROM messages WHERE ${conditions.join(' AND ')} ORDER BY timestamp DESC LIMIT ?
|
|
82
|
+
) ORDER BY timestamp ASC
|
|
83
|
+
`).all(...params, limit);
|
|
84
|
+
}
|
|
85
|
+
getToday(options = {}) {
|
|
86
|
+
const [start, nextStart] = this.todayRange(options.tzOffsetHours);
|
|
87
|
+
const params = [start, nextStart];
|
|
88
|
+
const conditions = ['timestamp >= ?', 'timestamp < ?'];
|
|
89
|
+
this.addFilters(conditions, params, { chatId: options.chatId });
|
|
90
|
+
return this.db.prepare(`
|
|
91
|
+
SELECT * FROM messages
|
|
92
|
+
WHERE ${conditions.join(' AND ')}
|
|
93
|
+
ORDER BY chat_name COLLATE NOCASE ASC, timestamp ASC
|
|
94
|
+
LIMIT ?
|
|
95
|
+
`).all(...params, options.limit ?? 500);
|
|
96
|
+
}
|
|
97
|
+
findChats(chat) {
|
|
98
|
+
const chats = this.getChats();
|
|
99
|
+
const numeric = Number.parseInt(chat, 10);
|
|
100
|
+
if (!Number.isNaN(numeric) && String(numeric) === chat.trim()) {
|
|
101
|
+
const id = canonicalChatId(numeric);
|
|
102
|
+
const matches = chats.filter((row) => row.chat_id === id);
|
|
103
|
+
if (matches.length > 0)
|
|
104
|
+
return matches;
|
|
105
|
+
}
|
|
106
|
+
const exact = chats.filter((row) => row.chat_name?.toLocaleLowerCase() === chat.toLocaleLowerCase());
|
|
107
|
+
if (exact.length > 0)
|
|
108
|
+
return exact;
|
|
109
|
+
return chats.filter((row) => row.chat_name?.toLocaleLowerCase().includes(chat.toLocaleLowerCase()));
|
|
110
|
+
}
|
|
111
|
+
resolveChatId(chat) {
|
|
112
|
+
const matches = this.findChats(chat);
|
|
113
|
+
return matches.length === 1 ? matches[0].chat_id : null;
|
|
114
|
+
}
|
|
115
|
+
getChats() {
|
|
116
|
+
return this.db.prepare(`
|
|
117
|
+
SELECT
|
|
118
|
+
m.chat_id,
|
|
119
|
+
(
|
|
120
|
+
SELECT latest.chat_name
|
|
121
|
+
FROM messages latest
|
|
122
|
+
WHERE latest.chat_id = m.chat_id
|
|
123
|
+
AND latest.chat_name IS NOT NULL
|
|
124
|
+
ORDER BY latest.timestamp DESC, latest.id DESC
|
|
125
|
+
LIMIT 1
|
|
126
|
+
) as chat_name,
|
|
127
|
+
COUNT(*) as msg_count,
|
|
128
|
+
MIN(m.timestamp) as first_msg,
|
|
129
|
+
MAX(m.timestamp) as last_msg
|
|
130
|
+
FROM messages m GROUP BY m.chat_id ORDER BY msg_count DESC
|
|
131
|
+
`).all();
|
|
132
|
+
}
|
|
133
|
+
topSenders(options = {}) {
|
|
134
|
+
const params = [];
|
|
135
|
+
const conditions = ['(sender_id IS NOT NULL OR sender_name IS NOT NULL)'];
|
|
136
|
+
const since = options.hours == null ? undefined : new Date(Date.now() - options.hours * 60 * 60 * 1000).toISOString();
|
|
137
|
+
const filterOptions = { chatId: options.chatId, hours: options.hours, since };
|
|
138
|
+
this.addFilters(conditions, params, filterOptions);
|
|
139
|
+
const latestParams = [];
|
|
140
|
+
const latestConditions = [this.senderGroupPredicate('latest')];
|
|
141
|
+
this.addFilters(latestConditions, latestParams, filterOptions, 'latest');
|
|
142
|
+
return this.db.prepare(`
|
|
143
|
+
SELECT
|
|
144
|
+
(
|
|
145
|
+
SELECT latest.sender_name
|
|
146
|
+
FROM messages latest
|
|
147
|
+
WHERE ${latestConditions.join(' AND ')}
|
|
148
|
+
ORDER BY latest.timestamp DESC, latest.id DESC
|
|
149
|
+
LIMIT 1
|
|
150
|
+
) as sender_name,
|
|
151
|
+
sender_id,
|
|
152
|
+
COUNT(*) as msg_count,
|
|
153
|
+
MIN(timestamp) as first_msg,
|
|
154
|
+
MAX(timestamp) as last_msg
|
|
155
|
+
FROM messages WHERE ${conditions.join(' AND ')}
|
|
156
|
+
GROUP BY CASE WHEN sender_id IS NULL THEN 'name:' || COALESCE(sender_name, '') ELSE 'id:' || CAST(sender_id AS TEXT) END
|
|
157
|
+
ORDER BY msg_count DESC LIMIT ?
|
|
158
|
+
`).all(...latestParams, ...params, options.limit ?? 20);
|
|
159
|
+
}
|
|
160
|
+
timeline(options = {}) {
|
|
161
|
+
const params = [];
|
|
162
|
+
const conditions = ['1=1'];
|
|
163
|
+
this.addFilters(conditions, params, { chatId: options.chatId, hours: options.hours });
|
|
164
|
+
const expr = options.granularity === 'hour' ? 'substr(timestamp, 1, 13)' : 'substr(timestamp, 1, 10)';
|
|
165
|
+
return this.db.prepare(`
|
|
166
|
+
SELECT ${expr} as period, COUNT(*) as msg_count
|
|
167
|
+
FROM messages WHERE ${conditions.join(' AND ')}
|
|
168
|
+
GROUP BY period ORDER BY period ASC
|
|
169
|
+
`).all(...params);
|
|
170
|
+
}
|
|
171
|
+
getLastMsgId(chatId) {
|
|
172
|
+
const row = this.db.prepare('SELECT MAX(msg_id) as value FROM messages WHERE chat_id = ?').get(canonicalChatId(chatId));
|
|
173
|
+
return row.value;
|
|
174
|
+
}
|
|
175
|
+
count(chatId) {
|
|
176
|
+
const row = chatId == null
|
|
177
|
+
? this.db.prepare('SELECT COUNT(*) as value FROM messages').get()
|
|
178
|
+
: this.db.prepare('SELECT COUNT(*) as value FROM messages WHERE chat_id = ?').get(canonicalChatId(chatId));
|
|
179
|
+
return row.value;
|
|
180
|
+
}
|
|
181
|
+
getLatestTimestamp(chatId) {
|
|
182
|
+
const row = chatId == null
|
|
183
|
+
? this.db.prepare('SELECT MAX(timestamp) as value FROM messages').get()
|
|
184
|
+
: this.db.prepare('SELECT MAX(timestamp) as value FROM messages WHERE chat_id = ?').get(canonicalChatId(chatId));
|
|
185
|
+
return row.value;
|
|
186
|
+
}
|
|
187
|
+
deleteChat(chatId) {
|
|
188
|
+
return this.db.prepare('DELETE FROM messages WHERE chat_id = ?').run(canonicalChatId(chatId)).changes;
|
|
189
|
+
}
|
|
190
|
+
close() {
|
|
191
|
+
this.db.close();
|
|
192
|
+
}
|
|
193
|
+
filteredQuery(firstCondition, firstParams, options) {
|
|
194
|
+
const conditions = [firstCondition];
|
|
195
|
+
const params = [...firstParams];
|
|
196
|
+
this.addFilters(conditions, params, options);
|
|
197
|
+
return { sql: `SELECT * FROM messages WHERE ${conditions.join(' AND ')}`, params };
|
|
198
|
+
}
|
|
199
|
+
insertPrepared(stmt, row) {
|
|
200
|
+
const { preview_jpeg_base64, ...persisted } = row;
|
|
201
|
+
void preview_jpeg_base64;
|
|
202
|
+
return stmt.run({
|
|
203
|
+
...persisted,
|
|
204
|
+
chat_id: canonicalChatId(row.chat_id),
|
|
205
|
+
raw_json: row.raw_json == null ? null : JSON.stringify(row.raw_json),
|
|
206
|
+
}).changes;
|
|
207
|
+
}
|
|
208
|
+
addFilters(conditions, params, options, tableAlias) {
|
|
209
|
+
const prefix = tableAlias ? `${tableAlias}.` : '';
|
|
210
|
+
if (options.chatId != null) {
|
|
211
|
+
conditions.push(`${prefix}chat_id = ?`);
|
|
212
|
+
params.push(canonicalChatId(options.chatId));
|
|
213
|
+
}
|
|
214
|
+
if (options.sender) {
|
|
215
|
+
conditions.push(`${prefix}sender_name LIKE ?`);
|
|
216
|
+
params.push(`%${options.sender}%`);
|
|
217
|
+
}
|
|
218
|
+
if (options.hours != null) {
|
|
219
|
+
conditions.push(`${prefix}timestamp >= ?`);
|
|
220
|
+
params.push(options.since ?? new Date(Date.now() - options.hours * 60 * 60 * 1000).toISOString());
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
todayRange(tzOffsetHours) {
|
|
224
|
+
if (tzOffsetHours == null) {
|
|
225
|
+
const now = new Date();
|
|
226
|
+
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
227
|
+
const nextStart = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
|
|
228
|
+
return [start.toISOString(), nextStart.toISOString()];
|
|
229
|
+
}
|
|
230
|
+
const offsetMs = tzOffsetHours * 60 * 60 * 1000;
|
|
231
|
+
const shifted = new Date(Date.now() + offsetMs);
|
|
232
|
+
const startMs = Date.UTC(shifted.getUTCFullYear(), shifted.getUTCMonth(), shifted.getUTCDate()) - offsetMs;
|
|
233
|
+
return [new Date(startMs).toISOString(), new Date(startMs + 24 * 60 * 60 * 1000).toISOString()];
|
|
234
|
+
}
|
|
235
|
+
senderGroupPredicate(alias) {
|
|
236
|
+
return `(
|
|
237
|
+
(${alias}.sender_id IS NOT NULL AND ${alias}.sender_id = messages.sender_id)
|
|
238
|
+
OR (
|
|
239
|
+
${alias}.sender_id IS NULL
|
|
240
|
+
AND messages.sender_id IS NULL
|
|
241
|
+
AND COALESCE(${alias}.sender_name, '') = COALESCE(messages.sender_name, '')
|
|
242
|
+
)
|
|
243
|
+
)`;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
//# sourceMappingURL=message-db.js.map
|