diff --git "a/app (6).py" "b/app (6).py" new file mode 100644--- /dev/null +++ "b/app (6).py" @@ -0,0 +1,2997 @@ + + +from flask import Flask, render_template_string, request, redirect, url_for, session, flash, jsonify +from flask_caching import Cache +import json +import os +import logging +import threading +import time +from datetime import datetime, timedelta +from huggingface_hub import HfApi, hf_hub_download +from werkzeug.utils import secure_filename +import random + +app = Flask(__name__) +app.secret_key = os.getenv("FLASK_SECRET_KEY", "verysecretkey") +app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=30) +DATA_FILE = 'data_adusis.json' +REPO_ID = "Eluza133/A12d12s12" +HF_TOKEN_WRITE = os.getenv("HF_TOKEN") +HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") or HF_TOKEN_WRITE +UPLOAD_FOLDER = 'uploads' +os.makedirs(UPLOAD_FOLDER, exist_ok=True) + +cache = Cache(app, config={'CACHE_TYPE': 'simple'}) +logging.basicConfig(level=logging.INFO) + +def initialize_data_structure(data): + if not isinstance(data, dict): + logging.warning("Data is not a dict, initializing empty database") + return {'posts': [], 'users': {}} + + data.setdefault('posts', []) + data.setdefault('users', {}) + + for post in data['posts']: + post.setdefault('likes', []) + post.setdefault('views', 0) + post.setdefault('comments', []) + post.setdefault('jerked_off_count', 0) + post.setdefault('id', str(random.randint(100000, 999999))) + + for user in data['users']: + data['users'][user].setdefault('last_seen', '1970-01-01 00:00:00') + data['users'][user].setdefault('bio', '') + data['users'][user].setdefault('link', '') + data['users'][user].setdefault('avatar', None) + + return data + +@cache.memoize(timeout=120) +def load_data(): + try: + download_db_from_hf() + if os.path.exists(DATA_FILE) and os.path.getsize(DATA_FILE) > 0: + with open(DATA_FILE, 'r', encoding='utf-8') as file: + data = json.load(file) + else: + data = {'posts': [], 'users': {}} + + data = initialize_data_structure(data) + logging.info("Data loaded successfully") + return data + except json.JSONDecodeError: + logging.error(f"Error decoding JSON from {DATA_FILE}. Initializing empty data.") + return initialize_data_structure({}) + except Exception as e: + logging.error(f"Error loading data: {e}") + return initialize_data_structure({}) + +def save_data(data): + try: + temp_file = DATA_FILE + '.tmp' + with open(temp_file, 'w', encoding='utf-8') as file: + json.dump(data, file, ensure_ascii=False, indent=4) + os.replace(temp_file, DATA_FILE) + upload_db_to_hf() + cache.clear() + logging.info("Data saved and uploaded to HF") + except Exception as e: + logging.error(f"Error saving data: {e}") + if os.path.exists(temp_file): + os.remove(temp_file) + raise + +def upload_db_to_hf(): + if not HF_TOKEN_WRITE: + logging.warning("HF_TOKEN_WRITE not set. Skipping upload.") + return + try: + api = HfApi() + api.upload_file( + path_or_fileobj=DATA_FILE, + path_in_repo=DATA_FILE, + repo_id=REPO_ID, + repo_type="dataset", + token=HF_TOKEN_WRITE, + commit_message=f"Backup {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" + ) + logging.info("Database uploaded to Hugging Face") + except Exception as e: + logging.error(f"Error uploading database: {e}") + +def download_db_from_hf(): + if not HF_TOKEN_READ: + logging.warning("HF_TOKEN_READ not set. Skipping download.") + if not os.path.exists(DATA_FILE): + with open(DATA_FILE, 'w', encoding='utf-8') as f: + json.dump(initialize_data_structure({}), f) + return + try: + hf_hub_download( + repo_id=REPO_ID, + filename=DATA_FILE, + repo_type="dataset", + token=HF_TOKEN_READ, + local_dir=".", + local_dir_use_symlinks=False, + force_download=True + ) + logging.info("Database downloaded from Hugging Face") + except Exception as e: + logging.error(f"Error downloading database: {e}") + if not os.path.exists(DATA_FILE): + logging.info("Creating empty database file as download failed and file doesn't exist.") + with open(DATA_FILE, 'w', encoding='utf-8') as f: + json.dump(initialize_data_structure({}), f) + +def periodic_backup(): + while True: + time.sleep(1800) + logging.info("Initiating periodic backup.") + try: + data = load_data() + save_data(data) + except Exception as e: + logging.error(f"Error during periodic backup: {e}") + +def is_user_online(data, username): + if username not in data.get('users', {}): + return False + last_seen_str = data['users'][username].get('last_seen', '1970-01-01 00:00:00') + try: + last_seen = datetime.strptime(last_seen_str, '%Y-%m-%d %H:%M:%S') + return (datetime.now() - last_seen).total_seconds() < 300 + except ValueError: + logging.error(f"Invalid last_seen format for user {username}: {last_seen_str}") + return False + +def update_last_seen(username): + if username: + try: + data = load_data() + if username in data['users']: + data['users'][username]['last_seen'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + save_data(data) + else: + logging.warning(f"Attempted to update last_seen for non-existent user: {username}") + except Exception as e: + logging.error(f"Error updating last_seen for {username}: {e}") + +@app.before_request +def before_request_func(): + if 'username' in session: + update_last_seen(session['username']) + +BASE_STYLE = ''' +:root { + --primary: #ff1f8f; + --secondary: #a0f; + --accent: #ffcc00; + --background-light: #1a1a1a; + --background-dark: #0d0d0d; + --card-bg: rgba(30, 30, 30, 0.9); + --card-bg-dark: rgba(15, 15, 15, 0.95); + --text-light: #f0f0f0; + --text-dark: #cccccc; + --shadow: 0 8px 25px rgba(255, 31, 143, 0.3); + --glass-bg: rgba(40, 40, 40, 0.5); + --transition: all 0.3s ease; + --online: #0f0; + --offline: #ff3b30; + --like-color: #ff1f8f; + --jerk-color: #ffcc00; +} +* { margin: 0; padding: 0; box-sizing: border-box; } +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: var(--background-dark); + color: var(--text-light); + line-height: 1.6; + overflow-x: hidden; +} +body.dark { + background: var(--background-dark); + color: var(--text-light); +} +.sidebar { + position: fixed; + top: 0; + left: 0; + width: 280px; + height: 100%; + background: var(--card-bg-dark); + backdrop-filter: blur(10px); + padding: 20px; + box-shadow: 5px 0 15px rgba(0, 0, 0, 0.5); + z-index: 1000; + transition: transform var(--transition); + border-right: 1px solid var(--primary); +} +.sidebar.hidden { + transform: translateX(-100%); +} +.sidebar-header { + display: flex; + align-items: center; + gap: 15px; + margin-bottom: 30px; + border-bottom: 1px solid var(--primary); + padding-bottom: 15px; +} +.nav-brand { + font-size: 1.6em; + font-weight: 700; + color: var(--primary); + text-shadow: 0 0 10px var(--primary); +} +.logo { + width: 45px; + height: 45px; + border-radius: 10px; + border: 2px solid var(--primary); +} +.nav-links { + display: flex; + flex-direction: column; + gap: 12px; +} +.nav-link { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 20px; + background: transparent; + color: var(--text-light); + text-decoration: none; + border-radius: 8px; + font-size: 1.05em; + font-weight: 500; + transition: var(--transition); + border-left: 3px solid transparent; +} +body.dark .nav-link { + color: var(--text-light); +} +.nav-link:hover, .nav-link.active { + background: var(--glass-bg); + color: var(--primary); + border-left: 3px solid var(--primary); + transform: translateX(5px); +} +.nav-link span:first-child { + font-size: 1.2em; +} +.logout-btn { + color: var(--secondary); +} +.logout-btn:hover { + background: var(--secondary); + color: white; + border-left: 3px solid var(--secondary); +} +.menu-btn { + display: none; + font-size: 26px; + background: var(--card-bg-dark); + border: 1px solid var(--primary); + color: var(--primary); + cursor: pointer; + position: fixed; + top: 15px; + left: 15px; + z-index: 1001; + padding: 10px; + border-radius: 50%; + box-shadow: var(--shadow); + transition: var(--transition); +} +.menu-btn:hover { + background: var(--primary); + color: var(--background-dark); +} +.container { + margin: 20px auto 20px 300px; + max-width: 1100px; + padding: 20px; + transition: margin-left var(--transition); +} +.btn { + padding: 12px 25px; + background: var(--primary); + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 1em; + font-weight: 600; + transition: var(--transition); + display: inline-flex; + align-items: center; + gap: 8px; + box-shadow: 0 4px 15px rgba(255, 31, 143, 0.4); + text-transform: uppercase; + letter-spacing: 1px; +} +.btn:hover { + transform: translateY(-2px); + background: #e01b7e; + box-shadow: var(--shadow); +} +.btn-secondary { + background: var(--secondary); + box-shadow: 0 4px 15px rgba(170, 0, 255, 0.4); +} +.btn-secondary:hover { + background: #90f; + box-shadow: 0 6px 20px rgba(170, 0, 255, 0.5); +} +.btn-accent { + background: var(--accent); + color: var(--background-dark); + box-shadow: 0 4px 15px rgba(255, 204, 0, 0.4); +} +.btn-accent:hover { + background: #e6b800; + box-shadow: 0 6px 20px rgba(255, 204, 0, 0.5); +} +input, textarea, select { + width: 100%; + padding: 12px 15px; + margin: 10px 0; + border: 1px solid var(--glass-bg); + border-radius: 8px; + background: var(--glass-bg); + color: var(--text-light); + font-size: 1em; + transition: var(--transition); +} +body.dark input, body.dark textarea, body.dark select { + color: var(--text-light); +} +input:focus, textarea:focus, select:focus { + outline: none; + border-color: var(--primary); + background: rgba(50, 50, 50, 0.5); + box-shadow: 0 0 0 3px rgba(255, 31, 143, 0.3); +} +textarea { + min-height: 100px; + resize: vertical; +} +.modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.9); + z-index: 2000; + justify-content: center; + align-items: center; + transition: opacity var(--transition); +} +.modal img, .modal video { + max-width: 90%; + max-height: 90%; + object-fit: contain; + border-radius: 10px; + box-shadow: 0 0 30px rgba(255, 31, 143, 0.5); + animation: zoomIn 0.3s ease; +} +.theme-toggle { + position: fixed; + top: 15px; + right: 15px; + background: var(--glass-bg); + border: 1px solid var(--secondary); + padding: 10px; + border-radius: 50%; + cursor: pointer; + font-size: 20px; + color: var(--secondary); + box-shadow: var(--shadow); + transition: var(--transition); + z-index: 1001; +} +.theme-toggle:hover { + transform: rotate(180deg); + background: var(--secondary); + color: white; +} +.status-dot { + width: 10px; + height: 10px; + border-radius: 50%; + display: inline-block; + margin-left: 6px; + vertical-align: middle; + box-shadow: 0 0 5px currentColor; +} +.online { background: var(--online); } +.offline { background: var(--offline); } +.badge { + background-color: var(--primary); + color: white; + padding: 2px 6px; + border-radius: 4px; + font-size: 0.8em; + margin-left: 8px; +} +.flash { + padding: 15px; + margin-bottom: 20px; + border-radius: 8px; + font-weight: 600; + text-align: center; +} +.flash.success { + background-color: rgba(0, 255, 0, 0.2); + color: #90ee90; + border: 1px solid #90ee90; +} +.flash.error { + background-color: rgba(255, 31, 143, 0.2); + color: var(--primary); + border: 1px solid var(--primary); +} + +@keyframes zoomIn { + from { opacity: 0; transform: scale(0.8); } + to { opacity: 1; transform: scale(1); } +} +@media (max-width: 900px) { + .sidebar { + transform: translateX(-100%); + width: 250px; + } + .sidebar.active { + transform: translateX(0); + box-shadow: 5px 0 15px rgba(0, 0, 0, 0.7); + } + .menu-btn { + display: block; + } + .container { + margin: 70px 15px 15px 15px; + max-width: calc(100% - 30px); + padding: 15px; + } + .theme-toggle { + top: 70px; + } +} +@media (max-width: 480px) { + .nav-brand { font-size: 1.4em; } + .nav-link { font-size: 1em; padding: 10px 15px; } + .btn { padding: 10px 20px; font-size: 0.9em; } + input, textarea, select { padding: 10px 12px; font-size: 0.95em;} +} +''' + +NAV_HTML = ''' + +''' + +@app.route('/register', methods=['GET', 'POST']) +def register(): + if request.method == 'POST': + username = request.form.get('username') + password = request.form.get('password') + if not username or not password: + flash('Username and password are required.', 'error') + return redirect(url_for('register')) + if len(username) < 3: + flash('Username must be at least 3 characters long.', 'error') + return redirect(url_for('register')) + if len(password) < 6: + flash('Password must be at least 6 characters long.', 'error') + return redirect(url_for('register')) + + data = load_data() + if username in data['users']: + flash('Username already exists! Choose another.', 'error') + return redirect(url_for('register')) + + data['users'][username] = { + 'password': password, + 'bio': '', + 'link': '', + 'avatar': None, + 'last_seen': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } + try: + save_data(data) + flash('Registration successful! Please login.', 'success') + return redirect(url_for('login')) + except Exception as e: + flash('Registration failed. Please try again.', 'error') + logging.error(f"Failed to save data during registration: {e}") + return redirect(url_for('register')) + + is_authenticated = 'username' in session + username = session.get('username') + data = load_data() + user_count = len(data.get('users', {})) + is_online = is_user_online(data, username) if username else False + html = ''' + + + + + + Register - Adusis - BBC QoS hub + + + + + + ''' + NAV_HTML + ''' + +
+

Join the Fun

+ {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} + {% endwith %} +
+ + + +
+ Already joined? Login here +
+ + + +''' + return render_template_string(html, is_authenticated=is_authenticated, username=username, user_count=user_count, is_online=is_online) + +@app.route('/login', methods=['GET', 'POST']) +def login(): + if request.method == 'POST': + username = request.form.get('username') + password = request.form.get('password') + data = load_data() + if username in data.get('users', {}) and data['users'][username].get('password') == password: + session['username'] = username + session.permanent = True + update_last_seen(username) + flash('Login successful!', 'success') + return redirect(url_for('feed')) + else: + flash('Invalid username or password.', 'error') + return redirect(url_for('login')) + + is_authenticated = 'username' in session + username = session.get('username') + data = load_data() + user_count = len(data.get('users', {})) + is_online = is_user_online(data, username) if username else False + html = ''' + + + + + + Login - Adusis - BBC QoS hub + + + + + + ''' + NAV_HTML + ''' + +
+

Welcome Back

+ {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} + {% endwith %} +
+ + + +
+ New here? Register an account +
+ + + +''' + return render_template_string(html, is_authenticated=is_authenticated, username=username, user_count=user_count, is_online=is_online) + + +@app.route('/logout') +def logout(): + username = session.get('username') + if username: + update_last_seen(username) + session.pop('username', None) + flash('You have been logged out.', 'success') + return redirect(url_for('login')) + +@app.route('/', methods=['GET']) +def feed(): + data = load_data() + username = session.get('username') + posts_list = data.get('posts', []) + if not isinstance(posts_list, list): + logging.error("Posts data is not a list, resetting.") + posts_list = [] + + stories = sorted(posts_list, key=lambda x: datetime.strptime(x.get('upload_date', '1970-01-01 00:00:00'), '%Y-%m-%d %H:%M:%S'), reverse=True) + is_authenticated = 'username' in session + user_count = len(data.get('users', {})) + is_online = is_user_online(data, username) if username else False + + html = ''' + + + + + + Feed - Adusis - BBC QoS hub + + + + + + ''' + NAV_HTML + ''' + + +
+ {% if not stories %} +
+

No stories yet!

+

Be the first one to upload something hot.

+
+ {% else %} + {% for story in stories %} +
+ {% set media_url = "https://huggingface.co/datasets/" + repo_id + "/resolve/main/" + story['type'] + "s/" + story['filename'] %} + {% if story['type'] == 'video' %} + + + {% else %} + {{ story['title'] }} + {% endif %} + +
+
+ {% if is_authenticated %} + + {% else %} +
+ ❀️ + +
+ {% endif %} + +
+ πŸ‘οΈ + {{ story.get('views', 0) }} +
+ + πŸ’¬ + {{ story.get('comments', []) | length }} + +
+ +
+
+ {% endfor %} + {% endif %} +
+ + + + + + +''' + return render_template_string(html, + stories=stories, + is_authenticated=is_authenticated, + username=username, + repo_id=REPO_ID, + user_count=user_count, + is_online=is_online, + is_user_online=lambda u: is_user_online(data, u)) + +@app.route('/profile', methods=['GET', 'POST']) +def profile(): + if 'username' not in session: + flash('Login to view your profile!', 'error') + return redirect(url_for('login')) + + data = load_data() + username = session['username'] + + if request.method == 'POST': + if 'delete_story' in request.form: + story_id = request.form.get('story_id') + if story_id: + original_length = len(data['posts']) + data['posts'] = [p for p in data['posts'] if not (p.get('id') == story_id and p.get('uploader') == username)] + if len(data['posts']) < original_length: + try: + save_data(data) + flash('Story deleted.', 'success') + except Exception as e: + flash('Failed to delete story.', 'error') + logging.error(f"Error saving data after deleting story {story_id}: {e}") + else: + flash('Story not found or you do not have permission.', 'error') + return redirect(url_for('profile')) + + elif 'update_profile' in request.form: + bio = request.form.get('bio', '').strip()[:500] + link = request.form.get('link', '').strip()[:200] + avatar_file = request.files.get('avatar') + + if avatar_file and avatar_file.filename: + if not allowed_file(avatar_file.filename, {'png', 'jpg', 'jpeg', 'gif'}): + flash('Invalid avatar file type. Use png, jpg, jpeg, gif.', 'error') + return redirect(url_for('profile')) + + filename = secure_filename(f"{username}_avatar_{random.randint(100,999)}{os.path.splitext(avatar_file.filename)[1]}") + temp_path = os.path.join(UPLOAD_FOLDER, filename) + + try: + avatar_file.save(temp_path) + api = HfApi() + avatar_path = f"avatars/{filename}" + + api.upload_file( + path_or_fileobj=temp_path, + path_in_repo=avatar_path, + repo_id=REPO_ID, + repo_type="dataset", + token=HF_TOKEN_WRITE, + commit_message=f"Avatar update for {username}" + ) + data['users'][username]['avatar'] = avatar_path + if os.path.exists(temp_path): + os.remove(temp_path) + except Exception as e: + flash('Failed to upload avatar.', 'error') + logging.error(f"Error uploading avatar for {username}: {e}") + if os.path.exists(temp_path): + os.remove(temp_path) + + data['users'][username]['bio'] = bio + data['users'][username]['link'] = link + try: + save_data(data) + flash('Profile updated!', 'success') + except Exception as e: + flash('Failed to update profile.', 'error') + logging.error(f"Error saving profile data for {username}: {e}") + return redirect(url_for('profile')) + + data = load_data() + user_data = data.get('users', {}).get(username, {}) + user_stories = sorted([p for p in data.get('posts', []) if p.get('uploader') == username], key=lambda x: datetime.strptime(x.get('upload_date', '1970-01-01 00:00:00'), '%Y-%m-%d %H:%M:%S'), reverse=True) + is_authenticated = True + user_count = len(data.get('users', {})) + is_online = is_user_online(data, username) + bio = user_data.get('bio', '') + link = user_data.get('link', '') + avatar = user_data.get('avatar', None) + last_seen = user_data.get('last_seen', 'Never') + + html = ''' + + + + + + Profile - {{ username }} - Adusis - BBC QoS hub + + + + + + ''' + NAV_HTML + ''' + +
+ {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} + {% endwith %} + +
+
+ {% if avatar %} + Avatar + {% else %} +
{{ username[0]|upper }}
+ {% endif %} +
+
+

{{ username }}

+

Bio: {{ bio if bio else 'No bio yet.' }}

+ {% if link %} +

Link: {{ link }}

+ {% endif %} +

Last Seen: {{ last_seen }}

+
+
+ +
+

Edit Profile

+
+ + + + + + + + +
+
+ +

Your Stories

+
+ {% for story in user_stories %} +
+ + {% set media_url = "https://huggingface.co/datasets/" + repo_id + "/resolve/main/" + story['type'] + "s/" + story['filename'] %} + {% if story['type'] == 'video' %} + + {% else %} + {{ story['title'] }} + {% endif %} + +
+

{{ story['title'] }}

+

{{ story['description'] | truncate(80) }}

+

Uploaded: {{ story['upload_date'] }}

+

❀️ {{ story.get('likes', [])|length }} | πŸ‘οΈ {{ story.get('views', 0) }} | πŸ† {{ story.get('jerked_off_count', 0) }} | πŸ’¬ {{ story.get('comments', [])|length }}

+
+ + +
+
+
+ {% endfor %} + {% if not user_stories %} +

You haven't uploaded any stories yet. Upload one now!

+ {% endif %} +
+
+ + + + + + +''' + return render_template_string(html, + username=username, + user_stories=user_stories, + bio=bio, + link=link, + avatar=avatar, + last_seen=last_seen, + is_authenticated=is_authenticated, + repo_id=REPO_ID, + user_count=user_count, + is_online=is_online, + random=random + ) + +def allowed_file(filename, allowed_extensions): + return '.' in filename and \ + filename.rsplit('.', 1)[1].lower() in allowed_extensions + +@app.route('/story/', methods=['GET', 'POST']) +def story_page(story_id): + data = load_data() + username = session.get('username') + + story_index = next((index for (index, d) in enumerate(data.get('posts',[])) if d.get('id') == story_id), None) + + if story_index is None: + flash('Story not found.', 'error') + return redirect(url_for('feed')) + + story = data['posts'][story_index] + + if request.method == 'POST': + if 'add_comment' in request.form: + if not username: + flash('You must be logged in to comment.', 'error') + return redirect(url_for('story_page', story_id=story_id)) + + comment_text = request.form.get('comment_text', '').strip() + if not comment_text: + flash('Comment cannot be empty.', 'error') + return redirect(url_for('story_page', story_id=story_id)) + if len(comment_text) > 1000: + flash('Comment too long (max 1000 characters).', 'error') + return redirect(url_for('story_page', story_id=story_id)) + + new_comment = { + 'username': username, + 'text': comment_text, + 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') + } + + if not isinstance(story.get('comments'), list): + story['comments'] = [] + + story['comments'].append(new_comment) + data['posts'][story_index] = story + + try: + save_data(data) + flash('Comment added!', 'success') + return redirect(url_for('story_page', story_id=story_id) + '#comments') + except Exception as e: + flash('Failed to add comment.', 'error') + logging.error(f"Error saving comment for story {story_id}: {e}") + if story['comments'] and story['comments'][-1] == new_comment: + story['comments'].pop() + return redirect(url_for('story_page', story_id=story_id)) + + data = load_data() + story_index = next((index for (index, d) in enumerate(data.get('posts',[])) if d.get('id') == story_id), None) + if story_index is None: + flash('Story disappeared.', 'error') + return redirect(url_for('feed')) + story = data['posts'][story_index] + + is_authenticated = 'username' in session + user_count = len(data.get('users', {})) + is_online = is_user_online(data, username) if username else False + uploader_online = is_user_online(data, story.get('uploader')) + + html = ''' + + + + + + {{ story['title'] }} - Adusis - BBC QoS hub + + + + + + ''' + NAV_HTML + ''' + +
+ {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} + {% endwith %} + +

{{ story['title'] }}

+ +
+ {% set media_url = "https://huggingface.co/datasets/" + repo_id + "/resolve/main/" + story['type'] + "s/" + story['filename'] %} + {% if story['type'] == 'video' %} + + + {% else %} + {{ story['title'] }} + {% endif %} +
+ +
+

Description: {{ story['description'] if story['description'] else 'No description.'}}

+

Uploaded by: {{ story['uploader'] }}

+

Uploaded on: {{ story['upload_date'] }}

+
+ +
+ {% if is_authenticated %} + + {% else %} +
+ ❀️ Likes {{ story.get('likes', []) | length }} +
+ {% endif %} + +
+ πŸ‘οΈ Views {{ story.get('views', 0) }} +
+
+ + +
+

Comments ({{ story.get('comments', []) | length }})

+ {% if is_authenticated %} +
+
+ + + +
+
+
+ {% else %} +

Login to post a comment.

+ {% endif %} + +
+ {% if story.get('comments') %} + {% for comment in story['comments'] | reverse %} +
+
+ {{ comment['username'] }} + {{ comment['timestamp'] }} +
+
+ {{ comment['text'] }} +
+
+ {% endfor %} + {% else %} +

Be the first to comment!

+ {% endif %} +
+
+ + Back to Feed +
+ + + + + + +''' + return render_template_string(html, + story=story, + repo_id=REPO_ID, + is_authenticated=is_authenticated, + username=username, + user_count=user_count, + is_online=is_online, + uploader_online=uploader_online) + + +@app.route('/upload', methods=['GET', 'POST']) +def upload(): + if 'username' not in session: + flash('Login to upload a story!', 'error') + return redirect(url_for('login')) + + data = load_data() + username = session['username'] + + if request.method == 'POST': + title = request.form.get('title', '').strip() + description = request.form.get('description', '').strip()[:2000] + file = request.files.get('file') + + if not file or not title: + flash('Title and file are required!', 'error') + return redirect(url_for('upload')) + + if not allowed_file(file.filename, {'png', 'jpg', 'jpeg', 'gif', 'mp4', 'mov', 'avi', 'webm'}): + flash('Invalid file type. Allowed: Images (png, jpg, gif), Videos (mp4, mov, avi, webm)', 'error') + return redirect(url_for('upload')) + + filename = secure_filename(f"{username}_{int(time.time())}_{file.filename}") + temp_path = os.path.join(UPLOAD_FOLDER, filename) + + try: + file.save(temp_path) + + file_type = 'video' if filename.lower().endswith(('.mp4', '.mov', 'avi', '.webm')) else 'photo' + + story_id = str(random.randint(100000, 999999)) + while any(p.get('id') == story_id for p in data.get('posts', [])): + story_id = str(random.randint(100000, 999999)) + + api = HfApi() + hf_filename = f"{story_id}_{filename}" + file_path = f"{file_type}s/{hf_filename}" + + api.upload_file( + path_or_fileobj=temp_path, + path_in_repo=file_path, + repo_id=REPO_ID, + repo_type="dataset", + token=HF_TOKEN_WRITE, + commit_message=f"Upload {file_type} by {username} (ID: {story_id})" + ) + + data.setdefault('posts', []).append({ + 'id': story_id, + 'title': title, + 'description': description, + 'filename': hf_filename, + 'type': file_type, + 'uploader': username, + 'upload_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), + 'likes': [], + 'views': 0, + 'comments': [], + 'jerked_off_count': 0 + }) + + save_data(data) + flash('Story uploaded successfully!', 'success') + + if os.path.exists(temp_path): + os.remove(temp_path) + return redirect(url_for('profile')) + + except Exception as e: + flash(f'Upload failed: {e}', 'error') + logging.error(f"Error during upload for {username}: {e}") + if os.path.exists(temp_path): + os.remove(temp_path) + return redirect(url_for('upload')) + + is_authenticated = True + user_count = len(data.get('users', {})) + is_online = is_user_online(data, username) + + html = ''' + + + + + + Upload Story - Adusis - BBC QoS hub + + + + + + ''' + NAV_HTML + ''' + +
+

Upload Your Story

+ {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} + {% endwith %} +
+ + + + + + + + + Allowed: JPG, PNG, GIF, MP4, MOV, AVI, WEBM + + +
+
+
+
+
+ + + +''' + return render_template_string(html, + username=username, + is_authenticated=is_authenticated, + repo_id=REPO_ID, + user_count=user_count, + is_online=is_online) + +@app.route('/users', methods=['GET', 'POST']) +def users(): + data = load_data() + username = session.get('username') + + is_authenticated = 'username' in session + user_count = len(data.get('users', {})) + is_online = is_user_online(data, username) if username else False + + search_query = request.form.get('search', '').strip().lower() if request.method == 'POST' else '' + + user_list = [] + for user, user_data in data.get('users', {}).items(): + if user == username: + continue + if not search_query or search_query in user.lower(): + user_list.append({ + 'name': user, + 'avatar': user_data.get('avatar'), + 'online': is_user_online(data, user) + }) + + user_list.sort(key=lambda u: (-u['online'], u['name'].lower())) + + html = ''' + + + + + + Users - Adusis - BBC QoS hub + + + + + + ''' + NAV_HTML + ''' + +
+

Explore Users ({{ user_count }})

+
+
+ + +
+
+
+ {% for user_info in user_list %} +
+ {% if user_info['avatar'] %} + {{ user_info['name'] }} Avatar + {% else %} +
{{ user_info['name'][0]|upper }}
+ {% endif %} + +
+ {% endfor %} + {% if not user_list and search_query %} +

No users found matching "{{ search_query }}".

+ {% elif not user_list %} +

Looks like it's just you (and maybe the admin)!

+ {% endif %} +
+
+ + + +''' + return render_template_string(html, + user_list=user_list, + username=username, + is_authenticated=is_authenticated, + repo_id=REPO_ID, + user_count=user_count, + is_online=is_online, + search_query=search_query, + random=random) + +@app.route('/user/', methods=['GET']) +def user_profile(username): + data = load_data() + current_user = session.get('username') + + if username not in data.get('users', {}): + flash(f'User "{username}" not found.', 'error') + return redirect(url_for('users')) + + is_own_profile = (username == current_user) + if is_own_profile: + return redirect(url_for('profile')) + + user_data = data['users'][username] + user_stories = sorted([p for p in data.get('posts', []) if p.get('uploader') == username], key=lambda x: datetime.strptime(x.get('upload_date','1970-01-01 00:00:00'), '%Y-%m-%d %H:%M:%S'), reverse=True) + + is_authenticated = 'username' in session + user_count = len(data.get('users', {})) + current_user_online = is_user_online(data, current_user) if current_user else False + profile_user_online = is_user_online(data, username) + + bio = user_data.get('bio', '') + link = user_data.get('link', '') + avatar = user_data.get('avatar', None) + last_seen = user_data.get('last_seen', 'Never') + + html = ''' + + + + + + Profile - {{ username }} - Adusis - BBC QoS hub + + + + + + ''' + NAV_HTML + ''' + +
+ {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} + {% endwith %} + +
+
+ {% if avatar %} + Avatar + {% else %} +
{{ username[0]|upper }}
+ {% endif %} +
+
+

{{ username }}

+

Bio: {{ bio if bio else 'No bio provided.' }}

+ {% if link %} +

Link: {{ link }}

+ {% endif %} +

Last Seen: {{ last_seen }}

+
+
+ +

{{ username }}'s Stories

+
+ {% for story in user_stories %} +
+ + {% set media_url = "https://huggingface.co/datasets/" + repo_id + "/resolve/main/" + story['type'] + "s/" + story['filename'] %} + {% if story['type'] == 'video' %} + + {% else %} + {{ story['title'] }} + {% endif %} + +
+

{{ story['title'] }}

+

{{ story['description'] | truncate(80) }}

+

Uploaded: {{ story['upload_date'] }}

+

❀️ {{ story.get('likes', [])|length }} | πŸ‘οΈ {{ story.get('views', 0) }} | πŸ† {{ story.get('jerked_off_count', 0) }} | πŸ’¬ {{ story.get('comments', [])|length }}

+
+
+ {% endfor %} + {% if not user_stories %} +

This user hasn't uploaded any stories yet.

+ {% endif %} +
+
+ + + + + + +''' + return render_template_string(html, + username=username, + user_stories=user_stories, + bio=bio, + link=link, + avatar=avatar, + last_seen=last_seen, + is_authenticated=is_authenticated, + current_user=current_user, + repo_id=REPO_ID, + user_count=user_count, + is_online=current_user_online, + profile_user_online=profile_user_online, + random=random) + + +@app.route('/like/', methods=['POST']) +def like_story(story_id): + if 'username' not in session: + return jsonify({'message': 'Login required'}), 401 + + username = session['username'] + data = load_data() + + story_index = next((index for (index, d) in enumerate(data.get('posts',[])) if d.get('id') == story_id), None) + if story_index is None: + return jsonify({'message': 'Story not found'}), 404 + + story = data['posts'][story_index] + + if not isinstance(story.get('likes'), list): + story['likes'] = [] + + liked = False + if username in story['likes']: + story['likes'].remove(username) + liked = False + else: + story['likes'].append(username) + liked = True + + data['posts'][story_index] = story + + try: + save_data(data) + return jsonify({'message': 'Success', 'likes': len(story['likes']), 'liked': liked}), 200 + except Exception as e: + logging.error(f"Error saving like for story {story_id} by {username}: {e}") + if liked: + if username in story['likes']: story['likes'].remove(username) + else: + if username not in story['likes']: story['likes'].append(username) + return jsonify({'message': 'Failed to save like'}), 500 + + +@app.route('/jerk_off/', methods=['POST']) +def jerk_off_story(story_id): + data = load_data() + + story_index = next((index for (index, d) in enumerate(data.get('posts',[])) if d.get('id') == story_id), None) + if story_index is None: + return jsonify({'message': 'Story not found'}), 404 + + story = data['posts'][story_index] + + if not isinstance(story.get('jerked_off_count'), int): + story['jerked_off_count'] = 0 + + story['jerked_off_count'] += 1 + data['posts'][story_index] = story + + try: + save_data(data) + return jsonify({'message': 'Success', 'jerked_off_count': story['jerked_off_count']}), 200 + except Exception as e: + logging.error(f"Error saving jerk_off count for story {story_id}: {e}") + story['jerked_off_count'] -= 1 + return jsonify({'message': 'Failed to save count'}), 500 + +@app.route('/view/', methods=['POST']) +def increment_view(story_id): + + data = load_data() + story_index = next((index for (index, d) in enumerate(data.get('posts',[])) if d.get('id') == story_id), None) + if story_index is None: + return jsonify({'message': 'Story not found'}), 404 + + story = data['posts'][story_index] + + if not isinstance(story.get('views'), int): + story['views'] = 0 + + story['views'] += 1 + data['posts'][story_index] = story + + try: + save_data(data) + return jsonify({'message': 'Success', 'views': story['views']}), 200 + except Exception as e: + logging.error(f"Error saving view count for story {story_id}: {e}") + story['views'] -= 1 + return jsonify({'message': 'Failed to save view count'}), 500 + +if __name__ == '__main__': + backup_thread = threading.Thread(target=periodic_backup, daemon=True) + backup_thread.start() + app.run(debug=False, host='0.0.0.0', port=7860) +