supertelegram 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.
@@ -0,0 +1,111 @@
1
+ ---
2
+ description: Use Bun instead of Node.js, npm, pnpm, or vite.
3
+ globs: "*.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json"
4
+ alwaysApply: false
5
+ ---
6
+
7
+ Default to using Bun instead of Node.js.
8
+
9
+ - Use `bun <file>` instead of `node <file>` or `ts-node <file>`
10
+ - Use `bun test` instead of `jest` or `vitest`
11
+ - Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
12
+ - Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
13
+ - Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
14
+ - Bun automatically loads .env, so don't use dotenv.
15
+
16
+ ## APIs
17
+
18
+ - `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
19
+ - `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
20
+ - `Bun.redis` for Redis. Don't use `ioredis`.
21
+ - `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
22
+ - `WebSocket` is built-in. Don't use `ws`.
23
+ - Prefer `Bun.file` over `node:fs`'s readFile/writeFile
24
+ - Bun.$`ls` instead of execa.
25
+
26
+ ## Testing
27
+
28
+ Use `bun test` to run tests.
29
+
30
+ ```ts#index.test.ts
31
+ import { test, expect } from "bun:test";
32
+
33
+ test("hello world", () => {
34
+ expect(1).toBe(1);
35
+ });
36
+ ```
37
+
38
+ ## Frontend
39
+
40
+ Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
41
+
42
+ Server:
43
+
44
+ ```ts#index.ts
45
+ import index from "./index.html"
46
+
47
+ Bun.serve({
48
+ routes: {
49
+ "/": index,
50
+ "/api/users/:id": {
51
+ GET: (req) => {
52
+ return new Response(JSON.stringify({ id: req.params.id }));
53
+ },
54
+ },
55
+ },
56
+ // optional websocket support
57
+ websocket: {
58
+ open: (ws) => {
59
+ ws.send("Hello, world!");
60
+ },
61
+ message: (ws, message) => {
62
+ ws.send(message);
63
+ },
64
+ close: (ws) => {
65
+ // handle close
66
+ }
67
+ },
68
+ development: {
69
+ hmr: true,
70
+ console: true,
71
+ }
72
+ })
73
+ ```
74
+
75
+ HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
76
+
77
+ ```html#index.html
78
+ <html>
79
+ <body>
80
+ <h1>Hello, world!</h1>
81
+ <script type="module" src="./frontend.tsx"></script>
82
+ </body>
83
+ </html>
84
+ ```
85
+
86
+ With the following `frontend.tsx`:
87
+
88
+ ```tsx#frontend.tsx
89
+ import React from "react";
90
+
91
+ // import .css files directly and it works
92
+ import './index.css';
93
+
94
+ import { createRoot } from "react-dom/client";
95
+
96
+ const root = createRoot(document.body);
97
+
98
+ export default function Frontend() {
99
+ return <h1>Hello, world!</h1>;
100
+ }
101
+
102
+ root.render(<Frontend />);
103
+ ```
104
+
105
+ Then, run index.ts
106
+
107
+ ```sh
108
+ bun --hot ./index.ts
109
+ ```
110
+
111
+ For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`.
package/.env.example ADDED
@@ -0,0 +1,2 @@
1
+ TELEGRAM_API_ID=12345678
2
+ TELEGRAM_API_HASH=abcdef1234567890abcdef1234567890
package/PUBLISHING.md ADDED
@@ -0,0 +1,44 @@
1
+ # publishing guide
2
+
3
+ ## setup
4
+
5
+ 1. login to npm:
6
+ ```bash
7
+ npm login
8
+ ```
9
+
10
+ 2. authenticate with your 2fa if needed
11
+
12
+ ## publish new version
13
+
14
+ 1. bump version in `package.json`:
15
+ ```bash
16
+ npm version patch # or minor, major
17
+ ```
18
+
19
+ 2. publish to npm:
20
+ ```bash
21
+ npm publish --access public
22
+ ```
23
+
24
+ note: scoped packages (`@caffeinum/...`) need `--access public` flag to be free
25
+
26
+ ## verify publication
27
+
28
+ ```bash
29
+ npm view @caffeinum/telegram-cli
30
+ ```
31
+
32
+ ## test installation
33
+
34
+ ```bash
35
+ npm install -g @caffeinum/telegram-cli
36
+ telegram --help
37
+ ```
38
+
39
+ ## troubleshooting
40
+
41
+ - **402 payment required**: add `--access public` flag
42
+ - **403 forbidden (name conflict)**: rename package or use scoped name `@username/package`
43
+ - **otp required**: add `--otp=<code>` flag with 2fa code
44
+ - **401 unauthorized**: run `npm login` again
package/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # @caffeinum/telegram-cli
2
+
3
+ telegram cli for ai to read/write messages using gramjs.
4
+
5
+ ## installation
6
+
7
+ ```bash
8
+ npm install -g @caffeinum/telegram-cli
9
+ ```
10
+
11
+ ## setup
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
22
+
23
+ ```bash
24
+ telegram login
25
+ ```
26
+
27
+ session is saved to `session.txt` in current directory.
28
+
29
+ ## usage
30
+
31
+ ```bash
32
+ # send a message
33
+ telegram send <username> <message>
34
+
35
+ # read messages
36
+ telegram read <username> [limit]
37
+
38
+ # reply to latest message
39
+ telegram reply <username> <message>
40
+
41
+ # get unread messages (json output)
42
+ telegram unread [limit]
43
+
44
+ # list dialogs
45
+ telegram dialogs [limit]
46
+ ```
47
+
48
+ ### flags
49
+
50
+ - `-v, --verbose` - show debug logs
51
+ - `--help` - show help
52
+ - `--version` - show version
53
+
54
+ ## development
55
+
56
+ clone repo and install:
57
+ ```bash
58
+ git clone https://github.com/caffeinum/telegram-cli.git
59
+ cd telegram-cli
60
+ bun install
61
+ ```
62
+
63
+ run locally:
64
+ ```bash
65
+ bun run cli <command>
66
+ ```
package/bun.lock ADDED
@@ -0,0 +1,186 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "bot-creator",
7
+ "dependencies": {
8
+ "input": "^1.0.1",
9
+ "telegram": "^2.26.22",
10
+ },
11
+ "devDependencies": {
12
+ "@types/bun": "latest",
13
+ },
14
+ "peerDependencies": {
15
+ "typescript": "^5",
16
+ },
17
+ },
18
+ },
19
+ "packages": {
20
+ "@cryptography/aes": ["@cryptography/aes@0.1.1", "", {}, "sha512-PcYz4FDGblO6tM2kSC+VzhhK62vml6k6/YAkiWtyPvrgJVfnDRoHGDtKn5UiaRRUrvUTTocBpvc2rRgTCqxjsg=="],
21
+
22
+ "@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
23
+
24
+ "@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
25
+
26
+ "ansi-escapes": ["ansi-escapes@1.4.0", "", {}, "sha512-wiXutNjDUlNEDWHcYH3jtZUhd3c4/VojassD8zHdHCY13xbZy2XbW+NKQwA0tWGBVzDA9qEzYwfoSsWmviidhw=="],
27
+
28
+ "ansi-regex": ["ansi-regex@2.1.1", "", {}, "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="],
29
+
30
+ "ansi-styles": ["ansi-styles@2.2.1", "", {}, "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA=="],
31
+
32
+ "async-mutex": ["async-mutex@0.3.2", "", { "dependencies": { "tslib": "^2.3.1" } }, "sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA=="],
33
+
34
+ "babel-runtime": ["babel-runtime@6.26.0", "", { "dependencies": { "core-js": "^2.4.0", "regenerator-runtime": "^0.11.0" } }, "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g=="],
35
+
36
+ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
37
+
38
+ "big-integer": ["big-integer@1.6.52", "", {}, "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg=="],
39
+
40
+ "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
41
+
42
+ "bufferutil": ["bufferutil@4.1.0", "", { "dependencies": { "node-gyp-build": "^4.3.0" } }, "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw=="],
43
+
44
+ "bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
45
+
46
+ "chalk": ["chalk@1.1.3", "", { "dependencies": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", "has-ansi": "^2.0.0", "strip-ansi": "^3.0.0", "supports-color": "^2.0.0" } }, "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A=="],
47
+
48
+ "cli-cursor": ["cli-cursor@1.0.2", "", { "dependencies": { "restore-cursor": "^1.0.1" } }, "sha512-25tABq090YNKkF6JH7lcwO0zFJTRke4Jcq9iX2nr/Sz0Cjjv4gckmwlW6Ty/aoyFd6z3ysR2hMGC2GFugmBo6A=="],
49
+
50
+ "cli-width": ["cli-width@2.2.1", "", {}, "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw=="],
51
+
52
+ "code-point-at": ["code-point-at@1.1.0", "", {}, "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA=="],
53
+
54
+ "core-js": ["core-js@2.6.12", "", {}, "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ=="],
55
+
56
+ "d": ["d@1.0.2", "", { "dependencies": { "es5-ext": "^0.10.64", "type": "^2.7.2" } }, "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw=="],
57
+
58
+ "debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
59
+
60
+ "dom-serializer": ["dom-serializer@1.4.1", "", { "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^4.2.0", "entities": "^2.0.0" } }, "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag=="],
61
+
62
+ "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
63
+
64
+ "domhandler": ["domhandler@4.3.1", "", { "dependencies": { "domelementtype": "^2.2.0" } }, "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ=="],
65
+
66
+ "domutils": ["domutils@2.8.0", "", { "dependencies": { "dom-serializer": "^1.0.1", "domelementtype": "^2.2.0", "domhandler": "^4.2.0" } }, "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A=="],
67
+
68
+ "entities": ["entities@2.2.0", "", {}, "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="],
69
+
70
+ "es5-ext": ["es5-ext@0.10.64", "", { "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", "esniff": "^2.0.1", "next-tick": "^1.1.0" } }, "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg=="],
71
+
72
+ "es6-iterator": ["es6-iterator@2.0.3", "", { "dependencies": { "d": "1", "es5-ext": "^0.10.35", "es6-symbol": "^3.1.1" } }, "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g=="],
73
+
74
+ "es6-symbol": ["es6-symbol@3.1.4", "", { "dependencies": { "d": "^1.0.2", "ext": "^1.7.0" } }, "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg=="],
75
+
76
+ "escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
77
+
78
+ "esniff": ["esniff@2.0.1", "", { "dependencies": { "d": "^1.0.1", "es5-ext": "^0.10.62", "event-emitter": "^0.3.5", "type": "^2.7.2" } }, "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg=="],
79
+
80
+ "event-emitter": ["event-emitter@0.3.5", "", { "dependencies": { "d": "1", "es5-ext": "~0.10.14" } }, "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA=="],
81
+
82
+ "exit-hook": ["exit-hook@1.1.1", "", {}, "sha512-MsG3prOVw1WtLXAZbM3KiYtooKR1LvxHh3VHsVtIy0uiUu8usxgB/94DP2HxtD/661lLdB6yzQ09lGJSQr6nkg=="],
83
+
84
+ "ext": ["ext@1.7.0", "", { "dependencies": { "type": "^2.7.2" } }, "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw=="],
85
+
86
+ "figures": ["figures@1.7.0", "", { "dependencies": { "escape-string-regexp": "^1.0.5", "object-assign": "^4.1.0" } }, "sha512-UxKlfCRuCBxSXU4C6t9scbDyWZ4VlaFFdojKtzJuSkuOBQ5CNFum+zZXFwHjo+CxBC1t6zlYPgHIgFjL8ggoEQ=="],
87
+
88
+ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
89
+
90
+ "has-ansi": ["has-ansi@2.0.0", "", { "dependencies": { "ansi-regex": "^2.0.0" } }, "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg=="],
91
+
92
+ "htmlparser2": ["htmlparser2@6.1.0", "", { "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^4.0.0", "domutils": "^2.5.2", "entities": "^2.0.0" } }, "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A=="],
93
+
94
+ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
95
+
96
+ "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
97
+
98
+ "input": ["input@1.0.1", "", { "dependencies": { "babel-runtime": "^6.6.1", "chalk": "^1.1.1", "inquirer": "^0.12.0", "lodash": "^4.6.1" } }, "sha512-5DKQKQ7Nm/CaPGYKF74uUvk5ftC3S04fLYWcDrNG2rOVhhRgB4E2J8JNb7AAh+RlQ/954ukas4bEbrRQ3/kPGA=="],
99
+
100
+ "inquirer": ["inquirer@0.12.0", "", { "dependencies": { "ansi-escapes": "^1.1.0", "ansi-regex": "^2.0.0", "chalk": "^1.0.0", "cli-cursor": "^1.0.1", "cli-width": "^2.0.0", "figures": "^1.3.5", "lodash": "^4.3.0", "readline2": "^1.0.1", "run-async": "^0.1.0", "rx-lite": "^3.1.2", "string-width": "^1.0.1", "strip-ansi": "^3.0.0", "through": "^2.3.6" } }, "sha512-bOetEz5+/WpgaW4D1NYOk1aD+JCqRjqu/FwRFgnIfiP7FC/zinsrfyO1vlS3nyH/R7S0IH3BIHBu4DBIDSqiGQ=="],
101
+
102
+ "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="],
103
+
104
+ "is-fullwidth-code-point": ["is-fullwidth-code-point@1.0.0", "", { "dependencies": { "number-is-nan": "^1.0.0" } }, "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw=="],
105
+
106
+ "is-typedarray": ["is-typedarray@1.0.0", "", {}, "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="],
107
+
108
+ "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
109
+
110
+ "mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
111
+
112
+ "ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
113
+
114
+ "mute-stream": ["mute-stream@0.0.5", "", {}, "sha512-EbrziT4s8cWPmzr47eYVW3wimS4HsvlnV5ri1xw1aR6JQo/OrJX5rkl32K/QQHdxeabJETtfeaROGhd8W7uBgg=="],
115
+
116
+ "next-tick": ["next-tick@1.1.0", "", {}, "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="],
117
+
118
+ "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="],
119
+
120
+ "node-localstorage": ["node-localstorage@2.2.1", "", { "dependencies": { "write-file-atomic": "^1.1.4" } }, "sha512-vv8fJuOUCCvSPjDjBLlMqYMHob4aGjkmrkaE42/mZr0VT+ZAU10jRF8oTnX9+pgU9/vYJ8P7YT3Vd6ajkmzSCw=="],
121
+
122
+ "number-is-nan": ["number-is-nan@1.0.1", "", {}, "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ=="],
123
+
124
+ "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
125
+
126
+ "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
127
+
128
+ "onetime": ["onetime@1.1.0", "", {}, "sha512-GZ+g4jayMqzCRMgB2sol7GiCLjKfS1PINkjmx8spcKce1LiVqcbQreXwqs2YAFXC6R03VIG28ZS31t8M866v6A=="],
129
+
130
+ "pako": ["pako@2.1.0", "", {}, "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="],
131
+
132
+ "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
133
+
134
+ "readline2": ["readline2@1.0.1", "", { "dependencies": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", "mute-stream": "0.0.5" } }, "sha512-8/td4MmwUB6PkZUbV25uKz7dfrmjYWxsW8DVfibWdlHRk/l/DfHKn4pU+dfcoGLFgWOdyGCzINRQD7jn+Bv+/g=="],
135
+
136
+ "real-cancellable-promise": ["real-cancellable-promise@1.2.3", "", {}, "sha512-hBI5Gy/55VEeeMtImMgEirD7eq5UmqJf1J8dFZtbJZA/3rB0pYFZ7PayMGueb6v4UtUtpKpP+05L0VwyE1hI9Q=="],
137
+
138
+ "regenerator-runtime": ["regenerator-runtime@0.11.1", "", {}, "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="],
139
+
140
+ "restore-cursor": ["restore-cursor@1.0.1", "", { "dependencies": { "exit-hook": "^1.0.0", "onetime": "^1.0.0" } }, "sha512-reSjH4HuiFlxlaBaFCiS6O76ZGG2ygKoSlCsipKdaZuKSPx/+bt9mULkn4l0asVzbEfQQmXRg6Wp6gv6m0wElw=="],
141
+
142
+ "run-async": ["run-async@0.1.0", "", { "dependencies": { "once": "^1.3.0" } }, "sha512-qOX+w+IxFgpUpJfkv2oGN0+ExPs68F4sZHfaRRx4dDexAQkG83atugKVEylyT5ARees3HBbfmuvnjbrd8j9Wjw=="],
143
+
144
+ "rx-lite": ["rx-lite@3.1.2", "", {}, "sha512-1I1+G2gteLB8Tkt8YI1sJvSIfa0lWuRtC8GjvtyPBcLSF5jBCCJJqKrpER5JU5r6Bhe+i9/pK3VMuUcXu0kdwQ=="],
145
+
146
+ "slide": ["slide@1.1.6", "", {}, "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw=="],
147
+
148
+ "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
149
+
150
+ "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="],
151
+
152
+ "store2": ["store2@2.14.4", "", {}, "sha512-srTItn1GOvyvOycgxjAnPA63FZNwy0PTyUBFMHRM+hVFltAeoh0LmNBz9SZqUS9mMqGk8rfyWyXn3GH5ReJ8Zw=="],
153
+
154
+ "string-width": ["string-width@1.0.2", "", { "dependencies": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", "strip-ansi": "^3.0.0" } }, "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw=="],
155
+
156
+ "strip-ansi": ["strip-ansi@3.0.1", "", { "dependencies": { "ansi-regex": "^2.0.0" } }, "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg=="],
157
+
158
+ "supports-color": ["supports-color@2.0.0", "", {}, "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g=="],
159
+
160
+ "telegram": ["telegram@2.26.22", "", { "dependencies": { "@cryptography/aes": "^0.1.1", "async-mutex": "^0.3.0", "big-integer": "^1.6.48", "buffer": "^6.0.3", "htmlparser2": "^6.1.0", "mime": "^3.0.0", "node-localstorage": "^2.2.1", "pako": "^2.0.3", "path-browserify": "^1.0.1", "real-cancellable-promise": "^1.1.1", "socks": "^2.6.2", "store2": "^2.13.0", "ts-custom-error": "^3.2.0", "websocket": "^1.0.34" }, "optionalDependencies": { "bufferutil": "^4.0.3", "utf-8-validate": "^5.0.5" } }, "sha512-EIj7Yrjiu0Yosa3FZ/7EyPg9s6UiTi/zDQrFmR/2Mg7pIUU+XjAit1n1u9OU9h2oRnRM5M+67/fxzQluZpaJJg=="],
161
+
162
+ "through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="],
163
+
164
+ "ts-custom-error": ["ts-custom-error@3.3.1", "", {}, "sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A=="],
165
+
166
+ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
167
+
168
+ "type": ["type@2.7.3", "", {}, "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="],
169
+
170
+ "typedarray-to-buffer": ["typedarray-to-buffer@3.1.5", "", { "dependencies": { "is-typedarray": "^1.0.0" } }, "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q=="],
171
+
172
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
173
+
174
+ "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
175
+
176
+ "utf-8-validate": ["utf-8-validate@5.0.10", "", { "dependencies": { "node-gyp-build": "^4.3.0" } }, "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ=="],
177
+
178
+ "websocket": ["websocket@1.0.35", "", { "dependencies": { "bufferutil": "^4.0.1", "debug": "^2.2.0", "es5-ext": "^0.10.63", "typedarray-to-buffer": "^3.1.5", "utf-8-validate": "^5.0.2", "yaeti": "^0.0.6" } }, "sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q=="],
179
+
180
+ "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
181
+
182
+ "write-file-atomic": ["write-file-atomic@1.3.4", "", { "dependencies": { "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", "slide": "^1.1.5" } }, "sha512-SdrHoC/yVBPpV0Xq/mUZQIpW2sWXAShb/V4pomcJXh92RuaO+f3UTWItiR3Px+pLnV2PvC2/bfn5cwr5X6Vfxw=="],
183
+
184
+ "yaeti": ["yaeti@0.0.6", "", {}, "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug=="],
185
+ }
186
+ }
package/index.ts ADDED
@@ -0,0 +1 @@
1
+ console.log("Hello via Bun!");
package/login.ts ADDED
@@ -0,0 +1,25 @@
1
+ import { isLoggedIn, login, disconnect } from "./src/client/telegram";
2
+ import { askPhoneNumber, askPhoneCode, askPassword } from "./src/cli/prompts";
3
+
4
+ async function main() {
5
+ console.log("telegram login script");
6
+
7
+ const loggedIn = await isLoggedIn();
8
+ if (loggedIn) {
9
+ console.log("already logged in!");
10
+ await disconnect();
11
+ return;
12
+ }
13
+
14
+ console.log("starting login...");
15
+ await login({
16
+ phoneNumber: askPhoneNumber,
17
+ phoneCode: askPhoneCode,
18
+ password: askPassword,
19
+ });
20
+
21
+ console.log("login complete!");
22
+ await disconnect();
23
+ }
24
+
25
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "supertelegram",
3
+ "version": "0.1.0",
4
+ "description": "telegram cli for humans and bots",
5
+ "module": "index.ts",
6
+ "type": "module",
7
+ "bin": {
8
+ "telegram": "src/cli/run.ts"
9
+ },
10
+ "scripts": {
11
+ "login": "bun run login.ts",
12
+ "tg": "bun run src/cli/run.ts",
13
+ "cli": "bun run src/cli/run.ts",
14
+ "cli:interactive": "bun run src/cli/index.ts"
15
+ },
16
+ "devDependencies": {
17
+ "@types/bun": "latest"
18
+ },
19
+ "peerDependencies": {
20
+ "typescript": "^5"
21
+ },
22
+ "dependencies": {
23
+ "input": "^1.0.1",
24
+ "telegram": "^2.26.22"
25
+ }
26
+ }
@@ -0,0 +1,56 @@
1
+ # polling skill: message answering loop
2
+
3
+ ## what it does
4
+ runs a check-answer-sleep loop where you stay in control between iterations. no background processes - you manually poll, decide, respond, then sleep.
5
+
6
+ ## why this pattern
7
+ - you keep control: each iteration returns, letting you think and decide
8
+ - no daemon: avoids running detached processes you can't monitor
9
+ - flexible: you can use any tool (web fetch, file read, etc) before responding
10
+ - simple: just cli commands + sleep, no complex state management
11
+
12
+ ## how to activate
13
+ say: "go into polling mode" or "check messages and answer in a loop"
14
+
15
+ ## the loop pattern
16
+ ```
17
+ while true:
18
+ 1. check for new messages: `bun run cli unread 10`
19
+ 2. for each unread personal message:
20
+ - read the message
21
+ - think about response (can use tools if needed)
22
+ - reply: `bun run cli reply "<chat>" "<message>"`
23
+ 3. sleep: `sleep 60`
24
+ 4. repeat
25
+ ```
26
+
27
+ ## commands available
28
+ - `bun run cli unread [limit]` - get unread messages as json
29
+ - `bun run cli reply <chat> <message>` - reply to a chat by name (partial match)
30
+ - `bun run cli dialogs [limit]` - list recent chats
31
+ - `bun run cli read <username> [limit]` - read message history
32
+
33
+ ## tips
34
+ - ignore system/bot channels (high unread counts like 10000+)
35
+ - use partial chat names for reply (e.g., "Паша" matches "Паша СЕО")
36
+ - can fetch external data before replying (weather, web, files, etc.)
37
+ - sleep for 60 seconds is a good default, adjust as needed
38
+
39
+ ## example session
40
+ ```
41
+ > bun run cli unread 10
42
+ [{"chat": "Alice", "unreadCount": 1, "messages": [{"text": "what time is it?"}]}]
43
+
44
+ > # think: they want the time
45
+ > date
46
+ Fri Dec 19 22:20:00 PST 2025
47
+
48
+ > bun run cli reply "Alice" "it's 10:20pm PST!"
49
+ sent to Alice: it's 10:20pm PST! (id: 123)
50
+
51
+ > sleep 60
52
+ # ... wait ...
53
+
54
+ > bun run cli unread 10
55
+ # repeat
56
+ ```
@@ -0,0 +1,143 @@
1
+ import {
2
+ isLoggedIn,
3
+ sendMessage,
4
+ getMessages,
5
+ getDialogs,
6
+ disconnect,
7
+ getClient,
8
+ login as telegramLogin,
9
+ } from "../client/telegram";
10
+ import { askPhoneNumber, askPhoneCode, askPassword } from "./prompts";
11
+
12
+ export async function send(username: string, message: string) {
13
+ if (!(await isLoggedIn())) {
14
+ console.error("not logged in. run: tg login");
15
+ process.exit(1);
16
+ }
17
+
18
+ const result = await sendMessage(username, message);
19
+ console.log(`sent message id: ${result.id}`);
20
+ await disconnect();
21
+ }
22
+
23
+ export async function read(username: string, limit = 10) {
24
+ if (!(await isLoggedIn())) {
25
+ console.error("not logged in. run: tg login");
26
+ process.exit(1);
27
+ }
28
+
29
+ const messages = await getMessages(username, limit);
30
+ for (const msg of messages.reverse()) {
31
+ const sender = msg.senderId?.toString() ?? "unknown";
32
+ const date = msg.date ? new Date(msg.date * 1000).toISOString() : "";
33
+ console.log(`[${date}] [${sender}]: ${msg.message}`);
34
+ }
35
+ await disconnect();
36
+ }
37
+
38
+ export async function dialogs(limit = 10) {
39
+ if (!(await isLoggedIn())) {
40
+ console.error("not logged in. run: tg login");
41
+ process.exit(1);
42
+ }
43
+
44
+ const dialogList = await getDialogs(limit);
45
+ for (const dialog of dialogList) {
46
+ console.log(`- ${dialog.title}`);
47
+ }
48
+ await disconnect();
49
+ }
50
+
51
+ export async function unread(limit = 20) {
52
+ if (!(await isLoggedIn())) {
53
+ console.error("not logged in. run: tg login");
54
+ process.exit(1);
55
+ }
56
+
57
+ const client = await getClient();
58
+ const me = await client.getMe();
59
+ const myId = me.id.toString();
60
+
61
+ const dialogList = await getDialogs(limit);
62
+
63
+ const unreadDialogs = dialogList.filter((d) => d.unreadCount > 0 && d.entity);
64
+
65
+ if (unreadDialogs.length === 0) {
66
+ console.log("no unread messages");
67
+ await disconnect();
68
+ return;
69
+ }
70
+
71
+ // output as JSON for easy parsing
72
+ const output = [];
73
+
74
+ for (const dialog of unreadDialogs) {
75
+ if (!dialog.entity) continue;
76
+
77
+ // get last few messages
78
+ const messages = await client.getMessages(dialog.entity, {
79
+ limit: Math.min(dialog.unreadCount, 5),
80
+ });
81
+
82
+ const fromOthers = messages.filter((m) => m.senderId?.toString() !== myId);
83
+
84
+ output.push({
85
+ chat: dialog.title,
86
+ unreadCount: dialog.unreadCount,
87
+ messages: fromOthers.map((m) => ({
88
+ id: m.id,
89
+ text: m.message,
90
+ date: m.date ? new Date(m.date * 1000).toISOString() : null,
91
+ })),
92
+ });
93
+ }
94
+
95
+ console.log(JSON.stringify(output, null, 2));
96
+ await disconnect();
97
+ }
98
+
99
+ export async function reply(chatName: string, message: string) {
100
+ if (!(await isLoggedIn())) {
101
+ console.error("not logged in. run: tg login");
102
+ process.exit(1);
103
+ }
104
+
105
+ const client = await getClient();
106
+ const dialogList = await getDialogs(50);
107
+
108
+ const dialog = dialogList.find((d) =>
109
+ d.title?.toLowerCase().includes(chatName.toLowerCase())
110
+ );
111
+
112
+ if (!dialog || !dialog.entity) {
113
+ console.error(`chat "${chatName}" not found`);
114
+ process.exit(1);
115
+ }
116
+
117
+ const result = await client.sendMessage(dialog.entity, { message });
118
+ console.log(`sent to ${dialog.title}: ${message} (id: ${result.id})`);
119
+
120
+ // mark as read
121
+ await client.markAsRead(dialog.entity);
122
+
123
+ await disconnect();
124
+ }
125
+
126
+ export async function login() {
127
+ const loggedIn = await isLoggedIn();
128
+ if (loggedIn) {
129
+ console.log("already logged in!");
130
+ await disconnect();
131
+ return;
132
+ }
133
+
134
+ console.log("starting login...");
135
+ await telegramLogin({
136
+ phoneNumber: askPhoneNumber,
137
+ phoneCode: askPhoneCode,
138
+ password: askPassword,
139
+ });
140
+
141
+ console.log("login complete!");
142
+ await disconnect();
143
+ }
@@ -0,0 +1,90 @@
1
+ import {
2
+ isLoggedIn,
3
+ login,
4
+ sendMessage,
5
+ getMessages,
6
+ getDialogs,
7
+ disconnect,
8
+ } from "../client/telegram";
9
+ import {
10
+ askPhoneNumber,
11
+ askPhoneCode,
12
+ askPassword,
13
+ askMessage,
14
+ askUsername,
15
+ askCommand,
16
+ } from "./prompts";
17
+
18
+ async function ensureLoggedIn() {
19
+ const loggedIn = await isLoggedIn();
20
+ if (!loggedIn) {
21
+ console.log("not logged in, starting login flow...");
22
+ await login({
23
+ phoneNumber: askPhoneNumber,
24
+ phoneCode: askPhoneCode,
25
+ password: askPassword,
26
+ });
27
+ }
28
+ console.log("logged in!");
29
+ }
30
+
31
+ async function handleSend() {
32
+ const username = await askUsername();
33
+ const message = await askMessage();
34
+ const result = await sendMessage(username, message);
35
+ console.log("sent:", result.id);
36
+ }
37
+
38
+ async function handleRead() {
39
+ const username = await askUsername();
40
+ const messages = await getMessages(username, 10);
41
+ console.log("\n--- messages ---");
42
+ for (const msg of messages.reverse()) {
43
+ const sender = msg.senderId?.toString() ?? "unknown";
44
+ console.log(`[${sender}]: ${msg.message}`);
45
+ }
46
+ console.log("----------------\n");
47
+ }
48
+
49
+ async function handleDialogs() {
50
+ const dialogs = await getDialogs(10);
51
+ console.log("\n--- dialogs ---");
52
+ for (const dialog of dialogs) {
53
+ console.log(`- ${dialog.title}`);
54
+ }
55
+ console.log("---------------\n");
56
+ }
57
+
58
+ async function main() {
59
+ console.log("telegram cli - meow!");
60
+
61
+ await ensureLoggedIn();
62
+
63
+ let running = true;
64
+ while (running) {
65
+ const cmd = await askCommand();
66
+ switch (cmd.toLowerCase()) {
67
+ case "send":
68
+ await handleSend();
69
+ break;
70
+ case "read":
71
+ await handleRead();
72
+ break;
73
+ case "dialogs":
74
+ await handleDialogs();
75
+ break;
76
+ case "quit":
77
+ case "exit":
78
+ case "q":
79
+ running = false;
80
+ break;
81
+ default:
82
+ console.log("unknown command. try: send, read, dialogs, quit");
83
+ }
84
+ }
85
+
86
+ await disconnect();
87
+ console.log("bye!");
88
+ }
89
+
90
+ main().catch(console.error);
@@ -0,0 +1,25 @@
1
+ import input from "input";
2
+
3
+ export async function askPhoneNumber(): Promise<string> {
4
+ return input.text("enter phone number (with country code):");
5
+ }
6
+
7
+ export async function askPhoneCode(): Promise<string> {
8
+ return input.text("enter the code you received:");
9
+ }
10
+
11
+ export async function askPassword(): Promise<string> {
12
+ return input.text("enter 2fa password (if any):");
13
+ }
14
+
15
+ export async function askMessage(): Promise<string> {
16
+ return input.text("message:");
17
+ }
18
+
19
+ export async function askUsername(): Promise<string> {
20
+ return input.text("username/chat:");
21
+ }
22
+
23
+ export async function askCommand(): Promise<string> {
24
+ return input.text("command (send/read/dialogs/quit):");
25
+ }
package/src/cli/run.ts ADDED
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env bun
2
+ import { send, read, dialogs, unread, reply, login } from "./commands";
3
+ import { setVerbose } from "../client/telegram";
4
+
5
+ const VERSION = "0.1.0";
6
+ const NAME = "tg";
7
+
8
+ const HELP = `
9
+ ${NAME} - telegram cli for humans and bots
10
+
11
+ usage:
12
+ ${NAME} <command> [options]
13
+
14
+ commands:
15
+ send <chat> <message> send a message to a chat
16
+ read <chat> [limit] read messages from a chat (default: 10)
17
+ reply <chat> <message> reply to a chat by name (partial match)
18
+ dialogs [limit] list recent dialogs (default: 10)
19
+ unread [limit] show unread messages as json (default: 20)
20
+ login authenticate with telegram (run separately)
21
+
22
+ options:
23
+ -v, --verbose show debug logs
24
+ -h, --help show this help
25
+ --version show version
26
+
27
+ examples:
28
+ ${NAME} send @username "hello there"
29
+ ${NAME} read @username 5
30
+ ${NAME} reply "John" "hey!"
31
+ ${NAME} unread
32
+ ${NAME} dialogs 20
33
+ `.trim();
34
+
35
+ const rawArgs = process.argv.slice(2);
36
+
37
+ // handle help/version first
38
+ if (rawArgs.includes("-h") || rawArgs.includes("--help") || rawArgs.length === 0) {
39
+ console.log(HELP);
40
+ process.exit(0);
41
+ }
42
+
43
+ if (rawArgs.includes("--version")) {
44
+ console.log(`${NAME} v${VERSION}`);
45
+ process.exit(0);
46
+ }
47
+
48
+ const verbose = rawArgs.includes("--verbose") || rawArgs.includes("-v");
49
+ const args = rawArgs.filter((a) => !a.startsWith("-"));
50
+ const [command, ...rest] = args;
51
+
52
+ if (verbose) {
53
+ setVerbose(true);
54
+ }
55
+
56
+ async function main() {
57
+ switch (command) {
58
+ case "send":
59
+ if (rest.length < 2) {
60
+ console.error("usage: tg send <chat> <message>");
61
+ process.exit(1);
62
+ }
63
+ await send(rest[0], rest.slice(1).join(" "));
64
+ break;
65
+
66
+ case "read":
67
+ if (rest.length < 1) {
68
+ console.error("usage: tg read <chat> [limit]");
69
+ process.exit(1);
70
+ }
71
+ await read(rest[0], rest[1] ? Number.parseInt(rest[1]) : 10);
72
+ break;
73
+
74
+ case "dialogs":
75
+ await dialogs(rest[0] ? Number.parseInt(rest[0]) : 10);
76
+ break;
77
+
78
+ case "unread":
79
+ await unread(rest[0] ? Number.parseInt(rest[0]) : 20);
80
+ break;
81
+
82
+ case "reply":
83
+ if (rest.length < 2) {
84
+ console.error("usage: tg reply <chat> <message>");
85
+ process.exit(1);
86
+ }
87
+ await reply(rest[0], rest.slice(1).join(" "));
88
+ break;
89
+
90
+ case "login":
91
+ await login();
92
+ break;
93
+
94
+ case "help":
95
+ console.log(HELP);
96
+ break;
97
+
98
+ default:
99
+ console.error(`unknown command: ${command}`);
100
+ console.error(`run '${NAME} --help' for usage`);
101
+ process.exit(1);
102
+ }
103
+ }
104
+
105
+ main().catch((err) => {
106
+ console.error("error:", err.message);
107
+ process.exit(1);
108
+ });
@@ -0,0 +1,92 @@
1
+ import { TelegramClient, type Api } from "telegram";
2
+ import { StringSession } from "telegram/sessions";
3
+ import { Logger } from "telegram/extensions/Logger";
4
+ import type { LogLevel } from "telegram/extensions/Logger";
5
+ import { loadSession, saveSession } from "../session/storage";
6
+
7
+ let verbose = false;
8
+
9
+ export function setVerbose(v: boolean) {
10
+ verbose = v;
11
+ }
12
+
13
+ class SilentLogger extends Logger {
14
+ log(_level: LogLevel, _message: string, _color: string): void {
15
+ if (verbose) {
16
+ super.log(_level, _message, _color);
17
+ }
18
+ }
19
+ }
20
+
21
+ const API_ID = Number(process.env.TELEGRAM_APP_ID);
22
+ const API_HASH = process.env.TELEGRAM_APP_HASH ?? "";
23
+
24
+ let client: TelegramClient | null = null;
25
+
26
+ export async function getClient(): Promise<TelegramClient> {
27
+ if (client) return client;
28
+
29
+ const sessionStr = loadSession();
30
+ const session = new StringSession(sessionStr);
31
+
32
+ client = new TelegramClient(session, API_ID, API_HASH, {
33
+ connectionRetries: 5,
34
+ baseLogger: new SilentLogger(),
35
+ });
36
+
37
+ await client.connect();
38
+ return client;
39
+ }
40
+
41
+ export async function isLoggedIn(): Promise<boolean> {
42
+ const c = await getClient();
43
+ return c.checkAuthorization();
44
+ }
45
+
46
+ export async function login(callbacks: {
47
+ phoneNumber: () => Promise<string>;
48
+ phoneCode: () => Promise<string>;
49
+ password: () => Promise<string>;
50
+ }): Promise<void> {
51
+ const c = await getClient();
52
+
53
+ await c.start({
54
+ phoneNumber: callbacks.phoneNumber,
55
+ phoneCode: callbacks.phoneCode,
56
+ password: callbacks.password,
57
+ onError: (err) => console.error("login error:", err),
58
+ });
59
+
60
+ const sessionStr = c.session.save() as unknown as string;
61
+ saveSession(sessionStr);
62
+ console.log("session saved!");
63
+ }
64
+
65
+ export async function sendMessage(
66
+ username: string,
67
+ message: string
68
+ ): Promise<Api.Message> {
69
+ const c = await getClient();
70
+ return c.sendMessage(username, { message });
71
+ }
72
+
73
+ export async function getMessages(
74
+ username: string,
75
+ limit = 10
76
+ ): Promise<Api.Message[]> {
77
+ const c = await getClient();
78
+ const messages = await c.getMessages(username, { limit });
79
+ return messages as Api.Message[];
80
+ }
81
+
82
+ export async function getDialogs(limit = 10) {
83
+ const c = await getClient();
84
+ return c.getDialogs({ limit });
85
+ }
86
+
87
+ export async function disconnect(): Promise<void> {
88
+ if (client) {
89
+ await client.disconnect();
90
+ client = null;
91
+ }
92
+ }
@@ -0,0 +1,14 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+
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();
8
+ }
9
+ return "";
10
+ }
11
+
12
+ export function saveSession(session: string): void {
13
+ writeFileSync(SESSION_FILE, session, "utf-8");
14
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Environment setup & latest features
4
+ "lib": ["ESNext"],
5
+ "target": "ESNext",
6
+ "module": "Preserve",
7
+ "moduleDetection": "force",
8
+ "jsx": "react-jsx",
9
+ "allowJs": true,
10
+
11
+ // Bundler mode
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "noEmit": true,
16
+
17
+ // Best practices
18
+ "strict": true,
19
+ "skipLibCheck": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+ "noUncheckedIndexedAccess": true,
22
+ "noImplicitOverride": true,
23
+
24
+ // Some stricter flags (disabled by default)
25
+ "noUnusedLocals": false,
26
+ "noUnusedParameters": false,
27
+ "noPropertyAccessFromIndexSignature": false
28
+ }
29
+ }