nonotify 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/config.ts CHANGED
@@ -1,74 +1,77 @@
1
- import { homedir } from 'node:os'
2
- import { join, resolve } from 'node:path'
3
- import { mkdir, readFile, writeFile, chmod } from 'node:fs/promises'
1
+ import { homedir } from "node:os";
2
+ import { join, resolve } from "node:path";
3
+ import { mkdir, readFile, writeFile, chmod } from "node:fs/promises";
4
4
 
5
5
  export type TelegramProfile = {
6
- type: 'telegram'
7
- name: string
8
- botToken: string
9
- chatId: string
10
- createdAt: string
11
- }
6
+ type: "telegram";
7
+ name: string;
8
+ botToken: string;
9
+ chatId: string;
10
+ createdAt: string;
11
+ };
12
12
 
13
13
  export type NntConfig = {
14
- defaultProfile: string | null
15
- profiles: Record<string, TelegramProfile>
16
- }
14
+ defaultProfile: string | null;
15
+ profiles: Record<string, TelegramProfile>;
16
+ };
17
17
 
18
18
  const DEFAULT_CONFIG: NntConfig = {
19
19
  defaultProfile: null,
20
20
  profiles: {},
21
- }
21
+ };
22
22
 
23
23
  export function getConfigDir(): string {
24
- const dir = process.env.NNT_CONFIG_DIR
25
- if (dir && dir.trim() !== '') {
26
- return resolve(dir)
24
+ const dir = process.env.NNT_CONFIG_DIR;
25
+ if (dir && dir.trim() !== "") {
26
+ return resolve(dir);
27
27
  }
28
28
 
29
- return join(homedir(), '.nnt')
29
+ return join(homedir(), ".nnt");
30
30
  }
31
31
 
32
32
  export function getConfigPath(): string {
33
- return join(getConfigDir(), 'config')
33
+ return join(getConfigDir(), "config");
34
34
  }
35
35
 
36
36
  export async function loadConfig(): Promise<NntConfig> {
37
- const path = getConfigPath()
37
+ const path = getConfigPath();
38
38
 
39
39
  try {
40
- const raw = await readFile(path, 'utf8')
41
- const parsed = JSON.parse(raw) as Partial<NntConfig>
40
+ const raw = await readFile(path, "utf8");
41
+ const parsed = JSON.parse(raw) as Partial<NntConfig>;
42
42
 
43
43
  return {
44
- defaultProfile: typeof parsed.defaultProfile === 'string' ? parsed.defaultProfile : null,
44
+ defaultProfile:
45
+ typeof parsed.defaultProfile === "string"
46
+ ? parsed.defaultProfile
47
+ : null,
45
48
  profiles: parsed.profiles ?? {},
46
- }
47
- }
48
- catch (error) {
49
- if (isNodeError(error) && error.code === 'ENOENT') {
50
- return { ...DEFAULT_CONFIG }
49
+ };
50
+ } catch (error) {
51
+ if (isNodeError(error) && error.code === "ENOENT") {
52
+ return { ...DEFAULT_CONFIG };
51
53
  }
52
54
 
53
- throw error
55
+ throw error;
54
56
  }
55
57
  }
56
58
 
57
59
  export async function saveConfig(config: NntConfig): Promise<void> {
58
- const dir = getConfigDir()
59
- const path = getConfigPath()
60
+ const dir = getConfigDir();
61
+ const path = getConfigPath();
60
62
 
61
- await mkdir(dir, { recursive: true })
62
- await writeFile(path, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 })
63
+ await mkdir(dir, { recursive: true });
64
+ await writeFile(path, `${JSON.stringify(config, null, 2)}\n`, {
65
+ mode: 0o600,
66
+ });
63
67
 
64
68
  try {
65
- await chmod(path, 0o600)
66
- }
67
- catch {
69
+ await chmod(path, 0o600);
70
+ } catch {
68
71
  // Best effort only.
69
72
  }
70
73
  }
71
74
 
