akdNIKY commited on
Commit
91e7eb3
·
verified ·
1 Parent(s): 0de365d

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +17 -0
  2. app.py +751 -0
  3. channels.json +1 -0
  4. requirements.txt +4 -0
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ # نصب ffmpeg و پیش‌نیازها
4
+ RUN apt-get update && apt-get install -y ffmpeg && apt-get clean
5
+
6
+ # تنظیم دایرکتوری کاری
7
+ WORKDIR /app
8
+
9
+ # کپی فایل‌های پروژه
10
+ COPY requirements.txt .
11
+ COPY app.py .
12
+
13
+ # نصب وابستگی‌ها
14
+ RUN pip install --no-cache-dir -r requirements.txt
15
+
16
+ # اجرای برنامه
17
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,751 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import re
4
+ import random
5
+ import subprocess
6
+ import json
7
+ from telegram import Update, InputFile, InlineKeyboardButton, InlineKeyboardMarkup
8
+ from telegram.ext import Application, CommandHandler, MessageHandler, CallbackQueryHandler, filters, ConversationHandler
9
+ from pydub import AudioSegment
10
+ from io import BytesIO
11
+ from apscheduler.schedulers.asyncio import AsyncIOScheduler
12
+
13
+ # تنظیمات اولیه
14
+ TOKEN = os.getenv('TELEGRAM_BOT_TOKEN')
15
+ if not TOKEN:
16
+ raise ValueError("TELEGRAM_BOT_TOKEN not set in Secrets")
17
+ ADMIN_ID = int(os.getenv('ADMIN_ID', '0'))
18
+ if not ADMIN_ID:
19
+ print("Warning: ADMIN_ID not set, admin features may not work", file=sys.stderr)
20
+
21
+ BOT_USERNAME = "Voice2mp3_RoBot"
22
+ DOWNLOAD_DIR = "/tmp/downloads"
23
+ OUTPUT_DIR = "/tmp/outputs"
24
+ CHANNELS_FILE = "channels.json"
25
+ os.makedirs(DOWNLOAD_DIR, exist_ok=True)
26
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
27
+
28
+ # بارگذاری کانال‌های اجباری
29
+ def load_required_channels():
30
+ if os.path.exists(CHANNELS_FILE):
31
+ try:
32
+ with open(CHANNELS_FILE, 'r', encoding='utf-8') as f:
33
+ return json.load(f)
34
+ except json.JSONDecodeError:
35
+ print("Error decoding JSON from channels.json", file=sys.stderr)
36
+ return []
37
+ return []
38
+
39
+ def save_required_channels(channels):
40
+ with open(CHANNELS_FILE, 'w', encoding='utf-8') as f:
41
+ json.dump(channels, f, indent=4, ensure_ascii=False)
42
+
43
+ REQUIRED_CHANNELS = load_required_channels()
44
+
45
+ # تعریف حالت‌های مکالمه
46
+ LANGUAGE_SELECTION, MAIN_MENU, CONVERT_AUDIO, CUT_AUDIO_FILE, CUT_AUDIO_RANGE, VIDEO_CONVERSION_MODE, WAITING_FOR_MEMBERSHIP, ADMIN_MENU, ADD_CHANNEL, LIST_REMOVE_CHANNELS = range(10)
47
+
48
+ # فرهنگ لغت پیام‌ها
49
+ MESSAGES = {
50
+ 'fa': {
51
+ 'start_welcome': "سلام! من یک ربات تبدیل فرمت صوتی و ویدیویی هستم.\n\nبرای شروع، از منوی زیر یک قابلیت را انتخاب کنید.",
52
+ 'choose_language': "زبان مورد نظر خود را انتخاب کنید:",
53
+ 'processing_start': "⏳ در حال شروع پردازش...",
54
+ 'file_received': "⬇️ فایل دریافت شد. در حال تبدیل...",
55
+ 'conversion_done': "⚙️ تبدیل فرمت انجام شد. در حال ارسال...",
56
+ 'mp3_to_voice_reply': "ویس تلگرام شما (تبدیل شده از MP3)",
57
+ 'voice_to_mp3_caption': "فایل MP3 شما (تبدیل شده از ویس تلگرام)",
58
+ 'error_mp3_to_voice': "❌ خطا در تبدیل MP3 به ویس تلگرام: ",
59
+ 'error_voice_to_mp3': "❌ خطا در تبدیل ویس تلگرام به MP3: ",
60
+ 'general_error': "متاسفم، مشکلی پیش آمد. لطفاً دوباره تلاش کنید.",
61
+ 'main_menu_prompt': "چه کاری می‌خواهید روی فایل خود انجام دهید؟",
62
+ 'btn_convert_format': "تغییر فرمت صدا 🎵",
63
+ 'btn_cut_audio': "برش قسمتی از صدا ✂️",
64
+ 'btn_video_conversion': "تبدیل ویدیو دایره‌ای 🎥",
65
+ 'convert_mode_active': "شما در حالت 'تغییر فرمت صدا' هستید. حالا فایل صوتی (ویس یا MP3) خود را برای من ارسال کنید.",
66
+ 'cut_mode_active_file': "شما در قسمت 'برش و کات کردن صدا' هستید.\n\nابتدا فایل صوتی (MP3 یا ویس) خود را ارسال کنید.",
67
+ 'cut_mode_active_range': "حالا بازه زمانی مورد نظر برای برش را به صورت 'دقیقه.ثانیه-دقیقه.ثانیه' (مثال: 00.21-00.54) ارسال کنید.",
68
+ 'invalid_time_format': "فرمت زمان وارد شده صحیح نیست. لطفاً از فرمت 'MM.SS-MM.SS' استفاده کنید. (مثال: 00.21-00.54)",
69
+ 'invalid_time_range': "بازه زمانی نامعتبر است یا زمان پایان از زمان شروع کمتر است. لطفاً بازه صحیح را وارد کنید.",
70
+ 'audio_cut_success': "✅ برش صدا با موفقیت انجام شد. فایل شما آماده است.",
71
+ 'no_audio_for_cut': "فایلی برای برش پیدا نشد. لطفاً ابتدا فایل صوتی را ارسال کنید.",
72
+ 'cut_processing': "✂️ در حال برش صدا...",
73
+ 'returning_to_main_menu': "بازگشت به منوی اصلی...",
74
+ 'cancel_message': "عملیات لغو شد. به منوی اصلی بازگشتید.",
75
+ 'video_conversion_mode_active': "شما در حالت 'تبدیل ویدیو دایره‌ای' هستید.\n\nیک ویدیو معمولی یا یک ویدیو دایره‌ای (Video Message) برای من ارسال کنید.",
76
+ 'file_received_video': "⬇️ فایل ویدیویی دریافت شد. در حال پردازش...",
77
+ 'converting_video_note_to_video': "🔄 در حال تبدیل ویدیو دایره‌ای به ویدیو معمولی...",
78
+ 'converting_video_to_video_note': "🔄 در حال تبدیل ویدیو معمولی به ویدیو دایره‌ای...",
79
+ 'conversion_done_video': "✅ تبدیل ویدیو با موفقیت انجام شد. در حال ارسال...",
80
+ 'video_note_to_video_caption': "ویدیو معمولی شما (تبدیل شده از ویدیو دایره‌ای)",
81
+ 'video_to_video_note_reply': "ویدیو دایره‌ای شما (تبدیل شده از ویدیو معمولی)",
82
+ 'error_video_conversion': "❌ خطا در تبدیل ویدیو: ",
83
+ 'invalid_file_type_video': "لطفاً یک فایل ویدیویی یا ویدیو دایره‌ای ارسال کنید.",
84
+ 'membership_required': "برای ادامه کار با ربات و استفاده نامحدود، لطفاً ابتدا عضو کانال‌های زیر شوید:",
85
+ 'btn_join_channel': "عضو شدن 🤝",
86
+ 'btn_check_membership': "بررسی عضویت ✅",
87
+ 'membership_success': "✅ عضویت شما تأیید شد! اکنون می‌توانید به صورت نامحدود از ربات استفاده کنید.",
88
+ 'membership_failed': "❌ متاسفم، شما هنوز عضو تمام کانال‌های مورد نیاز نیستید. لطفاً ابتدا عضو شوید و سپس دوباره 'بررسی عضویت' را بزنید.",
89
+ 'not_admin': "شما اجازه دسترسی به این بخش را ندارید.",
90
+ 'admin_menu_prompt': "به پنل مدیریت لینک‌ها خوش آمدید:",
91
+ 'btn_add_channel': "افزودن لینک کانال ➕",
92
+ 'btn_list_channels': "لیست کانال‌ها و حذف 🗑️",
93
+ 'send_channel_link': "لطفاً لینک (مانند @mychannel) یا آیدی عددی کانال را ارسال کنید:",
94
+ 'channel_added': "✅ کانال '{channel_id}' با موفقیت اضافه شد.",
95
+ 'channel_already_exists': "❗️ این کانال قبلاً اضافه شده است.",
96
+ 'no_channels_configured': "هیچ کانالی برای عضویت پیکربندی نشده است.",
97
+ 'channel_list_prompt': "لیست کانال‌های فعلی برای عضویت اجباری:",
98
+ 'btn_remove_channel': "حذف ❌",
99
+ 'channel_removed': "✅ کانال '{channel_id}' با موفقیت حذف شد.",
100
+ 'channel_not_found': "❗️ کانال مورد نظر یافت نشد.",
101
+ 'invalid_channel_id': "آیدی/لینک کانال نامعتبر است. لطفاً @username یا آیدی عددی (مانند -1001234567890) را ارسال کنید.",
102
+ 'bot_not_admin_in_channel': "ربات ادمین کانال '{channel_id}' نیست یا مجوزهای کافی برای بررسی عضویت را ندارد. لطفاً ربات را به عنوان ادمین با مجوز 'بررسی وضعیت اعضا' در کانال اضافه کنید.",
103
+ 'group_only_command': "❌ این دستور فقط در گروه‌ها قابل اجراست.",
104
+ 'not_group_admin': "❌ شما ادمین این گروه نیستید و نمی‌توانید این دستور را اجرا کنید.",
105
+ 'random_message_already_active': "❗️ ارسال پیام‌های تصادفی در این گروه قبلاً فعال شده است.",
106
+ 'random_message_not_active': "❗️ ارسال پیام‌های تصادفی در این گروه فعال نیست.",
107
+ 'random_message_started': "✅ ارسال پیام‌های تصادفی در گروه فعال شد.\nپیام بعدی {interval} دیگر ارسال می‌شود.",
108
+ 'random_message_stopped': "✅ ارسال پیام‌های تصادفی در گروه متوقف شد.",
109
+ 'admin_check_error': "❌ خطا در بررسی وضعیت ادمین."
110
+ },
111
+ 'en': {
112
+ 'start_welcome': "Hello! I am an audio and video format conversion bot.\n\nTo start, select a feature from the menu below.",
113
+ 'choose_language': "Choose your preferred language:",
114
+ 'processing_start': "⏳ Starting processing...",
115
+ 'file_received': "⬇️ File received. Processing...",
116
+ 'conversion_done': "⚙️ Conversion complete. Sending...",
117
+ 'mp3_to_voice_reply': "Your Telegram voice (converted from MP3)",
118
+ 'voice_to_mp3_caption': "Your MP3 file (converted from Telegram voice)",
119
+ 'error_mp3_to_voice': "❌ Error converting MP3 to Telegram voice: ",
120
+ 'error_voice_to_mp3': "❌ Error converting Telegram voice to MP3: ",
121
+ 'general_error': "Sorry, something went wrong. Please try again.",
122
+ 'main_menu_prompt': "What would you like to do with your file?",
123
+ 'btn_convert_format': "Change Audio Format 🎵",
124
+ 'btn_cut_audio': "Cut Part of Audio ✂️",
125
+ 'btn_video_conversion': "Convert Circular Video 🎥",
126
+ 'convert_mode_active': "You are now in 'Change Audio Format' mode. Send me your audio file (voice or MP3).",
127
+ 'cut_mode_active_file': "You are in the 'Cut Audio' section.\n\nFirst, send your audio file (MP3 or voice).",
128
+ 'cut_mode_active_range': "Now send the desired time range for cutting in 'MM.SS-MM.SS' format (example: 00.21-00.54).",
129
+ 'invalid_time_format': "Invalid time format. Please use 'MM.SS-MM.SS' format. (example: 00.21-00.54)",
130
+ 'invalid_time_range': "Invalid time range or end time is less than start time. Please enter a valid range.",
131
+ 'audio_cut_success': "✅ Audio cut successfully. Your file is ready.",
132
+ 'no_audio_for_cut': "No audio file found for cutting. Please send the audio file first.",
133
+ 'cut_processing': "✂️ Cutting audio...",
134
+ 'returning_to_main_menu': "Returning to main menu...",
135
+ 'cancel_message': "Operation cancelled. Returned to main menu.",
136
+ 'video_conversion_mode_active': "You are in 'Circular Video Conversion' mode.\n\nSend me a regular video or a circular video message (Video Message).",
137
+ 'file_received_video': "⬇️ Video file received. Processing...",
138
+ 'converting_video_note_to_video': "🔄 Converting circular video to regular video...",
139
+ 'converting_video_to_video_note': "🔄 Converting regular video to circular video...",
140
+ 'conversion_done_video': "✅ Video conversion successful. Sending...",
141
+ 'video_note_to_video_caption': "Your regular video (converted from circular video)",
142
+ 'video_to_video_note_reply': "Your circular video (converted from regular video)",
143
+ 'error_video_conversion': "❌ Error converting video: ",
144
+ 'invalid_file_type_video': "Please send a video file or a video message.",
145
+ 'membership_required': "To continue using the bot and access unlimited features, please join the following channels first:",
146
+ 'btn_join_channel': "Join Channel 🤝",
147
+ 'btn_check_membership': "Check Membership ✅",
148
+ 'membership_success': "✅ Your membership has been verified! You can now use the bot unlimitedly.",
149
+ 'membership_failed': "❌ Sorry, you are not yet a member of all required channels. Please join first and then press 'Check Membership' again.",
150
+ 'not_admin': "You do not have permission to access this section.",
151
+ 'admin_menu_prompt': "Welcome to the link management panel:",
152
+ 'btn_add_channel': "Add Channel Link ➕",
153
+ 'btn_list_channels': "List Channels & Remove 🗑️",
154
+ 'send_channel_link': "Please send the channel link (e.g., @mychannel) or numeric ID:",
155
+ 'channel_added': "✅ Channel '{channel_id}' successfully added.",
156
+ 'channel_already_exists': "❗️ This channel has already been added.",
157
+ 'no_channels_configured': "No channels configured for membership.",
158
+ 'channel_list_prompt': "Current list of channels for mandatory membership:",
159
+ 'btn_remove_channel': "Remove ❌",
160
+ 'channel_removed': "✅ Channel '{channel_id}' successfully removed.",
161
+ 'channel_not_found': "❗️ Channel not found.",
162
+ 'invalid_channel_id': "Invalid channel ID/link. Please send @username or numeric ID (e.g., -1001234567890).",
163
+ 'bot_not_admin_in_channel': "The bot is not an admin in channel '{channel_id}' or does not have sufficient permissions to check membership. Please add the bot as an admin with 'Check members' permission in the channel.",
164
+ 'group_only_command': "❌ This command can only be used in groups.",
165
+ 'not_group_admin': "❌ You are not an admin of this group and cannot execute this command.",
166
+ 'random_message_already_active': "❗️ Random message sending is already active in this group.",
167
+ 'random_message_not_active': "❗️ Random message sending is not active in this group.",
168
+ 'random_message_started': "✅ Random message sending activated in the group.\nNext message will be sent in {interval}.",
169
+ 'random_message_stopped': "✅ Random message sending stopped in the group.",
170
+ 'admin_check_error': "❌ Error checking admin status."
171
+ }
172
+ }
173
+
174
+ def get_message(context, key, **kwargs):
175
+ lang = context.user_data.get('language', 'fa')
176
+ message_template = MESSAGES[lang].get(key, MESSAGES['fa'][key])
177
+ return message_template.format(**kwargs)
178
+
179
+ def parse_time_to_ms(time_str):
180
+ match = re.match(r'^(\d{2})\.(\d{2})$', time_str)
181
+ if not match:
182
+ raise ValueError("Invalid time format")
183
+ minutes = int(match.group(1))
184
+ seconds = int(match.group(2))
185
+ if seconds >= 60:
186
+ raise ValueError("Seconds must be between 00 and 59")
187
+ return (minutes * 60 + seconds) * 1000
188
+
189
+ async def check_user_membership(update: Update, context):
190
+ user_id = update.effective_user.id
191
+ if not REQUIRED_CHANNELS:
192
+ return True
193
+ all_channels_joined = True
194
+ for channel_id in REQUIRED_CHANNELS:
195
+ try:
196
+ chat_member = await context.bot.get_chat_member(chat_id=channel_id, user_id=user_id)
197
+ if chat_member.status not in ['member', 'administrator', 'creator']:
198
+ all_channels_joined = False
199
+ break
200
+ except Exception as e:
201
+ print(f"Error checking membership for channel {channel_id}: {e}", file=sys.stderr)
202
+ all_channels_joined = False
203
+ break
204
+ return all_channels_joined
205
+
206
+ async def show_membership_required_message(update: Update, context):
207
+ keyboard = []
208
+ if not REQUIRED_CHANNELS:
209
+ return await show_main_menu(update, context)
210
+ for channel_id in REQUIRED_CHANNELS:
211
+ try:
212
+ chat = await context.bot.get_chat(chat_id=channel_id)
213
+ if chat.invite_link:
214
+ keyboard.append([InlineKeyboardButton(f"{get_message(context, 'btn_join_channel')} {chat.title or channel_id}", url=chat.invite_link)])
215
+ elif chat.username:
216
+ keyboard.append([InlineKeyboardButton(f"{get_message(context, 'btn_join_channel')} @{chat.username}", url=f"https://t.me/{chat.username}")])
217
+ else:
218
+ keyboard.append([InlineKeyboardButton(f"{get_message(context, 'btn_join_channel')} {channel_id}", callback_data=f"no_link_{channel_id}")])
219
+ except Exception as e:
220
+ print(f"Could not get chat info for {channel_id}: {e}", file=sys.stderr)
221
+ keyboard.append([InlineKeyboardButton(f"{get_message(context, 'btn_join_channel')} {channel_id}", callback_data=f"no_link_{channel_id}")])
222
+ keyboard.append([InlineKeyboardButton(get_message(context, 'btn_check_membership'), callback_data='check_membership')])
223
+ reply_markup = InlineKeyboardMarkup(keyboard)
224
+ if update.callback_query:
225
+ await update.callback_query.edit_message_text(get_message(context, 'membership_required'), reply_markup=reply_markup)
226
+ else:
227
+ await update.effective_message.reply_text(get_message(context, 'membership_required'), reply_markup=reply_markup)
228
+ return WAITING_FOR_MEMBERSHIP
229
+
230
+ async def process_feature_or_check_membership(update: Update, context, feature_func, *args, **kwargs):
231
+ user_id = update.effective_user.id
232
+ if user_id == ADMIN_ID:
233
+ return await feature_func(update, context, *args, **kwargs)
234
+ if not REQUIRED_CHANNELS:
235
+ return await feature_func(update, context, *args, **kwargs)
236
+ if context.user_data.get('used_bot_once', False) and not context.user_data.get('is_member', False):
237
+ return await show_membership_required_message(update, context)
238
+ if not context.user_data.get('used_bot_once', False):
239
+ context.user_data['used_bot_once'] = True
240
+ is_member = await check_user_membership(update, context)
241
+ context.user_data['is_member'] = is_member
242
+ if not is_member:
243
+ return await show_membership_required_message(update, context)
244
+ result = await feature_func(update, context, *args, **kwargs)
245
+ if not context.user_data.get('is_member', False):
246
+ return await show_membership_required_message(update, context)
247
+ return result
248
+
249
+ async def check_membership_callback(update: Update, context):
250
+ query = update.callback_query
251
+ await query.answer()
252
+ is_member = await check_user_membership(update, context)
253
+ if is_member:
254
+ context.user_data['is_member'] = True
255
+ await query.edit_message_text(get_message(context, 'membership_success'))
256
+ return await show_main_menu(update, context)
257
+ else:
258
+ await query.edit_message_text(get_message(context, 'membership_failed'), reply_markup=query.message.reply_markup)
259
+ return WAITING_FOR_MEMBERSHIP
260
+
261
+ async def start(update: Update, context):
262
+ keyboard = [
263
+ [InlineKeyboardButton("فارسی 🇮🇷", callback_data='set_lang_fa')],
264
+ [InlineKeyboardButton("English 🇬🇧", callback_data='set_lang_en')]
265
+ ]
266
+ reply_markup = InlineKeyboardMarkup(keyboard)
267
+ await update.message.reply_text("زبان مورد نظر خود را انتخاب کنید:\nChoose your preferred language:", reply_markup=reply_markup)
268
+ return LANGUAGE_SELECTION
269
+
270
+ async def set_language(update: Update, context):
271
+ query = update.callback_query
272
+ await query.answer()
273
+ lang_code = query.data.replace('set_lang_', '')
274
+ context.user_data['language'] = lang_code
275
+ await query.edit_message_text(text=get_message(context, 'start_welcome'))
276
+ return await process_feature_or_check_membership(update, context, show_main_menu)
277
+
278
+ async def show_main_menu(update: Update, context):
279
+ keyboard = [
280
+ [InlineKeyboardButton(get_message(context, 'btn_convert_format'), callback_data='select_convert_format')],
281
+ [InlineKeyboardButton(get_message(context, 'btn_cut_audio'), callback_data='select_cut_audio')],
282
+ [InlineKeyboardButton(get_message(context, 'btn_video_conversion'), callback_data='select_video_conversion')]
283
+ ]
284
+ reply_markup = InlineKeyboardMarkup(keyboard)
285
+ if update.callback_query:
286
+ await update.callback_query.edit_message_text(text=get_message(context, 'main_menu_prompt'), reply_markup=reply_markup)
287
+ else:
288
+ await update.message.reply_text(text=get_message(context, 'main_menu_prompt'), reply_markup=reply_markup)
289
+ return MAIN_MENU
290
+
291
+ async def change_format_selected(update: Update, context):
292
+ query = update.callback_query
293
+ await query.answer()
294
+ await query.edit_message_text(text=get_message(context, 'convert_mode_active'))
295
+ return CONVERT_AUDIO
296
+
297
+ async def cut_audio_selected(update: Update, context):
298
+ query = update.callback_query
299
+ await query.answer()
300
+ await query.edit_message_text(text=get_message(context, 'cut_mode_active_file'))
301
+ context.user_data['audio_for_cut_path'] = None
302
+ return CUT_AUDIO_FILE
303
+
304
+ async def video_conversion_selected(update: Update, context):
305
+ query = update.callback_query
306
+ await query.answer()
307
+ await query.edit_message_text(text=get_message(context, 'video_conversion_mode_active'))
308
+ return VIDEO_CONVERSION_MODE
309
+
310
+ async def handle_audio(update: Update, context):
311
+ if not update.message.audio:
312
+ return CONVERT_AUDIO
313
+ async def _perform_conversion(update: Update, context):
314
+ file_id = update.message.audio.file_id
315
+ file_name = update.message.audio.file_name or f"audio_{file_id}.mp3"
316
+ processing_message = await update.message.reply_text(get_message(context, 'processing_start'))
317
+ download_path = os.path.join(DOWNLOAD_DIR, file_name)
318
+ output_ogg_path = os.path.join(OUTPUT_DIR, f"{os.path.splitext(file_name)[0]}.ogg")
319
+ try:
320
+ new_file = await context.bot.get_file(file_id)
321
+ await new_file.download_to_drive(download_path)
322
+ await processing_message.edit_text(get_message(context, 'file_received'))
323
+ audio = AudioSegment.from_mp3(download_path)
324
+ audio.export(output_ogg_path, format="ogg", codec="libopus", parameters=["-b:a", "32k"])
325
+ await processing_message.edit_text(get_message(context, 'conversion_done'))
326
+ with open(output_ogg_path, 'rb') as f:
327
+ sent_voice_message = await update.message.reply_voice(InputFile(f, filename=os.path.basename(output_ogg_path)))
328
+ if sent_voice_message:
329
+ await sent_voice_message.reply_text(get_message(context, 'mp3_to_voice_reply'))
330
+ await processing_message.delete()
331
+ return CONVERT_AUDIO
332
+ except Exception as e:
333
+ print(f"Error converting MP3 to voice: {e}", file=sys.stderr)
334
+ await processing_message.edit_text(get_message(context, 'error_mp3_to_voice') + str(e))
335
+ return CONVERT_AUDIO
336
+ finally:
337
+ if os.path.exists(download_path):
338
+ os.remove(download_path)
339
+ if os.path.exists(output_ogg_path):
340
+ os.remove(output_ogg_path)
341
+ return await process_feature_or_check_membership(update, context, _perform_conversion)
342
+
343
+ async def handle_voice(update: Update, context):
344
+ if not update.message.voice:
345
+ return CONVERT_AUDIO
346
+ async def _perform_conversion(update: Update, context):
347
+ file_id = update.message.voice.file_id
348
+ processing_message = await update.message.reply_text(get_message(context, 'processing_start'))
349
+ download_path = os.path.join(DOWNLOAD_DIR, f"voice_{file_id}.ogg")
350
+ output_mp3_path = os.path.join(OUTPUT_DIR, f"@{BOT_USERNAME}_{file_id}.mp3")
351
+ try:
352
+ new_file = await context.bot.get_file(file_id)
353
+ await new_file.download_to_drive(download_path)
354
+ await processing_message.edit_text(get_message(context, 'file_received'))
355
+ audio = AudioSegment.from_file(download_path, format="ogg")
356
+ export_tags = {'album': BOT_USERNAME, 'artist': BOT_USERNAME}
357
+ audio.export(output_mp3_path, format="mp3", tags=export_tags)
358
+ await processing_message.edit_text(get_message(context, 'conversion_done'))
359
+ with open(output_mp3_path, 'rb') as f:
360
+ await update.message.reply_audio(InputFile(f, filename=os.path.basename(output_mp3_path)), caption=get_message(context, 'voice_to_mp3_caption'))
361
+ await processing_message.delete()
362
+ return CONVERT_AUDIO
363
+ except Exception as e:
364
+ print(f"Error converting voice to MP3: {e}", file=sys.stderr)
365
+ await processing_message.edit_text(get_message(context, 'error_voice_to_mp3') + str(e))
366
+ return CONVERT_AUDIO
367
+ finally:
368
+ if os.path.exists(download_path):
369
+ os.remove(download_path)
370
+ if os.path.exists(output_mp3_path):
371
+ os.remove(output_mp3_path)
372
+ return await process_feature_or_check_membership(update, context, _perform_conversion)
373
+
374
+ async def handle_cut_audio_file(update: Update, context):
375
+ audio_file = update.message.audio or update.message.voice
376
+ if not audio_file:
377
+ await update.message.reply_text(get_message(context, 'no_audio_for_cut'))
378
+ return CUT_AUDIO_FILE
379
+ async def _perform_file_receive(update: Update, context):
380
+ file_id = audio_file.file_id
381
+ processing_message = await update.message.reply_text(get_message(context, 'processing_start'))
382
+ download_path = os.path.join(DOWNLOAD_DIR, f"cut_audio_{file_id}.{'mp3' if update.message.audio else 'ogg'}")
383
+ try:
384
+ new_file = await context.bot.get_file(file_id)
385
+ await new_file.download_to_drive(download_path)
386
+ context.user_data['audio_for_cut_path'] = download_path
387
+ context.user_data['audio_for_cut_type'] = 'mp3' if update.message.audio else 'ogg'
388
+ await processing_message.edit_text(get_message(context, 'cut_mode_active_range'))
389
+ await processing_message.delete()
390
+ return CUT_AUDIO_RANGE
391
+ except Exception as e:
392
+ print(f"Error handling cut audio file: {e}", file=sys.stderr)
393
+ await processing_message.edit_text(get_message(context, 'general_error') + str(e))
394
+ return CUT_AUDIO_FILE
395
+ return await process_feature_or_check_membership(update, context, _perform_file_receive)
396
+
397
+ async def handle_cut_audio_range(update: Update, context):
398
+ time_range_str = update.message.text
399
+ audio_path = context.user_data.get('audio_for_cut_path')
400
+ audio_type = context.user_data.get('audio_for_cut_type')
401
+ if not audio_path or not os.path.exists(audio_path):
402
+ await update.message.reply_text(get_message(context, 'no_audio_for_cut'))
403
+ return CUT_AUDIO_FILE
404
+ async def _perform_cut(update: Update, context):
405
+ processing_message = await update.message.reply_text(get_message(context, 'cut_processing'))
406
+ output_cut_path = None
407
+ try:
408
+ start_time_str, end_time_str = time_range_str.split('-')
409
+ start_ms = parse_time_to_ms(start_time_str.strip())
410
+ end_ms = parse_time_to_ms(end_time_str.strip())
411
+ if start_ms >= end_ms:
412
+ await processing_message.edit_text(get_message(context, 'invalid_time_range'))
413
+ return CUT_AUDIO_RANGE
414
+ audio = AudioSegment.from_file(audio_path, format=audio_type)
415
+ cut_audio = audio[start_ms:end_ms]
416
+ output_cut_path = os.path.join(OUTPUT_DIR, f"cut_{os.path.basename(audio_path).split('.')[0]}.mp3")
417
+ cut_audio.export(output_cut_path, format="mp3")
418
+ await processing_message.edit_text(get_message(context, 'audio_cut_success'))
419
+ with open(output_cut_path, 'rb') as f:
420
+ await update.message.reply_audio(InputFile(f, filename=os.path.basename(output_cut_path)), caption=f"برش از {start_time_str} تا {end_time_str}")
421
+ await processing_message.delete()
422
+ return await show_main_menu(update, context)
423
+ except ValueError as ve:
424
+ print(f"Time format or range error: {ve}", file=sys.stderr)
425
+ await processing_message.edit_text(get_message(context, 'invalid_time_format'))
426
+ return CUT_AUDIO_RANGE
427
+ except Exception as e:
428
+ print(f"Error cutting audio: {e}", file=sys.stderr)
429
+ await processing_message.edit_text(get_message(context, 'general_error') + str(e))
430
+ return CUT_AUDIO_FILE
431
+ finally:
432
+ if os.path.exists(audio_path):
433
+ os.remove(audio_path)
434
+ if 'audio_for_cut_path' in context.user_data:
435
+ del context.user_data['audio_for_cut_path']
436
+ if 'audio_for_cut_type' in context.user_data:
437
+ del context.user_data['audio_for_cut_type']
438
+ if output_cut_path and os.path.exists(output_cut_path):
439
+ os.remove(output_cut_path)
440
+ return await process_feature_or_check_membership(update, context, _perform_cut)
441
+
442
+ async def handle_video_conversion(update: Update, context):
443
+ file_to_process = None
444
+ is_video_note = False
445
+ if update.message.video:
446
+ file_to_process = update.message.video
447
+ file_name_prefix = "regular_video"
448
+ input_ext = ".mp4"
449
+ elif update.message.video_note:
450
+ file_to_process = update.message.video_note
451
+ is_video_note = True
452
+ file_name_prefix = "video_note"
453
+ input_ext = ".mp4"
454
+ else:
455
+ await update.message.reply_text(get_message(context, 'invalid_file_type_video'))
456
+ return VIDEO_CONVERSION_MODE
457
+
458
+ async def _perform_video_conversion(update: Update, context):
459
+ file_id = file_to_process.file_id
460
+ download_path = os.path.join(DOWNLOAD_DIR, f"{file_name_prefix}_{file_id}{input_ext}")
461
+ processing_message = await update.message.reply_text(get_message(context, 'processing_start'))
462
+ output_path = None
463
+ try:
464
+ new_file = await context.bot.get_file(file_id)
465
+ await new_file.download_to_drive(download_path)
466
+ await processing_message.edit_text(get_message(context, 'file_received_video'))
467
+ output_file_name = f"{file_name_prefix}_converted_{file_id}.mp4"
468
+ output_path = os.path.join(OUTPUT_DIR, output_file_name)
469
+ if is_video_note:
470
+ await processing_message.edit_text(get_message(context, 'converting_video_note_to_video'))
471
+ ffmpeg_command = [
472
+ 'ffmpeg', '-i', download_path,
473
+ '-c:v', 'libx264', '-crf', '23', '-preset', 'medium',
474
+ '-c:a', 'aac', '-b:a', '128k', '-movflags', '+faststart',
475
+ output_path
476
+ ]
477
+ subprocess.run(ffmpeg_command, check=True, capture_output=True)
478
+ await processing_message.edit_text(get_message(context, 'conversion_done_video'))
479
+ with open(output_path, 'rb') as f:
480
+ await update.message.reply_video(InputFile(f, filename=output_file_name), caption=get_message(context, 'video_note_to_video_caption'))
481
+ else:
482
+ await processing_message.edit_text(get_message(context, 'converting_video_to_video_note'))
483
+ ffmpeg_command = [
484
+ 'ffmpeg', '-i', download_path,
485
+ '-vf', 'crop=min(iw\,ih):min(iw\,ih),scale=640:640',
486
+ '-c:v', 'libx264', '-crf', '28', '-preset', 'veryfast',
487
+ '-c:a', 'aac', '-b:a', '64k', '-movflags', '+faststart',
488
+ output_path
489
+ ]
490
+ subprocess.run(ffmpeg_command, check=True, capture_output=True)
491
+ await processing_message.edit_text(get_message(context, 'conversion_done_video'))
492
+ with open(output_path, 'rb') as f:
493
+ await update.message.reply_video_note(InputFile(f, filename=output_file_name))
494
+ await update.message.reply_text(get_message(context, 'video_to_video_note_reply'))
495
+ await processing_message.delete()
496
+ return VIDEO_CONVERSION_MODE
497
+ except subprocess.CalledProcessError as e:
498
+ print(f"FFmpeg error: {e.stderr.decode()}", file=sys.stderr)
499
+ await processing_message.edit_text(get_message(context, 'error_video_conversion') + f"FFmpeg Error: {e.stderr.decode()}")
500
+ return VIDEO_CONVERSION_MODE
501
+ except Exception as e:
502
+ print(f"General video conversion error: {e}", file=sys.stderr)
503
+ await processing_message.edit_text(get_message(context, 'error_video_conversion') + str(e))
504
+ return VIDEO_CONVERSION_MODE
505
+ finally:
506
+ if os.path.exists(download_path):
507
+ os.remove(download_path)
508
+ if output_path and os.path.exists(output_path):
509
+ os.remove(output_path)
510
+ return await process_feature_or_check_membership(update, context, _perform_video_conversion)
511
+
512
+ async def cancel(update: Update, context):
513
+ await update.message.reply_text(get_message(context, 'cancel_message'))
514
+ if 'audio_for_cut_path' in context.user_data and os.path.exists(context.user_data['audio_for_cut_path']):
515
+ os.remove(context.user_data['audio_for_cut_path'])
516
+ del context.user_data['audio_for_cut_path']
517
+ if 'audio_for_cut_type' in context.user_data:
518
+ del context.user_data['audio_for_cut_type']
519
+ context.user_data['is_member'] = False
520
+ context.user_data['used_bot_once'] = False
521
+ return await show_main_menu(update, context)
522
+
523
+ async def error_handler(update: Update, context):
524
+ print(f"Update {update} caused error {context.error}", file=sys.stderr)
525
+ if update.message:
526
+ await update.message.reply_text(get_message(context, 'general_error'))
527
+ return ConversationHandler.END
528
+
529
+ async def admin_link_command(update: Update, context):
530
+ if update.effective_user.id != ADMIN_ID:
531
+ await update.message.reply_text(get_message(context, 'not_admin'))
532
+ return ConversationHandler.END
533
+ keyboard = [
534
+ [InlineKeyboardButton(get_message(context, 'btn_add_channel'), callback_data='admin_add_channel')],
535
+ [InlineKeyboardButton(get_message(context, 'btn_list_channels'), callback_data='admin_list_channels')]
536
+ ]
537
+ reply_markup = InlineKeyboardMarkup(keyboard)
538
+ await update.message.reply_text(get_message(context, 'admin_menu_prompt'), reply_markup=reply_markup)
539
+ return ADMIN_MENU
540
+
541
+ async def admin_add_channel_prompt(update: Update, context):
542
+ query = update.callback_query
543
+ await query.answer()
544
+ await query.edit_message_text(get_message(context, 'send_channel_link'))
545
+ return ADD_CHANNEL
546
+
547
+ async def admin_handle_add_channel(update: Update, context):
548
+ channel_input = update.message.text.strip()
549
+ if not (channel_input.startswith('@') or channel_input.startswith('-100')):
550
+ await update.message.reply_text(get_message(context, 'invalid_channel_id'))
551
+ return ADD_CHANNEL
552
+ try:
553
+ chat_member = await context.bot.get_chat_member(chat_id=channel_input, user_id=context.bot.id)
554
+ if not (chat_member.status == 'administrator' and chat_member.can_invite_users):
555
+ await update.message.reply_text(get_message(context, 'bot_not_admin_in_channel', channel_id=channel_input))
556
+ return ADD_CHANNEL
557
+ except Exception as e:
558
+ print(f"Error checking bot's admin status in channel {channel_input}: {e}", file=sys.stderr)
559
+ await update.message.reply_text(get_message(context, 'invalid_channel_id') + f"\nError: {e}")
560
+ return ADD_CHANNEL
561
+ if channel_input not in REQUIRED_CHANNELS:
562
+ REQUIRED_CHANNELS.append(channel_input)
563
+ save_required_channels(REQUIRED_CHANNELS)
564
+ await update.message.reply_text(get_message(context, 'channel_added', channel_id=channel_input))
565
+ else:
566
+ await update.message.reply_text(get_message(context, 'channel_already_exists'))
567
+ return await admin_link_command(update, context)
568
+
569
+ async def admin_list_channels(update: Update, context):
570
+ query = update.callback_query
571
+ await query.answer()
572
+ if not REQUIRED_CHANNELS:
573
+ await query.edit_message_text(get_message(context, 'no_channels_configured'))
574
+ return ADMIN_MENU
575
+ keyboard = []
576
+ message_text = get_message(context, 'channel_list_prompt') + "\n\n"
577
+ for i, channel_id in enumerate(REQUIRED_CHANNELS):
578
+ try:
579
+ chat = await context.bot.get_chat(chat_id=channel_id)
580
+ channel_name = chat.title if chat.title else channel_id
581
+ message_text += f"{i+1}. {channel_name} (`{channel_id}`)\n"
582
+ keyboard.append([InlineKeyboardButton(f"{get_message(context, 'btn_remove_channel')} {channel_name}", callback_data=f'remove_channel_{channel_id}')])
583
+ except Exception as e:
584
+ message_text += f"{i+1}. {channel_id} (خطا در دریافت اطلاعات: {e})\n"
585
+ keyboard.append([InlineKeyboardButton(f"{get_message(context, 'btn_remove_channel')} {channel_id}", callback_data=f'remove_channel_{channel_id}')])
586
+ reply_markup = InlineKeyboardMarkup(keyboard)
587
+ await query.edit_message_text(message_text, reply_markup=reply_markup, parse_mode='Markdown')
588
+ return LIST_REMOVE_CHANNELS
589
+
590
+ async def admin_handle_remove_channel(update: Update, context):
591
+ query = update.callback_query
592
+ await query.answer()
593
+ channel_id_to_remove = query.data.replace('remove_channel_', '')
594
+ if channel_id_to_remove in REQUIRED_CHANNELS:
595
+ REQUIRED_CHANNELS.remove(channel_id_to_remove)
596
+ save_required_channels(REQUIRED_CHANNELS)
597
+ await query.edit_message_text(get_message(context, 'channel_removed', channel_id=channel_id_to_remove))
598
+ else:
599
+ await query.edit_message_text(get_message(context, 'channel_not_found'))
600
+ return await admin_link_command(update, context)
601
+
602
+ # قابلیت ارسال پیام‌های تصادفی در گروه‌ها
603
+ scheduler = AsyncIOScheduler()
604
+ scheduler.start()
605
+
606
+ group_jobs = {}
607
+
608
+ def format_interval(seconds):
609
+ if seconds < 60:
610
+ return f"{seconds} ثانیه"
611
+ minutes = seconds // 60
612
+ seconds = seconds % 60
613
+ return f"{minutes:02d}:{seconds:02d}"
614
+
615
+ async def send_random_message(context, chat_id):
616
+ try:
617
+ await context.bot.send_message(chat_id=chat_id, text="سلام به همه")
618
+ next_interval = random.randint(21, 180)
619
+ group_jobs[chat_id].reschedule('interval', seconds=next_interval)
620
+ await context.bot.send_message(
621
+ chat_id=chat_id,
622
+ text=f"پیام بعدی {format_interval(next_interval)} دیگر ارسال می‌شود."
623
+ )
624
+ except Exception as e:
625
+ print(f"Error sending message to {chat_id}: {e}")
626
+
627
+ async def run_command(update: Update, context):
628
+ chat_id = update.effective_chat.id
629
+ user_id = update.effective_user.id
630
+ if update.effective_chat.type not in ['group', 'supergroup']:
631
+ await update.message.reply_text(get_message(context, 'group_only_command'))
632
+ return
633
+ try:
634
+ chat_member = await context.bot.get_chat_member(chat_id=chat_id, user_id=user_id)
635
+ if chat_member.status not in ['administrator', 'creator']:
636
+ await update.message.reply_text(get_message(context, 'not_group_admin'))
637
+ return
638
+ except Exception as e:
639
+ print(f"Error checking admin status: {e}", file=sys.stderr)
640
+ await update.message.reply_text(get_message(context, 'admin_check_error'))
641
+ return
642
+ if chat_id in group_jobs:
643
+ await update.message.reply_text(get_message(context, 'random_message_already_active'))
644
+ return
645
+ initial_interval = random.randint(21, 180)
646
+ job = scheduler.add_job(
647
+ send_random_message,
648
+ 'interval',
649
+ seconds=initial_interval,
650
+ args=[context, chat_id],
651
+ id=str(chat_id),
652
+ replace_existing=True
653
+ )
654
+ group_jobs[chat_id] = job
655
+ await update.message.reply_text(
656
+ get_message(context, 'random_message_started', interval=format_interval(initial_interval))
657
+ )
658
+
659
+ async def stop_command(update: Update, context):
660
+ chat_id = update.effective_chat.id
661
+ user_id = update.effective_user.id
662
+ if update.effective_chat.type not in ['group', 'supergroup']:
663
+ await update.message.reply_text(get_message(context, 'group_only_command'))
664
+ return
665
+ try:
666
+ chat_member = await context.bot.get_chat_member(chat_id=chat_id, user_id=user_id)
667
+ if chat_member.status not in ['administrator', 'creator']:
668
+ await update.message.reply_text(get_message(context, 'not_group_admin'))
669
+ return
670
+ except Exception as e:
671
+ print(f"Error checking admin status: {e}", file=sys.stderr)
672
+ await update.message.reply_text(get_message(context, 'admin_check_error'))
673
+ return
674
+ if chat_id not in group_jobs:
675
+ await update.message.reply_text(get_message(context, 'random_message_not_active'))
676
+ return
677
+ job = group_jobs.pop(chat_id)
678
+ job.remove()
679
+ await update.message.reply_text(get_message(context, 'random_message_stopped'))
680
+
681
+ def main():
682
+ application = Application.builder().token(TOKEN).build()
683
+ conv_handler = ConversationHandler(
684
+ entry_points=[
685
+ CommandHandler("start", start),
686
+ CommandHandler("link", admin_link_command)
687
+ ],
688
+ states={
689
+ LANGUAGE_SELECTION: [CallbackQueryHandler(set_language, pattern='^set_lang_')],
690
+ MAIN_MENU: [
691
+ CallbackQueryHandler(change_format_selected, pattern='^select_convert_format$'),
692
+ CallbackQueryHandler(cut_audio_selected, pattern='^select_cut_audio$'),
693
+ CallbackQueryHandler(video_conversion_selected, pattern='^select_video_conversion$')
694
+ ],
695
+ CONVERT_AUDIO: [
696
+ MessageHandler(filters.AUDIO & ~filters.COMMAND, handle_audio),
697
+ MessageHandler(filters.VOICE & ~filters.COMMAND, handle_voice),
698
+ CommandHandler("start", start),
699
+ CommandHandler("cancel", cancel)
700
+ ],
701
+ CUT_AUDIO_FILE: [
702
+ MessageHandler(filters.AUDIO & ~filters.COMMAND, handle_cut_audio_file),
703
+ MessageHandler(filters.VOICE & ~filters.COMMAND, handle_cut_audio_file),
704
+ CommandHandler("start", start),
705
+ CommandHandler("cancel", cancel)
706
+ ],
707
+ CUT_AUDIO_RANGE: [
708
+ MessageHandler(filters.TEXT & ~filters.COMMAND, handle_cut_audio_range),
709
+ CommandHandler("start", start),
710
+ CommandHandler("cancel", cancel)
711
+ ],
712
+ VIDEO_CONVERSION_MODE: [
713
+ MessageHandler(filters.VIDEO & ~filters.COMMAND, handle_video_conversion),
714
+ MessageHandler(filters.VIDEO_NOTE & ~filters.COMMAND, handle_video_conversion),
715
+ CommandHandler("start", start),
716
+ CommandHandler("cancel", cancel)
717
+ ],
718
+ WAITING_FOR_MEMBERSHIP: [
719
+ CallbackQueryHandler(check_membership_callback, pattern='^check_membership$'),
720
+ CommandHandler("start", start),
721
+ CommandHandler("cancel", cancel)
722
+ ],
723
+ ADMIN_MENU: [
724
+ CallbackQueryHandler(admin_add_channel_prompt, pattern='^admin_add_channel$'),
725
+ CallbackQueryHandler(admin_list_channels, pattern='^admin_list_channels$'),
726
+ CommandHandler("start", start),
727
+ CommandHandler("cancel", cancel)
728
+ ],
729
+ ADD_CHANNEL: [
730
+ MessageHandler(filters.TEXT & ~filters.COMMAND, admin_handle_add_channel),
731
+ CommandHandler("start", start),
732
+ CommandHandler("cancel", cancel)
733
+ ],
734
+ LIST_REMOVE_CHANNELS: [
735
+ CallbackQueryHandler(admin_handle_remove_channel, pattern='^remove_channel_'),
736
+ CommandHandler("start", start),
737
+ CommandHandler("cancel", cancel)
738
+ ]
739
+ },
740
+ fallbacks=[CommandHandler("cancel", cancel), CommandHandler("start", start)],
741
+ allow_reentry=True
742
+ )
743
+ application.add_handler(conv_handler)
744
+ application.add_handler(CommandHandler("run", run_command))
745
+ application.add_handler(CommandHandler("stop", stop_command))
746
+ application.add_error_handler(error_handler)
747
+ print("ربات در حال اجرا است...")
748
+ application.run_polling(allowed_updates=Update.ALL_TYPES)
749
+
750
+ if __name__ == "__main__":
751
+ main()
channels.json ADDED
@@ -0,0 +1 @@
 
 
1
+ [ ]
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ python-telegram-bot==20.7
2
+ pydub==0.25.1
3
+ ffmpeg-python==0.2.0
4
+ apscheduler==3.10.4