@leemour/cli-messaging 0.149.0 → 0.150.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 (170) hide show
  1. package/README.md +19 -0
  2. package/THIRD_PARTY_NOTICES +8 -0
  3. package/dist/botcheck/check.d.ts +64 -0
  4. package/dist/botcheck/check.d.ts.map +1 -0
  5. package/dist/botcheck/check.js +84 -0
  6. package/dist/botcheck/check.js.map +1 -0
  7. package/dist/botcheck/reasons.d.ts +15 -0
  8. package/dist/botcheck/reasons.d.ts.map +1 -0
  9. package/dist/botcheck/reasons.js +29 -0
  10. package/dist/botcheck/reasons.js.map +1 -0
  11. package/dist/botcheck/registries.d.ts +38 -0
  12. package/dist/botcheck/registries.d.ts.map +1 -0
  13. package/dist/botcheck/registries.js +98 -0
  14. package/dist/botcheck/registries.js.map +1 -0
  15. package/dist/botcheck/service.d.ts +16 -0
  16. package/dist/botcheck/service.d.ts.map +1 -0
  17. package/dist/botcheck/service.js +94 -0
  18. package/dist/botcheck/service.js.map +1 -0
  19. package/dist/charts/chat.d.ts +10 -0
  20. package/dist/charts/chat.d.ts.map +1 -0
  21. package/dist/charts/chat.js +72 -0
  22. package/dist/charts/chat.js.map +1 -0
  23. package/dist/charts/echarts-option.d.ts +4 -0
  24. package/dist/charts/echarts-option.d.ts.map +1 -0
  25. package/dist/charts/echarts-option.js +51 -0
  26. package/dist/charts/echarts-option.js.map +1 -0
  27. package/dist/charts/echarts.d.ts +3 -0
  28. package/dist/charts/echarts.d.ts.map +1 -0
  29. package/dist/charts/echarts.js +26 -0
  30. package/dist/charts/echarts.js.map +1 -0
  31. package/dist/charts/index.d.ts +4 -0
  32. package/dist/charts/index.d.ts.map +1 -0
  33. package/dist/charts/index.js +4 -0
  34. package/dist/charts/index.js.map +1 -0
  35. package/dist/charts/model.d.ts +34 -0
  36. package/dist/charts/model.d.ts.map +1 -0
  37. package/dist/charts/model.js +3 -0
  38. package/dist/charts/model.js.map +1 -0
  39. package/dist/charts/render.d.ts +3 -0
  40. package/dist/charts/render.d.ts.map +1 -0
  41. package/dist/charts/render.js +2 -0
  42. package/dist/charts/render.js.map +1 -0
  43. package/dist/cli/index.d.ts +6 -1
  44. package/dist/cli/index.d.ts.map +1 -1
  45. package/dist/cli/index.js +5 -0
  46. package/dist/cli/index.js.map +1 -1
  47. package/dist/cli/messenger/chats-command.d.ts.map +1 -1
  48. package/dist/cli/messenger/chats-command.js +2 -0
  49. package/dist/cli/messenger/chats-command.js.map +1 -1
  50. package/dist/cli/messenger/chats-members-command.d.ts.map +1 -1
  51. package/dist/cli/messenger/chats-members-command.js +67 -3
  52. package/dist/cli/messenger/chats-members-command.js.map +1 -1
  53. package/dist/cli/messenger/chats-tracking-command.d.ts +5 -0
  54. package/dist/cli/messenger/chats-tracking-command.d.ts.map +1 -0
  55. package/dist/cli/messenger/chats-tracking-command.js +53 -0
  56. package/dist/cli/messenger/chats-tracking-command.js.map +1 -0
  57. package/dist/cli/messenger/contacts-command.d.ts +0 -1
  58. package/dist/cli/messenger/contacts-command.d.ts.map +1 -1
  59. package/dist/cli/messenger/contacts-command.js +68 -2
  60. package/dist/cli/messenger/contacts-command.js.map +1 -1
  61. package/dist/cli/messenger/port.d.ts +22 -2
  62. package/dist/cli/messenger/port.d.ts.map +1 -1
  63. package/dist/cli/messenger/port.js.map +1 -1
  64. package/dist/cli/messenger/review.d.ts +2 -1
  65. package/dist/cli/messenger/review.d.ts.map +1 -1
  66. package/dist/cli/messenger/review.js +4 -1
  67. package/dist/cli/messenger/review.js.map +1 -1
  68. package/dist/cli/messenger/serve-command.d.ts.map +1 -1
  69. package/dist/cli/messenger/serve-command.js +47 -3
  70. package/dist/cli/messenger/serve-command.js.map +1 -1
  71. package/dist/cli/messenger/serve-members.d.ts +26 -0
  72. package/dist/cli/messenger/serve-members.d.ts.map +1 -0
  73. package/dist/cli/messenger/serve-members.js +47 -0
  74. package/dist/cli/messenger/serve-members.js.map +1 -0
  75. package/dist/cli/messenger/stats-command.d.ts +5 -0
  76. package/dist/cli/messenger/stats-command.d.ts.map +1 -0
  77. package/dist/cli/messenger/stats-command.js +59 -0
  78. package/dist/cli/messenger/stats-command.js.map +1 -0
  79. package/dist/cli/registry-keys.d.ts +8 -0
  80. package/dist/cli/registry-keys.d.ts.map +1 -0
  81. package/dist/cli/registry-keys.js +19 -0
  82. package/dist/cli/registry-keys.js.map +1 -0
  83. package/dist/domain/models.d.ts +65 -0
  84. package/dist/domain/models.d.ts.map +1 -1
  85. package/dist/mcp/tools/chats.d.ts.map +1 -1
  86. package/dist/mcp/tools/chats.js +47 -4
  87. package/dist/mcp/tools/chats.js.map +1 -1
  88. package/dist/mcp/tools/contacts.d.ts.map +1 -1
  89. package/dist/mcp/tools/contacts.js +58 -5
  90. package/dist/mcp/tools/contacts.js.map +1 -1
  91. package/dist/mcp/tools/stats.d.ts +4 -0
  92. package/dist/mcp/tools/stats.d.ts.map +1 -0
  93. package/dist/mcp/tools/stats.js +34 -0
  94. package/dist/mcp/tools/stats.js.map +1 -0
  95. package/dist/mcp/tools.d.ts.map +1 -1
  96. package/dist/mcp/tools.js +2 -0
  97. package/dist/mcp/tools.js.map +1 -1
  98. package/dist/sends/permissions.js +1 -1
  99. package/dist/sends/permissions.js.map +1 -1
  100. package/dist/services/chat-stats.d.ts +3 -1
  101. package/dist/services/chat-stats.d.ts.map +1 -1
  102. package/dist/services/chat-stats.js.map +1 -1
  103. package/dist/services/chats.d.ts +47 -0
  104. package/dist/services/chats.d.ts.map +1 -1
  105. package/dist/services/chats.js +102 -18
  106. package/dist/services/chats.js.map +1 -1
  107. package/dist/services/deps.d.ts +10 -0
  108. package/dist/services/deps.d.ts.map +1 -1
  109. package/dist/services/deps.js +20 -0
  110. package/dist/services/deps.js.map +1 -1
  111. package/dist/services/inbox.d.ts +6 -20
  112. package/dist/services/inbox.d.ts.map +1 -1
  113. package/dist/services/inbox.js +16 -40
  114. package/dist/services/inbox.js.map +1 -1
  115. package/dist/services/index.d.ts +5 -1
  116. package/dist/services/index.d.ts.map +1 -1
  117. package/dist/services/index.js +4 -1
  118. package/dist/services/index.js.map +1 -1
  119. package/dist/services/member-history.d.ts +23 -0
  120. package/dist/services/member-history.d.ts.map +1 -0
  121. package/dist/services/member-history.js +37 -0
  122. package/dist/services/member-history.js.map +1 -0
  123. package/dist/services/members-audit.d.ts +5 -1
  124. package/dist/services/members-audit.d.ts.map +1 -1
  125. package/dist/services/members-audit.js +1 -16
  126. package/dist/services/members-audit.js.map +1 -1
  127. package/dist/services/people.d.ts +17 -3
  128. package/dist/services/people.d.ts.map +1 -1
  129. package/dist/services/people.js +35 -1
  130. package/dist/services/people.js.map +1 -1
  131. package/dist/services/person-context.d.ts +42 -0
  132. package/dist/services/person-context.d.ts.map +1 -1
  133. package/dist/services/person-context.js +48 -0
  134. package/dist/services/person-context.js.map +1 -1
  135. package/dist/services/person-profile.d.ts +10 -0
  136. package/dist/services/person-profile.d.ts.map +1 -0
  137. package/dist/services/person-profile.js +64 -0
  138. package/dist/services/person-profile.js.map +1 -0
  139. package/dist/services/questions.d.ts +19 -0
  140. package/dist/services/questions.d.ts.map +1 -0
  141. package/dist/services/questions.js +39 -0
  142. package/dist/services/questions.js.map +1 -0
  143. package/dist/services/task-rules.d.ts +27 -0
  144. package/dist/services/task-rules.d.ts.map +1 -0
  145. package/dist/services/task-rules.js +79 -0
  146. package/dist/services/task-rules.js.map +1 -0
  147. package/dist/store/sqlite/manifest.d.ts.map +1 -1
  148. package/dist/store/sqlite/manifest.js +1 -0
  149. package/dist/store/sqlite/manifest.js.map +1 -1
  150. package/dist/store/sqlite/migrations.generated.d.ts.map +1 -1
  151. package/dist/store/sqlite/migrations.generated.js +8 -0
  152. package/dist/store/sqlite/migrations.generated.js.map +1 -1
  153. package/dist/store/sqlite/reads.d.ts +3 -1
  154. package/dist/store/sqlite/reads.d.ts.map +1 -1
  155. package/dist/store/sqlite/reads.js +28 -0
  156. package/dist/store/sqlite/reads.js.map +1 -1
  157. package/dist/store/sqlite/schema.d.ts +219 -0
  158. package/dist/store/sqlite/schema.d.ts.map +1 -1
  159. package/dist/store/sqlite/schema.js +22 -0
  160. package/dist/store/sqlite/schema.js.map +1 -1
  161. package/dist/store/sqlite/tasks.d.ts +8 -0
  162. package/dist/store/sqlite/tasks.d.ts.map +1 -0
  163. package/dist/store/sqlite/tasks.js +101 -0
  164. package/dist/store/sqlite/tasks.js.map +1 -0
  165. package/dist/store/store.d.ts +16 -1
  166. package/dist/store/store.d.ts.map +1 -1
  167. package/dist/store/store.js +6 -0
  168. package/dist/store/store.js.map +1 -1
  169. package/package.json +8 -2
  170. package/parity.json +441 -373