72
75
  function isNodeError(error: unknown): error is NodeJS.ErrnoException {
73
- return typeof error === 'object' && error !== null && 'code' in error
76
+ return typeof error === "object" && error !== null && "code" in error;
74
77
  }
package/src/display.ts CHANGED
@@ -1,38 +1,41 @@
1
- import Table from 'cli-table3'
1
+ import Table from "cli-table3";
2
2
 
3
3
  type ProfileRow = {
4
- name: string
5
- provider: string
6
- isDefault: boolean
7
- }
4
+ name: string;
5
+ provider: string;
6
+ isDefault: boolean;
7
+ };
8
8
 
9
9
  export function printProfilesTable(rows: ProfileRow[]): void {
10
10
  if (rows.length === 0) {
11
- process.stdout.write('No profiles configured. Run `nnt profile add`.\n')
12
- return
11
+ process.stdout.write("No profiles configured. Run `nnt profile add`.\n");
12
+ return;
13
13
  }
14
14
 
15
15
  const table = new Table({
16
- head: ['Profile', 'Provider', 'Default'],
17
- })
16
+ head: ["Profile", "Provider", "Default"],
17
+ });
18
18
 
19
19
  for (const row of rows) {
20
- table.push([row.name, row.provider, row.isDefault ? 'yes' : ''])
20
+ table.push([row.name, row.provider, row.isDefault ? "yes" : ""]);
21
21
  }
22
22
 
23
- process.stdout.write(`${table.toString()}\n`)
23
+ process.stdout.write(`${table.toString()}\n`);
24
24
  }
25
25
 
26
- export function printKeyValueTable(title: string, rows: Array<{ key: string, value: string }>): void {
27
- process.stdout.write(`${title}\n`)
26
+ export function printKeyValueTable(
27
+ title: string,
28
+ rows: Array<{ key: string; value: string }>,
29
+ ): void {
30
+ process.stdout.write(`${title}\n`);
28
31
 
29
32
  const table = new Table({
30
- head: ['Field', 'Value'],
31
- })
33
+ head: ["Field", "Value"],
34
+ });
32
35
 
33
36
  for (const row of rows) {
34
- table.push([row.key, row.value])
37
+ table.push([row.key, row.value]);
35
38
  }
36
39
 
37
- process.stdout.write(`${table.toString()}\n`)
40
+ process.stdout.write(`${table.toString()}\n`);
38
41
  }
package/src/prompt.ts CHANGED
@@ -1,67 +1,76 @@
1
- import { cancel, confirm, isCancel, select, text } from '@clack/prompts'
1
+ import { cancel, confirm, isCancel, select, text } from "@clack/prompts";
2
2
 
3
3
  export async function askRequired(question: string): Promise<string> {
4
- return askRequiredWithInitial(question)
4
+ return askRequiredWithInitial(question);
5
5
  }
6
6
 
7
- export async function askRequiredWithInitial(question: string, initialValue?: string): Promise<string> {
8
- const message = normalizeQuestion(question)
7
+ export async function askRequiredWithInitial(
8
+ question: string,
9
+ initialValue?: string,
10
+ ): Promise<string> {
11
+ const message = normalizeQuestion(question);
9
12
 
10
13
  const value = await text({
11
14
  message,
12
15
  initialValue,
13
16
  validate(input) {
14
- if (!input || input.trim() === '') {
15
- return 'Value cannot be empty'
17
+ if (!input || input.trim() === "") {
18
+ return "Value cannot be empty";
16
19
  }
17
20
 
18
- return undefined
21
+ return undefined;
19
22
  },
20
- })
23
+ });
21
24
 
22
25
  if (isCancel(value)) {
23
- cancel('Operation cancelled.')
24
- process.exit(1)
26
+ cancel("Operation cancelled.");
27
+ process.exit(1);
25
28
  }
26
29
 
27
- return value.trim()
30
+ return value.trim();
28
31
  }
29
32
 
30
- export async function askConfirm(question: string, initialValue = false): Promise<boolean> {
33
+ export async function askConfirm(
34
+ question: string,
35
+ initialValue = false,
36
+ ): Promise<boolean> {
31
37
  const value = await confirm({
32
38
  message: normalizeQuestion(question),
33
39
  initialValue,
34
- })
40
+ });
35
41
 
