botslt 1.0.0__tar.gz
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.
- botslt-1.0.0/PKG-INFO +138 -0
- botslt-1.0.0/README.md +119 -0
- botslt-1.0.0/botslt/__init__.py +4 -0
- botslt-1.0.0/botslt/api.py +77 -0
- botslt-1.0.0/botslt/auth.py +45 -0
- botslt-1.0.0/botslt/cli.py +397 -0
- botslt-1.0.0/botslt/config.py +94 -0
- botslt-1.0.0/botslt.egg-info/PKG-INFO +138 -0
- botslt-1.0.0/botslt.egg-info/SOURCES.txt +13 -0
- botslt-1.0.0/botslt.egg-info/dependency_links.txt +1 -0
- botslt-1.0.0/botslt.egg-info/entry_points.txt +2 -0
- botslt-1.0.0/botslt.egg-info/requires.txt +2 -0
- botslt-1.0.0/botslt.egg-info/top_level.txt +1 -0
- botslt-1.0.0/pyproject.toml +33 -0
- botslt-1.0.0/setup.cfg +4 -0
botslt-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: botslt
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official CLI for Bots.LT — sync your bot commands like Git
|
|
5
|
+
Author-email: "Bots.LT Team" <support@bots.lt>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://bots.lt
|
|
8
|
+
Project-URL: Documentation, https://bots.lt/docs
|
|
9
|
+
Keywords: telegram,bot,botslt,cli,sync
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
14
|
+
Classifier: Topic :: Communications :: Chat
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: requests>=2.28.0
|
|
18
|
+
Requires-Dist: click>=8.1.0
|
|
19
|
+
|
|
20
|
+
# blp — Official Bots.LT CLI
|
|
21
|
+
|
|
22
|
+
> Sync your Telegram bot commands like Git.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install botslt
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# 1. Save your API key (from Settings → Security on bots.lt)
|
|
34
|
+
blp login
|
|
35
|
+
|
|
36
|
+
# 2. Create a project in your bot's folder
|
|
37
|
+
mkdir my-bot && cd my-bot
|
|
38
|
+
blp init
|
|
39
|
+
|
|
40
|
+
# 3. Map your commands to .py files
|
|
41
|
+
blp add /start start.py
|
|
42
|
+
blp add /help help.py
|
|
43
|
+
blp add @ at_handler.py
|
|
44
|
+
|
|
45
|
+
# 4. Write your code, then push!
|
|
46
|
+
blp push
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## How it Works
|
|
52
|
+
|
|
53
|
+
Your project folder will look like this:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
my-telegram-bot/
|
|
57
|
+
├── botslt.json ← Bot ID + command mappings (safe to commit to Git)
|
|
58
|
+
├── botslt.lock ← Hash cache for change detection (Git-ignored)
|
|
59
|
+
├── start.py ← Code for /start command
|
|
60
|
+
├── help.py ← Code for /help command
|
|
61
|
+
└── at_handler.py ← Code for @ (global) handler
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Your **API key is stored globally** in `~/.botslt/credentials.json` — never in the project folder, so it's never accidentally committed to Git.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Commands
|
|
69
|
+
|
|
70
|
+
| Command | Description |
|
|
71
|
+
|---------|-------------|
|
|
72
|
+
| `blp login` | Save your API key |
|
|
73
|
+
| `blp logout` | Remove saved credentials |
|
|
74
|
+
| `blp init` | Create `botslt.json` in current folder |
|
|
75
|
+
| `blp add /cmd file.py` | Map a command to a local file |
|
|
76
|
+
| `blp push` | Push changed commands to server |
|
|
77
|
+
| `blp push file.py` | Push a specific file |
|
|
78
|
+
| `blp push --all` | Push all commands (ignore change detection) |
|
|
79
|
+
| `blp pull` | Pull all server commands to local files |
|
|
80
|
+
| `blp status` | Show which files have changed |
|
|
81
|
+
| `blp logs` | Show recent bot error logs |
|
|
82
|
+
| `blp bots` | List all your bots |
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Example `botslt.json`
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"bot_id": "12345678",
|
|
91
|
+
"commands": {
|
|
92
|
+
"/start": "start.py",
|
|
93
|
+
"/help": "help.py",
|
|
94
|
+
"/balance": "balance.py",
|
|
95
|
+
"@": "at_handler.py",
|
|
96
|
+
"*": "fallback.py"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Example Bot Command (start.py)
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
bot.sendMessage(
|
|
107
|
+
text=f"<b>Welcome, {user.first_name}!</b>\n\nHow can I help you?",
|
|
108
|
+
parse_mode="html",
|
|
109
|
+
reply_markup={
|
|
110
|
+
"keyboard": [
|
|
111
|
+
[{"text": "💰 Balance"}],
|
|
112
|
+
[{"text": "❓ Help"}]
|
|
113
|
+
],
|
|
114
|
+
"resize_keyboard": True
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## .gitignore
|
|
122
|
+
|
|
123
|
+
`blp init` automatically adds `botslt.lock` to your `.gitignore`:
|
|
124
|
+
|
|
125
|
+
```gitignore
|
|
126
|
+
# BotsLT
|
|
127
|
+
botslt.lock
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
> **Never commit `~/.botslt/credentials.json`** — it contains your API key.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Links
|
|
135
|
+
|
|
136
|
+
- Platform: [https://bots.lt](https://bots.lt)
|
|
137
|
+
- Documentation: [https://bots.lt/docs](https://bots.lt/docs)
|
|
138
|
+
- Support: [https://t.me/JSOrganization](https://t.me/JSOrganization)
|
botslt-1.0.0/README.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# blp — Official Bots.LT CLI
|
|
2
|
+
|
|
3
|
+
> Sync your Telegram bot commands like Git.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install botslt
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# 1. Save your API key (from Settings → Security on bots.lt)
|
|
15
|
+
blp login
|
|
16
|
+
|
|
17
|
+
# 2. Create a project in your bot's folder
|
|
18
|
+
mkdir my-bot && cd my-bot
|
|
19
|
+
blp init
|
|
20
|
+
|
|
21
|
+
# 3. Map your commands to .py files
|
|
22
|
+
blp add /start start.py
|
|
23
|
+
blp add /help help.py
|
|
24
|
+
blp add @ at_handler.py
|
|
25
|
+
|
|
26
|
+
# 4. Write your code, then push!
|
|
27
|
+
blp push
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## How it Works
|
|
33
|
+
|
|
34
|
+
Your project folder will look like this:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
my-telegram-bot/
|
|
38
|
+
├── botslt.json ← Bot ID + command mappings (safe to commit to Git)
|
|
39
|
+
├── botslt.lock ← Hash cache for change detection (Git-ignored)
|
|
40
|
+
├── start.py ← Code for /start command
|
|
41
|
+
├── help.py ← Code for /help command
|
|
42
|
+
└── at_handler.py ← Code for @ (global) handler
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Your **API key is stored globally** in `~/.botslt/credentials.json` — never in the project folder, so it's never accidentally committed to Git.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Commands
|
|
50
|
+
|
|
51
|
+
| Command | Description |
|
|
52
|
+
|---------|-------------|
|
|
53
|
+
| `blp login` | Save your API key |
|
|
54
|
+
| `blp logout` | Remove saved credentials |
|
|
55
|
+
| `blp init` | Create `botslt.json` in current folder |
|
|
56
|
+
| `blp add /cmd file.py` | Map a command to a local file |
|
|
57
|
+
| `blp push` | Push changed commands to server |
|
|
58
|
+
| `blp push file.py` | Push a specific file |
|
|
59
|
+
| `blp push --all` | Push all commands (ignore change detection) |
|
|
60
|
+
| `blp pull` | Pull all server commands to local files |
|
|
61
|
+
| `blp status` | Show which files have changed |
|
|
62
|
+
| `blp logs` | Show recent bot error logs |
|
|
63
|
+
| `blp bots` | List all your bots |
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Example `botslt.json`
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"bot_id": "12345678",
|
|
72
|
+
"commands": {
|
|
73
|
+
"/start": "start.py",
|
|
74
|
+
"/help": "help.py",
|
|
75
|
+
"/balance": "balance.py",
|
|
76
|
+
"@": "at_handler.py",
|
|
77
|
+
"*": "fallback.py"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Example Bot Command (start.py)
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
bot.sendMessage(
|
|
88
|
+
text=f"<b>Welcome, {user.first_name}!</b>\n\nHow can I help you?",
|
|
89
|
+
parse_mode="html",
|
|
90
|
+
reply_markup={
|
|
91
|
+
"keyboard": [
|
|
92
|
+
[{"text": "💰 Balance"}],
|
|
93
|
+
[{"text": "❓ Help"}]
|
|
94
|
+
],
|
|
95
|
+
"resize_keyboard": True
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## .gitignore
|
|
103
|
+
|
|
104
|
+
`blp init` automatically adds `botslt.lock` to your `.gitignore`:
|
|
105
|
+
|
|
106
|
+
```gitignore
|
|
107
|
+
# BotsLT
|
|
108
|
+
botslt.lock
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
> **Never commit `~/.botslt/credentials.json`** — it contains your API key.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Links
|
|
116
|
+
|
|
117
|
+
- Platform: [https://bots.lt](https://bots.lt)
|
|
118
|
+
- Documentation: [https://bots.lt/docs](https://bots.lt/docs)
|
|
119
|
+
- Support: [https://t.me/JSOrganization](https://t.me/JSOrganization)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
api.py — All API calls to the Bots.LT backend.
|
|
3
|
+
Uses X-API-Key header for authentication.
|
|
4
|
+
"""
|
|
5
|
+
import requests
|
|
6
|
+
from .auth import get_api_key, get_base_url
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _headers() -> dict:
|
|
10
|
+
return {"X-API-Key": get_api_key()}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _url(path: str) -> str:
|
|
14
|
+
return f"{get_base_url()}{path}"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def verify_api_key() -> dict:
|
|
18
|
+
"""Verify that the stored API key is valid by calling /api/account."""
|
|
19
|
+
try:
|
|
20
|
+
r = requests.get(_url("/account"), headers=_headers(), timeout=10)
|
|
21
|
+
if r.status_code == 200:
|
|
22
|
+
return r.json()
|
|
23
|
+
return {"ok": False, "error": f"HTTP {r.status_code}"}
|
|
24
|
+
except Exception as e:
|
|
25
|
+
return {"ok": False, "error": str(e)}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def list_bots() -> dict:
|
|
29
|
+
"""List all bots for the authenticated user."""
|
|
30
|
+
r = requests.get(_url("/bots-list"), headers=_headers(), timeout=10)
|
|
31
|
+
return r.json()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_all_commands(bot_id: str) -> dict:
|
|
35
|
+
"""Get all commands for a bot."""
|
|
36
|
+
r = requests.get(_url(f"/bots/{bot_id}/commands"), headers=_headers(), timeout=10)
|
|
37
|
+
return r.json()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_command_code(bot_id: str, command_name: str) -> str | None:
|
|
41
|
+
"""Get the code for a single command."""
|
|
42
|
+
import base64
|
|
43
|
+
encoded = base64.b64encode(command_name.encode("utf-8")).decode("utf-8")
|
|
44
|
+
r = requests.get(_url(f"/bots/{bot_id}/commands/{encoded}"), headers=_headers(), timeout=10)
|
|
45
|
+
if r.status_code == 200:
|
|
46
|
+
data = r.json()
|
|
47
|
+
return data.get("code") or data.get("result", {}).get("code")
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def push_command(bot_id: str, command_name: str, code: str) -> dict:
|
|
52
|
+
"""Create or update a command on the server."""
|
|
53
|
+
import base64
|
|
54
|
+
encoded = base64.b64encode(command_name.encode("utf-8")).decode("utf-8")
|
|
55
|
+
# Try update first
|
|
56
|
+
r = requests.put(
|
|
57
|
+
_url(f"/bots/{bot_id}/commands/{encoded}"),
|
|
58
|
+
headers=_headers(),
|
|
59
|
+
json={"code": code},
|
|
60
|
+
timeout=15
|
|
61
|
+
)
|
|
62
|
+
if r.status_code == 200:
|
|
63
|
+
return r.json()
|
|
64
|
+
# If not found, create it
|
|
65
|
+
r2 = requests.post(
|
|
66
|
+
_url(f"/bots/{bot_id}/commands"),
|
|
67
|
+
headers=_headers(),
|
|
68
|
+
json={"command": command_name, "code": code},
|
|
69
|
+
timeout=15
|
|
70
|
+
)
|
|
71
|
+
return r2.json()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_bot_errors(bot_id: str) -> dict:
|
|
75
|
+
"""Get recent error logs for a bot."""
|
|
76
|
+
r = requests.get(_url(f"/bots/{bot_id}/errors"), headers=_headers(), timeout=10)
|
|
77
|
+
return r.json()
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
auth.py — Handles login and credential storage.
|
|
3
|
+
Credentials are stored in ~/.botslt/credentials.json (never in the project folder).
|
|
4
|
+
"""
|
|
5
|
+
import os
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
CRED_DIR = Path.home() / ".botslt"
|
|
10
|
+
CRED_FILE = CRED_DIR / "credentials.json"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_credentials() -> dict:
|
|
14
|
+
"""Load credentials from ~/.botslt/credentials.json"""
|
|
15
|
+
if not CRED_FILE.exists():
|
|
16
|
+
return {}
|
|
17
|
+
try:
|
|
18
|
+
with open(CRED_FILE, "r", encoding="utf-8") as f:
|
|
19
|
+
return json.load(f)
|
|
20
|
+
except Exception:
|
|
21
|
+
return {}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def save_credentials(api_key: str, base_url: str = "https://bots.lt/api"):
|
|
25
|
+
"""Save credentials to ~/.botslt/credentials.json"""
|
|
26
|
+
CRED_DIR.mkdir(parents=True, exist_ok=True)
|
|
27
|
+
data = {"api_key": api_key, "base_url": base_url}
|
|
28
|
+
with open(CRED_FILE, "w", encoding="utf-8") as f:
|
|
29
|
+
json.dump(data, f, indent=4)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_api_key() -> str | None:
|
|
33
|
+
"""Return the stored API key, or None if not logged in."""
|
|
34
|
+
return get_credentials().get("api_key")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_base_url() -> str:
|
|
38
|
+
"""Return the stored base URL (defaults to bots.lt)."""
|
|
39
|
+
return get_credentials().get("base_url", "https://bots.lt/api")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def clear_credentials():
|
|
43
|
+
"""Remove stored credentials (logout)."""
|
|
44
|
+
if CRED_FILE.exists():
|
|
45
|
+
CRED_FILE.unlink()
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
"""
|
|
2
|
+
cli.py — Main entry point for the blp CLI.
|
|
3
|
+
Command: blp <subcommand>
|
|
4
|
+
"""
|
|
5
|
+
import click
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from . import auth, config, api
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# ─── Helpers ────────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
def require_login():
|
|
14
|
+
"""Abort with a helpful message if not logged in."""
|
|
15
|
+
key = auth.get_api_key()
|
|
16
|
+
if not key:
|
|
17
|
+
click.echo(click.style("[-] Not logged in.", fg="red"))
|
|
18
|
+
click.echo(" Run: blp login")
|
|
19
|
+
sys.exit(1)
|
|
20
|
+
|
|
21
|
+
def require_config():
|
|
22
|
+
"""Abort with a helpful message if botslt.json is missing."""
|
|
23
|
+
if not config.config_exists():
|
|
24
|
+
click.echo(click.style("[-] No botslt.json found in current directory.", fg="red"))
|
|
25
|
+
click.echo(" Run: blp init")
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ─── CLI Group ───────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
@click.group()
|
|
32
|
+
@click.version_option("1.0.0", prog_name="blp")
|
|
33
|
+
def cli():
|
|
34
|
+
"""
|
|
35
|
+
blp — Official CLI for Bots.LT
|
|
36
|
+
|
|
37
|
+
Sync your Telegram bot commands like Git.
|
|
38
|
+
|
|
39
|
+
\b
|
|
40
|
+
Quick start:
|
|
41
|
+
blp login Save your API key
|
|
42
|
+
blp init Setup a project in the current folder
|
|
43
|
+
blp push Push all changed commands to server
|
|
44
|
+
blp pull Pull all commands from server
|
|
45
|
+
blp status Show which files have changed
|
|
46
|
+
|
|
47
|
+
Get your API Key from: https://bots.lt/dashboard -> Settings -> Security
|
|
48
|
+
"""
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# ─── login ───────────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
@cli.command()
|
|
55
|
+
@click.option("--key", "-k", prompt=False, default=None, help="API Key (from Settings -> Security)")
|
|
56
|
+
def login(key):
|
|
57
|
+
"""Save your Bots.LT API key to ~/.botslt/credentials.json"""
|
|
58
|
+
if not key:
|
|
59
|
+
click.echo("Get your API Key from: https://bots.lt/dashboard -> Settings -> Security")
|
|
60
|
+
key = click.prompt("API Key", hide_input=True)
|
|
61
|
+
|
|
62
|
+
key = key.strip()
|
|
63
|
+
if not key:
|
|
64
|
+
click.echo(click.style("[-] API Key cannot be empty.", fg="red"))
|
|
65
|
+
sys.exit(1)
|
|
66
|
+
|
|
67
|
+
# Validate key against server
|
|
68
|
+
click.echo("Verifying API key...")
|
|
69
|
+
auth.save_credentials(key)
|
|
70
|
+
result = api.verify_api_key()
|
|
71
|
+
|
|
72
|
+
if result.get("ok"):
|
|
73
|
+
acc = result.get("result", {}).get("account", {})
|
|
74
|
+
email = acc.get("email", "unknown")
|
|
75
|
+
click.echo(click.style(f"[+] Logged in as: {email}", fg="green"))
|
|
76
|
+
click.echo(click.style(f" Credentials saved to: {auth.CRED_FILE}", fg="bright_black"))
|
|
77
|
+
else:
|
|
78
|
+
auth.clear_credentials()
|
|
79
|
+
click.echo(click.style(f"[-] Invalid API key: {result.get('error', 'Unknown error')}", fg="red"))
|
|
80
|
+
sys.exit(1)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ─── logout ──────────────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
@cli.command()
|
|
86
|
+
def logout():
|
|
87
|
+
"""Remove stored credentials from ~/.botslt/credentials.json"""
|
|
88
|
+
auth.clear_credentials()
|
|
89
|
+
click.echo(click.style("[+] Logged out successfully.", fg="green"))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# ─── init ────────────────────────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
@cli.command()
|
|
95
|
+
@click.option("--bot-id", "-b", prompt="Bot ID", help="Your Bots.LT Bot ID")
|
|
96
|
+
def init(bot_id):
|
|
97
|
+
"""Create a botslt.json config file in the current directory."""
|
|
98
|
+
require_login()
|
|
99
|
+
|
|
100
|
+
if config.config_exists():
|
|
101
|
+
if not click.confirm("botslt.json already exists. Overwrite?"):
|
|
102
|
+
click.echo("Aborted.")
|
|
103
|
+
sys.exit(0)
|
|
104
|
+
|
|
105
|
+
bot_id = bot_id.strip()
|
|
106
|
+
data = {
|
|
107
|
+
"bot_id": bot_id,
|
|
108
|
+
"commands": {}
|
|
109
|
+
}
|
|
110
|
+
config.save_config(data)
|
|
111
|
+
config.ensure_gitignore()
|
|
112
|
+
|
|
113
|
+
click.echo(click.style(f"[+] Initialized project for bot ID: {bot_id}", fg="green"))
|
|
114
|
+
click.echo(click.style(" botslt.json created. Add commands with: blp add /start start.py", fg="bright_black"))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# ─── add ─────────────────────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
@cli.command()
|
|
120
|
+
@click.argument("command_name")
|
|
121
|
+
@click.argument("filename")
|
|
122
|
+
def add(command_name, filename):
|
|
123
|
+
"""Map a command name to a local .py file.
|
|
124
|
+
|
|
125
|
+
\b
|
|
126
|
+
Example:
|
|
127
|
+
blp add /start start.py
|
|
128
|
+
blp add @ at_handler.py
|
|
129
|
+
"""
|
|
130
|
+
require_config()
|
|
131
|
+
|
|
132
|
+
cfg = config.load_config()
|
|
133
|
+
commands = cfg.setdefault("commands", {})
|
|
134
|
+
commands[command_name] = filename
|
|
135
|
+
config.save_config(cfg)
|
|
136
|
+
|
|
137
|
+
click.echo(click.style(f"[+] Mapped '{command_name}' -> '{filename}'", fg="green"))
|
|
138
|
+
click.echo(" Remember to create the file, then run: blp push")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# ─── status ──────────────────────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
@cli.command()
|
|
144
|
+
def status():
|
|
145
|
+
"""Show which local files have changed since last push."""
|
|
146
|
+
require_login()
|
|
147
|
+
require_config()
|
|
148
|
+
|
|
149
|
+
cfg = config.load_config()
|
|
150
|
+
commands = cfg.get("commands", {})
|
|
151
|
+
|
|
152
|
+
if not commands:
|
|
153
|
+
click.echo("No commands mapped. Use: blp add /start start.py")
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
changed = config.get_changed_files()
|
|
157
|
+
changed_files = {c["file"] for c in changed}
|
|
158
|
+
|
|
159
|
+
click.echo(f"\n Bot ID: {click.style(cfg.get('bot_id', '?'), fg='cyan')}\n")
|
|
160
|
+
|
|
161
|
+
modified = []
|
|
162
|
+
ok = []
|
|
163
|
+
missing = []
|
|
164
|
+
|
|
165
|
+
for cmd_name, filename in commands.items():
|
|
166
|
+
if not Path(filename).exists():
|
|
167
|
+
missing.append((cmd_name, filename))
|
|
168
|
+
elif filename in changed_files:
|
|
169
|
+
modified.append((cmd_name, filename))
|
|
170
|
+
else:
|
|
171
|
+
ok.append((cmd_name, filename))
|
|
172
|
+
|
|
173
|
+
if modified:
|
|
174
|
+
click.echo(click.style(" Modified (not pushed):", fg="yellow"))
|
|
175
|
+
for cmd, f in modified:
|
|
176
|
+
click.echo(f" {click.style('M', fg='yellow')} {f:<25} -> {cmd}")
|
|
177
|
+
|
|
178
|
+
if missing:
|
|
179
|
+
click.echo(click.style("\n File not found:", fg="red"))
|
|
180
|
+
for cmd, f in missing:
|
|
181
|
+
click.echo(f" {click.style('!', fg='red')} {f:<25} -> {cmd}")
|
|
182
|
+
|
|
183
|
+
if ok:
|
|
184
|
+
click.echo(click.style("\n Up to date:", fg="green"))
|
|
185
|
+
for cmd, f in ok:
|
|
186
|
+
click.echo(f" {click.style('[+]', fg='green')} {f:<25} -> {cmd}")
|
|
187
|
+
|
|
188
|
+
if not modified and not missing:
|
|
189
|
+
click.echo(click.style("\n Everything is up to date!", fg="green"))
|
|
190
|
+
else:
|
|
191
|
+
click.echo(f"\n Run {click.style('blp push', fg='cyan')} to sync changes.")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# ─── push ────────────────────────────────────────────────────────────────────
|
|
195
|
+
|
|
196
|
+
@cli.command()
|
|
197
|
+
@click.argument("files", nargs=-1)
|
|
198
|
+
@click.option("--all", "-a", "push_all", is_flag=True, help="Push all commands, even if unchanged")
|
|
199
|
+
@click.option("--force", "-f", is_flag=True, help="Same as --all")
|
|
200
|
+
def push(files, push_all, force):
|
|
201
|
+
"""Push changed commands to Bots.LT server.
|
|
202
|
+
|
|
203
|
+
\b
|
|
204
|
+
Examples:
|
|
205
|
+
blp push Push only changed files
|
|
206
|
+
blp push start.py Push a specific file
|
|
207
|
+
blp push --all Push all commands regardless of changes
|
|
208
|
+
"""
|
|
209
|
+
require_login()
|
|
210
|
+
require_config()
|
|
211
|
+
|
|
212
|
+
cfg = config.load_config()
|
|
213
|
+
bot_id = cfg.get("bot_id")
|
|
214
|
+
commands = cfg.get("commands", {})
|
|
215
|
+
|
|
216
|
+
if not commands:
|
|
217
|
+
click.echo("No commands configured. Run: blp add /start start.py")
|
|
218
|
+
return
|
|
219
|
+
|
|
220
|
+
push_all = push_all or force
|
|
221
|
+
|
|
222
|
+
# If specific files given, only push those
|
|
223
|
+
if files:
|
|
224
|
+
to_push = [(cmd, f) for cmd, f in commands.items() if f in files]
|
|
225
|
+
if not to_push:
|
|
226
|
+
click.echo(click.style("[-] No matching commands found for the given files.", fg="red"))
|
|
227
|
+
click.echo(" Check your botslt.json mappings.")
|
|
228
|
+
sys.exit(1)
|
|
229
|
+
elif push_all:
|
|
230
|
+
to_push = list(commands.items())
|
|
231
|
+
else:
|
|
232
|
+
changed = config.get_changed_files()
|
|
233
|
+
changed_files = {c["file"] for c in changed}
|
|
234
|
+
to_push = [(cmd, f) for cmd, f in commands.items() if f in changed_files]
|
|
235
|
+
|
|
236
|
+
if not to_push:
|
|
237
|
+
click.echo(click.style("[+] Nothing to push. Everything is up to date.", fg="green"))
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
click.echo(f"\n Syncing bot {click.style(bot_id, fg='cyan')}...\n")
|
|
241
|
+
|
|
242
|
+
updated = 0
|
|
243
|
+
failed = 0
|
|
244
|
+
|
|
245
|
+
for cmd_name, filename in to_push:
|
|
246
|
+
if not Path(filename).exists():
|
|
247
|
+
click.echo(f" {click.style('!', fg='red')} {filename:<25} -> {cmd_name} (file not found, skipped)")
|
|
248
|
+
failed += 1
|
|
249
|
+
continue
|
|
250
|
+
|
|
251
|
+
with open(filename, "r", encoding="utf-8") as f:
|
|
252
|
+
code = f.read()
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
result = api.push_command(bot_id, cmd_name, code)
|
|
256
|
+
if result.get("ok"):
|
|
257
|
+
click.echo(f" {click.style('[+]', fg='green')} {filename:<25} -> {cmd_name}")
|
|
258
|
+
config.update_lock_for_file(filename)
|
|
259
|
+
updated += 1
|
|
260
|
+
else:
|
|
261
|
+
err = result.get("error") or result.get("detail", "Unknown error")
|
|
262
|
+
click.echo(f" {click.style('[-]', fg='red')} {filename:<25} -> {cmd_name} ({err})")
|
|
263
|
+
failed += 1
|
|
264
|
+
except Exception as e:
|
|
265
|
+
click.echo(f" {click.style('[-]', fg='red')} {filename:<25} -> {cmd_name} (Error: {e})")
|
|
266
|
+
failed += 1
|
|
267
|
+
|
|
268
|
+
click.echo()
|
|
269
|
+
click.echo(f" Done! {click.style(str(updated), fg='green')} pushed, {click.style(str(failed), fg='red' if failed else 'bright_black')} failed.")
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
# ─── pull ────────────────────────────────────────────────────────────────────
|
|
273
|
+
|
|
274
|
+
@cli.command()
|
|
275
|
+
@click.option("--overwrite", is_flag=True, help="Overwrite local files without asking")
|
|
276
|
+
def pull(overwrite):
|
|
277
|
+
"""Pull all commands from server and create local .py files."""
|
|
278
|
+
require_login()
|
|
279
|
+
require_config()
|
|
280
|
+
|
|
281
|
+
cfg = config.load_config()
|
|
282
|
+
bot_id = cfg.get("bot_id")
|
|
283
|
+
|
|
284
|
+
click.echo(f"\n Pulling from bot {click.style(bot_id, fg='cyan')}...\n")
|
|
285
|
+
|
|
286
|
+
result = api.get_all_commands(bot_id)
|
|
287
|
+
if not result.get("ok"):
|
|
288
|
+
click.echo(click.style(f"[-] Failed to fetch commands: {result.get('error', 'Unknown error')}", fg="red"))
|
|
289
|
+
sys.exit(1)
|
|
290
|
+
|
|
291
|
+
commands_data = result.get("commands") or result.get("result", {}).get("commands", [])
|
|
292
|
+
|
|
293
|
+
if not commands_data:
|
|
294
|
+
click.echo(" No commands found on the server.")
|
|
295
|
+
return
|
|
296
|
+
|
|
297
|
+
cfg_commands = cfg.setdefault("commands", {})
|
|
298
|
+
created = 0
|
|
299
|
+
skipped = 0
|
|
300
|
+
|
|
301
|
+
for cmd in commands_data:
|
|
302
|
+
cmd_name = cmd.get("command") or cmd.get("name") or cmd.get("trigger", "")
|
|
303
|
+
code = cmd.get("code", "")
|
|
304
|
+
|
|
305
|
+
if not cmd_name:
|
|
306
|
+
continue
|
|
307
|
+
|
|
308
|
+
# Generate a safe filename from the command name
|
|
309
|
+
safe_name = cmd_name.lstrip("/").replace(" ", "_").replace("*", "star").replace("@", "at") or "command"
|
|
310
|
+
filename = f"{safe_name}.py"
|
|
311
|
+
|
|
312
|
+
if Path(filename).exists() and not overwrite:
|
|
313
|
+
if not click.confirm(f" '{filename}' already exists. Overwrite?"):
|
|
314
|
+
click.echo(f" {click.style('-', fg='yellow')} {filename:<25} skipped")
|
|
315
|
+
skipped += 1
|
|
316
|
+
continue
|
|
317
|
+
|
|
318
|
+
with open(filename, "w", encoding="utf-8") as f:
|
|
319
|
+
f.write(code)
|
|
320
|
+
|
|
321
|
+
cfg_commands[cmd_name] = filename
|
|
322
|
+
config.update_lock_for_file(filename)
|
|
323
|
+
click.echo(f" {click.style('[+]', fg='green')} {filename:<25} <- {cmd_name}")
|
|
324
|
+
created += 1
|
|
325
|
+
|
|
326
|
+
config.save_config(cfg)
|
|
327
|
+
click.echo()
|
|
328
|
+
click.echo(f" Done! {click.style(str(created), fg='green')} pulled, {skipped} skipped.")
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
# ─── logs ────────────────────────────────────────────────────────────────────
|
|
332
|
+
|
|
333
|
+
@cli.command()
|
|
334
|
+
@click.option("--limit", "-n", default=10, help="Number of recent errors to show (default: 10)")
|
|
335
|
+
def logs(limit):
|
|
336
|
+
"""Show recent error logs for your bot."""
|
|
337
|
+
require_login()
|
|
338
|
+
require_config()
|
|
339
|
+
|
|
340
|
+
cfg = config.load_config()
|
|
341
|
+
bot_id = cfg.get("bot_id")
|
|
342
|
+
|
|
343
|
+
result = api.get_bot_errors(bot_id)
|
|
344
|
+
if not result.get("ok"):
|
|
345
|
+
click.echo(click.style(f"[-] Failed to fetch logs: {result.get('error', 'Unknown error')}", fg="red"))
|
|
346
|
+
sys.exit(1)
|
|
347
|
+
|
|
348
|
+
errors = result.get("errors") or result.get("result", {}).get("errors", [])
|
|
349
|
+
|
|
350
|
+
if not errors:
|
|
351
|
+
click.echo(click.style("[+] No recent errors. Your bot is running clean!", fg="green"))
|
|
352
|
+
return
|
|
353
|
+
|
|
354
|
+
click.echo(f"\n Recent errors for bot {click.style(bot_id, fg='cyan')}:\n")
|
|
355
|
+
|
|
356
|
+
for i, err in enumerate(errors[:limit]):
|
|
357
|
+
cmd = err.get("command_name", "?")
|
|
358
|
+
msg = err.get("error_message", "?")
|
|
359
|
+
ts = err.get("created_at", "")
|
|
360
|
+
click.echo(f" [{click.style(str(i+1), fg='yellow')}] {click.style(cmd, fg='cyan')} — {ts}")
|
|
361
|
+
for line in msg.splitlines():
|
|
362
|
+
click.echo(f" {line}")
|
|
363
|
+
click.echo()
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
# ─── bots ────────────────────────────────────────────────────────────────────
|
|
367
|
+
|
|
368
|
+
@cli.command()
|
|
369
|
+
def bots():
|
|
370
|
+
"""List all your bots."""
|
|
371
|
+
require_login()
|
|
372
|
+
|
|
373
|
+
result = api.list_bots()
|
|
374
|
+
if not result.get("ok"):
|
|
375
|
+
click.echo(click.style(f"[-] {result.get('error', 'Failed to list bots')}", fg="red"))
|
|
376
|
+
sys.exit(1)
|
|
377
|
+
|
|
378
|
+
bots_list = result.get("result", {}).get("bots", [])
|
|
379
|
+
|
|
380
|
+
if not bots_list:
|
|
381
|
+
click.echo("No bots found.")
|
|
382
|
+
return
|
|
383
|
+
|
|
384
|
+
click.echo(f"\n {click.style(str(len(bots_list)), fg='cyan')} bot(s) found:\n")
|
|
385
|
+
for b in bots_list:
|
|
386
|
+
status_color = "green" if b.get("is_active") else "red"
|
|
387
|
+
status = "Running" if b.get("is_active") else "Stopped"
|
|
388
|
+
click.echo(
|
|
389
|
+
f" {click.style(str(b.get('id', '?')), fg='cyan'):<12}"
|
|
390
|
+
f" @{str(b.get('username', '?')):<25}"
|
|
391
|
+
f" {click.style(status, fg=status_color)}"
|
|
392
|
+
)
|
|
393
|
+
click.echo()
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
if __name__ == "__main__":
|
|
397
|
+
cli()
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
config.py — Reads and writes botslt.json in the current project directory.
|
|
3
|
+
This file is safe to commit to Git — it only contains bot_id and command mappings.
|
|
4
|
+
"""
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
CONFIG_FILE = Path("botslt.json")
|
|
9
|
+
LOCK_FILE = Path("botslt.lock")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def config_exists() -> bool:
|
|
13
|
+
return CONFIG_FILE.exists()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def load_config() -> dict:
|
|
17
|
+
"""Load botslt.json from current directory."""
|
|
18
|
+
if not CONFIG_FILE.exists():
|
|
19
|
+
return {}
|
|
20
|
+
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
|
|
21
|
+
return json.load(f)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def save_config(data: dict):
|
|
25
|
+
"""Save botslt.json to current directory."""
|
|
26
|
+
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
|
|
27
|
+
json.dump(data, f, indent=4)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def load_lock() -> dict:
|
|
31
|
+
"""Load botslt.lock (local hash cache, NOT for Git)."""
|
|
32
|
+
if not LOCK_FILE.exists():
|
|
33
|
+
return {"files": {}}
|
|
34
|
+
try:
|
|
35
|
+
with open(LOCK_FILE, "r", encoding="utf-8") as f:
|
|
36
|
+
return json.load(f)
|
|
37
|
+
except Exception:
|
|
38
|
+
return {"files": {}}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def save_lock(data: dict):
|
|
42
|
+
"""Save botslt.lock."""
|
|
43
|
+
with open(LOCK_FILE, "w", encoding="utf-8") as f:
|
|
44
|
+
json.dump(data, f, indent=4)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_file_hash(filepath: str) -> str:
|
|
48
|
+
"""Return SHA256 hash of a local file."""
|
|
49
|
+
import hashlib
|
|
50
|
+
h = hashlib.sha256()
|
|
51
|
+
try:
|
|
52
|
+
with open(filepath, "rb") as f:
|
|
53
|
+
h.update(f.read())
|
|
54
|
+
return h.hexdigest()
|
|
55
|
+
except FileNotFoundError:
|
|
56
|
+
return ""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def update_lock_for_file(filename: str):
|
|
60
|
+
"""Update the hash entry for a specific file in botslt.lock."""
|
|
61
|
+
lock = load_lock()
|
|
62
|
+
lock.setdefault("files", {})[filename] = get_file_hash(filename)
|
|
63
|
+
save_lock(lock)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_changed_files() -> list[dict]:
|
|
67
|
+
"""Compare local files with botslt.lock hashes. Returns list of changed files."""
|
|
68
|
+
config = load_config()
|
|
69
|
+
lock = load_lock()
|
|
70
|
+
cached_hashes = lock.get("files", {})
|
|
71
|
+
commands = config.get("commands", {})
|
|
72
|
+
|
|
73
|
+
changed = []
|
|
74
|
+
for cmd_name, filename in commands.items():
|
|
75
|
+
if not Path(filename).exists():
|
|
76
|
+
continue
|
|
77
|
+
current_hash = get_file_hash(filename)
|
|
78
|
+
cached_hash = cached_hashes.get(filename, "")
|
|
79
|
+
if current_hash != cached_hash:
|
|
80
|
+
changed.append({"command": cmd_name, "file": filename})
|
|
81
|
+
return changed
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def ensure_gitignore():
|
|
85
|
+
"""Add botslt.lock to .gitignore if it's not already there."""
|
|
86
|
+
gitignore = Path(".gitignore")
|
|
87
|
+
entries_to_add = ["# BotsLT", "botslt.lock"]
|
|
88
|
+
if gitignore.exists():
|
|
89
|
+
content = gitignore.read_text(encoding="utf-8")
|
|
90
|
+
if "botslt.lock" not in content:
|
|
91
|
+
with open(gitignore, "a", encoding="utf-8") as f:
|
|
92
|
+
f.write("\n" + "\n".join(entries_to_add) + "\n")
|
|
93
|
+
else:
|
|
94
|
+
gitignore.write_text("\n".join(entries_to_add) + "\n", encoding="utf-8")
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: botslt
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official CLI for Bots.LT — sync your bot commands like Git
|
|
5
|
+
Author-email: "Bots.LT Team" <support@bots.lt>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://bots.lt
|
|
8
|
+
Project-URL: Documentation, https://bots.lt/docs
|
|
9
|
+
Keywords: telegram,bot,botslt,cli,sync
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
14
|
+
Classifier: Topic :: Communications :: Chat
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: requests>=2.28.0
|
|
18
|
+
Requires-Dist: click>=8.1.0
|
|
19
|
+
|
|
20
|
+
# blp — Official Bots.LT CLI
|
|
21
|
+
|
|
22
|
+
> Sync your Telegram bot commands like Git.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install botslt
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# 1. Save your API key (from Settings → Security on bots.lt)
|
|
34
|
+
blp login
|
|
35
|
+
|
|
36
|
+
# 2. Create a project in your bot's folder
|
|
37
|
+
mkdir my-bot && cd my-bot
|
|
38
|
+
blp init
|
|
39
|
+
|
|
40
|
+
# 3. Map your commands to .py files
|
|
41
|
+
blp add /start start.py
|
|
42
|
+
blp add /help help.py
|
|
43
|
+
blp add @ at_handler.py
|
|
44
|
+
|
|
45
|
+
# 4. Write your code, then push!
|
|
46
|
+
blp push
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## How it Works
|
|
52
|
+
|
|
53
|
+
Your project folder will look like this:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
my-telegram-bot/
|
|
57
|
+
├── botslt.json ← Bot ID + command mappings (safe to commit to Git)
|
|
58
|
+
├── botslt.lock ← Hash cache for change detection (Git-ignored)
|
|
59
|
+
├── start.py ← Code for /start command
|
|
60
|
+
├── help.py ← Code for /help command
|
|
61
|
+
└── at_handler.py ← Code for @ (global) handler
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Your **API key is stored globally** in `~/.botslt/credentials.json` — never in the project folder, so it's never accidentally committed to Git.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Commands
|
|
69
|
+
|
|
70
|
+
| Command | Description |
|
|
71
|
+
|---------|-------------|
|
|
72
|
+
| `blp login` | Save your API key |
|
|
73
|
+
| `blp logout` | Remove saved credentials |
|
|
74
|
+
| `blp init` | Create `botslt.json` in current folder |
|
|
75
|
+
| `blp add /cmd file.py` | Map a command to a local file |
|
|
76
|
+
| `blp push` | Push changed commands to server |
|
|
77
|
+
| `blp push file.py` | Push a specific file |
|
|
78
|
+
| `blp push --all` | Push all commands (ignore change detection) |
|
|
79
|
+
| `blp pull` | Pull all server commands to local files |
|
|
80
|
+
| `blp status` | Show which files have changed |
|
|
81
|
+
| `blp logs` | Show recent bot error logs |
|
|
82
|
+
| `blp bots` | List all your bots |
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Example `botslt.json`
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"bot_id": "12345678",
|
|
91
|
+
"commands": {
|
|
92
|
+
"/start": "start.py",
|
|
93
|
+
"/help": "help.py",
|
|
94
|
+
"/balance": "balance.py",
|
|
95
|
+
"@": "at_handler.py",
|
|
96
|
+
"*": "fallback.py"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Example Bot Command (start.py)
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
bot.sendMessage(
|
|
107
|
+
text=f"<b>Welcome, {user.first_name}!</b>\n\nHow can I help you?",
|
|
108
|
+
parse_mode="html",
|
|
109
|
+
reply_markup={
|
|
110
|
+
"keyboard": [
|
|
111
|
+
[{"text": "💰 Balance"}],
|
|
112
|
+
[{"text": "❓ Help"}]
|
|
113
|
+
],
|
|
114
|
+
"resize_keyboard": True
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## .gitignore
|
|
122
|
+
|
|
123
|
+
`blp init` automatically adds `botslt.lock` to your `.gitignore`:
|
|
124
|
+
|
|
125
|
+
```gitignore
|
|
126
|
+
# BotsLT
|
|
127
|
+
botslt.lock
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
> **Never commit `~/.botslt/credentials.json`** — it contains your API key.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Links
|
|
135
|
+
|
|
136
|
+
- Platform: [https://bots.lt](https://bots.lt)
|
|
137
|
+
- Documentation: [https://bots.lt/docs](https://bots.lt/docs)
|
|
138
|
+
- Support: [https://t.me/JSOrganization](https://t.me/JSOrganization)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
botslt/__init__.py
|
|
4
|
+
botslt/api.py
|
|
5
|
+
botslt/auth.py
|
|
6
|
+
botslt/cli.py
|
|
7
|
+
botslt/config.py
|
|
8
|
+
botslt.egg-info/PKG-INFO
|
|
9
|
+
botslt.egg-info/SOURCES.txt
|
|
10
|
+
botslt.egg-info/dependency_links.txt
|
|
11
|
+
botslt.egg-info/entry_points.txt
|
|
12
|
+
botslt.egg-info/requires.txt
|
|
13
|
+
botslt.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
botslt
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "botslt"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Official CLI for Bots.LT — sync your bot commands like Git"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Bots.LT Team", email = "support@bots.lt" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["telegram", "bot", "botslt", "cli", "sync"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
21
|
+
"Topic :: Communications :: Chat",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"requests>=2.28.0",
|
|
25
|
+
"click>=8.1.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
blp = "botslt.cli:cli"
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://bots.lt"
|
|
33
|
+
Documentation = "https://bots.lt/docs"
|
botslt-1.0.0/setup.cfg
ADDED