@woai commited on
Commit
9f52946
·
1 Parent(s): 7cc4e00

Fix telegram bot MCP API parameters compatibility

Browse files
TUNNEL_SOLUTIONS.md ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Решения для стабильного доступа к MCP серверу
2
+
3
+ ## 🔴 Проблема
4
+ LocalTunnel нестабилен - туннели отваливаются, URL меняются при перезапуске.
5
+
6
+ ## ✅ Решения (по порядку предпочтения)
7
+
8
+ ### 1. 🌟 **Ngrok (Рекомендуется)**
9
+ ```bash
10
+ # Установка
11
+ npm install -g ngrok
12
+
13
+ # Бесплатная регистрация: https://ngrok.com/
14
+ # Получить authtoken и добавить:
15
+ ngrok authtoken YOUR_TOKEN
16
+
17
+ # Запуск (стабильный URL на платном плане)
18
+ ngrok http 8080
19
+ ```
20
+
21
+ **Преимущества:**
22
+ - Более стабильный чем LocalTunnel
23
+ - Платный план дает постоянный URL
24
+ - Лучшая производительность
25
+
26
+ ### 2. 🔧 **Cloudflare Tunnel (Бесплатно + Стабильно)**
27
+ ```bash
28
+ # Установка
29
+ npm install -g cloudflared
30
+
31
+ # Авторизация (один раз)
32
+ cloudflared tunnel login
33
+
34
+ # Создание туннеля
35
+ cloudflared tunnel create youtube-mcp
36
+
37
+ # Запуск
38
+ cloudflared tunnel --url http://localhost:8080
39
+ ```
40
+
41
+ **Преимущества:**
42
+ - Бесплатный и стабильный
43
+ - Постоянный URL
44
+ - Отличная производительность
45
+
46
+ ### 3. 💻 **VPS развертывание**
47
+ - Aruba Cloud (от €1/месяц)
48
+ - DigitalOcean (от $5/месяц)
49
+ - Hetzner Cloud (от €3/месяц)
50
+
51
+ ### 4. 🏠 **Локальная сеть + DDNS**
52
+ - No-IP, DuckDNS для динамического DNS
53
+ - Настройка роутера для port forwarding
54
+
55
+ ## 🛠️ Текущее быстрое решение
56
+
57
+ Когда LocalTunnel отваливается:
58
+
59
+ 1. **Перезапустить туннель:**
60
+ ```bash
61
+ npx localtunnel --port 8080
62
+ ```
63
+
64
+ 2. **Обновить URL:**
65
+ ```bash
66
+ python update_tunnel.py https://new-tunnel-url.loca.lt
67
+ ```
68
+
69
+ 3. **Перезапустить бота:**
70
+ ```bash
71
+ python run_telegram_bot.py
72
+ ```
73
+
74
+ ## 📝 Автоматизация
75
+
76
+ Можно создать скрипт для автоматического перезапуска при падении туннеля:
77
+
78
+ ```bash
79
+ # restart_tunnel.sh
80
+ while true; do
81
+ npx localtunnel --port 8080
82
+ echo "Tunnel disconnected, restarting in 5 seconds..."
83
+ sleep 5
84
+ done
85
+ ```
deploy_changes.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Скрипт для быстрого деплоя изменений
4
+ Usage: python deploy_changes.py "commit message"
5
+ """
6
+
7
+ import sys
8
+ import subprocess
9
+ import os
10
+
11
+ def run_command(command, description):
12
+ """Выполнить команду с описанием"""
13
+ print(f"🔄 {description}...")
14
+ try:
15
+ result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True)
16
+ print(f"✅ {description} - успешно")
17
+ return True
18
+ except subprocess.CalledProcessError as e:
19
+ print(f"❌ {description} - ошибка: {e.stderr}")
20
+ return False
21
+
22
+ def main():
23
+ if len(sys.argv) != 2:
24
+ print("Usage: python deploy_changes.py \"commit message\"")
25
+ print("Example: python deploy_changes.py \"Fix telegram bot MCP parameters\"")
26
+ sys.exit(1)
27
+
28
+ commit_message = sys.argv[1]
29
+
30
+ print("🚀 Начинаем деплой изменений...")
31
+
32
+ # 1. Git add
33
+ if not run_command("git add .", "Добавление файлов в git"):
34
+ return
35
+
36
+ # 2. Git commit
37
+ if not run_command(f'git commit -m "{commit_message}"', "Создание коммита"):
38
+ return
39
+
40
+ # 3. Git push
41
+ if not run_command("git push", "Отправка в удаленный репозиторий"):
42
+ return
43
+
44
+ print("\n✅ Изменения успешно отправлены!")
45
+ print("\n📋 Следующие шаги на удаленном сервере:")
46
+ print("1. git pull")
47
+ print("2. Перезапустить MCP сервер: python main.py --mode api --host 0.0.0.0 --port 8080")
48
+ print("3. Перезапустить Telegram бота: python run_telegram_bot.py")
49
+ print("\n🔗 Или используйте команду:")
50
+ print("ssh your-server 'cd /path/to/project && git pull && pkill -f main.py && python main.py --mode api --host 0.0.0.0 --port 8080 &'")
51
+
52
+ if __name__ == "__main__":
53
+ main()
run_telegram_bot.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ TubeMeta Telegram Bot Launcher
4
+ Run this script to start the Telegram bot that integrates with the MCP server.
5
+ """
6
+
7
+ import asyncio
8
+ import sys
9
+ import logging
10
+ from telegram_bot import main
11
+
12
+ def setup_logging():
13
+ """Setup logging configuration"""
14
+ logging.basicConfig(
15
+ level=logging.INFO,
16
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
17
+ handlers=[
18
+ logging.FileHandler('telegram_bot.log'),
19
+ logging.StreamHandler(sys.stdout)
20
+ ]
21
+ )
22
+
23
+ if __name__ == "__main__":
24
+ print("🤖 Starting TubeMeta Telegram Bot...")
25
+ print("📋 Make sure your MCP server is running at: https://ag-source-knowledge-internal.trycloudflare.com")
26
+ print("📱 Bot username: @tubemeta_bot")
27
+ print("🔗 Bot link: https://t.me/tubemeta_bot")
28
+ print("⏹️ Press Ctrl+C to stop\n")
29
+
30
+ setup_logging()
31
+
32
+ try:
33
+ asyncio.run(main())
34
+ except KeyboardInterrupt:
35
+ print("\n🛑 Bot stopped by user")
36
+ except Exception as e:
37
+ print(f"❌ Error starting bot: {e}")
38
+ logging.error(f"Bot startup error: {e}")
39
+ sys.exit(1)
telegram_bot.py ADDED
@@ -0,0 +1,342 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import json
3
+ import logging
4
+ from typing import Optional
5
+ import aiohttp
6
+ from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
7
+ from telegram.ext import Application, CommandHandler, MessageHandler, CallbackQueryHandler, filters, ContextTypes
8
+ from telegram.constants import ParseMode
9
+ import os
10
+ from dotenv import load_dotenv
11
+
12
+ # Load environment variables
13
+ load_dotenv()
14
+
15
+ # Configuration
16
+ TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN", "8168377961:AAHG-9KyFczCTkBo92ZS9OYKIY62s-NmuVo")
17
+ MCP_BASE_URL = os.getenv("MCP_BASE_URL", "https://ag-source-knowledge-internal.trycloudflare.com/api/mcp")
18
+
19
+ # Set up logging
20
+ logging.basicConfig(
21
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
22
+ level=logging.INFO
23
+ )
24
+ logger = logging.getLogger(__name__)
25
+
26
+ class TubeMetaBot:
27
+ def __init__(self):
28
+ self.app = Application.builder().token(TELEGRAM_TOKEN).build()
29
+ self.setup_handlers()
30
+
31
+ def setup_handlers(self):
32
+ """Set up command and message handlers"""
33
+ self.app.add_handler(CommandHandler("start", self.start_command))
34
+ self.app.add_handler(CommandHandler("help", self.help_command))
35
+ self.app.add_handler(CommandHandler("search", self.search_command))
36
+ self.app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, self.handle_message))
37
+ self.app.add_handler(CallbackQueryHandler(self.handle_callback))
38
+
39
+ async def start_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
40
+ """Handle /start command"""
41
+ welcome_text = """
42
+ 🎬 **Welcome to TubeMeta Bot!**
43
+
44
+ I can help you with YouTube videos:
45
+ • 🔍 Search for videos
46
+ • 📊 Get video metadata
47
+ • 📝 Extract transcripts
48
+ • ⏰ Generate AI timecodes with Gemini 2.0
49
+
50
+ **How to use:**
51
+ • Send me a YouTube URL for full analysis
52
+ • Use `/search <query>` to find videos
53
+ • Send any text to search YouTube
54
+
55
+ Type `/help` for more information!
56
+ """
57
+ await update.message.reply_text(welcome_text, parse_mode=ParseMode.MARKDOWN)
58
+
59
+ async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
60
+ """Handle /help command"""
61
+ help_text = """
62
+ 🤖 **TubeMeta Bot Help**
63
+
64
+ **Commands:**
65
+ • `/start` - Welcome message
66
+ • `/help` - Show this help
67
+ • `/search <query>` - Search YouTube videos
68
+
69
+ **Features:**
70
+ • 🔍 **Video Search** - Find YouTube videos by keywords
71
+ • 📊 **Video Info** - Get detailed metadata (title, duration, views, etc.)
72
+ • 📝 **Transcripts** - Extract video transcripts/subtitles
73
+ • ⏰ **AI Timecodes** - Generate smart timecodes with Gemini 2.0
74
+
75
+ **Usage Examples:**
76
+ • Send YouTube URL: `https://youtu.be/dQw4w9WgXcQ`
77
+ • Search: `/search machine learning tutorial`
78
+ • Or just send: `python programming`
79
+
80
+ **Supported Languages:**
81
+ 🇺🇦 Ukrainian | 🇷🇺 Russian | 🇬🇧 English
82
+
83
+ Powered by Gemini 2.0 AI 🧠
84
+ """
85
+ await update.message.reply_text(help_text, parse_mode=ParseMode.MARKDOWN)
86
+
87
+ async def search_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
88
+ """Handle /search command"""
89
+ if not context.args:
90
+ await update.message.reply_text("Please provide a search query. Example: `/search python tutorial`")
91
+ return
92
+
93
+ query = " ".join(context.args)
94
+ await self.handle_search(update, query)
95
+
96
+ async def handle_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
97
+ """Handle regular text messages"""
98
+ text = update.message.text.strip()
99
+
100
+ # Check if it's a YouTube URL
101
+ if self.is_youtube_url(text):
102
+ await self.handle_youtube_url(update, text)
103
+ else:
104
+ # Treat as search query
105
+ await self.handle_search(update, text)
106
+
107
+ def is_youtube_url(self, text: str) -> bool:
108
+ """Check if text contains a YouTube URL"""
109
+ youtube_domains = [
110
+ 'youtube.com', 'youtu.be', 'www.youtube.com',
111
+ 'm.youtube.com', 'music.youtube.com'
112
+ ]
113
+ return any(domain in text.lower() for domain in youtube_domains)
114
+
115
+ async def handle_youtube_url(self, update: Update, url: str):
116
+ """Handle YouTube URL - provide full analysis options"""
117
+ # Send initial message
118
+ processing_msg = await update.message.reply_text("🔍 Analyzing YouTube video...")
119
+
120
+ try:
121
+ # Get basic video info first
122
+ video_info = await self.call_mcp_action("video_info", {"video_id": url})
123
+
124
+ if not video_info or "error" in video_info:
125
+ await processing_msg.edit_text("❌ Could not analyze this YouTube video. Please check the URL.")
126
+ return
127
+
128
+ # Format video info
129
+ info_text = self.format_video_info(video_info)
130
+
131
+ # Create action buttons
132
+ keyboard = [
133
+ [
134
+ InlineKeyboardButton("📝 Get Transcript", callback_data=f"transcript:{url}"),
135
+ InlineKeyboardButton("⏰ AI Timecodes", callback_data=f"timecodes:{url}")
136
+ ],
137
+ [
138
+ InlineKeyboardButton("🔍 Search Similar", callback_data=f"search:{video_info.get('title', 'related videos')}")
139
+ ]
140
+ ]
141
+ reply_markup = InlineKeyboardMarkup(keyboard)
142
+
143
+ await processing_msg.edit_text(info_text, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN)
144
+
145
+ except Exception as e:
146
+ logger.error(f"Error handling YouTube URL: {e}")
147
+ await processing_msg.edit_text("❌ An error occurred while analyzing the video.")
148
+
149
+ async def handle_search(self, update: Update, query: str):
150
+ """Handle search query"""
151
+ processing_msg = await update.message.reply_text(f"🔍 Searching for: *{query}*", parse_mode=ParseMode.MARKDOWN)
152
+
153
+ try:
154
+ results = await self.call_mcp_action("search", {"query": query, "max_results": 5})
155
+
156
+ if not results or "error" in results:
157
+ await processing_msg.edit_text("❌ No results found for your search.")
158
+ return
159
+
160
+ # Format search results
161
+ search_text = f"🔍 **Search Results for:** {query}\n\n"
162
+
163
+ for i, video in enumerate(results.get("videos", []), 1):
164
+ search_text += f"**{i}. {video.get('title', 'Unknown Title')}**\n"
165
+ search_text += f"👤 {video.get('channel', 'Unknown Channel')}\n"
166
+ search_text += f"⏱️ {video.get('duration', 'Unknown')}\n"
167
+ search_text += f"👁️ {video.get('view_count', 'Unknown')} views\n"
168
+ search_text += f"🔗 {video.get('url', '')}\n\n"
169
+
170
+ # Add search refinement buttons
171
+ keyboard = [
172
+ [InlineKeyboardButton("🔍 New Search", callback_data="new_search")]
173
+ ]
174
+ reply_markup = InlineKeyboardMarkup(keyboard)
175
+
176
+ await processing_msg.edit_text(search_text, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN)
177
+
178
+ except Exception as e:
179
+ logger.error(f"Error handling search: {e}")
180
+ await processing_msg.edit_text("❌ An error occurred during search.")
181
+
182
+ async def handle_callback(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
183
+ """Handle inline keyboard callbacks"""
184
+ query = update.callback_query
185
+ await query.answer()
186
+
187
+ data = query.data
188
+
189
+ if data.startswith("transcript:"):
190
+ url = data.replace("transcript:", "")
191
+ await self.get_transcript(query, url)
192
+ elif data.startswith("timecodes:"):
193
+ url = data.replace("timecodes:", "")
194
+ await self.get_timecodes(query, url)
195
+ elif data.startswith("search:"):
196
+ search_query = data.replace("search:", "")
197
+ await self.handle_search_callback(query, search_query)
198
+ elif data == "new_search":
199
+ await query.edit_message_text("🔍 Send me a search query or YouTube URL!")
200
+
201
+ async def get_transcript(self, query, url: str):
202
+ """Get video transcript"""
203
+ await query.edit_message_text("📝 Extracting transcript...")
204
+
205
+ try:
206
+ transcript = await self.call_mcp_action("transcript", {"video_id": url})
207
+
208
+ if not transcript or "error" in transcript:
209
+ await query.edit_message_text("❌ Could not extract transcript. Video may not have subtitles or may be restricted.")
210
+ return
211
+
212
+ # Format transcript
213
+ transcript_text = f"📝 **Transcript**\n\n{transcript.get('text', 'No transcript available')}"
214
+
215
+ # Telegram message limit is 4096 characters
216
+ if len(transcript_text) > 4000:
217
+ transcript_text = transcript_text[:4000] + "...\n\n*Transcript truncated due to length*"
218
+
219
+ # Add back button
220
+ keyboard = [[InlineKeyboardButton("⬅️ Back", callback_data=f"back:{url}")]]
221
+ reply_markup = InlineKeyboardMarkup(keyboard)
222
+
223
+ await query.edit_message_text(transcript_text, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN)
224
+
225
+ except Exception as e:
226
+ logger.error(f"Error getting transcript: {e}")
227
+ await query.edit_message_text("❌ An error occurred while extracting transcript.")
228
+
229
+ async def get_timecodes(self, query, url: str):
230
+ """Generate AI timecodes"""
231
+ await query.edit_message_text("⏰ Generating AI timecodes with Gemini 2.0...")
232
+
233
+ try:
234
+ timecodes = await self.call_mcp_action("gemini_timecodes", {
235
+ "video_id": url,
236
+ "language_code": "en",
237
+ "format": "youtube"
238
+ })
239
+
240
+ if not timecodes or "error" in timecodes:
241
+ await query.edit_message_text("❌ Could not generate timecodes. Video may not have transcript or may be restricted.")
242
+ return
243
+
244
+ # Format timecodes
245
+ timecodes_text = f"⏰ **AI Generated Timecodes**\n\n{timecodes.get('timecodes', 'No timecodes generated')}"
246
+
247
+ # Telegram message limit
248
+ if len(timecodes_text) > 4000:
249
+ timecodes_text = timecodes_text[:4000] + "...\n\n*Timecodes truncated due to length*"
250
+
251
+ # Add back button
252
+ keyboard = [[InlineKeyboardButton("⬅️ Back", callback_data=f"back:{url}")]]
253
+ reply_markup = InlineKeyboardMarkup(keyboard)
254
+
255
+ await query.edit_message_text(timecodes_text, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN)
256
+
257
+ except Exception as e:
258
+ logger.error(f"Error generating timecodes: {e}")
259
+ await query.edit_message_text("❌ An error occurred while generating timecodes.")
260
+
261
+ async def handle_search_callback(self, query, search_query: str):
262
+ """Handle search from callback"""
263
+ await query.edit_message_text(f"🔍 Searching for: *{search_query}*", parse_mode=ParseMode.MARKDOWN)
264
+
265
+ # Simulate update object for reuse of search logic
266
+ class FakeUpdate:
267
+ def __init__(self, message):
268
+ self.message = message
269
+
270
+ fake_update = FakeUpdate(query.message)
271
+ await self.handle_search(fake_update, search_query)
272
+
273
+ async def call_mcp_action(self, action: str, params: dict) -> Optional[dict]:
274
+ """Call MCP server action"""
275
+ try:
276
+ async with aiohttp.ClientSession() as session:
277
+ payload = {
278
+ "action": action,
279
+ "parameters": params
280
+ }
281
+
282
+ async with session.post(MCP_BASE_URL, json=payload, timeout=30) as response:
283
+ if response.status == 200:
284
+ return await response.json()
285
+ else:
286
+ logger.error(f"MCP server error: {response.status}")
287
+ return None
288
+ except Exception as e:
289
+ logger.error(f"Error calling MCP server: {e}")
290
+ return None
291
+
292
+ def format_video_info(self, video_info: dict) -> str:
293
+ """Format video information for display"""
294
+ title = video_info.get("title", "Unknown Title")
295
+ channel = video_info.get("channel", "Unknown Channel")
296
+ duration = video_info.get("duration", "Unknown")
297
+ view_count = video_info.get("view_count", "Unknown")
298
+ upload_date = video_info.get("upload_date", "Unknown")
299
+ description = video_info.get("description", "")
300
+
301
+ # Truncate description if too long
302
+ if len(description) > 200:
303
+ description = description[:200] + "..."
304
+
305
+ info_text = f"""🎬 **{title}**
306
+
307
+ 👤 **Channel:** {channel}
308
+ ⏱️ **Duration:** {duration}
309
+ 👁️ **Views:** {view_count}
310
+ 📅 **Uploaded:** {upload_date}
311
+
312
+ 📝 **Description:**
313
+ {description}
314
+
315
+ Choose an action below:"""
316
+
317
+ return info_text
318
+
319
+ async def run(self):
320
+ """Start the bot"""
321
+ logger.info("Starting TubeMeta Bot...")
322
+ await self.app.initialize()
323
+ await self.app.start()
324
+ await self.app.updater.start_polling()
325
+
326
+ try:
327
+ # Keep the bot running
328
+ await asyncio.Event().wait()
329
+ except KeyboardInterrupt:
330
+ logger.info("Shutting down bot...")
331
+ finally:
332
+ await self.app.updater.stop()
333
+ await self.app.stop()
334
+ await self.app.shutdown()
335
+
336
+ async def main():
337
+ """Main function"""
338
+ bot = TubeMetaBot()
339
+ await bot.run()
340
+
341
+ if __name__ == "__main__":
342
+ asyncio.run(main())
telegram_requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ python-telegram-bot>=21.0
2
+ aiohttp>=3.10.0
3
+ python-dotenv>=1.0.0
update_server.sh ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Скрипт для обновления сервера после git push
3
+
4
+ echo "🔄 Обновление сервера..."
5
+
6
+ # 1. Остановка текущих процессов
7
+ echo "⏹️ Остановка текущих процессов..."
8
+ pkill -f "main.py" || echo "MCP сервер не был запущен"
9
+ pkill -f "run_telegram_bot.py" || echo "Telegram бот не был запущен"
10
+
11
+ # 2. Обновление кода
12
+ echo "📥 Получение последних изменений..."
13
+ git pull
14
+
15
+ # 3. Проверка и установка зависимостей
16
+ echo "📦 Проверка зависимостей..."
17
+ pip install -r requirements.txt --quiet
18
+ pip install -r telegram_requirements.txt --quiet
19
+
20
+ # 4. Запуск MCP сервера в фоне
21
+ echo "🚀 Запуск MCP сервера..."
22
+ nohup python main.py --mode api --host 0.0.0.0 --port 8080 > mcp_server.log 2>&1 &
23
+ MCP_PID=$!
24
+ echo "MCP сервер запущен с PID: $MCP_PID"
25
+
26
+ # 5. Ждем 3 секунды для запуска MCP сервера
27
+ sleep 3
28
+
29
+ # 6. Запуск Telegram бота в фоне
30
+ echo "🤖 Запуск Telegram бота..."
31
+ nohup python run_telegram_bot.py > telegram_bot.log 2>&1 &
32
+ BOT_PID=$!
33
+ echo "Telegram бот запущен с PID: $BOT_PID"
34
+
35
+ echo "✅ Сервер обновлен и перезапущен!"
36
+ echo "📋 Процессы:"
37
+ echo " MCP сервер PID: $MCP_PID"
38
+ echo " Telegram бот PID: $BOT_PID"
39
+ echo ""
40
+ echo "📊 Для просмотра логов:"
41
+ echo " MCP сервер: tail -f mcp_server.log"
42
+ echo " Telegram бот: tail -f telegram_bot.log"
update_tunnel.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Script to update MCP server URL when LocalTunnel changes
4
+ Usage: python update_tunnel.py <new_tunnel_url>
5
+ """
6
+
7
+ import sys
8
+ import re
9
+
10
+ def update_tunnel_url(new_url):
11
+ """Update the MCP_BASE_URL in telegram_config.env"""
12
+
13
+ # Validate URL format
14
+ if not new_url.startswith('https://') or '.loca.lt' not in new_url:
15
+ print("❌ Invalid URL format. Expected: https://xxx.loca.lt")
16
+ return False
17
+
18
+ # Add /api/mcp if not present
19
+ if not new_url.endswith('/api/mcp'):
20
+ if new_url.endswith('/'):
21
+ new_url = new_url + 'api/mcp'
22
+ else:
23
+ new_url = new_url + '/api/mcp'
24
+
25
+ try:
26
+ # Read current config
27
+ with open('telegram_config.env', 'r', encoding='utf-8') as f:
28
+ content = f.read()
29
+
30
+ # Update MCP_BASE_URL
31
+ updated_content = re.sub(
32
+ r'MCP_BASE_URL=.*',
33
+ f'MCP_BASE_URL={new_url}',
34
+ content
35
+ )
36
+
37
+ # Write back
38
+ with open('telegram_config.env', 'w', encoding='utf-8') as f:
39
+ f.write(updated_content)
40
+
41
+ print(f"✅ Updated MCP_BASE_URL to: {new_url}")
42
+ print("🔄 Please restart the Telegram bot to apply changes")
43
+ return True
44
+
45
+ except Exception as e:
46
+ print(f"❌ Error updating config: {e}")
47
+ return False
48
+
49
+ if __name__ == "__main__":
50
+ if len(sys.argv) != 2:
51
+ print("Usage: python update_tunnel.py <new_tunnel_url>")
52
+ print("Example: python update_tunnel.py https://abc-def-ghi.loca.lt")
53
+ sys.exit(1)
54
+
55
+ new_url = sys.argv[1]
56
+ update_tunnel_url(new_url)