@mikitasazan/notify 1.23.0 → 1.25.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/README.md +1 -1
- package/dist/cli.js +4 -4
- package/dist/events.d.ts +7 -1
- package/dist/events.js +8 -1
- package/dist/routes.d.ts +22 -9
- package/dist/routes.js +24 -12
- package/dist/setup.js +45 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -200,7 +200,7 @@ notify report --project playhub --json < payload.json # весь объект
|
|
|
200
200
|
- Автосоздания темы при первой отправке — сбой мог бы наплодить дублей;
|
|
201
201
|
создание — явная команда `notify setup`.
|
|
202
202
|
- Любых зависимостей, сборки, MarkdownV2, мультиязычности, троттла (троттл —
|
|
203
|
-
забота вызывающего кода, он есть в `
|
|
203
|
+
забота вызывающего кода, он есть в `Zabukai/app/web/src/server/alert.ts` и там
|
|
204
204
|
и остаётся: работает только внутри долгоживущего процесса, а CLI стартует
|
|
205
205
|
заново на каждый вызов).
|
|
206
206
|
|
package/dist/cli.js
CHANGED
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
* notify deploy --project playhub --status ok --commit "msg" [--commit-url "..."] --url "..."
|
|
14
14
|
* notify job --project playhub --job "Game import" --status ok --stat "added=5" [--via vps] [--took "4m 12s"]
|
|
15
15
|
* notify report --project playhub --title "Daily summary" --line "Games=1284"
|
|
16
|
-
* notify ci --project zabukai --status fail --branch master
|
|
17
|
-
* notify pr --project zabukai --action opened --number 142 --title "..."
|
|
18
|
-
* notify incident --project zabukai --title "Redis is unreachable" --detail "$ERR"
|
|
16
|
+
* notify ci --project zabukai-app --status fail --branch master
|
|
17
|
+
* notify pr --project zabukai-app --action opened --number 142 --title "..."
|
|
18
|
+
* notify incident --project zabukai-app --title "Redis is unreachable" --detail "$ERR"
|
|
19
19
|
* notify session --project mac-config --action "burning the limit" # DEPRECATED alias of incident
|
|
20
|
-
* notify file --project zabukai --title "Full dialogues" --path ./out.txt [--filename name.txt]
|
|
20
|
+
* notify file --project zabukai-app --title "Full dialogues" --path ./out.txt [--filename name.txt]
|
|
21
21
|
* notify <type> [--key stable-key] # the task's key on the card's last line
|
|
22
22
|
* notify <type> --json < payload.json # the whole event object on stdin
|
|
23
23
|
* notify setup <forum chat_id> <project key> # create the tabs, see setup.ts
|
package/dist/events.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - required fields are never added — only a new event type.
|
|
11
11
|
* That keeps old caller code and a new package compatible both ways.
|
|
12
12
|
*/
|
|
13
|
-
export type Project = 'playhub' | 'one-q' | 'zabukai' | 'game-publisher' | 'vault' | 'mac-config' | 'alitools' | 'htmlg' | '2roles' | 'market-lens';
|
|
13
|
+
export type Project = 'playhub' | 'one-q' | 'zabukai-app' | 'zabukai-site' | 'game-publisher' | 'vault' | 'mac-config' | 'alitools' | 'htmlg' | '2roles' | 'market-lens';
|
|
14
14
|
/**
|
|
15
15
|
* The name of a project — THE NAME ITS REPOSITORY CARRIES, character for
|
|
16
16
|
* character. One project, one name, everywhere: the repository, the folder on
|
|
@@ -27,6 +27,12 @@ export type Project = 'playhub' | 'one-q' | 'zabukai' | 'game-publisher' | 'vaul
|
|
|
27
27
|
* Do not restore a prettified name here. To change how a project is written,
|
|
28
28
|
* rename the REPOSITORY first, then bring this table and the chat title to it.
|
|
29
29
|
*
|
|
30
|
+
* A product built from SEVERAL repositories gets one row per repository, not
|
|
31
|
+
* one row for the product: `zabukai-app` and `zabukai-site` are two rows that
|
|
32
|
+
* share one chat. The product's own name lives in `title` on their ROUTES
|
|
33
|
+
* rows, because that is the only place a name belongs to a chat rather than
|
|
34
|
+
* to a repository.
|
|
35
|
+
*
|
|
30
36
|
* `cap()` guessed a name by capitalizing the key, which is how `htmlg` showed
|
|
31
37
|
* up as «Htmlg»; this table exists so nothing guesses.
|
|
32
38
|
*
|
package/dist/events.js
CHANGED
|
@@ -26,6 +26,12 @@
|
|
|
26
26
|
* Do not restore a prettified name here. To change how a project is written,
|
|
27
27
|
* rename the REPOSITORY first, then bring this table and the chat title to it.
|
|
28
28
|
*
|
|
29
|
+
* A product built from SEVERAL repositories gets one row per repository, not
|
|
30
|
+
* one row for the product: `zabukai-app` and `zabukai-site` are two rows that
|
|
31
|
+
* share one chat. The product's own name lives in `title` on their ROUTES
|
|
32
|
+
* rows, because that is the only place a name belongs to a chat rather than
|
|
33
|
+
* to a repository.
|
|
34
|
+
*
|
|
29
35
|
* `cap()` guessed a name by capitalizing the key, which is how `htmlg` showed
|
|
30
36
|
* up as «Htmlg»; this table exists so nothing guesses.
|
|
31
37
|
*
|
|
@@ -35,7 +41,8 @@
|
|
|
35
41
|
export const DISPLAY = {
|
|
36
42
|
playhub: 'playhub',
|
|
37
43
|
'one-q': 'one-q',
|
|
38
|
-
zabukai: 'zabukai',
|
|
44
|
+
'zabukai-app': 'zabukai-app',
|
|
45
|
+
'zabukai-site': 'zabukai-site',
|
|
39
46
|
'game-publisher': 'game-publisher',
|
|
40
47
|
vault: 'vault',
|
|
41
48
|
'mac-config': 'mac-config',
|
package/dist/routes.d.ts
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
* the projects themselves.
|
|
5
5
|
*
|
|
6
6
|
* The scheme: ONE CHAT PER PROJECT. A project with a team is a forum
|
|
7
|
-
* supergroup with an "
|
|
8
|
-
* (people's live chat). A project the owner runs alone
|
|
9
|
-
* supergroup without topics — the robots post straight into it
|
|
10
|
-
* is nobody to talk to in a Dev tab (owner's rule of 2026-09-05;
|
|
11
|
-
* such chat is `htmlg`). A plain chat has no `ops` number: the
|
|
12
|
-
* goes to the chat itself.
|
|
7
|
+
* supergroup with an "Ops" tab (robot notifications, native 🤖 topic icon)
|
|
8
|
+
* and a "Dev" tab (people's live chat, 💬). A project the owner runs alone
|
|
9
|
+
* is a plain supergroup without topics — the robots post straight into it
|
|
10
|
+
* and there is nobody to talk to in a Dev tab (owner's rule of 2026-09-05;
|
|
11
|
+
* the first such chat is `htmlg`). A plain chat has no `ops` number: the
|
|
12
|
+
* message goes to the chat itself.
|
|
13
13
|
*
|
|
14
14
|
* Why not one shared forum with a topic per project — that was the first
|
|
15
15
|
* version, and it turned out to be a mistake: Telegram cannot hide one
|
|
@@ -30,6 +30,15 @@ import type { NotifyEvent, Project } from './events.ts';
|
|
|
30
30
|
type Forum = {
|
|
31
31
|
/** The id of the project's supergroup (forum or plain). */
|
|
32
32
|
chat: string;
|
|
33
|
+
/**
|
|
34
|
+
* The chat's own title, wherever it cannot be derived from the project key:
|
|
35
|
+
* a product of several repositories (every row sharing that chat carries
|
|
36
|
+
* the same `title`), and a team forum whose product name is prettier than
|
|
37
|
+
* its repository key (owner's rule of 2026-09-10: a FORUM wears the
|
|
38
|
+
* product's own name; a solo chat wears the repository's). Left out
|
|
39
|
+
* everywhere else, where `chatTitle()` derives the name from the project.
|
|
40
|
+
*/
|
|
41
|
+
title?: string;
|
|
33
42
|
/**
|
|
34
43
|
* The "⚙️ Ops" tab — robots write here. Absent for a plain chat without
|
|
35
44
|
* topics (a solo project): the message then goes to the chat itself.
|
|
@@ -47,9 +56,13 @@ export declare const ROUTES: Record<Project, Forum>;
|
|
|
47
56
|
/**
|
|
48
57
|
* A solo project's chat is «<Name> · Ops»; a team forum is just the name — the
|
|
49
58
|
* forum already carries its own «Ops»/«Dev» tabs, so the chat's own title
|
|
50
|
-
* does not need to repeat the word.
|
|
51
|
-
*
|
|
52
|
-
*
|
|
59
|
+
* does not need to repeat the word. A chat shared by several repositories of
|
|
60
|
+
* one product, or a forum whose product name differs from the repo key,
|
|
61
|
+
* carries the product's name in `title`, and every row sharing that chat
|
|
62
|
+
* repeats it, so any of them answers with the same title. The real
|
|
63
|
+
* Telegram chat title and this function must agree: the avatar scripts and the
|
|
64
|
+
* name-drift check both read it through `notify routes --json`, not by asking
|
|
65
|
+
* Telegram.
|
|
53
66
|
*/
|
|
54
67
|
export declare const chatTitle: (p: Project) => string;
|
|
55
68
|
export type Target = {
|
package/dist/routes.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { DISPLAY, severity } from "./events.js";
|
|
2
2
|
export const ROUTES = {
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
|
|
3
|
+
// Zabukai is one product built from two repositories, so it is two rows
|
|
4
|
+
// sharing one forum: an Ops tab EACH, because a card belongs to the
|
|
5
|
+
// repository it came from, and ONE Dev tab, because the people talking in
|
|
6
|
+
// it are one team working on one product (owner's rule, 09.09.2026).
|
|
7
|
+
//
|
|
8
|
+
// ops/dev = 22/23, not 3/4: the old tabs were deleted by hand on
|
|
9
|
+
// 27.07.2026, and Telegram removes a topic's messages along with it. A
|
|
10
|
+
// recreated topic gets a NEW id — a topic's id is the id of its first
|
|
11
|
+
// message, it is never reused. 962 is the site's Ops tab, created
|
|
12
|
+
// 09.09.2026.
|
|
13
|
+
'zabukai-app': { chat: '-1004299939100', title: 'Zabukai', ops: 22, dev: 23 },
|
|
14
|
+
'zabukai-site': { chat: '-1004299939100', title: 'Zabukai', ops: 962, dev: 23 },
|
|
9
15
|
// Every row below is a project the owner runs alone. Their Topics were
|
|
10
16
|
// turned off on 06.09.2026 and their `ops` numbers went with them: a card
|
|
11
17
|
// now goes to the chat itself. Sending a thread id into a chat that is no
|
|
@@ -25,17 +31,23 @@ export const ROUTES = {
|
|
|
25
31
|
// runs it alone.
|
|
26
32
|
'2roles': { chat: '-1004314188744' },
|
|
27
33
|
// Created 09.09.2026 by create-ops-chat.py as «Market Lens»: the owner and
|
|
28
|
-
// Semyon, so a forum; the tab numbers come from `notify setup`.
|
|
29
|
-
|
|
34
|
+
// Semyon, so a forum; the tab numbers come from `notify setup`. A forum
|
|
35
|
+
// wears the product's own name even when it is one repository (owner's
|
|
36
|
+
// rule of 2026-09-10) — hence `title`, like Zabukai above.
|
|
37
|
+
'market-lens': { chat: '-1004489147617', title: 'Market Lens', ops: 4, dev: 5 }
|
|
30
38
|
};
|
|
31
39
|
/**
|
|
32
40
|
* A solo project's chat is «<Name> · Ops»; a team forum is just the name — the
|
|
33
41
|
* forum already carries its own «Ops»/«Dev» tabs, so the chat's own title
|
|
34
|
-
* does not need to repeat the word.
|
|
35
|
-
*
|
|
36
|
-
*
|
|
42
|
+
* does not need to repeat the word. A chat shared by several repositories of
|
|
43
|
+
* one product, or a forum whose product name differs from the repo key,
|
|
44
|
+
* carries the product's name in `title`, and every row sharing that chat
|
|
45
|
+
* repeats it, so any of them answers with the same title. The real
|
|
46
|
+
* Telegram chat title and this function must agree: the avatar scripts and the
|
|
47
|
+
* name-drift check both read it through `notify routes --json`, not by asking
|
|
48
|
+
* Telegram.
|
|
37
49
|
*/
|
|
38
|
-
export const chatTitle = (p) => ROUTES[p].ops === undefined ? `${DISPLAY[p]} · Ops` : DISPLAY[p];
|
|
50
|
+
export const chatTitle = (p) => ROUTES[p].title ?? (ROUTES[p].ops === undefined ? `${DISPLAY[p]} · Ops` : DISPLAY[p]);
|
|
39
51
|
/**
|
|
40
52
|
* Where an event goes. Everything goes to its own project's "Ops" tab; a
|
|
41
53
|
* red one arrives there too, just with sound.
|
package/dist/setup.js
CHANGED
|
@@ -1,24 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `notify setup
|
|
3
|
-
* forum that already exists, and prints the ready line for `ROUTES`.
|
|
2
|
+
* `notify setup <forum chat_id> <project key>` creates the "Ops" and "Dev"
|
|
3
|
+
* tabs in a forum that already exists, and prints the ready line for `ROUTES`.
|
|
4
|
+
*
|
|
5
|
+
* Tab shape (the standard tests/check-telegram-shape.py enforces): plain
|
|
6
|
+
* titles with a NATIVE topic icon, never an emoji in the title text — Ops
|
|
7
|
+
* carries 🤖 (robots post there), Dev carries 💬 (people talk there). The
|
|
8
|
+
* icon ids below are documents of Telegram's topic-icon sticker set,
|
|
9
|
+
* resolved live on 2026-09-10; ⚙️ is NOT in that set, which is why the
|
|
10
|
+
* robot glyph is 🤖.
|
|
11
|
+
*
|
|
12
|
+
* A product of SEVERAL repositories shares one forum, one Ops tab PER
|
|
13
|
+
* repository (a card belongs to the repo it came from) and ONE Dev tab.
|
|
14
|
+
* The Bot API cannot list a forum's topics, so the shape is decided from
|
|
15
|
+
* `ROUTES` itself: peers are rows that already point at this chat.
|
|
4
16
|
*
|
|
5
17
|
* The bot cannot create the forum supergroup itself — Telegram only allows a
|
|
6
|
-
* real account to do that. So the order for a new project is
|
|
18
|
+
* real account to do that. So the order for a new project is
|
|
19
|
+
* (mac-config's tg-forums skill, scripts/create-ops-chat.py --forum does all
|
|
20
|
+
* of step 1):
|
|
7
21
|
* 1. create a group in Telegram, turn on "Topics" in it, add
|
|
8
22
|
* @mikita_ops_bot as an admin with the "Manage topics" right;
|
|
9
|
-
* 2. run `notify setup <chat_id>` — it creates
|
|
23
|
+
* 2. run `notify setup <chat_id> <project key>` — it creates the tabs and
|
|
24
|
+
* prints the line;
|
|
10
25
|
* 3. paste the line into `src/routes.ts`.
|
|
11
|
-
*
|
|
12
|
-
* Step 1 happens once per project and takes half a minute. Steps 2 and 3 are
|
|
13
|
-
* mechanical.
|
|
14
26
|
*/
|
|
27
|
+
import { ROUTES } from "./routes.js";
|
|
15
28
|
const log = (msg) => console.error(`[notify] ${msg}`);
|
|
16
|
-
|
|
29
|
+
// Documents of Telegram's topic-icon set (InputStickerSetEmojiDefaultTopicIcons):
|
|
30
|
+
// 🤖 for Ops, 💬 for Dev. A wrong id here writes a dead icon into every new
|
|
31
|
+
// forum — tests/check-telegram-shape.py catches a dropped icon live.
|
|
32
|
+
const OPS_ICON_ID = '5309832892262654231'; // 🤖
|
|
33
|
+
const DEV_ICON_ID = '5417915203100613993'; // 💬
|
|
34
|
+
const createTopic = async (token, chat, name, iconId, color) => {
|
|
17
35
|
try {
|
|
18
36
|
const res = await fetch(`https://api.telegram.org/bot${token}/createForumTopic`, {
|
|
19
37
|
method: 'POST',
|
|
20
38
|
headers: { 'Content-Type': 'application/json' },
|
|
21
|
-
body: JSON.stringify({ chat_id: chat, name, icon_color: color }),
|
|
39
|
+
body: JSON.stringify({ chat_id: chat, name, icon_color: color, icon_custom_emoji_id: iconId }),
|
|
22
40
|
signal: AbortSignal.timeout(10_000)
|
|
23
41
|
});
|
|
24
42
|
const body = (await res.json());
|
|
@@ -40,21 +58,35 @@ export const setupTopic = async (chatId, projectKey) => {
|
|
|
40
58
|
log('no OPS_BOT_TOKEN — cannot create the tabs');
|
|
41
59
|
return;
|
|
42
60
|
}
|
|
43
|
-
|
|
44
|
-
|
|
61
|
+
// The Bot API cannot enumerate topics, so the forum's shape is read from
|
|
62
|
+
// ROUTES: a row already pointing here is a peer repository of the same
|
|
63
|
+
// product.
|
|
64
|
+
const existing = ROUTES[projectKey];
|
|
65
|
+
if (existing && existing.chat === chatId && existing.ops !== undefined) {
|
|
66
|
+
log(`${projectKey} is already in ROUTES with ops=${existing.ops} — nothing to create`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const peers = Object.entries(ROUTES).filter(([key, row]) => key !== projectKey && row.chat === chatId);
|
|
70
|
+
// A second repository of the same product gets its name on its own Ops tab;
|
|
71
|
+
// the product's FIRST tab is plain "Ops" (rename it when a second repo
|
|
72
|
+
// joins — tests/check-telegram-shape.py goes red until it is done).
|
|
73
|
+
const opsTitle = peers.length > 0 ? `Ops · ${projectKey}` : 'Ops';
|
|
74
|
+
const sharedDev = peers.map(([, row]) => row.dev).find((id) => id !== undefined);
|
|
75
|
+
const ops = await createTopic(token, chatId, opsTitle, OPS_ICON_ID, 9367192);
|
|
76
|
+
const dev = sharedDev ?? (await createTopic(token, chatId, 'Dev', DEV_ICON_ID, 7322096));
|
|
45
77
|
// Partial success: if only Ops was created, print it. Otherwise running the
|
|
46
78
|
// command again would create A DIFFERENT Ops topic, and the old id would be lost.
|
|
47
79
|
if (ops === null) {
|
|
48
80
|
log('Ops was not created — check: is the bot a group admin with "Manage topics", and are topics on?');
|
|
49
81
|
return;
|
|
50
82
|
}
|
|
51
|
-
if (dev === null) {
|
|
83
|
+
if (dev === null || dev === undefined) {
|
|
52
84
|
log(`Ops created (id=${ops}), Dev was not — add Dev by hand or run again, and keep ops=${ops}`);
|
|
53
85
|
log('add to src/routes.ts (fill dev in afterwards):');
|
|
54
86
|
log(` ${JSON.stringify(projectKey)}: { chat: '${chatId}', ops: ${ops}, dev: <fill in> },`);
|
|
55
87
|
return;
|
|
56
88
|
}
|
|
57
|
-
log(`tabs created: Ops
|
|
89
|
+
log(`tabs created: Ops="${opsTitle}" (${ops}), Dev (${dev})${sharedDev ? ' [shared with a peer repo]' : ''}`);
|
|
58
90
|
log('add to src/routes.ts:');
|
|
59
91
|
log(` ${JSON.stringify(projectKey)}: { chat: '${chatId}', ops: ${ops}, dev: ${dev} },`);
|
|
60
92
|
};
|
package/package.json
CHANGED