supertelegram 0.1.3 → 0.2.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 +39 -11
- package/package.json +1 -1
- package/src/cli/commands.ts +39 -1
- package/src/cli/prompts.ts +9 -0
- package/src/cli/run.ts +11 -6
- package/src/client/telegram.ts +21 -7
- package/src/config/manager.ts +95 -0
- package/src/session/storage.ts +18 -8
package/README.md
CHANGED
|
@@ -8,23 +8,22 @@ telegram cli for ai to read/write messages using gramjs.
|
|
|
8
8
|
npm install -g supertelegram
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## quick start
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
TELEGRAM_APP_ID="your_app_id"
|
|
16
|
-
TELEGRAM_APP_HASH="your_app_hash"
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
get credentials from https://my.telegram.org/apps
|
|
20
|
-
|
|
21
|
-
## login
|
|
13
|
+
just run login and follow the prompts:
|
|
22
14
|
|
|
23
15
|
```bash
|
|
24
16
|
telegram login
|
|
25
17
|
```
|
|
26
18
|
|
|
27
|
-
|
|
19
|
+
if you don't have API credentials configured, it will:
|
|
20
|
+
1. prompt you to get them from https://my.telegram.org/apps
|
|
21
|
+
2. ask for your app_id and app_hash
|
|
22
|
+
3. save them to `~/.supertelegram/config.json`
|
|
23
|
+
4. continue with telegram phone/code login
|
|
24
|
+
5. save session to `~/.supertelegram/session.txt`
|
|
25
|
+
|
|
26
|
+
that's it! now you're ready to use telegram from the cli.
|
|
28
27
|
|
|
29
28
|
## usage
|
|
30
29
|
|
|
@@ -45,12 +44,41 @@ telegram unread [limit]
|
|
|
45
44
|
telegram dialogs [limit]
|
|
46
45
|
```
|
|
47
46
|
|
|
47
|
+
### config
|
|
48
|
+
|
|
49
|
+
manually set API credentials (optional):
|
|
50
|
+
```bash
|
|
51
|
+
telegram config set appId "12345678"
|
|
52
|
+
telegram config set appHash "abc123..."
|
|
53
|
+
```
|
|
54
|
+
|
|
48
55
|
### flags
|
|
49
56
|
|
|
50
57
|
- `-v, --verbose` - show debug logs
|
|
51
58
|
- `--help` - show help
|
|
52
59
|
- `--version` - show version
|
|
53
60
|
|
|
61
|
+
### advanced
|
|
62
|
+
|
|
63
|
+
**multi-account / custom session location:**
|
|
64
|
+
```bash
|
|
65
|
+
# use env var
|
|
66
|
+
TELEGRAM_SESSION=./custom.txt telegram send @friend "hey"
|
|
67
|
+
|
|
68
|
+
# or pass flag (todo)
|
|
69
|
+
telegram send @friend "hey" --session ./custom.txt
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**precedence for API credentials:**
|
|
73
|
+
1. `TELEGRAM_APP_ID` and `TELEGRAM_APP_HASH` env vars
|
|
74
|
+
2. `~/.supertelegram/config.json` (global)
|
|
75
|
+
3. `.env` file in current directory (for dev)
|
|
76
|
+
|
|
77
|
+
**precedence for session file:**
|
|
78
|
+
1. `TELEGRAM_SESSION` env var
|
|
79
|
+
2. `~/.supertelegram/session.txt` (global default)
|
|
80
|
+
3. `./session.txt` (backwards compat)
|
|
81
|
+
|
|
54
82
|
## development
|
|
55
83
|
|
|
56
84
|
clone repo and install:
|
package/package.json
CHANGED
package/src/cli/commands.ts
CHANGED
|
@@ -7,7 +7,8 @@ import {
|
|
|
7
7
|
getClient,
|
|
8
8
|
login as telegramLogin,
|
|
9
9
|
} from "../client/telegram";
|
|
10
|
-
import { askPhoneNumber, askPhoneCode, askPassword } from "./prompts";
|
|
10
|
+
import { askPhoneNumber, askPhoneCode, askPassword, askAppId, askAppHash } from "./prompts";
|
|
11
|
+
import { getApiCredentials, setConfig } from "../config/manager";
|
|
11
12
|
|
|
12
13
|
export async function send(username: string, message: string) {
|
|
13
14
|
if (!(await isLoggedIn())) {
|
|
@@ -124,6 +125,18 @@ export async function reply(chatName: string, message: string) {
|
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
export async function login() {
|
|
128
|
+
// check if API credentials are configured
|
|
129
|
+
const creds = getApiCredentials();
|
|
130
|
+
if (!creds) {
|
|
131
|
+
console.log("no API credentials found. let's set them up first.");
|
|
132
|
+
const appId = await askAppId();
|
|
133
|
+
const appHash = await askAppHash();
|
|
134
|
+
|
|
135
|
+
setConfig("appId", appId);
|
|
136
|
+
setConfig("appHash", appHash);
|
|
137
|
+
console.log("credentials saved to ~/.supertelegram/config.json\n");
|
|
138
|
+
}
|
|
139
|
+
|
|
127
140
|
const loggedIn = await isLoggedIn();
|
|
128
141
|
if (loggedIn) {
|
|
129
142
|
console.log("already logged in!");
|
|
@@ -141,3 +154,28 @@ export async function login() {
|
|
|
141
154
|
console.log("login complete!");
|
|
142
155
|
await disconnect();
|
|
143
156
|
}
|
|
157
|
+
|
|
158
|
+
export async function config(action?: string, key?: string, value?: string) {
|
|
159
|
+
if (action === "set" && key && value) {
|
|
160
|
+
setConfig(key, value);
|
|
161
|
+
console.log(`set ${key} = ${value}`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (action === "get" && key) {
|
|
166
|
+
const cfg = getApiCredentials();
|
|
167
|
+
if (key === "appId" && cfg) {
|
|
168
|
+
console.log(cfg.appId);
|
|
169
|
+
} else if (key === "appHash" && cfg) {
|
|
170
|
+
console.log(cfg.appHash);
|
|
171
|
+
} else {
|
|
172
|
+
console.log("not found");
|
|
173
|
+
}
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log("usage:");
|
|
178
|
+
console.log(" telegram config set appId <id>");
|
|
179
|
+
console.log(" telegram config set appHash <hash>");
|
|
180
|
+
console.log(" telegram config get appId");
|
|
181
|
+
}
|
package/src/cli/prompts.ts
CHANGED
|
@@ -23,3 +23,12 @@ export async function askUsername(): Promise<string> {
|
|
|
23
23
|
export async function askCommand(): Promise<string> {
|
|
24
24
|
return input.text("command (send/read/dialogs/quit):");
|
|
25
25
|
}
|
|
26
|
+
|
|
27
|
+
export async function askAppId(): Promise<string> {
|
|
28
|
+
console.log("\nget your API credentials from: https://my.telegram.org/apps\n");
|
|
29
|
+
return input.text("enter TELEGRAM_APP_ID:");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function askAppHash(): Promise<string> {
|
|
33
|
+
return input.text("enter TELEGRAM_APP_HASH:");
|
|
34
|
+
}
|
package/src/cli/run.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { send, read, dialogs, unread, reply, login } from "./commands";
|
|
3
|
-
import { setVerbose } from "../client/telegram";
|
|
2
|
+
import { send, read, dialogs, unread, reply, login, config } from "./commands";
|
|
3
|
+
import { setVerbose, setSessionPath } from "../client/telegram";
|
|
4
4
|
|
|
5
5
|
const VERSION = "0.1.0";
|
|
6
6
|
const NAME = "telegram";
|
|
@@ -17,7 +17,8 @@ commands:
|
|
|
17
17
|
reply <chat> <message> reply to a chat by name (partial match)
|
|
18
18
|
dialogs [limit] list recent dialogs (default: 10)
|
|
19
19
|
unread [limit] show unread messages as json (default: 20)
|
|
20
|
-
login authenticate with telegram
|
|
20
|
+
login authenticate with telegram
|
|
21
|
+
config set <key> <val> set API credentials (appId, appHash)
|
|
21
22
|
|
|
22
23
|
options:
|
|
23
24
|
-v, --verbose show debug logs
|
|
@@ -56,7 +57,7 @@ if (verbose) {
|
|
|
56
57
|
async function main() {
|
|
57
58
|
switch (command) {
|
|
58
59
|
case "send":
|
|
59
|
-
if (rest.length < 2) {
|
|
60
|
+
if (rest.length < 2 || !rest[0]) {
|
|
60
61
|
console.error("usage: telegram send <chat> <message>");
|
|
61
62
|
process.exit(1);
|
|
62
63
|
}
|
|
@@ -64,7 +65,7 @@ async function main() {
|
|
|
64
65
|
break;
|
|
65
66
|
|
|
66
67
|
case "read":
|
|
67
|
-
if (rest.length < 1) {
|
|
68
|
+
if (rest.length < 1 || !rest[0]) {
|
|
68
69
|
console.error("usage: telegram read <chat> [limit]");
|
|
69
70
|
process.exit(1);
|
|
70
71
|
}
|
|
@@ -80,7 +81,7 @@ async function main() {
|
|
|
80
81
|
break;
|
|
81
82
|
|
|
82
83
|
case "reply":
|
|
83
|
-
if (rest.length < 2) {
|
|
84
|
+
if (rest.length < 2 || !rest[0]) {
|
|
84
85
|
console.error("usage: telegram reply <chat> <message>");
|
|
85
86
|
process.exit(1);
|
|
86
87
|
}
|
|
@@ -91,6 +92,10 @@ async function main() {
|
|
|
91
92
|
await login();
|
|
92
93
|
break;
|
|
93
94
|
|
|
95
|
+
case "config":
|
|
96
|
+
await config(rest[0], rest[1], rest[2]);
|
|
97
|
+
break;
|
|
98
|
+
|
|
94
99
|
case "help":
|
|
95
100
|
console.log(HELP);
|
|
96
101
|
break;
|
package/src/client/telegram.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { StringSession } from "telegram/sessions";
|
|
|
3
3
|
import { Logger } from "telegram/extensions/Logger";
|
|
4
4
|
import type { LogLevel } from "telegram/extensions/Logger";
|
|
5
5
|
import { loadSession, saveSession } from "../session/storage";
|
|
6
|
+
import { getApiCredentials } from "../config/manager";
|
|
6
7
|
|
|
7
8
|
let verbose = false;
|
|
8
9
|
|
|
@@ -11,25 +12,38 @@ export function setVerbose(v: boolean) {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
class SilentLogger extends Logger {
|
|
14
|
-
log(_level: LogLevel, _message: string, _color: string): void {
|
|
15
|
+
override log(_level: LogLevel, _message: string, _color: string): void {
|
|
15
16
|
if (verbose) {
|
|
16
17
|
super.log(_level, _message, _color);
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
const API_ID = Number(process.env.TELEGRAM_APP_ID);
|
|
22
|
-
const API_HASH = process.env.TELEGRAM_APP_HASH ?? "";
|
|
23
|
-
|
|
24
22
|
let client: TelegramClient | null = null;
|
|
23
|
+
let customSessionPath: string | undefined;
|
|
24
|
+
|
|
25
|
+
export function setSessionPath(path: string) {
|
|
26
|
+
customSessionPath = path;
|
|
27
|
+
}
|
|
25
28
|
|
|
26
29
|
export async function getClient(): Promise<TelegramClient> {
|
|
27
30
|
if (client) return client;
|
|
28
31
|
|
|
29
|
-
const
|
|
32
|
+
const creds = getApiCredentials();
|
|
33
|
+
if (!creds) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
"telegram API credentials not found.\n" +
|
|
36
|
+
"get them from: https://my.telegram.org/apps\n" +
|
|
37
|
+
"then run: telegram config set appId <id>\n" +
|
|
38
|
+
" telegram config set appHash <hash>\n" +
|
|
39
|
+
"or set env vars: TELEGRAM_APP_ID, TELEGRAM_APP_HASH"
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const sessionStr = loadSession(customSessionPath);
|
|
30
44
|
const session = new StringSession(sessionStr);
|
|
31
45
|
|
|
32
|
-
client = new TelegramClient(session,
|
|
46
|
+
client = new TelegramClient(session, creds.appId, creds.appHash, {
|
|
33
47
|
connectionRetries: 5,
|
|
34
48
|
baseLogger: new SilentLogger(),
|
|
35
49
|
});
|
|
@@ -58,7 +72,7 @@ export async function login(callbacks: {
|
|
|
58
72
|
});
|
|
59
73
|
|
|
60
74
|
const sessionStr = c.session.save() as unknown as string;
|
|
61
|
-
saveSession(sessionStr);
|
|
75
|
+
saveSession(sessionStr, customSessionPath);
|
|
62
76
|
console.log("session saved!");
|
|
63
77
|
}
|
|
64
78
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = join(homedir(), ".supertelegram");
|
|
6
|
+
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
7
|
+
const SESSION_FILE = join(CONFIG_DIR, "session.txt");
|
|
8
|
+
|
|
9
|
+
export interface Config {
|
|
10
|
+
appId?: string;
|
|
11
|
+
appHash?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function ensureConfigDir() {
|
|
15
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
16
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getConfig(): Config {
|
|
21
|
+
ensureConfigDir();
|
|
22
|
+
|
|
23
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const content = readFileSync(CONFIG_FILE, "utf-8");
|
|
29
|
+
return JSON.parse(content);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.error("failed to read config:", err);
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function setConfig(key: string, value: string) {
|
|
37
|
+
ensureConfigDir();
|
|
38
|
+
|
|
39
|
+
const config = getConfig();
|
|
40
|
+
config[key as keyof Config] = value;
|
|
41
|
+
|
|
42
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function getSessionPath(customPath?: string): string {
|
|
46
|
+
// precedence:
|
|
47
|
+
// 1. custom path from flag
|
|
48
|
+
// 2. TELEGRAM_SESSION env var
|
|
49
|
+
// 3. global ~/.supertelegram/session.txt
|
|
50
|
+
// 4. local ./session.txt (backwards compat)
|
|
51
|
+
|
|
52
|
+
if (customPath) {
|
|
53
|
+
return customPath;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (process.env.TELEGRAM_SESSION) {
|
|
57
|
+
return process.env.TELEGRAM_SESSION;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// check if local session.txt exists (backwards compat)
|
|
61
|
+
if (existsSync("./session.txt")) {
|
|
62
|
+
return "./session.txt";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return SESSION_FILE;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function getApiCredentials(): { appId: number; appHash: string } | null {
|
|
69
|
+
// precedence:
|
|
70
|
+
// 1. env vars
|
|
71
|
+
// 2. global config
|
|
72
|
+
// 3. local .env (handled by process.env already)
|
|
73
|
+
|
|
74
|
+
const envAppId = process.env.TELEGRAM_APP_ID;
|
|
75
|
+
const envAppHash = process.env.TELEGRAM_APP_HASH;
|
|
76
|
+
|
|
77
|
+
if (envAppId && envAppHash) {
|
|
78
|
+
return {
|
|
79
|
+
appId: Number.parseInt(envAppId),
|
|
80
|
+
appHash: envAppHash,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const config = getConfig();
|
|
85
|
+
if (config.appId && config.appHash) {
|
|
86
|
+
return {
|
|
87
|
+
appId: Number.parseInt(config.appId),
|
|
88
|
+
appHash: config.appHash,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export { CONFIG_DIR, CONFIG_FILE, SESSION_FILE };
|
package/src/session/storage.ts
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { getSessionPath } from "../config/manager";
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
if (existsSync(
|
|
7
|
-
return readFileSync(
|
|
5
|
+
export function loadSession(customPath?: string): string {
|
|
6
|
+
const sessionFile = getSessionPath(customPath);
|
|
7
|
+
|
|
8
|
+
if (existsSync(sessionFile)) {
|
|
9
|
+
return readFileSync(sessionFile, "utf-8").trim();
|
|
8
10
|
}
|
|
9
11
|
return "";
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
export function saveSession(session: string): void {
|
|
13
|
-
|
|
14
|
+
export function saveSession(session: string, customPath?: string): void {
|
|
15
|
+
const sessionFile = getSessionPath(customPath);
|
|
16
|
+
|
|
17
|
+
// ensure directory exists
|
|
18
|
+
const dir = dirname(sessionFile);
|
|
19
|
+
if (!existsSync(dir)) {
|
|
20
|
+
mkdirSync(dir, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
writeFileSync(sessionFile, session, "utf-8");
|
|
14
24
|
}
|