telegram-opencode-bridge-bot 0.1.5__tar.gz → 0.1.7__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.
- {telegram_opencode_bridge_bot-0.1.5/telegram_opencode_bridge_bot.egg-info → telegram_opencode_bridge_bot-0.1.7}/PKG-INFO +7 -3
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/README.md +6 -2
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/bot.py +86 -5
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/handlers/commands.py +443 -1
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/handlers/messages.py +222 -42
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/opencode/client.py +26 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/opencode/server.py +19 -16
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/pyproject.toml +1 -1
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/sessions/manager.py +31 -1
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7/telegram_opencode_bridge_bot.egg-info}/PKG-INFO +7 -3
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/utils/formatting.py +151 -1
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/.env.example +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/MANIFEST.in +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/config.py +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/handlers/__init__.py +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/list_session_models.py +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/opencode/__init__.py +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/requirements.txt +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/sessions/__init__.py +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/setup.cfg +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/telegram_opencode_bridge_bot.egg-info/SOURCES.txt +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/telegram_opencode_bridge_bot.egg-info/dependency_links.txt +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/telegram_opencode_bridge_bot.egg-info/entry_points.txt +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/telegram_opencode_bridge_bot.egg-info/requires.txt +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/telegram_opencode_bridge_bot.egg-info/top_level.txt +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/utils/__init__.py +0 -0
- {telegram_opencode_bridge_bot-0.1.5 → telegram_opencode_bridge_bot-0.1.7}/utils/security.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: telegram-opencode-bridge-bot
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: A Telegram bot that bridges messages directly to OpenCode — an AI coding agent running on your machine
|
|
5
5
|
Author-email: MaheshNagabhairava <maheshnagabhirava12345@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -23,7 +23,7 @@ A lightweight Python bot that bridges your Telegram messages directly to [OpenCo
|
|
|
23
23
|
|
|
24
24
|
- **Direct OpenCode integration** — routes your messages to OpenCode's HTTP API
|
|
25
25
|
- **Persistent sessions** — conversations maintain context across messages
|
|
26
|
-
- **Session management** — create, switch, list, and share sessions
|
|
26
|
+
- **Session management** — create, delete, switch, list, and share sessions
|
|
27
27
|
- **Model switching** — change AI models on the fly (`/model`)
|
|
28
28
|
- **Plan/Build modes** — toggle between read-only analysis and full execution
|
|
29
29
|
- **Smart formatting** — code blocks with syntax highlighting in Telegram
|
|
@@ -31,6 +31,7 @@ A lightweight Python bot that bridges your Telegram messages directly to [OpenCo
|
|
|
31
31
|
- **Security** — whitelist-based access control + rate limiting
|
|
32
32
|
- **Workspace Switching** — Switching from one workspace to another
|
|
33
33
|
- **Uploading from Mobile to WorkSpace** - U can upload the documents/images from your mobile to the opencode agent workspace
|
|
34
|
+
- **Workspace Management** — Creation and deletion of the workspace/folder
|
|
34
35
|
|
|
35
36
|
## 📋 Prerequisites
|
|
36
37
|
|
|
@@ -46,7 +47,7 @@ A lightweight Python bot that bridges your Telegram messages directly to [OpenCo
|
|
|
46
47
|
|
|
47
48
|
### Quick Installation:
|
|
48
49
|
```bash
|
|
49
|
-
pip install telegram-opencode-bridge-bot==0.1.
|
|
50
|
+
pip install telegram-opencode-bridge-bot==0.1.7
|
|
50
51
|
telegram-opencode-bot
|
|
51
52
|
telegram-opencode-bot --env (use --env flag if u want to re-configure later anytime)
|
|
52
53
|
```
|
|
@@ -88,6 +89,9 @@ Open Telegram, find your bot, and start asking! 🎉
|
|
|
88
89
|
| `/project` | To view the current workspace, sub folder workspaces and to change the workspace |
|
|
89
90
|
| `/enable` | To enable the streaming |
|
|
90
91
|
| `/disable` | To disable the streaming |
|
|
92
|
+
| `/create_project` | To create new project/folder/workspace |
|
|
93
|
+
| `/delete_project` | To create new project/folder/workspace |
|
|
94
|
+
| `/delete` | To delete a conversation |
|
|
91
95
|
|
|
92
96
|
## 🏗️ Architecture
|
|
93
97
|
|
|
@@ -6,7 +6,7 @@ A lightweight Python bot that bridges your Telegram messages directly to [OpenCo
|
|
|
6
6
|
|
|
7
7
|
- **Direct OpenCode integration** — routes your messages to OpenCode's HTTP API
|
|
8
8
|
- **Persistent sessions** — conversations maintain context across messages
|
|
9
|
-
- **Session management** — create, switch, list, and share sessions
|
|
9
|
+
- **Session management** — create, delete, switch, list, and share sessions
|
|
10
10
|
- **Model switching** — change AI models on the fly (`/model`)
|
|
11
11
|
- **Plan/Build modes** — toggle between read-only analysis and full execution
|
|
12
12
|
- **Smart formatting** — code blocks with syntax highlighting in Telegram
|
|
@@ -14,6 +14,7 @@ A lightweight Python bot that bridges your Telegram messages directly to [OpenCo
|
|
|
14
14
|
- **Security** — whitelist-based access control + rate limiting
|
|
15
15
|
- **Workspace Switching** — Switching from one workspace to another
|
|
16
16
|
- **Uploading from Mobile to WorkSpace** - U can upload the documents/images from your mobile to the opencode agent workspace
|
|
17
|
+
- **Workspace Management** — Creation and deletion of the workspace/folder
|
|
17
18
|
|
|
18
19
|
## 📋 Prerequisites
|
|
19
20
|
|
|
@@ -29,7 +30,7 @@ A lightweight Python bot that bridges your Telegram messages directly to [OpenCo
|
|
|
29
30
|
|
|
30
31
|
### Quick Installation:
|
|
31
32
|
```bash
|
|
32
|
-
pip install telegram-opencode-bridge-bot==0.1.
|
|
33
|
+
pip install telegram-opencode-bridge-bot==0.1.7
|
|
33
34
|
telegram-opencode-bot
|
|
34
35
|
telegram-opencode-bot --env (use --env flag if u want to re-configure later anytime)
|
|
35
36
|
```
|
|
@@ -71,6 +72,9 @@ Open Telegram, find your bot, and start asking! 🎉
|
|
|
71
72
|
| `/project` | To view the current workspace, sub folder workspaces and to change the workspace |
|
|
72
73
|
| `/enable` | To enable the streaming |
|
|
73
74
|
| `/disable` | To disable the streaming |
|
|
75
|
+
| `/create_project` | To create new project/folder/workspace |
|
|
76
|
+
| `/delete_project` | To create new project/folder/workspace |
|
|
77
|
+
| `/delete` | To delete a conversation |
|
|
74
78
|
|
|
75
79
|
## 🏗️ Architecture
|
|
76
80
|
|
|
@@ -19,6 +19,10 @@ import logging
|
|
|
19
19
|
import sys
|
|
20
20
|
import os
|
|
21
21
|
|
|
22
|
+
# Switch to Selector Event Loop on Windows for robust signal handling and clean shutdowns
|
|
23
|
+
if sys.platform == 'win32':
|
|
24
|
+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
25
|
+
|
|
22
26
|
from telegram import Update
|
|
23
27
|
from telegram.ext import (
|
|
24
28
|
ApplicationBuilder,
|
|
@@ -41,6 +45,7 @@ from handlers.commands import (
|
|
|
41
45
|
help_command,
|
|
42
46
|
new_command,
|
|
43
47
|
sessions_command,
|
|
48
|
+
delete_command,
|
|
44
49
|
plan_command,
|
|
45
50
|
build_command,
|
|
46
51
|
share_command,
|
|
@@ -49,6 +54,8 @@ from handlers.commands import (
|
|
|
49
54
|
models_command,
|
|
50
55
|
stop_command,
|
|
51
56
|
project_command,
|
|
57
|
+
create_project_command,
|
|
58
|
+
delete_project_command,
|
|
52
59
|
enable_command,
|
|
53
60
|
disable_command,
|
|
54
61
|
set_bot_commands,
|
|
@@ -64,6 +71,43 @@ logging.basicConfig(
|
|
|
64
71
|
)
|
|
65
72
|
logger = logging.getLogger("opencode-telegram-bot")
|
|
66
73
|
|
|
74
|
+
_lock_file = None
|
|
75
|
+
|
|
76
|
+
def acquire_bot_lock():
|
|
77
|
+
"""Acquire an exclusive lock file to prevent multiple instances from running concurrently."""
|
|
78
|
+
global _lock_file
|
|
79
|
+
lock_path = os.path.join(os.path.abspath("."), "bot.lock")
|
|
80
|
+
try:
|
|
81
|
+
_lock_file = open(lock_path, "w")
|
|
82
|
+
if os.name == 'nt':
|
|
83
|
+
import msvcrt
|
|
84
|
+
try:
|
|
85
|
+
_lock_file.seek(0)
|
|
86
|
+
msvcrt.locking(_lock_file.fileno(), msvcrt.LK_NBLCK, 1)
|
|
87
|
+
_lock_file.write(str(os.getpid()))
|
|
88
|
+
_lock_file.flush()
|
|
89
|
+
except (OSError, IOError):
|
|
90
|
+
print("\n" + "="*65)
|
|
91
|
+
print("❌ ERROR: Another instance of the Telegram bot is already running!")
|
|
92
|
+
print("Please close the other terminal or kill the stray Python process.")
|
|
93
|
+
print("="*65 + "\n")
|
|
94
|
+
sys.exit(1)
|
|
95
|
+
else:
|
|
96
|
+
import fcntl
|
|
97
|
+
try:
|
|
98
|
+
fcntl.flock(_lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
99
|
+
_lock_file.write(str(os.getpid()))
|
|
100
|
+
_lock_file.flush()
|
|
101
|
+
except (OSError, IOError):
|
|
102
|
+
print("\n" + "="*65)
|
|
103
|
+
print("❌ ERROR: Another instance of the Telegram bot is already running!")
|
|
104
|
+
print("Please close the other terminal or kill the stray Python process.")
|
|
105
|
+
print("="*65 + "\n")
|
|
106
|
+
sys.exit(1)
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logger.warning(f"Could not acquire bot lock: {e}")
|
|
109
|
+
|
|
110
|
+
|
|
67
111
|
|
|
68
112
|
class RetryingHTTPXRequest(HTTPXRequest):
|
|
69
113
|
"""Custom HTTPXRequest that automatically retries failed requests on connection timeouts/errors."""
|
|
@@ -105,6 +149,10 @@ def build_authorized_handlers(authorizer: UserAuthorizer, rate_limiter: RateLimi
|
|
|
105
149
|
async def _sessions(update, context):
|
|
106
150
|
await sessions_command(update, context)
|
|
107
151
|
|
|
152
|
+
@authorized(authorizer, rate_limiter)
|
|
153
|
+
async def _delete(update, context):
|
|
154
|
+
await delete_command(update, context)
|
|
155
|
+
|
|
108
156
|
@authorized(authorizer, rate_limiter)
|
|
109
157
|
async def _plan(update, context):
|
|
110
158
|
await plan_command(update, context)
|
|
@@ -137,6 +185,14 @@ def build_authorized_handlers(authorizer: UserAuthorizer, rate_limiter: RateLimi
|
|
|
137
185
|
async def _project(update, context):
|
|
138
186
|
await project_command(update, context)
|
|
139
187
|
|
|
188
|
+
@authorized(authorizer, rate_limiter)
|
|
189
|
+
async def _create_project(update, context):
|
|
190
|
+
await create_project_command(update, context)
|
|
191
|
+
|
|
192
|
+
@authorized(authorizer, rate_limiter)
|
|
193
|
+
async def _delete_project(update, context):
|
|
194
|
+
await delete_project_command(update, context)
|
|
195
|
+
|
|
140
196
|
@authorized(authorizer, rate_limiter)
|
|
141
197
|
async def _enable(update, context):
|
|
142
198
|
await enable_command(update, context)
|
|
@@ -162,9 +218,12 @@ def build_authorized_handlers(authorizer: UserAuthorizer, rate_limiter: RateLimi
|
|
|
162
218
|
"help": _help,
|
|
163
219
|
"new": _new,
|
|
164
220
|
"sessions": _sessions,
|
|
221
|
+
"delete": _delete,
|
|
165
222
|
"models": _models,
|
|
166
223
|
"stop": _stop,
|
|
167
224
|
"project": _project,
|
|
225
|
+
"create_project": _create_project,
|
|
226
|
+
"delete_project": _delete_project,
|
|
168
227
|
"enable": _enable,
|
|
169
228
|
"disable": _disable,
|
|
170
229
|
"plan": _plan,
|
|
@@ -180,8 +239,17 @@ def build_authorized_handlers(authorizer: UserAuthorizer, rate_limiter: RateLimi
|
|
|
180
239
|
|
|
181
240
|
async def error_handler(update: object, context: ContextTypes.DEFAULT_TYPE) -> None:
|
|
182
241
|
"""Log the error and send a Telegram message to notify the user."""
|
|
183
|
-
#
|
|
184
|
-
|
|
242
|
+
# Suppress full traceback for common transient network / timeout errors to keep logs clean
|
|
243
|
+
from telegram.error import NetworkError, TimedOut
|
|
244
|
+
|
|
245
|
+
err = context.error
|
|
246
|
+
err_str = str(err).lower()
|
|
247
|
+
if isinstance(err, (NetworkError, TimedOut)) or "httpx" in err_str or "httpcore" in err_str or "read error" in err_str:
|
|
248
|
+
logger.warning(f"📡 Transient Telegram network/timeout error: {err}")
|
|
249
|
+
return
|
|
250
|
+
|
|
251
|
+
# Log unexpected errors with traceback
|
|
252
|
+
logger.error("Exception while handling an update:", exc_info=err)
|
|
185
253
|
|
|
186
254
|
# Notify the user if the update is a Telegram Update with a message
|
|
187
255
|
if isinstance(update, Update) and update.effective_message:
|
|
@@ -215,19 +283,25 @@ async def post_shutdown(application) -> None:
|
|
|
215
283
|
# Stop the background opencode server process if running
|
|
216
284
|
try:
|
|
217
285
|
from opencode.server import stop_server
|
|
218
|
-
await stop_server()
|
|
286
|
+
await asyncio.wait_for(stop_server(), timeout=8.0)
|
|
219
287
|
except Exception as e:
|
|
220
288
|
logger.warning(f"Failed to stop background OpenCode server: {e}")
|
|
221
289
|
|
|
222
290
|
# Close session manager DB
|
|
223
291
|
session_mgr = application.bot_data.get("session_manager")
|
|
224
292
|
if session_mgr:
|
|
225
|
-
|
|
293
|
+
try:
|
|
294
|
+
await asyncio.wait_for(session_mgr.close(), timeout=3.0)
|
|
295
|
+
except Exception as e:
|
|
296
|
+
logger.warning(f"Failed to close session manager: {e}")
|
|
226
297
|
|
|
227
298
|
# Close HTTP client
|
|
228
299
|
oc_client = application.bot_data.get("opencode_client")
|
|
229
300
|
if oc_client:
|
|
230
|
-
|
|
301
|
+
try:
|
|
302
|
+
await asyncio.wait_for(oc_client.close(), timeout=3.0)
|
|
303
|
+
except Exception as e:
|
|
304
|
+
logger.warning(f"Failed to close HTTP client: {e}")
|
|
231
305
|
|
|
232
306
|
logger.info("Goodbye!")
|
|
233
307
|
|
|
@@ -394,6 +468,9 @@ def main():
|
|
|
394
468
|
logger.error("Copy .env.example → .env and fill in your values.")
|
|
395
469
|
sys.exit(1)
|
|
396
470
|
|
|
471
|
+
# ── Acquire exclusive bot lock to prevent concurrent instances ───────
|
|
472
|
+
acquire_bot_lock()
|
|
473
|
+
|
|
397
474
|
logger.info("=" * 50)
|
|
398
475
|
logger.info(" OpenCode Telegram Bot")
|
|
399
476
|
logger.info("=" * 50)
|
|
@@ -429,6 +506,7 @@ def main():
|
|
|
429
506
|
ApplicationBuilder()
|
|
430
507
|
.token(config.telegram_bot_token)
|
|
431
508
|
.request(request)
|
|
509
|
+
.get_updates_request(request)
|
|
432
510
|
.post_init(post_init)
|
|
433
511
|
.post_shutdown(post_shutdown)
|
|
434
512
|
.build()
|
|
@@ -451,9 +529,12 @@ def main():
|
|
|
451
529
|
application.add_handler(CommandHandler("help", handlers["help"], block=False))
|
|
452
530
|
application.add_handler(CommandHandler("new", handlers["new"], block=False))
|
|
453
531
|
application.add_handler(CommandHandler("sessions", handlers["sessions"], block=False))
|
|
532
|
+
application.add_handler(CommandHandler("delete", handlers["delete"], block=False))
|
|
454
533
|
application.add_handler(CommandHandler("models", handlers["models"], block=False))
|
|
455
534
|
application.add_handler(CommandHandler("stop", handlers["stop"], block=False))
|
|
456
535
|
application.add_handler(CommandHandler("project", handlers["project"], block=False))
|
|
536
|
+
application.add_handler(CommandHandler("create_project", handlers["create_project"], block=False))
|
|
537
|
+
application.add_handler(CommandHandler("delete_project", handlers["delete_project"], block=False))
|
|
457
538
|
application.add_handler(CommandHandler("enable", handlers["enable"], block=False))
|
|
458
539
|
application.add_handler(CommandHandler("disable", handlers["disable"], block=False))
|
|
459
540
|
application.add_handler(CommandHandler("plan", handlers["plan"], block=False))
|