File size: 7,980 Bytes
0165362
 
 
 
 
1fc2290
0165362
 
 
 
1fc2290
 
 
 
 
 
 
 
 
0165362
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1fc2290
0165362
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1fc2290
 
0165362
1fc2290
 
0165362
1fc2290
 
0165362
1fc2290
 
0165362
1fc2290
 
0165362
1fc2290
 
0165362
1fc2290
 
0165362
1fc2290
 
0165362
1fc2290
 
0165362
1fc2290
 
0165362
1fc2290
 
0165362
1fc2290
 
0165362
1fc2290
 
0165362
1fc2290
 
0165362
1fc2290
 
0165362
1fc2290
 
0165362
1fc2290
 
0165362
1fc2290
 
0165362
1fc2290
 
0165362
1fc2290
 
0165362
1fc2290
 
0165362
1fc2290
 
0165362
 
2212ad8
1fc2290
 
 
bb08073
dd71b0f
 
1fc2290
 
 
 
 
 
 
4765b72
1fc2290
4765b72
1fc2290
 
 
 
 
 
0165362
1fc2290
 
 
 
 
 
0165362
 
1fc2290
0165362
 
 
1fc2290
 
 
 
 
0165362
 
 
1fc2290
0165362
 
 
 
 
 
 
 
1fc2290
 
0165362
 
dd71b0f
0165362
 
1fc2290
 
 
 
 
 
0165362
1fc2290
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
import os
import sys
import re
import subprocess
import json
import threading
from io import BytesIO

from flask import Flask, request, jsonify
from telegram import Update, InputFile, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import (
    Application,
    CommandHandler,
    MessageHandler,
    CallbackQueryHandler,
    filters,
    ConversationHandler,
    ContextTypes
)
from telegram.constants import ParseMode
from pydub import AudioSegment

TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
if not TOKEN:
    print("Error: TELEGRAM_BOT_TOKEN environment variable not set.", file=sys.stderr)
    sys.exit(1)

BOT_USERNAME = os.getenv("TELEGRAM_BOT_USERNAME", "Voice2mp3_RoBot")

ADMIN_ID = int(os.getenv("TELEGRAM_ADMIN_ID", "0"))
if ADMIN_ID == 0:
    print("Warning: TELEGRAM_ADMIN_ID environment variable not set or set to 0. Admin features might not work.", file=sys.stderr)

DOWNLOAD_DIR = "/tmp/downloads"
OUTPUT_DIR = "/tmp/outputs"
CHANNELS_FILE = "channels.json"

os.makedirs(DOWNLOAD_DIR, exist_ok=True)
os.makedirs(OUTPUT_DIR, exist_ok=True)

def load_required_channels():
    if os.path.exists(CHANNELS_FILE):
        try:
            with open(CHANNELS_FILE, 'r', encoding='utf-8') as f:
                return json.load(f)
        except json.JSONDecodeError:
            print(f"Error decoding JSON from {CHANNELS_FILE}. Starting with empty list.", file=sys.stderr)
            return []
    return []

def save_required_channels(channels):
    with open(CHANNELS_FILE, 'w', encoding='utf-8') as f:
        json.dump(channels, f, indent=4, ensure_ascii=False)

REQUIRED_CHANNELS = load_required_channels()

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)

MESSAGES = {
    # ... [keep the MESSAGES dictionary unchanged] ...
}

def get_message(context, key, **kwargs):
    lang = context.user_data.get('language', 'fa')
    message_template = MESSAGES[lang].get(key, MESSAGES['fa'][key])
    return message_template.format(**kwargs)

def parse_time_to_ms(time_str):
    match = re.match(r'^(\d{2})\.(\d{2})$', time_str)
    if not match:
        raise ValueError("Invalid time format")

    minutes = int(match.group(1))
    seconds = int(match.group(2))

    if seconds >= 60:
        raise ValueError("Seconds must be between 00 and 59")

    return (minutes * 60 + seconds) * 1000

