nonotify 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/README.md +120 -0
- package/dist/cli.js +420 -0
- package/dist/config.js +49 -0
- package/dist/display.js +24 -0
- package/dist/prompt.js +47 -0
- package/dist/telegram.js +57 -0
- package/package.json +31 -0
- package/src/cli.ts +520 -0
- package/src/config.ts +74 -0
- package/src/display.ts +38 -0
- package/src/prompt.ts +67 -0
- package/src/telegram.ts +109 -0
- package/tsconfig.json +17 -0
package/src/display.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import Table from 'cli-table3'
|
|
2
|
+
|
|
3
|
+
type ProfileRow = {
|
|
4
|
+
name: string
|
|
5
|
+
provider: string
|
|
6
|
+
isDefault: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function printProfilesTable(rows: ProfileRow[]): void {
|
|
10
|
+
if (rows.length === 0) {
|
|
11
|
+
process.stdout.write('No profiles configured. Run `nnt profile add`.\n')
|
|
12
|
+
return
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const table = new Table({
|
|
16
|
+
head: ['Profile', 'Provider', 'Default'],
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
for (const row of rows) {
|
|
20
|
+
table.push([row.name, row.provider, row.isDefault ? 'yes' : ''])
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
process.stdout.write(`${table.toString()}\n`)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function printKeyValueTable(title: string, rows: Array<{ key: string, value: string }>): void {
|
|
27
|
+
process.stdout.write(`${title}\n`)
|
|
28
|
+
|
|
29
|
+
const table = new Table({
|
|
30
|
+
head: ['Field', 'Value'],
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
for (const row of rows) {
|
|
34
|
+
table.push([row.key, row.value])
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
process.stdout.write(`${table.toString()}\n`)
|
|
38
|
+
}
|
package/src/prompt.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { cancel, confirm, isCancel, select, text } from '@clack/prompts'
|
|
2
|
+
|
|
3
|
+
export async function askRequired(question: string): Promise<string> {
|
|
4
|
+
return askRequiredWithInitial(question)
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export async function askRequiredWithInitial(question: string, initialValue?: string): Promise<string> {
|
|
8
|
+
const message = normalizeQuestion(question)
|
|
9
|
+
|
|
10
|
+
const value = await text({
|
|
11
|
+
message,
|
|
12
|
+
initialValue,
|
|
13
|
+
validate(input) {
|
|
14
|
+
if (!input || input.trim() === '') {
|
|
15
|
+
return 'Value cannot be empty'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return undefined
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
if (isCancel(value)) {
|
|
23
|
+
cancel('Operation cancelled.')
|
|
24
|
+
process.exit(1)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return value.trim()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function askConfirm(question: string, initialValue = false): Promise<boolean> {
|
|
31
|
+
const value = await confirm({
|
|
32
|
+
message: normalizeQuestion(question),
|
|
33
|
+
initialValue,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
if (isCancel(value)) {
|
|
37
|
+
cancel('Operation cancelled.')
|
|
38
|
+
process.exit(1)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return value
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type SelectOption = {
|
|
45
|
+
value: string
|
|
46
|
+
label: string
|
|
47
|
+
hint?: string
|
|
48
|
+
disabled?: boolean
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function askSelect(question: string, options: SelectOption[]): Promise<string> {
|
|
52
|
+
const value = await select<string>({
|
|
53
|
+
message: normalizeQuestion(question),
|
|
54
|
+
options,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
if (isCancel(value)) {
|
|
58
|
+
cancel('Operation cancelled.')
|
|
59
|
+
process.exit(1)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return value
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normalizeQuestion(question: string): string {
|
|
66
|
+
return question.trim().replace(/:\s*$/, '')
|
|
67
|
+
}
|
package/src/telegram.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
type TelegramApiResponse<T> =
|
|
2
|
+
| {
|
|
3
|
+
ok: true
|
|
4
|
+
result: T
|
|
5
|
+
}
|
|
6
|
+
| {
|
|
7
|
+
ok: false
|
|
8
|
+
error_code: number
|
|
9
|
+
description: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type TelegramUpdate = {
|
|
13
|
+
update_id: number
|
|
14
|
+
message?: {
|
|
15
|
+
from?: {
|
|
16
|
+
username?: string
|
|
17
|
+
}
|
|
18
|
+
chat?: {
|
|
19
|
+
id: number
|
|
20
|
+
username?: string
|
|
21
|
+
}
|
|
22
|
+
text?: string
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type TelegramConnection = {
|
|
27
|
+
chatId: string
|
|
28
|
+
username: string | null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function telegramRequest<T>(
|
|
32
|
+
botToken: string,
|
|
33
|
+
method: string,
|
|
34
|
+
payload: Record<string, unknown>,
|
|
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',
|
|
40
|
+
},
|
|
41
|
+
body: JSON.stringify(payload),
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
throw new Error(`Telegram API HTTP ${response.status}`)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const json = await response.json() as TelegramApiResponse<T>
|
|
49
|
+
|
|
50
|
+
if (!json.ok) {
|
|
51
|
+
throw new Error(json.description)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return json.result
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function getLatestUpdateOffset(botToken: string): Promise<number> {
|
|
58
|
+
const updates = await telegramRequest<TelegramUpdate[]>(botToken, 'getUpdates', {
|
|
59
|
+
timeout: 0,
|
|
60
|
+
allowed_updates: ['message'],
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
if (updates.length === 0) {
|
|
64
|
+
return 0
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const maxUpdateId = updates.reduce((acc, item) => Math.max(acc, item.update_id), 0)
|
|
68
|
+
return maxUpdateId + 1
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function waitForChatId(
|
|
72
|
+
botToken: string,
|
|
73
|
+
offset: number,
|
|
74
|
+
timeoutSeconds = 120,
|
|
75
|
+
): Promise<TelegramConnection> {
|
|
76
|
+
const startedAt = Date.now()
|
|
77
|
+
let currentOffset = offset
|
|
78
|
+
|
|
79
|
+
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
|
+
})
|
|
88
|
+
|
|
89
|
+
for (const update of updates) {
|
|
90
|
+
currentOffset = Math.max(currentOffset, update.update_id + 1)
|
|
91
|
+
|
|
92
|
+
if (update.message?.chat?.id !== undefined) {
|
|
93
|
+
return {
|
|
94
|
+
chatId: String(update.message.chat.id),
|
|
95
|
+
username: update.message.from?.username ?? update.message.chat.username ?? null,
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
throw new Error('Timed out waiting for Telegram message. Send a message to your bot and try again.')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function sendTelegramMessage(botToken: string, chatId: string, text: string): Promise<void> {
|
|
105
|
+
await telegramRequest(botToken, 'sendMessage', {
|
|
106
|
+
chat_id: chatId,
|
|
107
|
+
text,
|
|
108
|
+
})
|
|
109
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"resolveJsonModule": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"outDir": "dist",
|
|
12
|
+
"rootDir": "src"
|
|
13
|
+
},
|
|
14
|
+
"include": [
|
|
15
|
+
"src/**/*.ts"
|
|
16
|
+
]
|
|
17
|
+
}
|