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.
- package/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc +111 -0
- package/.env.example +2 -0
- package/PUBLISHING.md +44 -0
- package/README.md +66 -0
- package/bun.lock +186 -0
- package/index.ts +1 -0
- package/login.ts +25 -0
- package/package.json +26 -0
- package/skills/polling/SKILL.md +56 -0
- package/src/cli/commands.ts +143 -0
- package/src/cli/index.ts +90 -0
- package/src/cli/prompts.ts +25 -0
- package/src/cli/run.ts +108 -0
- package/src/client/telegram.ts +92 -0
- package/src/session/storage.ts +14 -0
- package/tsconfig.json +29 -0
|
@@ -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
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
|
+
}
|
package/src/cli/index.ts
ADDED
|
@@ -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
|
+
}
|