36
42
  if (isCancel(value)) {
37
- cancel('Operation cancelled.')
38
- process.exit(1)
43
+ cancel("Operation cancelled.");
44
+ process.exit(1);
39
45
  }
40
46
 
41
- return value
47
+ return value;
42
48
  }
43
49
 
44
50
  type SelectOption = {
45
- value: string
46
- label: string
47
- hint?: string
48
- disabled?: boolean
49
- }
51
+ value: string;
52
+ label: string;
53
+ hint?: string;
54
+ disabled?: boolean;
55
+ };
50
56
 
51
- export async function askSelect(question: string, options: SelectOption[]): Promise<string> {
57
+ export async function askSelect(
58
+ question: string,
59
+ options: SelectOption[],
60
+ ): Promise<string> {
52
61
  const value = await select<string>({
53
62
  message: normalizeQuestion(question),
54
63
  options,
55
- })
64
+ });
56
65
 
57
66
  if (isCancel(value)) {
58
- cancel('Operation cancelled.')
59
- process.exit(1)
67
+ cancel("Operation cancelled.");
68
+ process.exit(1);
60
69
  }
61
70
 
62
- return value
71
+ return value;
63
72
  }
64
73
 
65
74
  function normalizeQuestion(question: string): string {
66
- return question.trim().replace(/:\s*$/, '')
75
+ return question.trim().replace(/:\s*$/, "");
67
76
  }
package/src/telegram.ts CHANGED
@@ -1,71 +1,81 @@
1
1
  type TelegramApiResponse<T> =
2
2
  | {
3
- ok: true
4
- result: T
5
- }
3
+ ok: true;
4
+ result: T;
5
+ }
6
6
  | {
7
- ok: false
8
- error_code: number
9
- description: string
10
- }
7
+ ok: false;
8
+ error_code: number;
9
+ description: string;
10
+ };
11
11
 
12
12
  type TelegramUpdate = {
13
- update_id: number
13
+ update_id: number;
14
14
  message?: {
15
15
  from?: {
16
- username?: string
17
- }
16
+ username?: string;
17
+ };
18
18
  chat?: {
19
- id: number
20
- username?: string
21
- }
22
- text?: string
23
- }
24
- }
19
+ id: number;
20
+ username?: string;
21
+ };
22
+ text?: string;
23
+ };
24
+ };
25
25
 
26
26
  export type TelegramConnection = {
27
- chatId: string
28
- username: string | null
29
- }
27
+ chatId: string;
28
+ username: string | null;
29
+ };
30
30
 