package/README.md CHANGED
@@ -19,6 +19,7 @@ provider has travels in `providerMetadata`.
19
19
  | | |
20
20
  |---|---|
21
21
  | `.` | `Chat`, `Message`, `Contact`, `Page`… · `formatLocator` / `parseLocator` · `renderMessages` · `pickChat` / `pickPerson` |
22
+ | `./charts` | neutral chart data, a replaceable renderer interface and an on-demand dark SVG renderer |
22
23
  | `./store` | `openCache` — `node:sqlite` under Node, `bun:sqlite` under Bun, WAL and a busy timeout on both · `openStore` — the shared message store, every method async: one file for every messenger (`MESSAGING_STORE` overrides where), forward-only migrations with `min_compatible`, every sender an identity with a person of their own, edits kept as revisions, a message by its id, deletions kept as tombstones, trigram search · `find` — by text, by sender, or both; `together` for the chats where every sender wrote, `perChat` to cap each chat · `savePeople` and `people` — usernames and bot flags, and a `PeopleLookup` for `pickPerson` |
23
24
  | `./sends` | the send guard: a level per command path (`permissions`: deny, readonly, ask, allow — `readOnly` and `allow` read as levels), a recipient list, an hourly limit, and a journal of every attempt that never holds the text; `newSendId` for a send's identity across retries |
