akdNIKY commited on
Commit
8735e81
·
verified ·
1 Parent(s): 48375b1

Update app.py

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