31
31
  async function telegramRequest<T>(
32
32
  botToken: string,
33
33
  method: string,
34
34
  payload: Record<string, unknown>,
35
35
  ): Promise<T> {
36
- const response = await fetch(`https://api.telegram.org/bot${botToken}/${method}`, {
37
- method: 'POST',
38
- headers: {
39
- 'content-type': 'application/json',
36
+ const response = await fetch(
37
+ `https://api.telegram.org/bot${botToken}/${method}`,
38
+ {
39
+ method: "POST",
40
+ headers: {
41
+ "content-type": "application/json",
42
+ },
43
+ body: JSON.stringify(payload),
40
44
  },
41
- body: JSON.stringify(payload),
42
- })
45
+ );
43
46
 
44
47
  if (!response.ok) {
45
- throw new Error(`Telegram API HTTP ${response.status}`)
48
+ throw new Error(`Telegram API HTTP ${response.status}`);
46
49
  }
47
50
 
48
- const json = await response.json() as TelegramApiResponse<T>
51
+ const json = (await response.json()) as TelegramApiResponse<T>;
49
52
 
50
53
  if (!json.ok) {
51
- throw new Error(json.description)
54
+ throw new Error(json.description);
52
55
  }
53
56
 
54
- return json.result
57
+ return json.result;
55
58
  }
56
59
 
57
60
  export async function getLatestUpdateOffset(botToken: string): Promise<number> {
58
- const updates = await telegramRequest<TelegramUpdate[]>(botToken, 'getUpdates', {
59
- timeout: 0,
60
- allowed_updates: ['message'],
61
- })
61
+ const updates = await telegramRequest<TelegramUpdate[]>(
62
+ botToken,
63
+ "getUpdates",
64
+ {
65
+ timeout: 0,
66
+ allowed_updates: ["message"],
67
+ },
68
+ );
62
69
 
63
70
  if (updates.length === 0) {
64
- return 0
71
+ return 0;
65
72
  }
66
73
 
67
- const maxUpdateId = updates.reduce((acc, item) => Math.max(acc, item.update_id), 0)
68
- return maxUpdateId + 1
74
+ const maxUpdateId = updates.reduce(
75
+ (acc, item) => Math.max(acc, item.update_id),
76
+ 0,
77
+ );
78
+ return maxUpdateId + 1;
69
79
  }
70
80
 
71
81
  export async function waitForChatId(
@@ -73,37 +83,51 @@ export async function waitForChatId(
73
83
  offset: number,
74
84
  timeoutSeconds = 120,
75
85
  ): Promise<TelegramConnection> {
76
- const startedAt = Date.now()
77
- let currentOffset = offset
86
+ const startedAt = Date.now();
87
+ let currentOffset = offset;
78
88
 
79
89
  while ((Date.now() - startedAt) / 1000 < timeoutSeconds) {
80
- const remainingSeconds = timeoutSeconds - Math.floor((Date.now() - startedAt) / 1000)
81
- const pollTimeout = Math.max(1, Math.min(25, remainingSeconds))
82
-
83
- const updates = await telegramRequest<TelegramUpdate[]>(botToken, 'getUpdates', {
84
- offset: currentOffset,
85
- timeout: pollTimeout,
86
- allowed_updates: ['message'],
87
- })
90
+ const remainingSeconds =
91
+ timeoutSeconds - Math.floor((Date.now() - startedAt) / 1000);
92
+ const pollTimeout = Math.max(1, Math.min(25, remainingSeconds));
93
+
94
+ const updates = await telegramRequest<TelegramUpdate[]>(
95
+ botToken,
96
+ "getUpdates",
97
+ {
98
+ offset: currentOffset,
99
+ timeout: pollTimeout,
100
+ allowed_updates: ["message"],
101
+ },
102
+ );
88
103
 
89
104
  for (const update of updates) {
90
- currentOffset = Math.max(currentOffset, update.update_id + 1)
105
+ currentOffset = Math.max(currentOffset, update.update_id + 1);
91
106
 
92
107
  if (update.message?.chat?.id !== undefined) {
93
108
  return {
94
109
  chatId: String(update.message.chat.id),
95
- username: update.message.from?.username ?? update.message.chat.username ?? null,
96
- }
110
+ username:
111
+ update.message.from?.username ??
112
+ update.message.chat.username ??
113
+ null,
114
+ };
97
115
  }
98
116
  }
99
117
  }
100
118
 
101
- throw new Error('Timed out waiting for Telegram message. Send a message to your bot and try again.')
119
+ throw new Error(
120
+ "Timed out waiting for Telegram message. Send a message to your bot and try again.",
121
+ );
102
122
  }
103
123
 
104
- export async function sendTelegramMessage(botToken: string, chatId: string, text: string): Promise<void> {
105
- await telegramRequest(botToken, 'sendMessage', {
124
+ export async function sendTelegramMessage(
125
+ botToken: string,
126
+ chatId: string,
127
+ text: string,
128
+ ): Promise<void> {
129
+ await telegramRequest(botToken, "sendMessage", {
106
130
  chat_id: chatId,
107
131
  text,
108
- })
132
+ });
109
133
  }
package/tsconfig.json CHANGED
@@ -11,7 +11,5 @@
11
11
  "outDir": "dist",
12
12
  "rootDir": "src"
13
13
  },
14
- "include": [
15
- "src/**/*.ts"
16
- ]
14
+ "include": ["src/**/*.ts"]
17
15
  }