supertelegram 0.1.2 → 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 CHANGED
@@ -1,30 +1,29 @@
1
- # @caffeinum/telegram-cli
1
+ # supertelegram
2
2
 
3
3
  telegram cli for ai to read/write messages using gramjs.
4
4
 
5
5
  ## installation
6
6
 
7
7
  ```bash
8
- npm install -g @caffeinum/telegram-cli
8
+ npm install -g supertelegram
9
9
  ```
10
10
 
11
- ## setup
11
+ ## quick start
12
12
 
13
- create `.env` file in your current directory:
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
- session is saved to `session.txt` in current directory.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supertelegram",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "telegram cli for humans and bots",
5
5
  "module": "index.ts",
6
6
  "type": "module",
@@ -7,11 +7,12 @@ 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())) {
14
- console.error("not logged in. run: tg login");
15
+ console.error("not logged in. run: telegram login");
15
16
  process.exit(1);
16
17
  }
17
18
 
@@ -22,7 +23,7 @@ export async function send(username: string, message: string) {
22
23
 
23
24
  export async function read(username: string, limit = 10) {
24
25
  if (!(await isLoggedIn())) {
25
- console.error("not logged in. run: tg login");
26
+ console.error("not logged in. run: telegram login");
26
27
  process.exit(1);
27
28
  }
28
29
 
@@ -37,7 +38,7 @@ export async function read(username: string, limit = 10) {
37
38
 
38
39
  export async function dialogs(limit = 10) {
39
40
  if (!(await isLoggedIn())) {
40
- console.error("not logged in. run: tg login");
41
+ console.error("not logged in. run: telegram login");
41
42
  process.exit(1);
42
43
  }
43
44
 
@@ -50,7 +51,7 @@ export async function dialogs(limit = 10) {
50
51
 
51
52
  export async function unread(limit = 20) {
52
53
  if (!(await isLoggedIn())) {
53
- console.error("not logged in. run: tg login");
54
+ console.error("not logged in. run: telegram login");
54
55
  process.exit(1);
55
56
  }
56
57
 
@@ -98,7 +99,7 @@ export async function unread(limit = 20) {
98
99
 
99
100
  export async function reply(chatName: string, message: string) {
100
101
  if (!(await isLoggedIn())) {
101
- console.error("not logged in. run: tg login");
102
+ console.error("not logged in. run: telegram login");
102
103
  process.exit(1);
103
104
  }
104
105
 
@@ -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
+ }
@@ -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,9 +1,9 @@
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
- const NAME = "tg";
6
+ const NAME = "telegram";
7
7
 
8
8
  const HELP = `
9
9
  ${NAME} - telegram cli for humans and bots
@@ -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 (run separately)
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,16 +57,16 @@ if (verbose) {
56
57
  async function main() {
57
58
  switch (command) {
58
59
  case "send":
59
- if (rest.length < 2) {
60
- console.error("usage: tg send <chat> <message>");
60
+ if (rest.length < 2 || !rest[0]) {
61
+ console.error("usage: telegram send <chat> <message>");
61
62
  process.exit(1);
62
63
  }
63
64
  await send(rest[0], rest.slice(1).join(" "));
64
65
  break;
65
66
 
66
67
  case "read":
67
- if (rest.length < 1) {
68
- console.error("usage: tg read <chat> [limit]");
68
+ if (rest.length < 1 || !rest[0]) {
69
+ console.error("usage: telegram read <chat> [limit]");
69
70
  process.exit(1);
70
71
  }
71
72
  await read(rest[0], rest[1] ? Number.parseInt(rest[1]) : 10);
@@ -80,8 +81,8 @@ async function main() {
80
81
  break;
81
82
 
82
83
  case "reply":
83
- if (rest.length < 2) {
84
- console.error("usage: tg reply <chat> <message>");
84
+ if (rest.length < 2 || !rest[0]) {
85
+ console.error("usage: telegram reply <chat> <message>");
85
86
  process.exit(1);
86
87
  }
87
88
  await reply(rest[0], rest.slice(1).join(" "));
@@ -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;
@@ -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 sessionStr = loadSession();
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, API_ID, API_HASH, {
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 };
@@ -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
- const SESSION_FILE = "./session.txt";
4
-
5
- export function loadSession(): string {
6
- if (existsSync(SESSION_FILE)) {
7
- return readFileSync(SESSION_FILE, "utf-8").trim();
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
- writeFileSync(SESSION_FILE, session, "utf-8");
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
  }