Eluza133 commited on
Commit
14548ae
·
verified ·
1 Parent(s): 2b62fbe

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +489 -25
app.py CHANGED
@@ -1,5 +1,4 @@
1
-
2
-
3
  from flask import Flask, render_template_string, request, redirect, url_for, session, flash, jsonify
4
  from flask_caching import Cache
5
  import json
@@ -11,6 +10,7 @@ from datetime import datetime, timedelta
11
  from huggingface_hub import HfApi, hf_hub_download
12
  from werkzeug.utils import secure_filename
13
  import random
 
14
 
15
  app = Flask(__name__)
16
  app.secret_key = os.getenv("FLASK_SECRET_KEY", "verysecretkey")
@@ -25,13 +25,16 @@ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
25
  cache = Cache(app, config={'CACHE_TYPE': 'simple'})
26
  logging.basicConfig(level=logging.INFO)
27
 
 
 
28
  def initialize_data_structure(data):
29
  if not isinstance(data, dict):
30
  logging.warning("Data is not a dict, initializing empty database")
31
- return {'posts': [], 'users': {}}
32
 
33
  data.setdefault('posts', [])
34
  data.setdefault('users', {})
 
35
 
36
  for post in data['posts']:
37
  post.setdefault('likes', [])
@@ -56,7 +59,7 @@ def load_data():
56
  with open(DATA_FILE, 'r', encoding='utf-8') as file:
57
  data = json.load(file)
58
  else:
59
- data = {'posts': [], 'users': {}}
60
 
61
  data = initialize_data_structure(data)
62
  logging.info("Data loaded successfully")
@@ -438,6 +441,112 @@ textarea {
438
  border: 1px solid var(--primary);
439
  }