async def check_user_membership(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # ... [keep this function unchanged] ...

async def show_membership_required_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # ... [keep this function unchanged] ...

async def process_feature_or_check_membership(update: Update, context: ContextTypes.DEFAULT_TYPE, feature_func, *args, **kwargs):
    # ... [keep this function unchanged] ...

async def check_membership_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # ... [keep this function unchanged] ...

async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # ... [keep this function unchanged] ...

async def set_language(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # ... [keep this function unchanged] ...

async def show_main_menu(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # ... [keep this function unchanged] ...

async def change_format_selected(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # ... [keep this function unchanged] ...

async def cut_audio_selected(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # ... [keep this function unchanged] ...

async def video_conversion_selected(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # ... [keep this function unchanged] ...

async def handle_audio(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # ... [keep this function unchanged] ...

async def handle_voice(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # ... [keep this function unchanged] ...

async def handle_cut_audio_file(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # ... [keep this function unchanged] ...

async def handle_cut_audio_range(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # ... [keep this function unchanged] ...

async def handle_video_conversion(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # ... [keep this function unchanged] ...

async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # ... [keep this function unchanged] ...

async def error_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # ... [keep this function unchanged] ...

async def admin_link_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # ... [keep this function unchanged] ...

async def admin_add_channel_prompt(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # ... [keep this function unchanged] ...

async def admin_handle_add_channel(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # ... [keep this function unchanged] ...

async def admin_list_channels(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # ... [keep this function unchanged] ...

async def admin_handle_remove_channel(update: Update, context: ContextTypes.DEFAULT_TYPE):
    # ... [keep this function unchanged] ...

app = Flask(__name__)

# مدیریت ایمن Application در محیط چندنخی
_app_lock = threading.Lock()
_application_instance = None

def get_telegram_application():
    global _application_instance
    with _app_lock:
        if _application_instance is None:
            print("Initializing new Telegram Application instance...")
            _app = Application.builder().token(TOKEN).build()
            
            conv_handler = ConversationHandler(
                entry_points=[
                    CommandHandler("start", start),
                    CommandHandler("link", admin_link_command)
                ],
                states={
                    # ... [keep the states unchanged] ...
                },
                fallbacks=[CommandHandler("cancel", cancel), CommandHandler("start", start)],
                allow_reentry=True
            )

            _app.add_handler(conv_handler)
            _app.add_error_handler(error_handler)
            
            _application_instance = _app
            print("Telegram Application initialized.")
    return _application_instance

@app.route("/")
def index():
    return jsonify({"status": "ok", "message": "Telegram bot is running."})

@app.route("/webhook", methods=["POST"])
def webhook():
    application = get_telegram_application()
    json_data = request.get_json(force=True)
    update = Update.de_json(json_data, application.bot)
    application.update_queue.put(update)
    return "ok"

@app.route("/set_webhook", methods=["GET"])
def set_webhook_route():
    webhook_url = os.getenv("WEBHOOK_URL")
    if not webhook_url:
        return jsonify({"status": "error", "message": "WEBHOOK_URL environment variable not set."}), 500

    if not webhook_url.endswith("/webhook"):
        webhook_url = f"{webhook_url.rstrip('/')}/webhook"

    try:
        application = get_telegram_application()
        application.bot.set_webhook(url=webhook_url)
        return jsonify({"status": "success", "message": f"Webhook set to {webhook_url}"})
    except Exception as e:
        print(f"Failed to set webhook: {e}", file=sys.stderr)
        return jsonify({"status": "error", "message": f"Failed to set webhook: {e}"}), 500

def run_bot():
    """Run the bot in polling mode for development"""
    print("Starting bot in polling mode...")
    application = get_telegram_application()
    application.run_polling()

if __name__ == "__main__":
    # Run Flask app and bot in polling mode for local development
    import threading
    threading.Thread(target=run_bot, daemon=True).start()
    app.run(host='0.0.0.0', port=int(os.environ.get("PORT", 7860)))