Update app.py
Browse files
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;
|
|
|
|
| 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 |
-
|
| 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)
|
|
|