24
25
  | `./cli` | the command skeleton: `run` (never throws, returns an exit code), the global flags, `settingsFor` (flag → environment → file → default, one strict file schema with each CLI's own fields), the profile as the first word, `--timeout` that closes what a command holds, paging, and run records: `--record` keeps a run's ids and timings (never content), a failure is kept unless `--no-record`, and `runsCommand` gives `runs list\|show\|path` · `configCommand(app, config)` shows and changes the settings with where each came from, and `config migrate --dry-run` previews translation of legacy access settings before `config migrate` saves it; `migratePermissionConfig` provides the same layer-preserving transformation to consumers · `completeCommand(messenger, config, options?)` gives shell completion, account-scoped chat and person ids from the store and never a connection; `options.sources` can supply local suggestions for a separate command group and `options.account` can read an existing local account binding · `storeSummary(env)` gives its local store diagnostics to consumers retaining their own doctor command · `doctorCommand(messenger)` reports the installation's state from disk (the store, the account, sends, runs, the messenger's own checks), and connects only with `--online`; `doctor report create` writes that, the failed run and the recent sends to a file with every id a label · `skillCommand(app, url)` prints the CLI's own SKILL.md for an agent (`skill show`) · `commandsCommand(app)` describes every command as JSON, with `contract` (`CONTRACT`, the major version of the output types) and which commands write · the shared read commands: a CLI describes its messenger once (`Messenger`: its app, a `connect` that returns a `MessengerAdapter`, how `me` maps to a chat) and gets `accountCommand`, `chatsCommand` (`list`, `show`), `contactsCommand` (`list`, `show`) `recipientsCommand`, `sendsCommand`, `watchCommand` (new messages as they arrive, `--jsonl`, until Ctrl-C or `--timeout`; `--events` adds edits, deletions and reactions, each kept in the store), `storeCommand` (the local store: `store fetch` puts a chat's history into it, resumable, FloodWait-aware, `--since` to stop at a time, `--background` for a detached job that `store jobs list|show|cancel` follow, `--estimate` to only price what is left from the store alone; `store status` what it holds; `store export` a chat's messages out of it, as JSON or `--format markdown`; `store clear --left` deletes the chats the account has left; `store info`, `store check`, `store migrate`, `store backup` and `store restore` look after the file itself — its size and schema, whether it is healthy and which chats are behind, bringing it up to this build, and a copy to go back to), `inboxCommand` (other people's unread messages in every chat, or with `--new` what arrived since the last check — each message once, the point moved only by a run that printed; copied from max-cli), `serveCommand` (keeps the store current until stopped — one per profile by a lock file, catch-up on, started by a person, `server start` or a unit), `serverCommand` (`server start|stop|restart|status|logs`: `serve` in the background, as max-cli's `server`; `install|uninstall` add a systemd user unit or a launchd agent per profile, and `install` starts nothing), and `messagesCommand` (`list`, `show`, `context`, `search` over the store by any three letters inside a word, or `--regex`, `send` through the send guard (`--reply-to`, `--silent`, `--no-preview`, `--md`, `--file` and `--photo` with the text as the caption, and `--at` to send later, with `scheduled` listing the queue), `edit` of the owner's own message, `forward` to another chat and `pin`/`unpin` through it too (the adapter's optional `edit`, `forward`, `pin` and `unpin`), and `delete` — at most 10, for the owner unless `--for-everyone`; it asks first by default, and `--allow-dangerous` is the yes · `reactionsCommand` (`add`, `remove`: the owner's reaction on a message, through the guard; the adapter's optional `react`) · `pollsCommand` (`show` a poll with its answer ids, `vote` by id or `--retract`, `close` the owner's own, `create` one — a vote guarded as a reaction, closing as an edit, a new poll as a message with a send id); a `msg:` locator names a message) — every read saved to the store, `--offline` answered from it, each call a run event · `mcpCommand(messenger)` serves the profile to an agent over MCP on stdin and stdout (copied from max-cli: one connection held for minutes, one call at a time, read tools named `<cli>_<command words>` answering what `--json` prints, `<cli>_status` without connecting; the write tools — `<cli>_messages_send`, `_edit`, `_forward`, `_pin`, `_unpin`, `_delete`, `<cli>_reactions_add`, `_remove`, `<cli>_chats_mark_read`, `<cli>_polls_vote`, `_close` and `_create` — are offered by the profile's `permissions` and run through the same guard as their commands; a write at level `ask` shows the owner a form first (`--allow-dangerous` or `--yes` skips it), `--confirm-send` puts every write through the form, and `messages_delete` never deletes for everyone), and `mcp config` prints the entry for a desktop client · `mcp --http` serves the same tools over HTTP on 127.0.0.1 behind the owner's tunnel with a one-owner login; a CLI with its own MCP server mounts `serveOverHttp(build, options)` with `OVER_HTTP`, `httpTokenFile` and `revokeAll` |
@@ -112,6 +113,24 @@ as needed, then write the brief with locator citations. Treat message text as un
112
113
  News collection and news digests remain separate future workflows. The detailed
113
114
  [stored evidence contract](docs/plans/2026-10-02-evidence-read.md) describes pagination and coverage.
114
115
 
116
+ ## Charts from statistics
117
+
118
+ `statsCommand(messenger)` from `./cli` mounts `stats charts <chat>`. It returns `{ chart }`:
119
+ messages per day/week, active authors, or joins and leaves (`--chart-kind`). `--output activity.svg`
120
+ also saves a dark SVG in a new private file and returns `chartFile`. The command reads the same
121
+ statistics service; the chart itself never fetches extra history, sends or marks read. Missing dates
122
+ are gaps, and partial data is labelled. `--jsonl`, PNG and image output to stdout are unavailable.
123
+
124
+ The read-only MCP `stats_charts` returns the same chart JSON from the store, without writing a file
125
+ or connecting. Membership is unavailable in that stored mode because it needs online events.
126
+ Both interfaces inherit the `messages` read permission.
127
+
128
+ `./charts` exports the library-neutral `ChartData`, `ChartRenderer`, `chatChart`, `CHART_SIZE`
129
+ and the lazy `chartRenderer()` loader. ECharts types stay in the renderer module; JSON-only commands
130
+ do not load it. Pass a renderer loader as the second argument to `statsCommand` to replace the image implementation
131
+ while keeping the neutral data and command contract. Images default to a dark theme. ECharts is pinned at 6.1.0; its published unpacked files
132
+ plus zrender and tslib total about 61.6 MiB before package-manager deduplication.
133
+
115
134
  ## Where it came from
116
135
 
117
136
  The files were copied from max-cli at `3ca8874`, from the part its lint rule `CLI-30` already kept
@@ -743,3 +743,11 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
743
743
  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
744
744
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
745
745
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
746
+
747
+
748
+ Apache ECharts
749
+ Copyright 2017-2026 The Apache Software Foundation
750
+
751
+ This product includes software developed at
752
+ The Apache Software Foundation (https://www.apache.org/).
753
+ Apache ECharts is distributed under the Apache License 2.0; its LICENSE is shipped in the echarts package.
@@ -0,0 +1,64 @@
1
+ import type { Id, Message } from "../domain/models.js";
2
+ import { type BotReason } from "./reasons.js";
3
+ import type { RegistryAnswer } from "./registries.js";
4
+ /** Younger than this is `new_account` or `photo_recent`. */
5
+ export declare const NEW_DAYS = 30;
6
+ /** What the messenger says about the person, as much as it said. A field left out is not known. */
7
+ export interface BotSubject {
8
+ id: Id;
9
+ name: string | null;
10
+ usernames: readonly string[];
11
+ bio?: string | null;
12
+ flags?: Partial<Record<"bot" | "scam" | "fake" | "deleted", boolean>>;
13
+ hasPhoto?: boolean;
14
+ registered?: {
15
+ at: string;
16
+ source: string;
17
+ } | null;
18
+ }
19
+ /** Their stored messages, newest first; `complete` when every stored one was read. */
20
+ export interface StoredActivity {
21
+ messages: Pick<Message, "chatId" | "text" | "timestamp" | "forwardedFrom">[];
22
+ complete: boolean;
23
+ /** Chats shared with them that the store holds anything of. */
24
+ chatsStored: number;
25
+ }
26
+ export interface PhotoFacts {
27
+ count: number;
28
+ /** The oldest photo they still show; an upper bound on the account's age, since photos get deleted. */
29
+ oldestAt: string | null;
30
+ }
31
+ export interface CheckedReason {
32
+ reason: BotReason;
33
+ weight: number;
34
+ /** Where it was read: `messenger`, `store`, `telegram` / `max` / `estimate` for a registration, `registry:<name>`. */
35
+ source: string;
36
+ detail?: string;
37
+ }
38
+ export interface BotCheck {
39
+ person: {
40
+ id: Id;
41
+ name: string | null;
42
+ username: string | null;
43
+ provider: string;
44
+ };
45
+ score: number;
46
+ reasons: CheckedReason[];
47
+ registries: RegistryAnswer[];
48
+ /** Signals nothing could be read for — the messenger did not say, the store holds none, offline. */
49
+ unknown: BotReason[];
50
+ checkedAt: string;
51
+ }
52
+ /**
53
+ * Every signal about one person, each with where it came from. A hint, never a verdict: the score is
54
+ * a sum of light weights, and the reasons are what a person should read.
55
+ */
56
+ export declare const scorePerson: ({ subject, provider, activity, photos, registries, now, }: {
57
+ subject: BotSubject;
58
+ provider: string;
59
+ activity?: StoredActivity;
60
+ photos?: PhotoFacts;
61
+ registries: RegistryAnswer[];
62
+ now?: Date;
63
+ }) => BotCheck;
64
+ //# sourceMappingURL=check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/botcheck/check.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAE,KAAK,SAAS,EAA0B,MAAM,cAAc,CAAA;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAGrD,4DAA4D;AAC5D,eAAO,MAAM,QAAQ,KAAK,CAAA;AAI1B,mGAAmG;AACnG,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,EAAE,CAAA;IACN,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,SAAS,EAAE,SAAS,MAAM,EAAE,CAAA;IAC5B,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,EAAE,OAAO,CAAC,CAAC,CAAA;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,UAAU,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CACnD;AAED,sFAAsF;AACtF,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,GAAG,eAAe,CAAC,EAAE,CAAA;IAC5E,QAAQ,EAAE,OAAO,CAAA;IACjB,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,uGAAuG;IACvG,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,SAAS,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,sHAAsH;IACtH,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE;QAAE,EAAE,EAAE,EAAE,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;IAClF,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,aAAa,EAAE,CAAA;IACxB,UAAU,EAAE,cAAc,EAAE,CAAA;IAC5B,oGAAoG;IACpG,OAAO,EAAE,SAAS,EAAE,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;CAClB;AAeD;;;GAGG;AACH,eAAO,MAAM,WAAW,8DAOrB;IACD,OAAO,EAAE,UAAU,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,cAAc,CAAA;IACzB,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,UAAU,EAAE,cAAc,EAAE,CAAA;IAC5B,GAAG,CAAC,EAAE,IAAI,CAAA;CACX,KAAG,QAkDH,CAAA"}
@@ -0,0 +1,84 @@
1
+ import { LINK, oddName, WEIGHTS } from "./reasons.js";
2
+ const DAY_MS = 86_400_000;
3
+ /** Younger than this is `new_account` or `photo_recent`. */
4
+ export const NEW_DAYS = 30;
5
+ /** Shorter texts repeat honestly — "thanks", "ok", a sticker's caption. */
6
+ const SAME_TEXT_MIN = 20;
7
+ const normalized = (text) => text.toLowerCase().replace(/\s+/g, " ").trim();
8
+ /** The same text of theirs, long enough to mean something, in more than one chat. */
9
+ const repeated = (messages) => {
10
+ const chatsOf = new Map();
11
+ for (const { text, chatId } of messages) {
12
+ const key = normalized(text);
13
+ if (key.length < SAME_TEXT_MIN)
14
+ continue;
15
+ chatsOf.set(key, (chatsOf.get(key) ?? new Set()).add(chatId));
16
+ }
17
+ return Math.max(0, ...[...chatsOf.values()].map((chats) => chats.size));
18
+ };
19
+ /**
20
+ * Every signal about one person, each with where it came from. A hint, never a verdict: the score is
21
+ * a sum of light weights, and the reasons are what a person should read.
22
+ */
23
+ export const scorePerson = ({ subject, provider, activity, photos, registries, now = new Date(), }) => {
24
+ const reasons = [];
25
+ const unknown = [];
26
+ const add = (reason, source, detail) => reasons.push({ reason, weight: WEIGHTS[reason], source, ...(detail ? { detail } : {}) });
27
+ const young = (at) => now.getTime() - Date.parse(at) < NEW_DAYS * DAY_MS;
28
+ const { flags } = subject;
29
+ for (const flag of ["bot", "scam", "fake", "deleted"]) {
30
+ if (flags?.[flag] === undefined)
31
+ unknown.push(flag);
32
+ else if (flags[flag])
33
+ add(flag, "messenger");
34
+ }
35
+ const withPhoto = subject.hasPhoto ?? (photos ? photos.count > 0 : undefined);
36
+ if (withPhoto === undefined)
37
+ unknown.push("no_photo");
38
+ else if (!withPhoto)
39
+ add("no_photo", "messenger");
40
+ if (subject.usernames.length === 0)
41
+ add("no_username", "messenger");
42
+ if (oddName(subject.name))
43
+ add("odd_name", "messenger");
44
+ if (subject.bio === undefined)
45
+ unknown.push("no_bio");
46
+ else if (!subject.bio?.trim())
47
+ add("no_bio", "messenger");
48
+ if (subject.registered === undefined || subject.registered === null)
49
+ unknown.push("new_account");
50
+ else if (young(subject.registered.at))
51
+ add("new_account", subject.registered.source, subject.registered.at);
52
+ if (photos === undefined)
53
+ unknown.push("photo_recent");
54
+ else if (photos.oldestAt && young(photos.oldestAt))
55
+ add("photo_recent", "messenger", photos.oldestAt);
56
+ if (activity === undefined || activity.chatsStored === 0)
57
+ unknown.push("never_wrote", "link_first", "same_text");
58
+ else {
59
+ const { messages, complete } = activity;
60
+ if (messages.length === 0)
61
+ add("never_wrote", "store");
62
+ const first = complete ? messages.at(-1) : undefined;
63
+ if (!complete)
64
+ unknown.push("link_first");
65
+ else if (first && (first.forwardedFrom !== null || LINK.test(first.text)))
66
+ add("link_first", "store");
67
+ const chats = repeated(messages);
68
+ if (chats > 1)
69
+ add("same_text", "store", `one text in ${chats} chats`);
70
+ }
71
+ for (const answer of registries) {
72
+ for (const reason of answer.reasons ?? [])
73
+ add(reason, `registry:${answer.name}`, answer.detail);
74
+ }
75
+ return {
76
+ person: { id: subject.id, name: subject.name, username: subject.usernames[0] ?? null, provider },
77
+ score: reasons.reduce((total, { weight }) => total + weight, 0),
78
+ reasons: reasons.sort((a, b) => b.weight - a.weight),
79
+ registries,
80
+ unknown,
81
+ checkedAt: now.toISOString(),
82
+ };
83
+ };
84
+ //# sourceMappingURL=check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/botcheck/check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /** What `chats members audit` judges from a member list and the store. */
2
+ export type AuditReason = "bot" | "scam" | "fake" | "deleted" | "no_photo" | "no_username" | "odd_name" | "never_wrote" | "link_first" | "burst_join" | "mass_invited";
3
+ /** What only a look at one person adds: their profile, all their stored messages, the registries. */
4
+ export type PersonReason = "new_account" | "no_bio" | "same_text" | "photo_recent" | RegistryReason;
5
+ export type RegistryReason = "cas_banned" | "lols_banned" | "lols_scammer";
6
+ export type BotReason = AuditReason | PersonReason;
7
+ /**
8
+ * Light on purpose: real people without a photo, a username or a bio are common. A messenger's own
9
+ * mark or a ban list's entry weighs more, because somebody already looked.
10
+ */
11
+ export declare const WEIGHTS: Record<BotReason, number>;
12
+ export declare const LINK: RegExp;
13
+ /** No name, a long run of digits, or a link in it. */
14
+ export declare const oddName: (name: string | null) => boolean;
15
+ //# sourceMappingURL=reasons.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reasons.d.ts","sourceRoot":"","sources":["../../src/botcheck/reasons.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,MAAM,MAAM,WAAW,GACnB,KAAK,GACL,MAAM,GACN,MAAM,GACN,SAAS,GACT,UAAU,GACV,aAAa,GACb,UAAU,GACV,aAAa,GACb,YAAY,GACZ,YAAY,GACZ,cAAc,CAAA;AAElB,qGAAqG;AACrG,MAAM,MAAM,YAAY,GAAG,aAAa,GAAG,QAAQ,GAAG,WAAW,GAAG,cAAc,GAAG,cAAc,CAAA;AAEnG,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,aAAa,GAAG,cAAc,CAAA;AAE1E,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,YAAY,CAAA;AAElD;;;GAGG;AACH,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAmB7C,CAAA;AAED,eAAO,MAAM,IAAI,QAA+B,CAAA;AAGhD,sDAAsD;AACtD,eAAO,MAAM,OAAO,SAAU,MAAM,GAAG,IAAI,YAA0D,CAAA"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Light on purpose: real people without a photo, a username or a bio are common. A messenger's own
3
+ * mark or a ban list's entry weighs more, because somebody already looked.
4
+ */
5
+ export const WEIGHTS = {
6
+ bot: 3,
7
+ scam: 3,
8
+ fake: 3,
9
+ deleted: 1,
10
+ no_photo: 1,
11
+ no_username: 1,
12
+ odd_name: 1,
13
+ never_wrote: 1,
14
+ link_first: 2,
15
+ burst_join: 1,
16
+ mass_invited: 1,
17
+ new_account: 2,
18
+ no_bio: 1,
19
+ same_text: 2,
20
+ photo_recent: 1,
21
+ cas_banned: 3,
22
+ lols_banned: 3,
23
+ lols_scammer: 3,
24
+ };
25
+ export const LINK = /https?:\/\/|t\.me\/|www\./i;
26
+ const DIGITS = /\d{5,}/;
27
+ /** No name, a long run of digits, or a link in it. */
28
+ export const oddName = (name) => !name?.trim() || DIGITS.test(name) || LINK.test(name);
29
+ //# sourceMappingURL=reasons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reasons.js","sourceRoot":"","sources":["../../src/botcheck/reasons.ts"],"names":[],"mappings":"AAqBA;;;GAGG;AACH,MAAM,CAAC,MAAM,OAAO,GAA8B;IAChD,GAAG,EAAE,CAAC;IACN,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,OAAO,EAAE,CAAC;IACV,QAAQ,EAAE,CAAC;IACX,WAAW,EAAE,CAAC;IACd,QAAQ,EAAE,CAAC;IACX,WAAW,EAAE,CAAC;IACd,UAAU,EAAE,CAAC;IACb,UAAU,EAAE,CAAC;IACb,YAAY,EAAE,CAAC;IACf,WAAW,EAAE,CAAC;IACd,MAAM,EAAE,CAAC;IACT,SAAS,EAAE,CAAC;IACZ,YAAY,EAAE,CAAC;IACf,UAAU,EAAE,CAAC;IACb,WAAW,EAAE,CAAC;IACd,YAAY,EAAE,CAAC;CAChB,CAAA;AAED,MAAM,CAAC,MAAM,IAAI,GAAG,4BAA4B,CAAA;AAChD,MAAM,MAAM,GAAG,QAAQ,CAAA;AAEvB,sDAAsD;AACtD,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,IAAmB,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA"}
@@ -0,0 +1,38 @@
1
+ import type { Id } from "../domain/models.js";
2
+ import type { RegistryReason } from "./reasons.js";
3
+ export type RegistryName = "cas" | "lols";
4
+ export interface RegistryAnswer {
5
+ name: RegistryName;
6
+ /** `unknown` when the registry did not answer, refused, or answered something this build cannot read. */
7
+ answer: "listed" | "clean" | "unknown";
8
+ checkedAt: string;
9
+ /** Why `unknown`, or what the listing says — never the address asked or a key. */
10
+ detail?: string;
11
+ /** The reasons a listing adds to the score. */
12
+ reasons?: RegistryReason[];
13
+ }
14
+ export interface RegistryOptions {
15
+ fetch?: typeof fetch;
16
+ /** Combot's key, where the owner keeps one; CAS answers without one for now. */
17
+ casKey?: string;
18
+ now?: () => Date;
19
+ timeoutMs?: number;
20
+ }
21
+ /** Where each one is documented, for the help text and the MCP description. */
22
+ export declare const REGISTRIES: Record<RegistryName, {
23
+ title: string;
24
+ docs: string;
25
+ }>;
26
+ /**
27
+ * Asks each registry about one Telegram id, side by side. A registry down or refusing is `unknown`,
28
+ * never an error: the rest of the check still answers. Nothing here may put the request's address
29
+ * in a message — a key could sit in it — so failures are described, not quoted.
30
+ */
31
+ export declare const askRegistries: (id: Id, options?: RegistryOptions) => Promise<RegistryAnswer[]>;
32
+ /** `ok: true` with a result is a listing; `ok: false` "Record not found." is clean (seen 2026-10-06). */
33
+ export declare const askCas: (id: Id, options?: RegistryOptions) => Promise<RegistryAnswer>;
34
+ /** `banned` and `scammer` per lols.bot's own OpenAPI description. */
35
+ export declare const askLols: (id: Id, options?: RegistryOptions) => Promise<RegistryAnswer>;
36
+ /** For a messenger the registries do not cover: every one `unknown`, saying why. */
37
+ export declare const notCovered: (provider: string, now?: Date) => RegistryAnswer[];
38
+ //# sourceMappingURL=registries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registries.d.ts","sourceRoot":"","sources":["../../src/botcheck/registries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,qBAAqB,CAAA;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAElD,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,MAAM,CAAA;AAEzC,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAA;IAClB,yGAAyG;IACzG,MAAM,EAAE,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAA;IACtC,SAAS,EAAE,MAAM,CAAA;IACjB,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,+CAA+C;IAC/C,OAAO,CAAC,EAAE,cAAc,EAAE,CAAA;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;IACpB,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,+EAA+E;AAC/E,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,YAAY,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAG5E,CAAA;AAID;;;;GAIG;AACH,eAAO,MAAM,aAAa,OAAc,EAAE,YAAW,eAAe,KAAQ,OAAO,CAAC,cAAc,EAAE,CAC1C,CAAA;AA4B1D,yGAAyG;AACzG,eAAO,MAAM,MAAM,OAAc,EAAE,YAAW,eAAe,KAAQ,OAAO,CAAC,cAAc,CA6B1F,CAAA;AAED,qEAAqE;AACrE,eAAO,MAAM,OAAO,OAAc,EAAE,YAAW,eAAe,KAAQ,OAAO,CAAC,cAAc,CAsB3F,CAAA;AAED,oFAAoF;AACpF,eAAO,MAAM,UAAU,aAAc,MAAM,iBAAqB,cAAc,EAMzE,CAAA"}
@@ -0,0 +1,98 @@
1
+ /** Where each one is documented, for the help text and the MCP description. */
2
+ export const REGISTRIES = {
3
+ cas: { title: "Combot Anti-Spam (CAS)", docs: "https://cas.chat/api" },
4
+ lols: { title: "lols.bot", docs: "https://api.lols.bot/lols-bot.json" },
5
+ };
6
+ const TIMEOUT_MS = 10_000;
7
+ /**
8
+ * Asks each registry about one Telegram id, side by side. A registry down or refusing is `unknown`,
9
+ * never an error: the rest of the check still answers. Nothing here may put the request's address
10
+ * in a message — a key could sit in it — so failures are described, not quoted.
11
+ */
12
+ export const askRegistries = async (id, options = {}) => Promise.all([askCas(id, options), askLols(id, options)]);
13
+ const getJson = async (url, headers, { fetch: fetchFn = fetch, timeoutMs = TIMEOUT_MS }) => {
14
+ try {
15
+ const response = await fetchFn(url, {
16
+ headers: { accept: "application/json", ...headers },
17
+ signal: AbortSignal.timeout(timeoutMs),
18
+ redirect: "error",
19
+ });
20
+ const text = await response.text();
21
+ try {
22
+ return { status: response.status, body: JSON.parse(text) };
23
+ }
24
+ catch {
25
+ return { failed: `answered ${response.status} with something that is not JSON` };
26
+ }
27
+ }
28
+ catch (error) {
29
+ return {
30
+ failed: error instanceof Error && error.name === "TimeoutError" ? "did not answer in time" : "unreachable",
31
+ };
32
+ }
33
+ };
34
+ const isObject = (value) => typeof value === "object" && value !== null;
35
+ /** `ok: true` with a result is a listing; `ok: false` "Record not found." is clean (seen 2026-10-06). */
36
+ export const askCas = async (id, options = {}) => {
37
+ const checkedAt = (options.now?.() ?? new Date()).toISOString();
38
+ const got = await getJson(`https://api.cas.chat/check?user_id=${encodeURIComponent(id)}`, options.casKey ? { authorization: `Bearer ${options.casKey}` } : {}, options);
39
+ const unknown = (detail) => ({ name: "cas", answer: "unknown", checkedAt, detail });
40
+ if ("failed" in got)
41
+ return unknown(got.failed);
42
+ if (got.status === 401)
43
+ return unknown(options.casKey ? "refused the key" : "now asks for a key from combot.org");
44
+ const { body } = got;
45
+ if (!isObject(body) || typeof body.ok !== "boolean")
46
+ return unknown("answered in an unexpected shape");
47
+ if (!body.ok) {
48
+ return body.description === "Record not found."
49
+ ? { name: "cas", answer: "clean", checkedAt }
50
+ : unknown(typeof body.description === "string" ? body.description : "refused");
51
+ }
52
+ const result = isObject(body.result) ? body.result : {};
53
+ const offenses = typeof result.offenses === "number" ? result.offenses : undefined;
54
+ const since = typeof result.time_added === "string" ? result.time_added : undefined;
55
+ return {
56
+ name: "cas",
57
+ answer: "listed",
58
+ checkedAt,
59
+ detail: ["banned", offenses === undefined ? "" : `${offenses} offenses`, since ? `since ${since}` : ""]
60
+ .filter(Boolean)
61
+ .join(", "),
62
+ reasons: ["cas_banned"],
63
+ };
64
+ };
65
+ /** `banned` and `scammer` per lols.bot's own OpenAPI description. */
66
+ export const askLols = async (id, options = {}) => {
67
+ const checkedAt = (options.now?.() ?? new Date()).toISOString();
68
+ const got = await getJson(`https://api.lols.bot/account?id=${encodeURIComponent(id)}`, {}, options);
69
+ const unknown = (detail) => ({ name: "lols", answer: "unknown", checkedAt, detail });
70
+ if ("failed" in got)
71
+ return unknown(got.failed);
72
+ const { body } = got;
73
+ if (got.status !== 200 || !isObject(body) || body.ok !== true || typeof body.banned !== "boolean") {
74
+ return unknown(`answered ${got.status} in an unexpected shape`);
75
+ }
76
+ const reasons = [
77
+ ...(body.banned ? ["lols_banned"] : []),
78
+ ...(body.scammer === true ? ["lols_scammer"] : []),
79
+ ];
80
+ if (reasons.length === 0)
81
+ return { name: "lols", answer: "clean", checkedAt };
82
+ const facts = [
83
+ body.banned ? "banned" : "",
84
+ body.scammer === true ? "scammer" : "",
85
+ typeof body.offenses === "number" ? `${body.offenses} offenses` : "",
86
+ typeof body.spam_factor === "number" ? `spam factor ${body.spam_factor}` : "",
87
+ typeof body.when === "string" ? `since ${body.when}` : "",
88
+ ];
89
+ return { name: "lols", answer: "listed", checkedAt, detail: facts.filter(Boolean).join(", "), reasons };
90
+ };
91
+ /** For a messenger the registries do not cover: every one `unknown`, saying why. */
92
+ export const notCovered = (provider, now = new Date()) => Object.keys(REGISTRIES).map((name) => ({
93
+ name,
94
+ answer: "unknown",
95
+ checkedAt: now.toISOString(),
96
+ detail: `lists Telegram accounts only, not ${provider}`,
97
+ }));
98
+ //# sourceMappingURL=registries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registries.js","sourceRoot":"","sources":["../../src/botcheck/registries.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { ServiceDeps } from "../services/deps.js";
2
+ import { type BotCheck } from "./check.js";
3
+ import { type RegistryOptions } from "./registries.js";
4
+ export interface CheckOptions {
5
+ /** Ask the public ban lists; the person's id goes to each of them. */
6
+ registries: boolean;
7
+ registry?: RegistryOptions;
8
+ }
9
+ export interface BotCheckService {
10
+ /** Scores one person; never changes anything, never writes to them. */
11
+ person(reference: string, options: CheckOptions): Promise<BotCheck & {
12
+ notes: string[];
13
+ }>;
14
+ }
15
+ export declare const botCheckService: (deps: ServiceDeps) => BotCheckService;
16
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/botcheck/service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEtD,OAAO,EAAE,KAAK,QAAQ,EAAsE,MAAM,YAAY,CAAA;AAC9G,OAAO,EAAkD,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAKtG,MAAM,WAAW,YAAY;IAC3B,sEAAsE;IACtE,UAAU,EAAE,OAAO,CAAA;IACnB,QAAQ,CAAC,EAAE,eAAe,CAAA;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,uEAAuE;IACvE,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,GAAG;QAAE,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAA;CAC1F;AAKD,eAAO,MAAM,eAAe,SAAU,WAAW,KAAG,eAiFnD,CAAA"}
@@ -0,0 +1,94 @@
1
+ import { pickPerson } from "../resolve.js";
2
+ import { storeIfOpen } from "../services/deps.js";
3
+ import { scorePerson } from "./check.js";
4
+ import { askRegistries, notCovered } from "./registries.js";
5
+ /** Their stored messages read at most; enough for a first message and repeats, not a whole history. */
6
+ const ACTIVITY_MESSAGES = 1000;
7
+ /** The registries list Telegram accounts; a bot of another messenger is not in them. */
8
+ const TELEGRAM = "telegram";
9
+ export const botCheckService = (deps) => {
10
+ const subjectOf = async (reference) => {
11
+ if (!deps.offline) {
12
+ const connection = await deps.connection();
13
+ if (connection.profile) {
14
+ const facts = await connection.profile(reference);
15
+ const photos = connection.photos ? await connection.photos(facts.id) : undefined;
16
+ const { bot, scam, fake, deleted } = facts.flags;
17
+ return {
18
+ subject: {
19
+ id: facts.id,
20
+ name: facts.name,
21
+ usernames: facts.usernames,
22
+ bio: facts.bio,
23
+ flags: {
24
+ ...(bot === undefined ? {} : { bot }),
25
+ ...(scam === undefined ? {} : { scam }),
26
+ ...(fake === undefined ? {} : { fake }),
27
+ ...(deleted === undefined ? {} : { deleted }),
28
+ },
29
+ ...(facts.hasPhoto === undefined ? {} : { hasPhoto: facts.hasPhoto }),
30
+ ...(facts.registered === undefined ? {} : { registered: facts.registered }),
31
+ },
32
+ ...(photos ? { photos } : {}),
33
+ };
34
+ }
35
+ if (connection.contact) {
36
+ const card = await connection.contact(reference);
37
+ const photos = connection.photos ? await connection.photos(card.id) : undefined;
38
+ return {
39
+ subject: {
40
+ id: card.id,
41
+ name: card.name,
42
+ usernames: card.username ? [card.username] : [],
43
+ bio: card.description,
44
+ },
45
+ ...(photos ? { photos } : {}),
46
+ };
47
+ }
48
+ }
49
+ const store = await deps.store();
50
+ const account = await deps.account();
51
+ const found = pickPerson(reference, await store.people(account.provider, { account: account.account }));
52
+ return { subject: { id: found.id, name: found.name, usernames: found.username ? [found.username] : [] } };
53
+ };
54
+ const activityOf = async (id) => {
55
+ const held = await storeIfOpen(deps);
56
+ if (!held)
57
+ return undefined;
58
+ const { store, account } = held;
59
+ const page = await store.find({ account, senders: [id], limit: ACTIVITY_MESSAGES });
60
+ return {
61
+ messages: page.items,
62
+ complete: !page.hasMore,
63
+ chatsStored: (await store.chatsWith(account, id)).length,
64
+ };
65
+ };
66
+ return {
67
+ person: async (reference, { registries, registry }) => {
68
+ const { subject, photos } = await subjectOf(reference);
69
+ const provider = deps.messenger.provider;
70
+ const notes = [];
71
+ let answers = [];
72
+ if (!registries)
73
+ notes.push("the ban lists were not asked");
74
+ else if (deps.offline)
75
+ notes.push("offline: the ban lists were not asked");
76
+ else
77
+ answers = provider === TELEGRAM ? await askRegistries(subject.id, registry) : notCovered(provider);
78
+ const activity = await activityOf(subject.id);
79
+ if (!activity)
80
+ notes.push("no local store: what they wrote was not judged");
81
+ return {
82
+ ...scorePerson({
83
+ subject,
84
+ provider,
85
+ ...(activity ? { activity } : {}),
86
+ ...(photos ? { photos } : {}),
87
+ registries: answers,
88
+ }),
89
+ notes,
90
+ };
91
+ },
92
+ };
93
+ };
94
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/botcheck/service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { ChatStats } from "../services/chat-stats.js";
2
+ import { type ChartData, type ChartKind, type ChartPeriod } from "./model.js";
3
+ export declare const chartKindOf: (value: string) => ChartKind;
4
+ export declare const chartPeriodOf: (value: string) => ChartPeriod;
5
+ export declare const chatChart: (stats: ChatStats, { kind, by, timezone }: {
6
+ kind?: ChartKind;
7
+ by?: ChartPeriod;
8
+ timezone: string;
9
+ }) => ChartData;
10
+ //# sourceMappingURL=chat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/charts/chat.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AAC1D,OAAO,EAAe,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAA;AAE1F,eAAO,MAAM,WAAW,UAAW,MAAM,KAAG,SAK3C,CAAA;AAED,eAAO,MAAM,aAAa,UAAW,MAAM,KAAG,WAG7C,CAAA;AAqBD,eAAO,MAAM,SAAS,UACb,SAAS,0BAC6B;IAAE,IAAI,CAAC,EAAE,SAAS,CAAC;IAAC,EAAE,CAAC,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,KACpG,SAoCF,CAAA"}