claude-telegram-bot 0.1.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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(uv run:*)"
5
+ ]
6
+ }
7
+ }
@@ -0,0 +1,7 @@
1
+ BOT_TOKEN=your-telegram-bot-token
2
+ ADMIN_USER_ID=123456789
3
+ ALLOWED_USER_IDS=123456789,987654321
4
+ CLAUDE_PATH=/home/zhangchi/.local/bin/claude
5
+ CLAUDE_TIMEOUT=120
6
+ MAX_HISTORY_MESSAGES=40
7
+ # DATA_DIR defaults to ./data (relative to project root)
@@ -0,0 +1,9 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ .env
6
+ data/
7
+ .idea/
8
+ .vscode/
9
+ *.log
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.4
2
+ Name: claude-telegram-bot
3
+ Version: 0.1.0
4
+ Summary: Telegram bot for interacting with Claude Code CLI
5
+ Project-URL: Homepage, https://github.com/littzhch/claude-telegram-bot
6
+ Project-URL: Repository, https://github.com/littzhch/claude-telegram-bot
7
+ Author-email: littzhch <2371050115@qq.com>
8
+ License: MIT
9
+ Keywords: ai,anthropic,bot,claude,telegram
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Python: >=3.9
19
+ Requires-Dist: python-dotenv>=1.0
20
+ Requires-Dist: python-telegram-bot>=21.0
21
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
22
+ Description-Content-Type: text/markdown
23
+
24
+ # Claude Telegram Bot
25
+
26
+ Telegram bot for interacting with Claude Code CLI
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ pip install claude-telegram-bot
32
+ ```
33
+
34
+ ## Configuration
35
+
36
+ Configuration priority: CLI > ENV > config file > defaults
37
+
38
+ ### Config File
39
+
40
+ Create `~/.config/claude-tg-bot.toml`:
41
+
42
+ ```toml
43
+ [telegram]
44
+ bot_token = "your-telegram-bot-token"
45
+ admin_user_id = 123456789
46
+ allowed_user_ids = [123456789]
47
+
48
+ [claude]
49
+ path = "/home/zhangchi/.local/bin/claude"
50
+ timeout = 120
51
+
52
+ [data]
53
+ dir = "~/.cache/claude-tg-bot"
54
+ ```
55
+
56
+ ### Environment Variables
57
+
58
+ - `BOT_TOKEN`: Telegram bot token
59
+ - `ADMIN_USER_ID`: Admin user ID
60
+ - `ALLOWED_USER_IDS`: Comma-separated user IDs
61
+ - `CLAUDE_PATH`: Path to Claude CLI
62
+ - `CLAUDE_TIMEOUT`: Command timeout in seconds
63
+ - `DATA_DIR`: Data directory
64
+
65
+ ### CLI Arguments
66
+
67
+ ```bash
68
+ claude-tg-bot --help
69
+ ```
70
+
71
+ ## Usage
72
+
73
+ ```bash
74
+ claude-tg-bot --bot-token YOUR_TOKEN
75
+ ```
@@ -0,0 +1,52 @@
1
+ # Claude Telegram Bot
2
+
3
+ Telegram bot for interacting with Claude Code CLI
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install claude-telegram-bot
9
+ ```
10
+
11
+ ## Configuration
12
+
13
+ Configuration priority: CLI > ENV > config file > defaults
14
+
15
+ ### Config File
16
+
17
+ Create `~/.config/claude-tg-bot.toml`:
18
+
19
+ ```toml
20
+ [telegram]
21
+ bot_token = "your-telegram-bot-token"
22
+ admin_user_id = 123456789
23
+ allowed_user_ids = [123456789]
24
+
25
+ [claude]
26
+ path = "/home/zhangchi/.local/bin/claude"
27
+ timeout = 120
28
+
29
+ [data]
30
+ dir = "~/.cache/claude-tg-bot"
31
+ ```
32
+
33
+ ### Environment Variables
34
+
35
+ - `BOT_TOKEN`: Telegram bot token
36
+ - `ADMIN_USER_ID`: Admin user ID
37
+ - `ALLOWED_USER_IDS`: Comma-separated user IDs
38
+ - `CLAUDE_PATH`: Path to Claude CLI
39
+ - `CLAUDE_TIMEOUT`: Command timeout in seconds
40
+ - `DATA_DIR`: Data directory
41
+
42
+ ### CLI Arguments
43
+
44
+ ```bash
45
+ claude-tg-bot --help
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ ```bash
51
+ claude-tg-bot --bot-token YOUR_TOKEN
52
+ ```
@@ -0,0 +1,16 @@
1
+ # Claude Telegram Bot Configuration
2
+ # Save this to ~/.config/claude-tg-bot.toml
3
+
4
+ [telegram]
5
+ bot_token = "your-telegram-bot-token"
6
+ admin_user_id = 123456789
7
+ allowed_user_ids = [123456789, 987654321]
8
+
9
+ [claude]
10
+ path = "/home/zhangchi/.local/bin/claude"
11
+ timeout = 120
12
+ max_history_messages = 40
13
+
14
+ [data]
15
+ # Directory for storing bot data (default: ~/.cache/claude-tg-bot/)
16
+ # dir = "/custom/path/to/data"
@@ -0,0 +1,44 @@
1
+ [project]
2
+ name = "claude-telegram-bot"
3
+ version = "0.1.0"
4
+ description = "Telegram bot for interacting with Claude Code CLI"
5
+ readme = "README.md"
6
+ requires-python = ">=3.9"
7
+ license = {text = "MIT"}
8
+ authors = [
9
+ {name = "littzhch", email = "2371050115@qq.com"}
10
+ ]
11
+ keywords = ["telegram", "bot", "claude", "ai", "anthropic"]
12
+ classifiers = [
13
+ "Development Status :: 3 - Alpha",
14
+ "Intended Audience :: Developers",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.9",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ ]
22
+
23
+ dependencies = [
24
+ "python-telegram-bot>=21.0",
25
+ "python-dotenv>=1.0",
26
+ "tomli>=2.0; python_version < '3.11'",
27
+ ]
28
+
29
+ [project.scripts]
30
+ claude-tg-bot = "claude_telegram_bot.bot:main"
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/littzhch/claude-telegram-bot"
34
+ Repository = "https://github.com/littzhch/claude-telegram-bot"
35
+
36
+ [tool.uv]
37
+ package = true
38
+
39
+ [build-system]
40
+ requires = ["hatchling"]
41
+ build-backend = "hatchling.build"
42
+
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["src/claude_telegram_bot"]
@@ -0,0 +1,3 @@
1
+ from claude_telegram_bot.bot import main
2
+
3
+ main()
@@ -0,0 +1,49 @@
1
+ import logging
2
+ from functools import wraps
3
+ from telegram import Update
4
+ from telegram.ext import ContextTypes
5
+
6
+ from claude_telegram_bot import config
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ def _load_allowed_ids() -> set[int]:
12
+ """Reload allowed user IDs from config (supports runtime admin changes)."""
13
+ return set(config.ALLOWED_USER_IDS)
14
+
15
+
16
+ def is_allowed(user_id: int) -> bool:
17
+ allowed = _load_allowed_ids()
18
+ return user_id in allowed
19
+
20
+
21
+ def is_admin(user_id: int) -> bool:
22
+ return user_id == config.ADMIN_USER_ID
23
+
24
+
25
+ def require_auth(func):
26
+ """Decorator: reject users not in the allowed list."""
27
+ @wraps(func)
28
+ async def wrapper(update: Update, context: ContextTypes.DEFAULT_TYPE, *args, **kwargs):
29
+ user_id = update.effective_user.id
30
+ if not is_allowed(user_id):
31
+ logger.warning("Unauthorized access from user %s", user_id)
32
+ if update.message:
33
+ await update.message.reply_text("You are not authorized to use this bot.")
34
+ return
35
+ return await func(update, context, *args, **kwargs)
36
+ return wrapper
37
+
38
+
39
+ def require_admin(func):
40
+ """Decorator: reject non-admin users."""
41
+ @wraps(func)
42
+ async def wrapper(update: Update, context: ContextTypes.DEFAULT_TYPE, *args, **kwargs):
43
+ user_id = update.effective_user.id
44
+ if not is_admin(user_id):
45
+ if update.message:
46
+ await update.message.reply_text("Admin only.")
47
+ return
48
+ return await func(update, context, *args, **kwargs)
49
+ return wrapper
@@ -0,0 +1,425 @@
1
+ #!/usr/bin/env python3
2
+ """Telegram Bot for Claude Code CLI."""
3
+
4
+ import asyncio
5
+ import logging
6
+ import os
7
+ import tempfile
8
+ from pathlib import Path
9
+ from telegram import Update, BotCommand
10
+ from telegram.ext import (
11
+ Application,
12
+ CommandHandler,
13
+ MessageHandler,
14
+ filters,
15
+ ContextTypes,
16
+ )
17
+
18
+ from claude_telegram_bot import config
19
+ from claude_telegram_bot.auth import require_auth, is_admin
20
+ from claude_telegram_bot.claude_runner import run_claude
21
+ from claude_telegram_bot.session_manager import SessionManager, init_db as init_session_db
22
+ from claude_telegram_bot.project_manager import (
23
+ init_db as init_project_db,
24
+ add_project,
25
+ remove_project,
26
+ list_projects,
27
+ get_project_path,
28
+ )
29
+
30
+ logging.basicConfig(
31
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
32
+ level=logging.INFO,
33
+ )
34
+ logger = logging.getLogger(__name__)
35
+
36
+ # In-memory session managers per user
37
+ _user_sessions: dict[int, SessionManager] = {}
38
+
39
+
40
+ def _get_session(user_id: int) -> SessionManager:
41
+ if user_id not in _user_sessions:
42
+ _user_sessions[user_id] = SessionManager(user_id)
43
+ return _user_sessions[user_id]
44
+
45
+
46
+ # ── Commands ──
47
+
48
+ @require_auth
49
+ async def cmd_start(update: Update, context: ContextTypes.DEFAULT_TYPE):
50
+ user = update.effective_user
51
+ await update.message.reply_text(
52
+ f"Hello {user.first_name}! I'm your Claude Code bot.\n\n"
53
+ "Send me any message and I'll run it through Claude Code CLI.\n\n"
54
+ "Key commands:\n"
55
+ "/new - Start a new conversation\n"
56
+ "/sessions - List all sessions\n"
57
+ "/history - View conversation history\n"
58
+ "/projects - List configured projects\n"
59
+ "/add_project <name> <path> - Add a project\n"
60
+ "/use <project> - Switch to a project\n"
61
+ "/status - Current status\n"
62
+ "/help - Show help"
63
+ )
64
+
65
+
66
+ @require_auth
67
+ async def cmd_help(update: Update, context: ContextTypes.DEFAULT_TYPE):
68
+ text = (
69
+ "*Claude Code Telegram Bot*\n\n"
70
+ "*Conversation*\n"
71
+ "/new - Start new conversation\n"
72
+ "/sessions - List all sessions\n"
73
+ "/switch <id> - Switch session\n"
74
+ "/history - Show recent history\n\n"
75
+ "*Projects*\n"
76
+ "/projects - List projects\n"
77
+ "/add_project <name> <path> - Add project\n"
78
+ "/remove_project <name> - Remove project\n"
79
+ "/use <name> - Switch active project\n\n"
80
+ "*Status*\n"
81
+ "/status - Current status\n"
82
+ "/help - This message\n\n"
83
+ "*Files*\n"
84
+ "Send a document to attach it to the conversation.\n"
85
+ "The file path will be included in the prompt to Claude."
86
+ )
87
+ if is_admin(update.effective_user.id):
88
+ text += (
89
+ "\n\n*Admin*\n"
90
+ "/admin add <user_id> - Add user\n"
91
+ "/admin remove <user_id> - Remove user\n"
92
+ "/admin list - List allowed users"
93
+ )
94
+ await update.message.reply_text(text, parse_mode="Markdown")
95
+
96
+
97
+ @require_auth
98
+ async def cmd_new(update: Update, context: ContextTypes.DEFAULT_TYPE):
99
+ sm = _get_session(update.effective_user.id)
100
+ sm.new_session()
101
+ await update.message.reply_text("New conversation started.")
102
+
103
+
104
+ @require_auth
105
+ async def cmd_sessions(update: Update, context: ContextTypes.DEFAULT_TYPE):
106
+ sm = _get_session(update.effective_user.id)
107
+ sessions = sm.list_sessions()
108
+ if not sessions:
109
+ await update.message.reply_text("No sessions yet. Send a message to start one.")
110
+ return
111
+
112
+ lines = []
113
+ active_id = sm.active_session_id
114
+ for s in sessions:
115
+ marker = " *" if s["id"] == active_id else ""
116
+ lines.append(f"{s['id']}. {s['name']} (updated: {s['updated_at'][:16]}){marker}")
117
+ await update.message.reply_text(
118
+ "Sessions (active marked with *):\n" + "\n".join(lines)
119
+ )
120
+
121
+
122
+ @require_auth
123
+ async def cmd_switch(update: Update, context: ContextTypes.DEFAULT_TYPE):
124
+ if not context.args:
125
+ await update.message.reply_text("Usage: /switch <session_id>")
126
+ return
127
+ try:
128
+ sid = int(context.args[0])
129
+ except ValueError:
130
+ await update.message.reply_text("Invalid session ID.")
131
+ return
132
+ sm = _get_session(update.effective_user.id)
133
+ if sm.switch_session(sid):
134
+ await update.message.reply_text(f"Switched to session {sid}.")
135
+ else:
136
+ await update.message.reply_text(f"Session {sid} not found.")
137
+
138
+
139
+ @require_auth
140
+ async def cmd_history(update: Update, context: ContextTypes.DEFAULT_TYPE):
141
+ sm = _get_session(update.effective_user.id)
142
+ history = sm.get_history(limit=20)
143
+ if not history:
144
+ await update.message.reply_text("No history in current session.")
145
+ return
146
+
147
+ lines = []
148
+ for msg in history:
149
+ role = "You" if msg["role"] == "user" else "Claude"
150
+ content = msg["content"][:200]
151
+ if len(msg["content"]) > 200:
152
+ content += "..."
153
+ lines.append(f"*{role}:* {content}")
154
+
155
+ text = "\n\n".join(lines)
156
+ # Telegram message limit
157
+ if len(text) > 4000:
158
+ text = text[:4000] + "\n... (truncated)"
159
+ await update.message.reply_text(text, parse_mode="Markdown")
160
+
161
+
162
+ @require_auth
163
+ async def cmd_projects(update: Update, context: ContextTypes.DEFAULT_TYPE):
164
+ user_id = update.effective_user.id
165
+ projects = list_projects(user_id)
166
+ if not projects:
167
+ await update.message.reply_text(
168
+ "No projects configured. Use /add_project <name> <path> to add one."
169
+ )
170
+ return
171
+
172
+ sm = _get_session(user_id)
173
+ active_cwd = sm.get_active_cwd()
174
+ lines = []
175
+ for p in projects:
176
+ active = " *" if p["path"] == active_cwd else ""
177
+ lines.append(f"• {p['name']}: {p['path']}{active}")
178
+ await update.message.reply_text(
179
+ "Projects (active marked with *):\n" + "\n".join(lines)
180
+ )
181
+
182
+
183
+ @require_auth
184
+ async def cmd_add_project(update: Update, context: ContextTypes.DEFAULT_TYPE):
185
+ if len(context.args) < 2:
186
+ await update.message.reply_text("Usage: /add_project <name> <path>")
187
+ return
188
+ name = context.args[0]
189
+ path = " ".join(context.args[1:])
190
+ user_id = update.effective_user.id
191
+ ok, msg = add_project(user_id, name, path)
192
+ await update.message.reply_text(msg)
193
+
194
+
195
+ @require_auth
196
+ async def cmd_remove_project(update: Update, context: ContextTypes.DEFAULT_TYPE):
197
+ if not context.args:
198
+ await update.message.reply_text("Usage: /remove_project <name>")
199
+ return
200
+ name = context.args[0]
201
+ user_id = update.effective_user.id
202
+ ok, msg = remove_project(user_id, name)
203
+ await update.message.reply_text(msg)
204
+
205
+
206
+ @require_auth
207
+ async def cmd_use(update: Update, context: ContextTypes.DEFAULT_TYPE):
208
+ if not context.args:
209
+ await update.message.reply_text("Usage: /use <project_name>")
210
+ return
211
+ name = context.args[0]
212
+ user_id = update.effective_user.id
213
+ path = get_project_path(user_id, name)
214
+ if path is None:
215
+ await update.message.reply_text(f"Project '{name}' not found. Use /projects to list.")
216
+ return
217
+ sm = _get_session(user_id)
218
+ sm.set_cwd(path)
219
+ await update.message.reply_text(f"Switched to project '{name}' ({path}).")
220
+
221
+
222
+ @require_auth
223
+ async def cmd_status(update: Update, context: ContextTypes.DEFAULT_TYPE):
224
+ user_id = update.effective_user.id
225
+ sm = _get_session(user_id)
226
+ active_cwd = sm.get_active_cwd() or "(default: home)"
227
+ sessions = sm.list_sessions()
228
+ active_sid = sm.active_session_id
229
+ projects = list_projects(user_id)
230
+
231
+ text = (
232
+ f"*Status*\n"
233
+ f"User ID: {user_id}\n"
234
+ f"Admin: {'Yes' if is_admin(user_id) else 'No'}\n"
235
+ f"Active session: {active_sid or 'None'}\n"
236
+ f"Working directory: {active_cwd}\n"
237
+ f"Sessions: {len(sessions)}\n"
238
+ f"Projects: {len(projects)}"
239
+ )
240
+ await update.message.reply_text(text, parse_mode="Markdown")
241
+
242
+
243
+ # ── Admin ──
244
+
245
+ @require_auth
246
+ async def cmd_admin(update: Update, context: ContextTypes.DEFAULT_TYPE):
247
+ user_id = update.effective_user.id
248
+ if not is_admin(user_id):
249
+ await update.message.reply_text("Admin only.")
250
+ return
251
+
252
+ if not context.args:
253
+ await update.message.reply_text(
254
+ "Usage:\n/admin add <user_id>\n/admin remove <user_id>\n/admin list"
255
+ )
256
+ return
257
+
258
+ action = context.args[0].lower()
259
+
260
+ if action == "list":
261
+ ids = ", ".join(str(uid) for uid in config.ALLOWED_USER_IDS)
262
+ await update.message.reply_text(f"Allowed user IDs:\n{ids}")
263
+ return
264
+
265
+ if action == "add" and len(context.args) >= 2:
266
+ try:
267
+ new_id = int(context.args[1])
268
+ except ValueError:
269
+ await update.message.reply_text("Invalid user ID.")
270
+ return
271
+ if new_id not in config.ALLOWED_USER_IDS:
272
+ config.ALLOWED_USER_IDS.append(new_id)
273
+ await update.message.reply_text(f"Added user {new_id}.")
274
+ return
275
+
276
+ if action == "remove" and len(context.args) >= 2:
277
+ try:
278
+ rem_id = int(context.args[1])
279
+ except ValueError:
280
+ await update.message.reply_text("Invalid user ID.")
281
+ return
282
+ if rem_id in config.ALLOWED_USER_IDS:
283
+ config.ALLOWED_USER_IDS.remove(rem_id)
284
+ await update.message.reply_text(f"Removed user {rem_id}.")
285
+ return
286
+
287
+ await update.message.reply_text("Unknown admin command.")
288
+
289
+
290
+ # ── File upload ──
291
+
292
+ @require_auth
293
+ async def handle_document(update: Update, context: ContextTypes.DEFAULT_TYPE):
294
+ """Handle uploaded documents - download and save to session's temp dir."""
295
+ doc = update.message.document
296
+ if not doc:
297
+ return
298
+
299
+ user_id = update.effective_user.id
300
+ sm = _get_session(user_id)
301
+ cwd = sm.get_active_cwd() or str(Path.home())
302
+
303
+ file = await context.bot.get_file(doc.file_id)
304
+ dest = os.path.join(cwd, doc.file_name or "uploaded_file")
305
+ await file.download_to_drive(dest)
306
+
307
+ sm.add_message("user", f"[Uploaded file: {dest}]")
308
+ await update.message.reply_text(f"File saved to: {dest}")
309
+
310
+
311
+ # ── Message handler ──
312
+
313
+ @require_auth
314
+ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
315
+ """Handle regular text messages - forward to Claude Code CLI."""
316
+ text = update.message.text
317
+ if not text:
318
+ return
319
+
320
+ user_id = update.effective_user.id
321
+ sm = _get_session(user_id)
322
+
323
+ # Ensure we have a session
324
+ if sm.active_session_id is None:
325
+ sm.new_session()
326
+
327
+ cwd = sm.get_active_cwd() or str(Path.home())
328
+
329
+ # Build prompt with history
330
+ prompt = sm.build_prompt(text)
331
+
332
+ # Save user message
333
+ sm.add_message("user", text)
334
+
335
+ # Send "thinking" indicator
336
+ thinking_msg = await update.message.reply_text("Claude is thinking...")
337
+
338
+ try:
339
+ output = await run_claude(prompt, cwd=cwd, continue_session=False)
340
+ except Exception as e:
341
+ output = f"Error: {e}"
342
+
343
+ # Save assistant response
344
+ sm.add_message("assistant", output)
345
+
346
+ # Send response (split if too long)
347
+ await thinking_msg.delete()
348
+ if len(output) <= 4000:
349
+ await update.message.reply_text(output)
350
+ else:
351
+ # Split into chunks
352
+ chunks = []
353
+ while output:
354
+ chunk = output[:4000]
355
+ # Try to split at a newline boundary
356
+ last_nl = chunk.rfind("\n")
357
+ if last_nl > 2000:
358
+ chunk = output[:last_nl]
359
+ output = output[last_nl + 1:]
360
+ else:
361
+ output = output[4000:]
362
+ chunks.append(chunk)
363
+ for chunk in chunks:
364
+ await update.message.reply_text(chunk)
365
+
366
+
367
+ # ── Main ──
368
+
369
+ async def post_init(app: Application):
370
+ """Set bot commands for the Telegram UI."""
371
+ commands = [
372
+ BotCommand("start", "Start using the bot"),
373
+ BotCommand("help", "Show help"),
374
+ BotCommand("new", "New conversation"),
375
+ BotCommand("sessions", "List sessions"),
376
+ BotCommand("switch", "Switch session"),
377
+ BotCommand("history", "Show history"),
378
+ BotCommand("projects", "List projects"),
379
+ BotCommand("add_project", "Add project"),
380
+ BotCommand("remove_project", "Remove project"),
381
+ BotCommand("use", "Switch project"),
382
+ BotCommand("status", "Current status"),
383
+ ]
384
+ await app.bot.set_my_commands(commands)
385
+
386
+
387
+ def main():
388
+ if not config.BOT_TOKEN:
389
+ print("Error: BOT_TOKEN not set. Copy .env.example to .env and configure it.")
390
+ return
391
+ if not config.ALLOWED_USER_IDS:
392
+ print("Warning: ALLOWED_USER_IDS is empty. Only admin can use the bot.")
393
+
394
+ # Init databases
395
+ init_session_db()
396
+ init_project_db()
397
+
398
+ app = Application.builder().token(config.BOT_TOKEN).post_init(post_init).build()
399
+
400
+ # Register handlers
401
+ app.add_handler(CommandHandler("start", cmd_start))
402
+ app.add_handler(CommandHandler("help", cmd_help))
403
+ app.add_handler(CommandHandler("new", cmd_new))
404
+ app.add_handler(CommandHandler("sessions", cmd_sessions))
405
+ app.add_handler(CommandHandler("switch", cmd_switch))
406
+ app.add_handler(CommandHandler("history", cmd_history))
407
+ app.add_handler(CommandHandler("projects", cmd_projects))
408
+ app.add_handler(CommandHandler("add_project", cmd_add_project))
409
+ app.add_handler(CommandHandler("remove_project", cmd_remove_project))
410
+ app.add_handler(CommandHandler("use", cmd_use))
411
+ app.add_handler(CommandHandler("status", cmd_status))
412
+ app.add_handler(CommandHandler("admin", cmd_admin))
413
+
414
+ # File uploads
415
+ app.add_handler(MessageHandler(filters.Document.ALL, handle_document))
416
+
417
+ # Text messages
418
+ app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
419
+
420
+ logger.info("Starting bot...")
421
+ app.run_polling(allowed_updates=Update.ALL_TYPES)
422
+
423
+
424
+ if __name__ == "__main__":
425
+ main()