440
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
441
  @keyframes zoomIn {
442
  from { opacity: 0; transform: scale(0.8); }
443
  to { opacity: 1; transform: scale(1); }
@@ -462,6 +571,15 @@ textarea {
462
  .theme-toggle {
463
  top: 70px;
464
  }
 
 
 
 
 
 
 
 
 
465
  }
466
  @media (max-width: 480px) {
467
  .nav-brand { font-size: 1.4em; }
@@ -483,6 +601,7 @@ NAV_HTML = '''
483
  <a href="{{ url_for('profile') }}" class="nav-link {% if request.endpoint == 'profile' %}active{% endif %}"><span>😈</span> Profile ({{ username }}) <span class="status-dot {{ 'online' if is_online else 'offline' }}"></span></a>
484
  <a href="{{ url_for('upload') }}" class="nav-link {% if request.endpoint == 'upload' %}active{% endif %}"><span>📤</span> Upload Story</a>
485
  <a href="{{ url_for('users') }}" class="nav-link {% if request.endpoint == 'users' %}active{% endif %}"><span>👀</span> Users <span class="badge">{{ user_count }}</span></a>
 
486
  <a href="{{ url_for('logout') }}" class="nav-link logout-btn"><span>🚪</span> Logout</a>
487
  {% else %}
488
  <a href="{{ url_for('login') }}" class="nav-link {% if request.endpoint == 'login' %}active{% endif %}"><span>🔑</span> Login</a>
@@ -1071,7 +1190,7 @@ def feed():
1071
  <h2>{{ story['title'] }}</h2>
1072
  <p>{{ story['description'] | truncate(100) }}</p>
1073
  <p>By: <a href="{{ url_for('user_profile', username=story['uploader']) }}" class="uploader-link">{{ story['uploader'] }}</a>
1074
- <span class="status-dot {{ 'online' if is_user_online(story['uploader']) else 'offline' }}"></span>
1075
  <br><small>{{ story['upload_date'] }}</small>
1076
  </p>
1077
  </div>
@@ -1580,20 +1699,6 @@ def profile():
1580
  </div>
1581
  </div>
1582
 
1583
- <div class="edit-form">
1584
- <h2 class="section-title">Edit Profile</h2>
1585
- <form method="POST" enctype="multipart/form-data">
1586
- <input type="hidden" name="update_profile" value="1">
1587
- <label for="bio">Bio (max 500 chars):</label>
1588
- <textarea id="bio" name="bio" placeholder="Tell us about yourself..." maxlength="500">{{ bio }}</textarea>
1589
- <label for="link">Website/Link (max 200 chars):</label>
1590
- <input id="link" type="text" name="link" placeholder="https://yourlink.com" value="{{ link }}" maxlength="200">
1591
- <label for="avatar">Change Avatar (JPG, PNG, GIF):</label>
1592
- <input id="avatar" type="file" name="avatar" accept="image/png, image/jpeg, image/gif">
1593
- <button type="submit" class="btn btn-secondary" style="margin-top: 10px;">Save Changes</button>
1594
- </form>
1595
- </div>
1596
-
1597
  <h2 class="section-title">Your Stories</h2>
1598
  <div class="story-grid">
1599
  {% for story in user_stories %}
@@ -2742,7 +2847,8 @@ def user_profile(username):
2742
  .story-preview-link { display: block; text-decoration: none; color: inherit; }
2743
  .story-preview { width: 100%; height: 180px; object-fit: cover; display: block; background-color: #222; loading: lazy; }
2744
  .story-item-info { padding: 15px; }
2745
- .story-item h3 { font-size: 1.15em; font-weight: 600; margin-bottom: 8px; color: var(--text-light); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
 
2746
  .story-item p.description { font-size: 0.9em; color: var(--text-dark); margin-bottom: 10px; height: 3.6em; overflow: hidden; }
2747
  .story-item p.date { font-size: 0.8em; color: var(--text-dark); margin-bottom: 12px; }
2748
  .story-item p.stats { font-size: 0.8em; color: var(--text-dark); }
@@ -2902,7 +3008,6 @@ def user_profile(username):
2902
  profile_user_online=profile_user_online,
2903
  random=random)
2904
 
2905
-
2906
  @app.route('/like/<story_id>', methods=['POST'])
2907
  def like_story(story_id):
2908
  if 'username' not in session:
@@ -2941,7 +3046,6 @@ def like_story(story_id):
2941
  if username not in story['likes']: story['likes'].append(username)
2942
  return jsonify({'message': 'Failed to save like'}), 500
2943
 
2944
-
2945
  @app.route('/jerk_off/<story_id>', methods=['POST'])
2946
  def jerk_off_story(story_id):
2947
  data = load_data()
@@ -2968,7 +3072,6 @@ def jerk_off_story(story_id):
2968
 
2969
  @app.route('/view/<story_id>', methods=['POST'])
2970
  def increment_view(story_id):
2971
-
2972
  data = load_data()
2973
  story_index = next((index for (index, d) in enumerate(data.get('posts',[])) if d.get('id') == story_id), None)
2974
  if story_index is None:
@@ -2990,8 +3093,369 @@ def increment_view(story_id):
2990
  story['views'] -= 1
2991
  return jsonify({'message': 'Failed to save view count'}), 500
2992
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2993
  if __name__ == '__main__':
2994
  backup_thread = threading.Thread(target=periodic_backup, daemon=True)
2995
  backup_thread.start()
2996
- app.run(debug=False, host='0.0.0.0', port=7860)
2997
-
 
1
+ ```python
 
2
  from flask import Flask, render_template_string, request, redirect, url_for, session, flash, jsonify
3
  from flask_caching import Cache
4
  import json
 
10
  from huggingface_hub import HfApi, hf_hub_download
11
  from werkzeug.utils import secure_filename
12
  import random
13
+ from flask_socketio import SocketIO, emit, join_room, leave_room
14
 
15
  app = Flask(__name__)
16
  app.secret_key = os.getenv("FLASK_SECRET_KEY", "verysecretkey")
 
25
  cache = Cache(app, config={'CACHE_TYPE': 'simple'})
26
  logging.basicConfig(level=logging.INFO)
27
 
28
+ socketio = SocketIO(app)
29
+
30
  def initialize_data_structure(data):
31
  if not isinstance(data, dict):
32
  logging.warning("Data is not a dict, initializing empty database")
33
+ return {'posts': [], 'users': {}, 'messages': {}}
34
 
35
  data.setdefault('posts', [])
36
  data.setdefault('users', {})
37
+ data.setdefault('messages', {})
38
 
39
  for post in data['posts']:
40
  post.setdefault('likes', [])
 
59
  with open(DATA_FILE, 'r', encoding='utf-8') as file:
60
  data = json.load(file)
61
  else:
62
+ data = {'posts': [], 'users': {}, 'messages': {}}
63
 
64
  data = initialize_data_structure(data)
65
  logging.info("Data loaded successfully")
 
441
  border: 1px solid var(--primary);
442
  }
443
 
444
+ .chat-popup {
445
+ position: fixed;
446
+ bottom: 20px;
447
+ right: 20px;
448
+ width: 320px;
449
+ background: var(--card-bg);
450
+ border: 1px solid var(--primary);
451
+ border-radius: 10px;
452
+ box-shadow: var(--shadow);
453
+ display: flex;
454
+ flex-direction: column;
455
+ z-index: 1000;
456
+ max-height: 400px;
457
+ overflow: hidden;
458
+ }
459
+
460
+ .chat-header {
461
+ background: var(--primary);
462
+ color: white;
463
+ padding: 10px;
464
+ font-weight: bold;
465
+ text-align: center;
466
+ cursor: pointer;
467
+ }
468
+
469
+ .chat-body {
470
+ flex-grow: 1;
471
+ padding: 10px;
472
+ overflow-y: auto;
473
+ display: flex;
474
+ flex-direction: column;
475
+ }
476
+
477
+ .message {
478
+ background: var(--glass-bg);
479
+ padding: 8px;
480
+ border-radius: 8px;
481
+ margin-bottom: 5px;
482
+ word-wrap: break-word;
483
+ }
484
+
485
+ .message .sender {
486
+ font-weight: bold;
487
+ color: var(--secondary);
488
+ margin-bottom: 3px;
489
+ }
490
+
491
+ .chat-input-area {
492
+ padding: 10px;
493
+ border-top: 1px solid var(--glass-bg);
494
+ }
495
+
496
+ .chat-input {
497
+ width: 100%;
498
+ padding: 8px;
499
+ border: 1px solid var(--glass-bg);
500
+ border-radius: 8px;
501
+ background: var(--background-dark);
502
+ color: var(--text-light);
503
+ resize: none;
504
+ min-height: 40px;
505
+ }
506
+
507
+ .chat-input:focus {
508
+ outline: none;
509
+ border-color: var(--primary);
510
+ box-shadow: 0 0 0 2px rgba(255, 31, 143, 0.3);
511
+ }
512
+
513
+ .online-users {
514
+ position: fixed;
515
+ top: 80px;
516
+ right: 20px;
517
+ background: var(--card-bg);
518
+ border: 1px solid var(--primary);
519
+ border-radius: 10px;
520
+ box-shadow: var(--shadow);
521
+ width: 200px;
522
+ padding: 10px;
523
+ z-index: 999;
524
+ }
525
+
526
+ .online-users h3 {
527
+ font-size: 1.2em;
528
+ color: var(--primary);
529
+ margin-bottom: 10px;
530
+ text-align: center;
531
+ }
532
+
533
+ .online-user {
534
+ display: flex;
535
+ align-items: center;
536
+ gap: 5px;
537
+ padding: 5px;
538
+ border-bottom: 1px solid var(--glass-bg);
539
+ }
540
+
541
+ .online-user:last-child {
542
+ border-bottom: none;
543
+ }
544
+
545
+ .online-user-name {
546
+ color: var(--text-light);
547
+ font-size: 0.9em;
548
+ }
549
+
550
  @keyframes zoomIn {
551
  from { opacity: 0; transform: scale(0.8); }
552
  to { opacity: 1; transform: scale(1); }
 
571
  .theme-toggle {
572
  top: 70px;
573
  }
574
+ .chat-popup {
575
+ bottom: 10px;
576
+ right: 10px;
577
+ width: 90%;
578
+ max-width: 400px;
579
+ }
580
+ .online-users {
581
+ display: none;
582
+ }
583
  }
584
  @media (max-width: 480px) {
585
  .nav-brand { font-size: 1.4em; }
 
601
  <a href="{{ url_for('profile') }}" class="nav-link {% if request.endpoint == 'profile' %}active{% endif %}"><span>😈</span> Profile ({{ username }}) <span class="status-dot {{ 'online' if is_online else 'offline' }}"></span></a>
602
  <a href="{{ url_for('upload') }}" class="nav-link {% if request.endpoint == 'upload' %}active{% endif %}"><span>📤</span> Upload Story</a>
603
  <a href="{{ url_for('users') }}" class="nav-link {% if request.endpoint == 'users' %}active{% endif %}"><span>👀</span> Users <span class="badge">{{ user_count }}</span></a>
604
+ <a href="{{ url_for('messages') }}" class="nav-link {% if request.endpoint == 'messages' %}active{% endif %}"><span>💬</span> Messages</a>
605
  <a href="{{ url_for('logout') }}" class="nav-link logout-btn"><span>🚪</span> Logout</a>
606
  {% else %}
607
  <a href="{{ url_for('login') }}" class="nav-link {% if request.endpoint == 'login' %}active{% endif %}"><span>🔑</span> Login</a>
 
1190
  <h2>{{ story['title'] }}</h2>
1191
  <p>{{ story['description'] | truncate(100) }}</p>
1192
  <p>By: <a href="{{ url_for('user_profile', username=story['uploader']) }}" class="uploader-link">{{ story['uploader'] }}</a>
1193
+ <span class="status-dot {{ 'online' if is_user_online(data, story['uploader']) else 'offline' }}"></span>
1194
  <br><small>{{ story['upload_date'] }}</small>
1195
  </p>
1196
  </div>
 
1699
  </div>
1700
  </div>
1701
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1702
  <h2 class="section-title">Your Stories</h2>
1703
  <div class="story-grid">
1704
  {% for story in user_stories %}
 
2847
  .story-preview-link { display: block; text-decoration: none; color: inherit; }
2848
  .story-preview { width: 100%; height: 180px; object-fit: cover; display: block; background-color: #222; loading: lazy; }
2849
  .story-item-info { padding: 15px; }
2850
+ .story-item h3 { font-size: 1.15em; }
2851
+ font-weight: 600; margin-bottom: 8px; color: var(--text-light); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
2852
  .story-item p.description { font-size: 0.9em; color: var(--text-dark); margin-bottom: 10px; height: 3.6em; overflow: hidden; }
2853
  .story-item p.date { font-size: 0.8em; color: var(--text-dark); margin-bottom: 12px; }
2854
  .story-item p.stats { font-size: 0.8em; color: var(--text-dark); }
 
3008
  profile_user_online=profile_user_online,
3009
  random=random)
3010
 
 
3011
  @app.route('/like/<story_id>', methods=['POST'])
3012
  def like_story(story_id):
3013
  if 'username' not in session:
 
3046
  if username not in story['likes']: story['likes'].append(username)
3047
  return jsonify({'message': 'Failed to save like'}), 500
3048
 
 
3049
  @app.route('/jerk_off/<story_id>', methods=['POST'])
3050
  def jerk_off_story(story_id):
3051
  data = load_data()
 
3072
 
3073
  @app.route('/view/<story_id>', methods=['POST'])
3074
  def increment_view(story_id):
 
3075
  data = load_data()
3076
  story_index = next((index for (index, d) in enumerate(data.get('posts',[])) if d.get('id') == story_id), None)
3077
  if story_index is None:
 
3093
  story['views'] -= 1
3094
  return jsonify({'message': 'Failed to save view count'}), 500
3095
 
3096
+ @app.route('/messages')
3097
+ def messages():
3098
+ if 'username' not in session:
3099
+ flash('Login to view messages!', 'error')
3100
+ return redirect(url_for('login'))
3101
+
3102
+ username = session['username']
3103
+ data = load_data()
3104
+ user_count = len(data.get('users', {}))
3105
+ is_online = is_user_online(data, username)
3106
+
3107
+ online_users = [user for user in data['users'] if is_user_online(data, user) and user != username]
3108
+ online_users.sort()
3109
+
3110
+ html = '''
3111
+ <!DOCTYPE html>
3112
+ <html lang="en">
3113
+ <head>
3114
+ <meta charset="UTF-8">
3115
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
3116
+ <title>Messages - Adusis - BBC QoS hub</title>
3117
+ <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
3118
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" integrity="sha512-9usAa10IRO0HhonpyAIVpjrylPvoDwiPUiKdWk5t3PyolY1cOd4DSE0Ga+ri4AuTroPR5aQvXU9xC6qOPnzFeg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
3119
+ <style>
3120
+ ''' + BASE_STYLE + '''
3121
+ .container { max-width: 1200px; }
3122
+ h1 {
3123
+ font-size: 2em;
3124
+ font-weight: 700;
3125
+ margin-bottom: 25px;
3126
+ color: var(--primary);
3127
+ text-shadow: 0 0 8px var(--primary);
3128
+ border-bottom: 1px solid var(--secondary);
3129
+ padding-bottom: 10px;
3130
+ }
3131
+ .messages-container {
3132
+ display: flex;
3133
+ gap: 20px;
3134
+ }
3135
+ .chat-list {
3136
+ flex: 0 0 250px;
3137
+ background: var(--card-bg);
3138
+ border-radius: 12px;
3139
+ box-shadow: var(--shadow);
3140
+ padding: 20px;
3141
+ border: 1px solid var(--primary);
3142
+ height: 600px;
3143
+ overflow-y: auto;
3144
+ }
3145
+ body.dark .chat-list { background: var(--card-bg); }
3146
+ .chat-list h2 {
3147
+ font-size: 1.4em;
3148
+ color: var(--secondary);
3149
+ margin-bottom: 15px;
3150
+ padding-bottom: 8px;
3151
+ border-bottom: 1px solid var(--glass-bg);
3152
+ }
3153
+ .chat-user {
3154
+ display: flex;
3155
+ align-items: center;
3156
+ gap: 10px;
3157
+ padding: 10px;
3158
+ border-radius: 8px;
3159
+ transition: var(--transition);
3160
+ text-decoration: none;
3161
+ color: var(--text-light);
3162
+ }
3163
+ .chat-user:hover, .chat-user.active-chat {
3164
+ background: var(--glass-bg);
3165
+ transform: translateX(5px);
3166
+ }
3167
+ .chat-user-name {
3168
+ font-weight: 500;
3169
+ font-size: 1.05em;
3170
+ flex-grow: 1;
3171
+ }
3172
+ .chat-window {
3173
+ flex: 1;
3174
+ background: var(--card-bg);
3175
+ border-radius: 12px;
3176
+ box-shadow: var(--shadow);
3177
+ border: 1px solid var(--secondary);
3178
+ display: flex;
3179
+ flex-direction: column;
3180
+ height: 600px;
3181
+ }
3182
+ body.dark .chat-window { background: var(--card-bg); }
3183
+ .chat-header {
3184
+ background: var(--secondary);
3185
+ color: white;
3186
+ padding: 15px;
3187
+ font-weight: bold;
3188
+ font-size: 1.2em;
3189
+ border-radius: 12px 12px 0 0;
3190
+ }
3191
+ .chat-body {
3192
+ flex-grow: 1;
3193
+ padding: 20px;
3194
+ overflow-y: auto;
3195
+ display: flex;
3196
+ flex-direction: column;
3197
+ gap: 10px;
3198
+ }
3199
+ .message {
3200
+ background: var(--glass-bg);
3201
+ padding: 10px;
3202
+ border-radius: 8px;
3203
+ max-width: 80%;
3204
+ word-wrap: break-word;
3205
+ }
3206
+ .message.sent {
3207
+ align-self: flex-end;
3208
+ background: var(--primary);
3209
+ color: white;
3210
+ }
3211
+ .message .sender {
3212
+ font-weight: bold;
3213
+ margin-bottom: 5px;
3214
+ display: block;
3215
+ font-size: 0.9em;
3216
+ }
3217
+ .chat-input-area {
3218
+ padding: 15px;
3219
+ border-top: 1px solid var(--glass-bg);
3220
+ }
3221
+ .chat-input {
3222
+ width: 100%;
3223
+ padding: 12px 15px;
3224
+ border: 1px solid var(--glass-bg);
3225
+ border-radius: 8px;
3226
+ background: var(--background-dark);
3227
+ color: var(--text-light);
3228
+ resize: none;
3229
+ min-height: 50px;
3230
+ }
3231
+ .chat-input:focus {
3232
+ outline: none;
3233
+ border-color: var(--primary);
3234
+ box-shadow: 0 0 0 3px rgba(255, 31, 143, 0.3);
3235
+ }
3236
+ .no-messages {
3237
+ color: var(--text-dark);
3238
+ text-align: center;
3239
+ padding: 20px;
3240
+ }
3241
+ .user-avatar {
3242
+ width: 35px;
3243
+ height: 35px;
3244
+ border-radius: 50%;
3245
+ object-fit: cover;
3246
+ border: 2px solid var(--primary);
3247
+ flex-shrink: 0;
3248
+ loading: lazy;
3249
+ }
3250
+ .user-avatar-placeholder {
3251
+ width: 35px;
3252
+ height: 35px;
3253
+ border-radius: 50%;
3254
+ background: linear-gradient(45deg, var(--primary), var(--secondary));
3255
+ display: flex;
3256
+ align-items: center;
3257
+ justify-content: center;
3258
+ font-size: 16px;
3259
+ color: white;
3260
+ font-weight: bold;
3261
+ flex-shrink: 0;
3262
+ }
3263
+
3264
+ @media (max-width: 900px) {
3265
+ .container {
3266
+ margin-left: auto;
3267
+ margin-right: auto;
3268
+ }
3269
+ .messages-container {
3270
+ flex-direction: column;
3271
+ gap: 15px;
3272
+ }
3273
+ .chat-list, .chat-window {
3274
+ width: 100%;
3275
+ height: auto;
3276
+ }
3277
+ }
3278
+ </style>
3279
+ </head>
3280
+ <body class="dark">
3281
+ <button class="menu-btn" onclick="toggleSidebar()">☰</button>
3282
+ ''' + NAV_HTML + '''
3283
+ <button class="theme-toggle" onclick="toggleTheme()">🎨</button>
3284
+ <div class="container">
3285
+ <h1>Messages</h1>
3286
+ <div class="messages-container">
3287
+ <div class="chat-list">
3288
+ <h2>Users</h2>
3289
+ {% if online_users %}
3290
+ {% for other_user in online_users %}
3291
+ <a href="{{ url_for('messages', receiver=other_user) }}" class="chat-user {% if receiver == other_user %}active-chat{% endif %}">
3292
+ {% if data['users'][other_user].get('avatar') %}
3293
+ <img src="https://huggingface.co/datasets/{{ repo_id }}/resolve/main/{{ data['users'][other_user]['avatar'] }}?rand={{ random.randint(1,1000) }}" alt="{{ other_user }} Avatar" class="user-avatar" loading="lazy">
3294
+ {% else %}
3295
+ <div class="user-avatar-placeholder">{{ other_user[0]|upper }}</div>
3296
+ {% endif %}
3297
+ <span class="chat-user-name">{{ other_user }}</span>
3298
+ </a>
3299
+ {% endfor %}
3300
+ {% else %}
3301
+ <p class="no-messages">No users online to chat with.</p>
3302
+ {% endif %}
3303
+ </div>
3304
+ <div class="chat-window">
3305
+ {% if receiver %}
3306
+ <div class="chat-header">Chat with {{ receiver }}</div>
3307
+ <div class="chat-body" id="chat-body">
3308
+ {% set messages = get_messages(data, username, receiver) %}
3309
+ {% if messages %}
3310
+ {% for message in messages %}
3311
+ <div class="message {% if message['sender'] == username %}sent{% endif %}">
3312
+ <span class="sender">{{ message['sender'] }}</span>
3313
+ {{ message['text'] }}
3314
+ </div>
3315
+ {% endfor %}
3316
+ {% else %}
3317
+ <p class="no-messages">No messages yet. Start the conversation!</p>
3318
+ {% endif %}
3319
+ </div>
3320
+ <div class="chat-input-area">
3321
+ <textarea class="chat-input" id="message-input" placeholder="Type your message..."></textarea>
3322
+ <button class="btn btn-secondary" onclick="sendMessage('{{ username }}', '{{ receiver }}')">Send</button>
3323
+ </div>
3324
+ {% else %}
3325
+ <div class="no-messages" style="display: flex; flex-grow: 1; align-items: center; justify-content: center;">Select a user to start chatting.</div>
3326
+ {% endif %}
3327
+ </div>
3328
+ </div>
3329
+ </div>
3330
+
3331
+ <script>
3332
+ function toggleSidebar() {
3333
+ const sidebar = document.getElementById('sidebar');
3334
+ sidebar.classList.toggle('active');
3335
+ sidebar.classList.toggle('hidden', !sidebar.classList.contains('active'));
3336
+ adjustContainerMargin();
3337
+ }
3338
+ function toggleTheme() {
3339
+ document.body.classList.toggle('dark');
3340
+ localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
3341
+ }
3342
+ function adjustContainerMargin() {
3343
+ const sidebar = document.getElementById('sidebar');
3344
+ const container = document.querySelector('.container');
3345
+ if (!container) return;
3346
+
3347
+ if (window.innerWidth > 900) {
3348
+ const isActive = sidebar.classList.contains('active');
3349
+ container.style.marginLeft = isActive ? '300px' : 'auto';
3350
+ } else {
3351
+ container.style.marginLeft = 'auto';
3352
+ }
3353
+ }
3354
+
3355
+ window.onload = () => {
3356
+ if (window.innerWidth > 900) {
3357
+ document.getElementById('sidebar').classList.add('active');
3358
+ } else {
3359
+ document.getElementById('sidebar').classList.add('hidden');
3360
+ }
3361
+ adjustContainerMargin();
3362
+ const chatBody = document.getElementById('chat-body');
3363
+ if (chatBody) {
3364
+ chatBody.scrollTop = chatBody.scrollHeight;
3365
+ }
3366
+ };
3367
+ window.onresize = adjustContainerMargin;
3368
+
3369
+ function sendMessage(sender, receiver) {
3370
+ const messageInput = document.getElementById('message-input');
3371
+ const messageText = messageInput.value.trim();
3372
+ if (messageText) {
3373
+ socket.emit('send_message', { sender: sender, receiver: receiver, text: messageText });
3374
+ messageInput.value = '';
3375
+ }
3376
+ }
3377
+
3378
+ const socket = io();
3379
+
3380
+ socket.on('connect', () => {
3381
+ console.log('Connected to WebSocket');
3382
+ });
3383
+
3384
+ socket.on('receive_message', (data) => {
3385
+ if ('{{ receiver }}' === data.sender || '{{ receiver }}' === data.receiver) {
3386
+ const chatBody = document.getElementById('chat-body');
3387
+ const messageElement = document.createElement('div');
3388
+ messageElement.classList.add('message');
3389
+ if (data.sender === '{{ username }}') {
3390
+ messageElement.classList.add('sent');
3391
+ }
3392
+ messageElement.innerHTML = `<span class="sender">${data.sender}</span>${data.text}`;
3393
+ chatBody.appendChild(messageElement);
3394
+ chatBody.scrollTop = chatBody.scrollHeight;
3395
+ }
3396
+ });
3397
+
3398
+ </script>
3399
+ </body>
3400
+ </html>
3401
+ '''
3402
+ receiver = request.args.get('receiver')
3403
+ return render_template_string(html,
3404
+ username=username,
3405
+ is_authenticated=True,
3406
+ repo_id=REPO_ID,
3407
+ user_count=user_count,
3408
+ is_online=is_online,
3409
+ online_users=online_users,
3410
+ receiver=receiver,
3411
+ data=data,
3412
+ get_messages=get_messages,
3413
+ random=random)
3414
+
3415
+ def get_messages(data, sender, receiver):
3416
+ messages = []
3417
+ if sender in data.get('messages', {}) and receiver in data['messages']:
3418
+ messages = data['messages'][sender].get(receiver, [])
3419
+ elif receiver in data.get('messages', {}) and sender in data['messages']:
3420
+ messages = data['messages'][receiver].get(sender, [])
3421
+ return messages
3422
+
3423
+ @socketio.on('send_message')
3424
+ def handle_send_message(data):
3425
+ sender = data['sender']
3426
+ receiver = data['receiver']
3427
+ text = data['text']
3428
+
3429
+ data_ = load_data()
3430
+
3431
+ if sender not in data_.get('messages', {}):
3432
+ data_['messages'][sender] = {}
3433
+ if receiver not in data_['messages'][sender]:
3434
+ data_['messages'][sender][receiver] = []
3435
+
3436
+ data_['messages'][sender][receiver].append({'sender': sender, 'text': text})
3437
+ try:
3438
+ save_data(data_)
3439
+ emit('receive_message', data, room=sender)
3440
+ emit('receive_message', data, room=receiver)
3441
+ except Exception as e:
3442
+ logging.error(f"Error saving message from {sender} to {receiver}: {e}")
3443
+
3444
+ @socketio.on('join')
3445
+ def on_join(data):
3446
+ username = data['username']
3447
+ room = data['room']
3448
+ join_room(room)
3449
+ logging.info(f"{username} has joined room {room}")
3450
+
3451
+ @socketio.on('leave')
3452
+ def on_leave(data):
3453
+ username = data['username']
3454
+ room = data['room']
3455
+ leave_room(room)
3456
+ logging.info(f"{username} has left room {room}")
3457
+
3458
  if __name__ == '__main__':
3459
  backup_thread = threading.Thread(target=periodic_backup, daemon=True)
3460
  backup_thread.start()
3461
+ socketio.run(app, debug=False, host='0.0.0.0', port=7860)