diff --git "a/app.py" "b/app.py" deleted file mode 100644--- "a/app.py" +++ /dev/null @@ -1,4518 +0,0 @@ - -# --- START OF FILE app.py --- -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 -import html -import uuid -from collections import defaultdict - -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) - -USER_ROLES = ["white girl", "white boy", "couple", "BBC alpha bull"] -REFERRAL_REWARDS = { - "white girl": 100, - "couple": 100, - "BBC alpha bull": 100, - "white boy": 50, -} -DEFAULT_REWARD = 0 -UPLOAD_REWARD = 10 -VIEW_REWARD_AMOUNT = 50 -VIEW_REWARD_THRESHOLD = 200 -PROMOTE_COST = 50 -PROMOTE_DURATION_DAYS = 7 -LOGO_URL = "https://cdn-avatars.huggingface.co/v1/production/uploads/673b00f35373479538ac373c/W_dumUND8K6IlMxVmpUgS.jpeg" - -def generate_unique_referral_code(data): - while True: - code = str(uuid.uuid4())[:8] - if not any(u.get('referral_code') == code for u in data.get('users', {}).values()): - return code - -def log_transaction(data, user, type, amount, description, related_user=None, related_story_id=None): - if user not in data['users']: - logging.warning(f"Attempted to log transaction for non-existent user: {user}") - return - - balance_after = data['users'][user].get('aducoin', 0) - - transaction = { - 'id': str(uuid.uuid4()), - 'user': user, - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), - 'type': type, - 'amount': amount, - 'description': description, - 'related_user': related_user, - 'related_story_id': related_story_id, - 'balance_after': balance_after - } - data.setdefault('transactions', []).append(transaction) - logging.info(f"Transaction logged: {transaction}") - - -def initialize_data_structure(data): - if not isinstance(data, dict): - logging.warning("Data is not a dict, initializing empty database") - data = {'posts': [], 'users': {}, 'transactions': [], 'direct_messages': {}} - - data.setdefault('posts', []) - data.setdefault('users', {}) - data.setdefault('transactions', []) - data.setdefault('direct_messages', {}) - - for post in data['posts']: - post.setdefault('likes', []) - post.setdefault('views', 0) - post.setdefault('comments', []) - post.setdefault('jerked_off_count', 0) - post.setdefault('views_reward_milestone', 0) - post.setdefault('promoted_until', None) - if 'id' not in post: - post['id'] = str(random.randint(100000, 999999)) - - all_codes = {u.get('referral_code') for u in data['users'].values() if u.get('referral_code')} - - for username, user_data in data['users'].items(): - user_data.setdefault('last_seen', '1970-01-01 00:00:00') - user_data.setdefault('bio', '') - user_data.setdefault('link', '') - user_data.setdefault('avatar', None) - user_data.setdefault('aducoin', 0) - user_data.setdefault('role', None) - user_data.setdefault('referred_by', None) - if 'referral_code' not in user_data or not user_data['referral_code'] or user_data['referral_code'] in all_codes: - new_code = generate_unique_referral_code(data) - user_data['referral_code'] = new_code - all_codes.add(new_code) - - if isinstance(data['direct_messages'], dict): - for conv_key, messages in data['direct_messages'].items(): - if not isinstance(messages, list): - logging.warning(f"Conversation {conv_key} is not a list, resetting.") - data['direct_messages'][conv_key] = [] - else: - for msg in messages: - if not isinstance(msg, dict): - logging.warning(f"Found non-dict message in {conv_key}, removing.") - data['direct_messages'][conv_key] = [m for m in messages if isinstance(m, dict)] - else: - msg.setdefault('sender', 'unknown') - msg.setdefault('timestamp', datetime.now().strftime('%Y-%m-%d %H:%M:%S')) - msg.setdefault('text', '') - msg.setdefault('message_id', str(uuid.uuid4())) - msg.setdefault('read', False) - - 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': {}, 'transactions': [], 'direct_messages': {}} - - 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 = cache.get('data') or load_data() - if username in data.get('users', {}): - now_str = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - if data['users'][username].get('last_seen') != now_str: - data['users'][username]['last_seen'] = now_str - save_data(data) - cache.set('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}") - -def get_conversation_key(user1, user2): - return tuple(sorted((user1, user2))) - -def count_unread_messages(data, current_user): - count = 0 - if current_user: - for conv_key, messages in data.get('direct_messages', {}).items(): - if current_user in conv_key: - for msg in messages: - if msg.get('sender') != current_user and not msg.get('read'): - count += 1 - return count - - -@app.before_request -def before_request_func(): - if 'username' in session: - username = session['username'] - last_update_key = f"last_seen_update_{username}" - last_update_time = cache.get(last_update_key) - if not last_update_time or (datetime.now() - last_update_time).total_seconds() > 60: - update_last_seen(username) - cache.set(last_update_key, datetime.now(), timeout=60) - - -BASE_STYLE = ''' -:root { - --primary: #FF007F; /* Deep Pink */ - --secondary: #8A2BE2; /* Blue Violet */ - --accent: #FFD700; /* Gold */ - --background-light: #140A1A; - --background-dark: #0A050F; - --card-bg: rgba(20, 10, 25, 0.9); - --card-bg-dark: rgba(10, 5, 15, 0.95); - --text-light: #F8F8F8; - --text-dark: #C0C0C0; - --shadow: 0 10px 30px rgba(255, 0, 127, 0.5); - --glass-bg: rgba(30, 15, 40, 0.6); - --transition: all 0.3s ease-in-out; - --online: #00FF7F; /* Neon Green */ - --offline: #FF4500; /* Orange Red */ - --like-color: #FF007F; - --jerk-color: #FFD700; - --aducoin-color: #FFD700; - --message-sent-bg: rgba(138, 43, 226, 0.3); - --message-received-bg: rgba(40, 20, 50, 0.7); -} -* { margin: 0; padding: 0; box-sizing: border-box; } -body { - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - background: radial-gradient(circle at top left, var(--background-light) 0%, var(--background-dark) 70%); - color: var(--text-light); - line-height: 1.6; - overflow-x: hidden; -} -body.dark { - background: radial-gradient(circle at top left, var(--background-light) 0%, var(--background-dark) 70%); - color: var(--text-light); -} -.sidebar { - position: fixed; - top: 0; - left: 0; - width: 280px; - height: 100%; - background: var(--card-bg-dark); - backdrop-filter: blur(15px); - padding: 20px; - box-shadow: 5px 0 20px rgba(0, 0, 0, 0.7); - z-index: 1000; - transition: transform var(--transition); - border-right: 1px solid var(--primary); - overflow-y: auto; -} -.sidebar.hidden { - transform: translateX(-100%); -} -.sidebar-header { - display: flex; - align-items: center; - gap: 15px; - margin-bottom: 30px; - border-bottom: 2px solid var(--primary); - padding-bottom: 15px; - background: linear-gradient(90deg, transparent, rgba(255, 0, 127, 0.1)); - border-radius: 5px; - padding-left: 10px; -} -.nav-brand { - font-size: 1.8em; - font-weight: 700; - color: var(--primary); - text-shadow: 0 0 15px var(--primary); - letter-spacing: 1px; -} -.logo { - width: 50px; - height: 50px; - border-radius: 12px; - border: 2px solid var(--primary); - box-shadow: 0 0 10px var(--primary); -} -.nav-links { - display: flex; - flex-direction: column; - gap: 15px; -} -.nav-link { - display: flex; - align-items: center; - gap: 12px; - padding: 14px 22px; - background: transparent; - color: var(--text-light); - text-decoration: none; - border-radius: 10px; - font-size: 1.1em; - font-weight: 500; - transition: var(--transition); - border-left: 4px solid transparent; - position: relative; - overflow: hidden; -} -body.dark .nav-link { - color: var(--text-light); -} -.nav-link::before { - content: ''; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: linear-gradient(90deg, rgba(255, 0, 127, 0.1), transparent); - transform: translateX(-100%); - transition: transform var(--transition); - z-index: -1; -} -.nav-link:hover::before { - transform: translateX(0); -} -.nav-link:hover, .nav-link.active { - color: var(--primary); - border-left: 4px solid var(--primary); - transform: translateX(8px); - background: transparent; -} -.nav-link.active::before { - transform: translateX(0); - background: linear-gradient(90deg, rgba(255, 0, 127, 0.2), transparent); -} -.nav-link span:first-child { - font-size: 1.3em; -} -.logout-btn { - color: var(--secondary); -} -.logout-btn:hover { - background: var(--secondary); - color: white; - border-left: 4px solid var(--secondary); -} -.menu-btn { - display: none; - font-size: 28px; - background: var(--card-bg-dark); - border: 1px solid var(--primary); - color: var(--primary); - cursor: pointer; - position: fixed; - top: 20px; - left: 20px; - z-index: 1001; - padding: 12px; - border-radius: 50%; - box-shadow: 0 0 15px rgba(255, 0, 127, 0.4); - transition: var(--transition); -} -.menu-btn:hover { - background: var(--primary); - color: var(--background-dark); - box-shadow: 0 0 20px var(--primary); -} -.container { - margin: 20px auto 20px 300px; - max-width: 1100px; - padding: 20px; - transition: margin-left var(--transition); -} -body:not(.sidebar-active-on-pc) .container { - margin-left: auto; - margin-right: auto; -} - -.btn { - padding: 14px 30px; - background: var(--primary); - color: white; - border: none; - border-radius: 10px; - cursor: pointer; - font-size: 1.05em; - font-weight: 600; - transition: var(--transition); - display: inline-flex; - align-items: center; - gap: 10px; - box-shadow: 0 5px 20px rgba(255, 0, 127, 0.4); - text-transform: uppercase; - letter-spacing: 1.5px; - position: relative; - overflow: hidden; -} -.btn::before { - content: ''; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: radial-gradient(circle, rgba(255,255,255,0.2) 0%, transparent 70%); - transform: scale(0); - transition: transform 0.5s ease; - border-radius: 50%; - opacity: 0; -} -.btn:hover::before { - transform: scale(2); - opacity: 1; -} -.btn:hover { - transform: translateY(-3px); - background: #e00070; - box-shadow: 0 8px 25px rgba(255, 0, 127, 0.6); -} -.btn-secondary { - background: var(--secondary); - box-shadow: 0 5px 20px rgba(138, 43, 226, 0.4); -} -.btn-secondary:hover { - background: #7a20c9; - box-shadow: 0 8px 25px rgba(138, 43, 226, 0.6); -} -.btn-accent { - background: var(--accent); - color: var(--background-dark); - box-shadow: 0 5px 20px rgba(255, 215, 0, 0.4); -} -.btn-accent:hover { - background: #e6c200; - box-shadow: 0 8px 25px rgba(255, 215, 0, 0.6); -} -input, textarea, select { - width: 100%; - padding: 14px 18px; - margin: 12px 0; - border: 1px solid var(--glass-bg); - border-radius: 10px; - background: var(--glass-bg); - color: var(--text-light); - font-size: 1.05em; - 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(40, 20, 50, 0.7); - box-shadow: 0 0 0 4px rgba(255, 0, 127, 0.3); -} -textarea { - min-height: 120px; - resize: vertical; -} -input[type=number]::-webkit-inner-spin-button, -input[type=number]::-webkit-outer-spin-button { - -webkit-appearance: none; - margin: 0; -} -input[type=number] { - -moz-appearance: textfield; -} -.modal { - display: none; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.95); - 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: 15px; - box-shadow: 0 0 40px rgba(255, 0, 127, 0.6); - animation: zoomIn 0.4s ease; -} -.theme-toggle { - position: fixed; - top: 20px; - right: 20px; - background: var(--glass-bg); - border: 1px solid var(--secondary); - padding: 12px; - border-radius: 50%; - cursor: pointer; - font-size: 22px; - color: var(--secondary); - box-shadow: 0 0 15px rgba(138, 43, 226, 0.4); - transition: var(--transition); - z-index: 1001; -} -.theme-toggle:hover { - transform: rotate(360deg); - background: var(--secondary); - color: white; - box-shadow: 0 0 20px var(--secondary); -} -.status-dot { - width: 12px; - height: 12px; - border-radius: 50%; - display: inline-block; - margin-left: 8px; - vertical-align: middle; - box-shadow: 0 0 7px currentColor; - border: 1px solid rgba(255,255,255,0.2); -} -.online { background: var(--online); } -.offline { background: var(--offline); } -.badge { - background-color: var(--primary); - color: white; - padding: 3px 8px; - border-radius: 6px; - font-size: 0.85em; - margin-left: 10px; - font-weight: bold; - box-shadow: 0 2px 5px rgba(0,0,0,0.3); -} -.nav-link .badge { - position: absolute; - right: 20px; - top: 50%; - transform: translateY(-50%); - background-color: var(--accent); - color: var(--background-dark); -} - -.flash { - padding: 18px; - margin-bottom: 25px; - border-radius: 10px; - font-weight: 600; - text-align: center; - box-shadow: 0 5px 15px rgba(0,0,0,0.3); - border: 1px solid transparent; -} -.flash.success { - background-color: rgba(0, 255, 127, 0.2); - color: var(--online); - border-color: var(--online); -} -.flash.error { - background-color: rgba(255, 0, 127, 0.2); - color: var(--primary); - border-color: var(--primary); -} -.flash.warning { - background-color: rgba(255, 215, 0, 0.2); - color: var(--accent); - border-color: var(--accent); -} -.story-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); - gap: 30px; -} -.story-item { - background: var(--card-bg); - border-radius: 15px; - box-shadow: 0 6px 15px rgba(0,0,0,0.4); - transition: var(--transition); - overflow: hidden; - border: 1px solid var(--glass-bg); - display: flex; - flex-direction: column; -} -body.dark .story-item { background: var(--card-bg); } -.story-item:hover { - transform: translateY(-8px) scale(1.02); - box-shadow: 0 12px 25px rgba(255, 0, 127, 0.4); - border-color: var(--primary); -} -.story-preview-link { - display: block; - text-decoration: none; - color: inherit; - position: relative; - background-color: #000; - height: 200px; - overflow: hidden; - display: flex; - align-items: center; - justify-content: center; -} -.story-preview { - width: 100%; - height: 100%; - object-fit: cover; - display: block; - border-bottom: 1px solid var(--glass-bg); - loading: lazy; - transition: transform 0.5s ease; -} -.story-item:hover .story-preview { - transform: scale(1.1); -} -.story-item-info { - padding: 20px; - flex-grow: 1; - display: flex; - flex-direction: column; - justify-content: space-between; -} -.story-item h3 { - font-size: 1.25em; - font-weight: 700; - margin-bottom: 10px; - color: var(--text-light); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.story-item p.uploader { - font-size: 0.95em; - color: var(--text-dark); - margin-bottom: 12px; - display: flex; - align-items: center; -} -.uploader-link { - color: var(--secondary); - font-weight: 500; - text-decoration: none; - margin-right: 5px; - transition: color var(--transition); -} -.uploader-link:hover { - color: var(--primary); - text-decoration: underline; -} -.story-item p.stats { - font-size: 0.85em; - color: var(--text-dark); - margin-top: 15px; - display: flex; - gap: 15px; - align-items: center; -} -.story-item p.stats span { - display: inline-flex; - align-items: center; - gap: 4px; -} -.aducoin-display { - display: inline-flex; - align-items: center; - gap: 7px; - background-color: rgba(255, 215, 0, 0.15); - padding: 5px 10px; - border-radius: 20px; - border: 1px solid var(--aducoin-color); - color: var(--aducoin-color); - font-weight: bold; - font-size: 1em; - margin-left: 15px; - vertical-align: middle; - text-decoration: none; - cursor: pointer; - transition: var(--transition); - box-shadow: 0 0 5px rgba(255, 215, 0, 0.2); -} -.aducoin-display:hover { - background-color: rgba(255, 215, 0, 0.3); - box-shadow: 0 0 12px var(--aducoin-color); - transform: translateY(-2px); -} -.aducoin-icon { - width: 20px; - height: 20px; - vertical-align: middle; - border-radius: 50%; -} -.aducoin-balance { - vertical-align: middle; -} -label { - display: block; - margin-bottom: 5px; - margin-top: 18px; - font-weight: 500; - color: var(--text-dark); -} -.referral-section, .transfer-section, .donation-section { - margin-top: 30px; - padding: 20px; - background: var(--glass-bg); - border-radius: 12px; - border: 1px solid var(--secondary); - box-shadow: 0 5px 15px rgba(0,0,0,0.3); -} -.referral-section h3, .transfer-section h3, .donation-section h3 { - font-size: 1.2em; - color: var(--secondary); - margin-bottom: 15px; - border-bottom: 1px dashed rgba(138, 43, 226, 0.5); - padding-bottom: 8px; -} -.referral-link-container, .transfer-input-container, .donation-input-container { - display: flex; - gap: 12px; - align-items: center; - margin-top: 15px; -} -.referral-link-input { - flex-grow: 1; - background: rgba(0,0,0,0.3); - color: var(--text-light); - border: 1px solid var(--glass-bg); - padding: 10px 15px; - border-radius: 8px; - font-size: 0.95em; - cursor: text; -} -.copy-btn, .transfer-btn, .donate-btn { - padding: 10px 20px; - font-size: 0.9em; - text-transform: none; - letter-spacing: normal; - flex-shrink: 0; -} -.transfer-input, .donate-input { - flex-grow: 1; - margin: 0; - padding: 10px 15px; - font-size: 0.95em; -} -.transfer-info, .donation-info { - font-size: 0.9em; - color: var(--text-dark); - margin-top: 8px; -} -.promote-btn { - background: var(--accent); - color: var(--background-dark); - font-size: 0.9em; - padding: 8px 15px; - margin-top: 12px; - width: 100%; - text-transform: none; - letter-spacing: normal; - box-shadow: 0 3px 10px rgba(255, 215, 0, 0.3); -} -.promote-btn:hover { - background: #e6b800; -} -.promoted-info { - font-size: 0.85em; - color: var(--accent); - margin-top: 10px; - display: block; - text-align: center; - font-style: italic; - font-weight: bold; - text-shadow: 0 0 5px rgba(255, 215, 0, 0.3); -} -.transaction-table { - width: 100%; - border-collapse: collapse; - margin-top: 25px; -} -.transaction-table th, .transaction-table td { - border: 1px solid var(--glass-bg); - padding: 12px 15px; - text-align: left; - font-size: 0.95em; -} -.transaction-table th { - background-color: rgba(255, 0, 127, 0.2); - color: var(--primary); - font-weight: 600; -} -.transaction-table td { color: var(--text-dark); } -.transaction-table td.amount-positive { color: var(--online); font-weight: bold;} -.transaction-table td.amount-negative { color: var(--offline); font-weight: bold;} -.transaction-table td.desc { white-space: normal; word-break: break-word; } -.transaction-table tr:nth-child(even) { background-color: rgba(0,0,0,0.1); } -.transaction-table tr:hover { background-color: rgba(255, 255, 255, 0.07); } - -.conversation-list { list-style: none; padding: 0; } -.conversation-item { display: flex; align-items: center; gap: 18px; padding: 18px; background: var(--card-bg); border-radius: 12px; margin-bottom: 15px; transition: var(--transition); border: 1px solid transparent; text-decoration: none; color: inherit; box-shadow: 0 4px 10px rgba(0,0,0,0.3);} -.conversation-item:hover { background: var(--glass-bg); border-color: var(--secondary); transform: translateX(5px); box-shadow: 0 6px 15px rgba(138, 43, 226, 0.3); } -.conversation-item.unread { border-left: 5px solid var(--accent); font-weight: bold; background: rgba(255, 215, 0, 0.1); box-shadow: 0 4px 15px rgba(255, 215, 0, 0.2);} -.conversation-avatar { width: 50px; height: 50px; border-radius: 50%; object-fit: cover; flex-shrink: 0; border: 3px solid var(--secondary); } -.conversation-avatar-placeholder { width: 50px; height: 50px; border-radius: 50%; background: linear-gradient(45deg, var(--primary), var(--secondary)); display: flex; align-items: center; justify-content: center; font-size: 24px; color: white; font-weight: bold; flex-shrink: 0;box-shadow: 0 0 10px rgba(138, 43, 226, 0.5);} -.conversation-details { flex-grow: 1; overflow: hidden; } -.conversation-username { font-weight: 600; color: var(--primary); display: block; margin-bottom: 5px; font-size: 1.1em;} -.conversation-last-msg { font-size: 0.95em; color: var(--text-dark); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.conversation-timestamp { font-size: 0.85em; color: var(--text-dark); flex-shrink: 0; margin-left: auto; text-align: right; } -.message-area { max-height: 65vh; overflow-y: auto; margin-bottom: 25px; padding: 15px; background: var(--card-bg-dark); border-radius: 12px; display: flex; flex-direction: column-reverse; scroll-behavior: smooth;} -.message-bubble { max-width: 80%; padding: 12px 18px; border-radius: 20px; margin-bottom: 12px; line-height: 1.4; word-wrap: break-word; position: relative; font-size: 1em; } -.message-bubble.sent { background: var(--message-sent-bg); border-bottom-right-radius: 8px; align-self: flex-end; color: var(--text-light); box-shadow: 0 3px 8px rgba(138, 43, 226, 0.2);} -.message-bubble.received { background: var(--message-received-bg); border-bottom-left-radius: 8px; align-self: flex-start; color: var(--text-light); box-shadow: 0 3px 8px rgba(0,0,0,0.4);} -.message-bubble .sender-name { font-size: 0.8em; font-weight: bold; color: var(--primary); display: block; margin-bottom: 5px; } -.message-bubble .timestamp { font-size: 0.78em; color: var(--text-dark); display: block; margin-top: 8px; text-align: right; } -.message-input-area { display: flex; gap: 12px; margin-top: 20px; } -.message-input-area textarea { margin: 0; min-height: 50px; max-height: 180px; resize: vertical; height: 50px; flex-grow: 1; padding: 12px 18px; } -.message-input-area button { flex-shrink: 0; height: 50px; align-self: flex-end; } - - -@keyframes zoomIn { - from { opacity: 0; transform: scale(0.7); } - to { opacity: 1; transform: scale(1); } -} -@media (max-width: 900px) { - .sidebar { - transform: translateX(-100%); - width: 260px; - } - .sidebar.active { - transform: translateX(0); - box-shadow: 5px 0 20px rgba(0, 0, 0, 0.8); - } - .menu-btn { - display: block; - } - .container { - margin: 80px 18px 18px 18px; - max-width: calc(100% - 36px); - padding: 18px; - } - body:not(.sidebar-active-on-pc) .container, - body.sidebar-active-on-pc .container { - margin-left: auto; - margin-right: auto; - } - - .theme-toggle { - top: 80px; - } - .story-grid { - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); - gap: 20px; - } - .story-preview-link { height: 180px; } - .transaction-table th, .transaction-table td { font-size: 0.9em; padding: 10px;} - .user-grid { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); } -} -@media (max-width: 480px) { - .nav-brand { font-size: 1.6em; } - .nav-link { font-size: 1.05em; padding: 12px 18px; } - .btn { padding: 12px 25px; font-size: 0.95em; } - input, textarea, select { padding: 12px 15px; font-size: 1em;} - .story-grid { - grid-template-columns: 1fr; - gap: 20px; - } - .story-item h3 { font-size: 1.1em; } - .story-preview-link { height: 220px; } - .aducoin-display { font-size: 0.95em; padding: 4px 8px;} - .aducoin-icon { width: 18px; height: 18px; } - .referral-link-container, .transfer-input-container, .donation-input-container { flex-direction: column; align-items: stretch;} - .copy-btn, .transfer-btn, .donate-btn { width: 100%; } - .transfer-input, .donate-input { width: 100%; margin-bottom: 12px; } - .transaction-table { font-size: 0.85em; } - .transaction-table th, .transaction-table td { font-size: 0.85em; padding: 8px;} - .user-grid { grid-template-columns: 1fr; } - .conversation-item { flex-direction: column; align-items: flex-start; text-align: left; gap: 10px; padding: 15px;} - .conversation-timestamp { align-self: flex-end; margin-left: 0; } - .message-bubble { max-width: 95%; padding: 10px 15px; } - .message-bubble .timestamp { margin-top: 5px; } -} -''' - -NAV_HTML = ''' - -''' - -@app.route('/register', methods=['GET', 'POST']) -def register(): - referral_code = request.args.get('ref') - - if request.method == 'POST': - username = request.form.get('username') - password = request.form.get('password') - role = request.form.get('role') - ref_code_used = request.form.get('referral_code') - - if not username or not password or not role: - flash('Username, password, and role are required.', 'error') - return redirect(url_for('register', ref=ref_code_used)) - if len(username) < 3: - flash('Username must be at least 3 characters long.', 'error') - return redirect(url_for('register', ref=ref_code_used)) - if len(password) < 6: - flash('Password must be at least 6 characters long.', 'error') - return redirect(url_for('register', ref=ref_code_used)) - if role not in USER_ROLES: - flash('Invalid role selected.', 'error') - return redirect(url_for('register', ref=ref_code_used)) - - data = load_data() - if username in data['users']: - flash('Username already exists! Choose another.', 'error') - return redirect(url_for('register', ref=ref_code_used)) - - referrer_username = None - reward = 0 - if ref_code_used: - for r_user, r_data in data['users'].items(): - if r_data.get('referral_code') == ref_code_used: - referrer_username = r_user - break - if not referrer_username: - flash('Invalid referral code provided.', 'warning') - else: - reward = REFERRAL_REWARDS.get(role, DEFAULT_REWARD) - - - new_user_referral_code = generate_unique_referral_code(data) - - data['users'][username] = { - 'password': password, - 'role': role, - 'bio': '', - 'link': '', - 'avatar': None, - 'aducoin': 0, - 'referral_code': new_user_referral_code, - 'referred_by': referrer_username, - 'last_seen': datetime.now().strftime('%Y-%m-%d %H:%M:%S') - } - - if referrer_username and reward > 0: - data['users'][referrer_username]['aducoin'] = data['users'][referrer_username].get('aducoin', 0) + reward - log_transaction(data, referrer_username, 'earn_referral', reward, f"Received for referring user '{username}' (Role: {role})", related_user=username) - logging.info(f"Awarded {reward} AduCoin to {referrer_username} for referring {username} (Role: {role})") - - - try: - save_data(data) - flash('Registration successful! Please login.', 'success') - if referrer_username and reward > 0: - flash(f'Your referrer {referrer_username} received {reward} AduCoin!', '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}") - if username in data['users']: - del data['users'][username] - if referrer_username and reward > 0: - data['users'][referrer_username]['aducoin'] = data['users'][referrer_username].get('aducoin', 0) - reward - - return redirect(url_for('register', ref=ref_code_used)) - - is_authenticated = 'username' in session - username_session = session.get('username') - data = load_data() - user_count = len(data.get('users', {})) - is_online = is_user_online(data, username_session) if username_session else False - unread_count = count_unread_messages(data, username_session) - - html_content = ''' - - - - - - Register - Adusis - BBC QoS hub - - - - - - ''' + NAV_HTML + ''' - -
-

Join the Adusis Family

- {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} -
{{ message | escape }}
- {% endfor %} - {% endif %} - {% endwith %} -
- - - - - - - - {% if referral_code %} -

Registering with referral code: {{ referral_code | escape }}

- {% endif %} - - -
- Already joined? Login here -
- - - -''' - return render_template_string(html_content, - is_authenticated=is_authenticated, - username=username_session, - user_count=user_count, - is_online=is_online, - unread_count=unread_count, - user_roles=USER_ROLES, - referral_code=referral_code, - logo_url=LOGO_URL) - -@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 = session.get('username') - data = load_data() - user_count = len(data.get('users', {})) - is_online = is_user_online(data, username_session) if username_session else False - unread_count = count_unread_messages(data, username_session) - - html_content = ''' - - - - - - 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 | escape }}
- {% endfor %} - {% endif %} - {% endwith %} -
- - - -
- New here? Register an account -
- - - -''' - return render_template_string(html_content, - is_authenticated=is_authenticated, - username=username_session, - user_count=user_count, - is_online=is_online, - unread_count=unread_count, - logo_url=LOGO_URL) - - -@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('/', defaults={'mode': 'latest'}) -@app.route('/feed/') -def feed(mode='latest'): - 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 = [] - data['posts'] = [] - - now = datetime.now() - processed_stories = [] - - for post in posts_list: - post.setdefault('likes', []) - post.setdefault('views', 0) - post.setdefault('comments', []) - post.setdefault('jerked_off_count', 0) - post.setdefault('views_reward_milestone', 0) - post.setdefault('promoted_until', None) - if 'id' not in post: - post['id'] = str(random.randint(100000, 999999)) - post.setdefault('upload_date', '1970-01-01 00:00:00') - post.setdefault('uploader', 'unknown') - post.setdefault('title', 'Untitled') - post.setdefault('type', 'photo') - post.setdefault('filename', '') - - is_promoted = False - if post.get('promoted_until'): - try: - expiry_date = datetime.strptime(post['promoted_until'], '%Y-%m-%d %H:%M:%S') - if expiry_date > now: - is_promoted = True - except (ValueError, TypeError): - post['promoted_until'] = None - - post['is_currently_promoted'] = is_promoted - - if mode == 'hot': - if is_promoted: - processed_stories.append(post) - else: - processed_stories.append(post) - - - if mode == 'hot': - stories = sorted(processed_stories, key=lambda x: datetime.strptime(x.get('promoted_until', '1970-01-01 00:00:00'), '%Y-%m-%d %H:%M:%S'), reverse=True) - page_title = "Hot Stories" - else: - stories = sorted(processed_stories, key=lambda x: datetime.strptime(x.get('upload_date', '1970-01-01 00:00:00'), '%Y-%m-%d %H:%M:%S'), reverse=True) - page_title = "Latest Stories" - - is_authenticated = 'username' in session - user_count = len(data.get('users', {})) - is_online = is_user_online(data, username) if username else False - unread_count = count_unread_messages(data, username) - - html_content = ''' - - - - - - {{ page_title }} - Adusis - BBC QoS hub - - - - - - ''' + NAV_HTML + ''' - - -
-

{{ page_title }}

- {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} -
{{ message | escape }}
- {% endfor %} - {% endif %} - {% endwith %} - - {% if not stories %} - {% if mode == 'hot' %} -
-

No hot stories right now!

-

Promote your own stories from your profile to feature them here.

-
- {% else %} -
-

No stories yet!

-

Be the first one to upload something hot.

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

- ❤️ {{ story.get('likes', [])|length }} - 👁️ {{ story.get('views', 0) }} - 🍆 {{ story.get('jerked_off_count', 0) }} - 💬 {{ story.get('comments', [])|length }} -

-

{{ story['upload_date'] }}

-
-
-
- {% endfor %} -
- {% endif %} -
- - - - -''' - return render_template_string(html_content, - page_title=page_title, - mode=mode, - stories=stories, - is_authenticated=is_authenticated, - username=username, - repo_id=REPO_ID, - user_count=user_count, - is_online=is_online, - unread_count=unread_count, - is_user_online=lambda u: is_user_online(data, u), - logo_url=LOGO_URL, - html=html) - - -@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')) - - username = session['username'] - data = load_data() - - if request.method == 'POST': - current_data = load_data() - - if 'delete_story' in request.form: - story_id = request.form.get('story_id') - if story_id: - original_length = len(current_data['posts']) - story_to_delete = next((p for p in current_data['posts'] if p.get('id') == story_id and p.get('uploader') == username), None) - - if story_to_delete: - current_data['posts'] = [p for p in current_data['posts'] if not (p.get('id') == story_id and p.get('uploader') == username)] - - if len(current_data['posts']) < original_length: - try: - save_data(current_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}") - current_data['posts'].append(story_to_delete) - else: - flash('Story not found or deletion failed unexpectedly.', 'error') - else: - flash('Story not found or you do not have permission.', 'error') - else: - flash('Missing story ID for deletion.', '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] - role = request.form.get('role') - avatar_file = request.files.get('avatar') - profile_updated = False - - if username in current_data['users']: - user_profile_data = current_data['users'][username] - - if user_profile_data.get('bio') != bio: - user_profile_data['bio'] = bio - profile_updated = True - if user_profile_data.get('link') != link: - user_profile_data['link'] = link - profile_updated = True - if role and role in USER_ROLES and user_profile_data.get('role') != role: - user_profile_data['role'] = role - profile_updated = True - elif role and role not in USER_ROLES: - flash('Invalid role selected.', 'error') - return redirect(url_for('profile')) - - - 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}" - - old_avatar = user_profile_data.get('avatar') - if old_avatar: - try: - api.delete_file(path_in_repo=old_avatar, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE, ignore_patterns=["*.md"]) - logging.info(f"Deleted old avatar {old_avatar} from HF for user {username}") - except Exception as hf_del_e: - logging.warning(f"Could not delete old avatar {old_avatar} from HF (maybe already deleted?): {hf_del_e}") - - - 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}" - ) - user_profile_data['avatar'] = avatar_path - profile_updated = True - 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) - - - if profile_updated: - try: - save_data(current_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}") - else: - flash('No changes detected.', 'warning') - - else: - flash('User not found.', 'error') - - return redirect(url_for('profile')) - - user_data = data.get('users', {}).get(username, {}) - all_posts = data.get('posts', []) - if not isinstance(all_posts, list): all_posts = [] - - now = datetime.now() - user_stories_processed = [] - for p in all_posts: - if p.get('uploader') == username and isinstance(p.get('upload_date'), str): - p_copy = p.copy() - is_promoted = False - promoted_until_str = p_copy.get('promoted_until') - if promoted_until_str: - try: - expiry_date = datetime.strptime(promoted_until_str, '%Y-%m-%d %H:%M:%S') - if expiry_date > now: - is_promoted = True - p_copy['promotion_expiry_formatted'] = expiry_date.strftime('%Y-%m-%d %H:%M') - else: - pass - except (ValueError, TypeError): - p_copy['promotion_expiry_formatted'] = "Invalid Date" - p_copy['is_currently_promoted'] = is_promoted - user_stories_processed.append(p_copy) - - user_stories = sorted(user_stories_processed, - 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) - unread_count = count_unread_messages(data, username) - bio = user_data.get('bio', '') - link = user_data.get('link', '') - avatar = user_data.get('avatar', None) - aducoin = user_data.get('aducoin', 0) - user_role = user_data.get('role', 'Not Set') - referral_code = user_data.get('referral_code', 'N/A') - referral_link = url_for('register', ref=referral_code, _external=True) if referral_code != 'N/A' else 'N/A' - - last_seen_str = user_data.get('last_seen', 'Never') - try: - if last_seen_str != 'Never': - last_seen_dt = datetime.strptime(last_seen_str, '%Y-%m-%d %H:%M:%S') - last_seen = last_seen_dt.strftime('%Y-%m-%d %H:%M') - else: - last_seen = 'Never' - except ValueError: - last_seen = 'Invalid Date' - - - html_content = ''' - - - - - - Profile - {{ username | escape }} - Adusis - BBC QoS hub - - - - - - ''' + NAV_HTML + ''' - -
- {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} -
{{ message | escape }}
- {% endfor %} - {% endif %} - {% endwith %} - -
-
- {% if avatar %} - Avatar - {% else %} -
{{ username[0]|upper }}
- {% endif %} -
-
-

- {{ username | escape }} - - - AduCoin - {{ aducoin }} - -

-

Role: {{ user_role|title if user_role else 'Not Set' }}

-

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

- {% if link %} -

Link: {{ link | escape }}

- {% else %} -

Link: None

- {% endif %} -

Last Seen: {{ last_seen }}

-
-
- -
-

Edit Profile

-
- - - - - - - - - - - - -
-
- -
-

Your Referral Link

-

Share this link! Earn 100 AduCoin for each referred Girl, Couple, or Bull, and 50 AduCoin for each Boy.

- -
- - -

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'] | escape }} - {% endif %} - -
-
-

{{ story['title'] | escape }}

- {% if story.description %} -

{{ story.description | escape }}

- {% endif %} -
-
-

- ❤️ {{ story.get('likes', [])|length }} - 👁️ {{ story.get('views', 0) }} - 🍆 {{ story.get('jerked_off_count', 0) }} - 💬 {{ story.get('comments', [])|length }} -

-

{{ story['upload_date'] }}

- - {% if story.is_currently_promoted %} - - {% else %} -
- -
- {% endif %} - -
- - -
-
-
-
- {% endfor %} - {% if not user_stories %} -

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

- {% endif %} -
-
- - - - - - -''' - return render_template_string(html_content, - username=username, - user_stories=user_stories, - bio=bio, - link=link, - avatar=avatar, - aducoin=aducoin, - user_role=user_role, - user_roles=USER_ROLES, - referral_link=referral_link, - last_seen=last_seen, - is_authenticated=is_authenticated, - repo_id=REPO_ID, - user_count=user_count, - is_online=is_online, - unread_count=unread_count, - random=random, - promote_cost=PROMOTE_COST, - promote_duration_days=PROMOTE_DURATION_DAYS, - logo_url=LOGO_URL, - html=html - ) - -@app.route('/promote_story/', methods=['POST']) -def promote_story(story_id): - if 'username' not in session: - flash('Login to promote a story!', 'error') - return redirect(url_for('profile')) - - username = session['username'] - data = load_data() - - if username not in data['users']: - flash('User not found.', 'error') - return redirect(url_for('profile')) - - 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('profile')) - - story = data['posts'][story_index] - - if story.get('uploader') != username: - flash('You can only promote your own stories.', 'error') - return redirect(url_for('profile')) - - now = datetime.now() - if story.get('promoted_until'): - try: - expiry_date = datetime.strptime(story['promoted_until'], '%Y-%m-%d %H:%M:%S') - if expiry_date > now: - flash('This story is already promoted.', 'warning') - return redirect(url_for('profile')) - except (ValueError, TypeError): - pass - - user_balance = data['users'][username].get('aducoin', 0) - if user_balance < PROMOTE_COST: - flash(f'Insufficient AduCoin to promote. You need {PROMOTE_COST}, but only have {user_balance}.', 'error') - return redirect(url_for('profile')) - - data['users'][username]['aducoin'] -= PROMOTE_COST - expiry_time = now + timedelta(days=PROMOTE_DURATION_DAYS) - story['promoted_until'] = expiry_time.strftime('%Y-%m-%d %H:%M:%S') - - log_transaction(data, username, 'promote_post', -PROMOTE_COST, f"Promoted story '{story.get('title', 'Untitled')}' (ID: {story_id})", related_story_id=story_id) - - try: - save_data(data) - flash(f'Story promoted to Hot for {PROMOTE_DURATION_DAYS} days! (-{PROMOTE_COST} AduCoin)', 'success') - except Exception as e: - flash('Failed to promote story due to a saving error.', 'error') - logging.error(f"Error saving data after promoting story {story_id}: {e}") - data['users'][username]['aducoin'] += PROMOTE_COST - story['promoted_until'] = None - - return redirect(url_for('profile')) - - -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') - sender_aducoin = data['users'].get(username, {}).get('aducoin', 0) if username else 0 - - 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].copy() - - 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') - } - - current_data = load_data() - current_story_index = next((index for (index, d) in enumerate(current_data.get('posts',[])) if d.get('id') == story_id), None) - if current_story_index is None: - flash('Story disappeared while commenting.', 'error') - return redirect(url_for('feed')) - - story_to_update = current_data['posts'][current_story_index] - if not isinstance(story_to_update.get('comments'), list): - story_to_update['comments'] = [] - - story_to_update['comments'].append(new_comment) - - try: - save_data(current_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 new_comment in story_to_update.get('comments', []): - story_to_update['comments'].remove(new_comment) - return redirect(url_for('story_page', story_id=story_id)) - - story.setdefault('title', 'Untitled') - story.setdefault('description', '') - story.setdefault('type', 'photo') - story.setdefault('filename', '') - story.setdefault('uploader', 'unknown') - story.setdefault('upload_date', 'Unknown date') - story.setdefault('likes', []) - story.setdefault('views', 0) - story.setdefault('jerked_off_count', 0) - story.setdefault('comments', []) - story.setdefault('views_reward_milestone', 0) - story.setdefault('promoted_until', None) - - is_promoted = False - if story.get('promoted_until'): - try: - if datetime.strptime(story['promoted_until'], '%Y-%m-%d %H:%M:%S') > datetime.now(): - is_promoted = True - except (ValueError, TypeError): pass - story['is_currently_promoted'] = is_promoted - - - is_authenticated = 'username' in session - user_count = len(data.get('users', {})) - current_user_is_online = is_user_online(data, username) if username else False - unread_count = count_unread_messages(data, username) - uploader_online = is_user_online(data, story.get('uploader')) - can_donate = is_authenticated and username != story.get('uploader') - - - html_content = ''' - - - - - - {{ story['title'] | escape }} - Adusis - BBC QoS hub - - - - - - ''' + NAV_HTML + ''' - -
- {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} -
{{ message | escape }}
- {% endfor %} - {% endif %} - {% endwith %} - -

- {{ story['title'] | escape }} - {% if story.is_currently_promoted %} - - {% endif %} -

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

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

-

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

-

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

-
- -
- {% if is_authenticated %} - - {% else %} -
- ❤️ Likes {{ story.get('likes', []) | length }} -
- {% endif %} - -
- 👁️ Views {{ story.get('views', 0) }} -
-
- - {% if can_donate %} -
-

Support {{ story.uploader | escape }}! Send AduCoin

-
-
- - -
-

Your balance: {{ sender_aducoin }} AduCoin

-
-
- {% endif %} - - -
-

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'] | escape }} - {{ comment['timestamp'] }} -
-
- {{ comment['text'] | escape }} -
-
- {% endfor %} - {% else %} -

Be the first to comment!

- {% endif %} -
-
- - Back to Feed -
- - - - - - -''' - return render_template_string(html_content, - story=story, - repo_id=REPO_ID, - is_authenticated=is_authenticated, - username=username, - user_count=user_count, - is_online=current_user_is_online, - unread_count=unread_count, - uploader_online=uploader_online, - can_donate=can_donate, - sender_aducoin=sender_aducoin, - html=html, - logo_url=LOGO_URL - ) - -@app.route('/donate/', methods=['POST']) -def donate_story(story_id): - if 'username' not in session: - flash('You must be logged in to donate AduCoin.', 'error') - return redirect(url_for('story_page', story_id=story_id)) - - sender_username = session['username'] - amount_str = request.form.get('amount') - - 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 not found.', 'error') - return redirect(url_for('feed')) - - story = data['posts'][story_index] - recipient_username = story.get('uploader') - - if not recipient_username or recipient_username not in data.get('users', {}): - flash('Story uploader not found or invalid.', 'error') - return redirect(url_for('story_page', story_id=story_id)) - - if sender_username == recipient_username: - flash('You cannot donate AduCoin to your own post.', 'error') - return redirect(url_for('story_page', story_id=story_id)) - - try: - amount = int(amount_str) - if amount <= 0: - raise ValueError("Amount must be positive.") - except (ValueError, TypeError): - flash('Invalid amount specified.', 'error') - return redirect(url_for('story_page', story_id=story_id)) - - if sender_username not in data.get('users', {}): - flash('Sender account not found. Please re-login.', 'error') - session.pop('username', None) - return redirect(url_for('login')) - - sender_balance = data['users'][sender_username].get('aducoin', 0) - - if sender_balance < amount: - flash(f'Insufficient funds. You only have {sender_balance} AduCoin.', 'error') - return redirect(url_for('story_page', story_id=story_id)) - - data['users'][sender_username]['aducoin'] -= amount - data['users'][recipient_username]['aducoin'] = data['users'][recipient_username].get('aducoin', 0) + amount - - log_transaction(data, sender_username, 'donation_sent', -amount, f"Donated to '{recipient_username}' for post '{story.get('title', 'Untitled')}'", related_user=recipient_username, related_story_id=story_id) - log_transaction(data, recipient_username, 'donation_received', amount, f"Received from '{sender_username}' for post '{story.get('title', 'Untitled')}'", related_user=sender_username, related_story_id=story_id) - - try: - save_data(data) - flash(f'Successfully donated {amount} AduCoin to {html.escape(recipient_username)}!', 'success') - logging.info(f"User {sender_username} donated {amount} AduCoin to {recipient_username} for story {story_id}") - except Exception as e: - flash('An error occurred during the donation. Please try again.', 'error') - logging.error(f"Error saving AduCoin donation from {sender_username} to {recipient_username} for story {story_id}: {e}") - try: - data['users'][sender_username]['aducoin'] += amount - data['users'][recipient_username]['aducoin'] -= amount - except Exception as rollback_e: - logging.error(f"Error rolling back failed donation: {rollback_e}") - - return redirect(url_for('story_page', story_id=story_id)) - - -@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')) - - username = session['username'] - - if request.method == 'POST': - title = request.form.get('title', '').strip()[:150] - description = request.form.get('description', '').strip()[:2000] - file = request.files.get('file') - - if not file or not file.filename: - flash('File is required!', 'error') - return redirect(url_for('upload')) - if not title: - flash('Title is 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')) - - file_ext = os.path.splitext(file.filename)[1] - base_filename = secure_filename(f"{username}_{int(time.time())}_{random.randint(100,999)}{file_ext}") - temp_path = os.path.join(UPLOAD_FOLDER, base_filename) - - try: - file.save(temp_path) - - if os.path.getsize(temp_path) > 100 * 1024 * 1024: - os.remove(temp_path) - flash('File is too large (max 100MB).', 'error') - return redirect(url_for('upload')) - - file_type = 'video' if base_filename.lower().endswith(('.mp4', '.mov', 'avi', '.webm')) else 'photo' - - data = load_data() - 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 = base_filename - file_path_in_repo = f"{file_type}s/{hf_filename}" - - logging.info(f"Uploading {temp_path} to {file_path_in_repo}...") - api.upload_file( - path_or_fileobj=temp_path, - path_in_repo=file_path_in_repo, - repo_id=REPO_ID, - repo_type="dataset", - token=HF_TOKEN_WRITE, - commit_message=f"Upload {file_type} by {username} (ID: {story_id})" - ) - logging.info("Upload to HF successful.") - - new_story = { - '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, - 'views_reward_milestone': 0, - 'promoted_until': None - } - - data.setdefault('posts', []).append(new_story) - - if username in data['users']: - data['users'][username]['aducoin'] = data['users'][username].get('aducoin', 0) + UPLOAD_REWARD - log_transaction(data, username, 'earn_upload', UPLOAD_REWARD, f"Earned for uploading story '{title}' (ID: {story_id})", related_story_id=story_id) - logging.info(f"Awarded {UPLOAD_REWARD} AduCoin to {username} for uploading story {story_id}") - else: - logging.warning(f"Uploader {username} not found in users data, cannot award AduCoin.") - - save_data(data) - flash(f'Story uploaded successfully! (+{UPLOAD_REWARD} AduCoin)', 'success') - - if os.path.exists(temp_path): - os.remove(temp_path) - return redirect(url_for('story_page', story_id=story_id)) - - except Exception as e: - flash(f'Upload failed: {html.escape(str(e))}', 'error') - logging.error(f"Error during upload for {username}: {e}", exc_info=True) - if os.path.exists(temp_path): - os.remove(temp_path) - return redirect(url_for('upload')) - - data = load_data() - is_authenticated = True - user_count = len(data.get('users', {})) - is_online = is_user_online(data, username) - unread_count = count_unread_messages(data, username) - - html_content = ''' - - - - - - 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 | safe }}
- {% endfor %} - {% endif %} - {% endwith %} -
- - - - - - - - - Allowed: JPG, PNG, GIF, MP4, MOV, AVI, WEBM (Max 100MB) - - -
-
- - - -''' - return render_template_string(html_content, - username=username, - is_authenticated=is_authenticated, - repo_id=REPO_ID, - user_count=user_count, - is_online=is_online, - unread_count=unread_count, - upload_reward=UPLOAD_REWARD, - logo_url=LOGO_URL) - -@app.route('/users', methods=['GET', 'POST']) -def users(): - data = load_data() - current_user = session.get('username') - - is_authenticated = 'username' in session - user_count = len(data.get('users', {})) - current_user_is_online = is_user_online(data, current_user) if current_user else False - unread_count = count_unread_messages(data, current_user) - - search_query = request.form.get('search', '').strip().lower() if request.method == 'POST' else '' - - user_list = [] - for username_loop, user_data in data.get('users', {}).items(): - if not search_query or search_query in username_loop.lower(): - user_list.append({ - 'name': username_loop, - 'avatar': user_data.get('avatar'), - 'online': is_user_online(data, username_loop), - 'role': user_data.get('role', 'N/A') - }) - - user_list.sort(key=lambda u: (not u['online'], u['name'].lower())) - - html_content = ''' - - - - - - Users - Adusis - BBC QoS hub - - - - - - ''' + NAV_HTML + ''' - -
-

Explore Users ({{ user_count }})

- {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} -
{{ message | escape }}
- {% endfor %} - {% endif %} - {% endwith %} -
-
- - -
-
-
- {% for user_info in user_list %} - - {% if user_info['avatar'] %} - {{ user_info['name'] | escape }} Avatar - {% else %} -
{{ user_info['name'][0]|upper }}
- {% endif %} - -
- {% endfor %} - {% if not user_list and search_query %} -

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

- {% elif not user_list %} -

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

- {% endif %} -
-
- - - -''' - return render_template_string(html_content, - user_list=user_list, - username=current_user, - is_authenticated=is_authenticated, - repo_id=REPO_ID, - user_count=user_count, - is_online=current_user_is_online, - unread_count=unread_count, - search_query=search_query, - random=random, - logo_url=LOGO_URL, - html=html) - -@app.route('/user/', methods=['GET']) -def user_profile(username): - data = load_data() - current_user = session.get('username') - - if username == current_user: - return redirect(url_for('profile')) - - if username not in data.get('users', {}): - flash(f'User "{html.escape(username)}" not found.', 'error') - return redirect(url_for('users')) - - user_data = data['users'][username] - all_posts = data.get('posts', []) - if not isinstance(all_posts, list): all_posts = [] - - now = datetime.now() - user_stories_processed = [] - for p in all_posts: - if p.get('uploader') == username and isinstance(p.get('upload_date'), str): - p_copy = p.copy() - is_promoted = False - promoted_until_str = p_copy.get('promoted_until') - if promoted_until_str: - try: - expiry_date = datetime.strptime(promoted_until_str, '%Y-%m-%d %H:%M:%S') - if expiry_date > now: - is_promoted = True - except (ValueError, TypeError): pass - p_copy['is_currently_promoted'] = is_promoted - user_stories_processed.append(p_copy) - - user_stories = sorted(user_stories_processed, - 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_is_online = is_user_online(data, current_user) if current_user else False - unread_count = count_unread_messages(data, current_user) - profile_user_online = is_user_online(data, username) - sender_aducoin = data['users'].get(current_user, {}).get('aducoin', 0) if current_user else 0 - - bio = user_data.get('bio', '') - link = user_data.get('link', '') - avatar = user_data.get('avatar', None) - aducoin = user_data.get('aducoin', 0) - user_role = user_data.get('role', 'Not Set') - - last_seen_str = user_data.get('last_seen', 'Never') - last_seen = 'Unknown' - try: - if last_seen_str != 'Never': - last_seen_dt = datetime.strptime(last_seen_str, '%Y-%m-%d %H:%M:%S') - time_diff = datetime.now() - last_seen_dt - - if profile_user_online: - last_seen = 'Online Now' - elif time_diff < timedelta(minutes=10): - last_seen = 'Seen recently' - elif time_diff < timedelta(hours=1): - last_seen = f"Seen {int(time_diff.total_seconds() / 60)}m ago" - elif time_diff < timedelta(days=1): - last_seen = last_seen_dt.strftime('Today %H:%M') - elif time_diff < timedelta(days=2): - last_seen = last_seen_dt.strftime('Yesterday %H:%M') - elif time_diff < timedelta(days=7): - last_seen = last_seen_dt.strftime('%a %H:%M') - else: - last_seen = last_seen_dt.strftime('%Y-%m-%d') - else: - last_seen = 'Never' - except ValueError: - last_seen = 'Invalid Date' - - html_content = ''' - - - - - - Profile - {{ profile_username | escape }} - Adusis - BBC QoS hub - - - - - - ''' + NAV_HTML + ''' - -
- {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} -
{{ message | escape }}
- {% endfor %} - {% endif %} - {% endwith %} - -
-
- {% if avatar %} - {{ profile_username | escape }} Avatar - {% else %} -
{{ profile_username[0]|upper }}
- {% endif %} -
-
-

- {{ profile_username | escape }} - - - AduCoin - {{ aducoin }} - -

-

Role: {{ user_role|title if user_role else 'Not Set' }}

-

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

- {% if link %} -

Link: {{ link | escape }}

- {% else %} -

Link: None

- {% endif %} -

Last Seen: {{ last_seen }}

- {% if is_authenticated %} - - {% endif %} -
-
- - {% if is_authenticated %} -
-

Send AduCoin to {{ profile_username | escape }}

-
-
- - -
-

Your balance: {{ sender_aducoin }} AduCoin

-
-
- {% endif %} - -

{{ profile_username | escape }}'s Stories

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

{{ story['title'] | escape }}

- {% if story.description %} -

{{ story.description | escape }}

- {% endif %} -
-
-

- ❤️ {{ story.get('likes', [])|length }} - 👁️ {{ story.get('views', 0) }} - 🍆 {{ story.get('jerked_off_count', 0) }} - 💬 {{ story.get('comments', [])|length }} -

-

{{ story['upload_date'] }}

-
-
-
- {% endfor %} - {% if not user_stories %} -

This user hasn't uploaded any stories yet.

- {% endif %} -
-
- - - - - - -''' - return render_template_string(html_content, - profile_username=username, - user_stories=user_stories, - bio=bio, - link=link, - avatar=avatar, - aducoin=aducoin, - user_role=user_role, - last_seen=last_seen, - is_authenticated=is_authenticated, - username=current_user, - sender_aducoin=sender_aducoin, - repo_id=REPO_ID, - user_count=user_count, - is_online=current_user_is_online, - unread_count=unread_count, - profile_user_online=profile_user_online, - random=random, - logo_url=LOGO_URL, - html=html) - -@app.route('/transactions') -def transactions(): - if 'username' not in session: - flash('Login to view your transaction history!', 'error') - return redirect(url_for('login')) - - username = session['username'] - data = load_data() - - user_transactions = sorted( - [t for t in data.get('transactions', []) if t.get('user') == username], - key=lambda x: x.get('timestamp', '0'), - reverse=True - ) - - user_data = data.get('users', {}).get(username, {}) - current_aducoin = user_data.get('aducoin', 0) - user_count = len(data.get('users', {})) - is_online = is_user_online(data, username) - unread_count = count_unread_messages(data, username) - - html_content = ''' - - - - - - AduCoin History - {{ username | escape }} - Adusis - BBC QoS hub - - - - - - ''' + NAV_HTML + ''' - -
-

- AduCoin AduCoin History -

-

Your current balance: {{ current_aducoin }} AduCoin

- - {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} -
{{ message | escape }}
- {% endfor %} - {% endif %} - {% endwith %} - - {% if transactions %} -
- - - - - - - - - - - - {% for tx in transactions %} - - - - - - - - {% endfor %} - -
DateTypeAmountDescriptionBalance After
{{ tx.timestamp }}{{ tx.type.replace('_', ' ')|title }}{{ '%+d'|format(tx.amount) if tx.amount != 0 else tx.amount }}{{ tx.description | escape }}{{ tx.balance_after }}
-
- {% else %} -

You have no transaction history yet.

- {% endif %} - - Back to Profile -
- - - -''' - return render_template_string(html_content, - transactions=user_transactions, - username=username, - current_aducoin=current_aducoin, - is_authenticated=True, - user_count=user_count, - is_online=is_online, - unread_count=unread_count, - logo_url=LOGO_URL, - html=html) - - -@app.route('/transfer_aducoin/', methods=['POST']) -def transfer_aducoin(recipient_username): - if 'username' not in session: - flash('You must be logged in to transfer AduCoin.', 'error') - return redirect(url_for('user_profile', username=recipient_username)) - - sender_username = session['username'] - amount_str = request.form.get('amount') - - if sender_username == recipient_username: - flash('You cannot transfer AduCoin to yourself.', 'error') - return redirect(url_for('user_profile', username=recipient_username)) - - try: - amount = int(amount_str) - if amount <= 0: - raise ValueError("Amount must be positive.") - except (ValueError, TypeError): - flash('Invalid amount specified.', 'error') - return redirect(url_for('user_profile', username=recipient_username)) - - data = load_data() - - if sender_username not in data.get('users', {}): - flash('Sender account not found. Please re-login.', 'error') - session.pop('username', None) - return redirect(url_for('login')) - - if recipient_username not in data.get('users', {}): - flash(f'Recipient user "{html.escape(recipient_username)}" not found.', 'error') - return redirect(url_for('user_profile', username=recipient_username)) - - sender_balance = data['users'][sender_username].get('aducoin', 0) - - if sender_balance < amount: - flash(f'Insufficient funds. You only have {sender_balance} AduCoin.', 'error') - return redirect(url_for('user_profile', username=recipient_username)) - - data['users'][sender_username]['aducoin'] -= amount - data['users'][recipient_username]['aducoin'] = data['users'][recipient_username].get('aducoin', 0) + amount - - log_transaction(data, sender_username, 'transfer_sent', -amount, f"Sent to user '{recipient_username}'", related_user=recipient_username) - log_transaction(data, recipient_username, 'transfer_received', amount, f"Received from user '{sender_username}'", related_user=sender_username) - - try: - save_data(data) - flash(f'Successfully transferred {amount} AduCoin to {html.escape(recipient_username)}!', 'success') - logging.info(f"User {sender_username} transferred {amount} AduCoin to {recipient_username}") - except Exception as e: - flash('An error occurred during the transfer. Please try again.', 'error') - logging.error(f"Error saving AduCoin transfer from {sender_username} to {recipient_username}: {e}") - try: - data['users'][sender_username]['aducoin'] += amount - data['users'][recipient_username]['aducoin'] -= amount - except Exception as rollback_e: - logging.error(f"Error rolling back failed transfer: {rollback_e}") - - - return redirect(url_for('user_profile', username=recipient_username)) - - -@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 - - 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 - - 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] - uploader = story.get('uploader') - - if not isinstance(story.get('views'), int): - story['views'] = 0 - if not isinstance(story.get('views_reward_milestone'), int): - story['views_reward_milestone'] = 0 - - story['views'] += 1 - current_views = story['views'] - last_milestone_views = story['views_reward_milestone'] - reward_message = None - reward_to_give = 0 - - if uploader and uploader in data.get('users', {}): - current_milestone = current_views // VIEW_REWARD_THRESHOLD - last_milestone = last_milestone_views // VIEW_REWARD_THRESHOLD - - if current_milestone > last_milestone: - milestones_to_reward = current_milestone - last_milestone - reward_to_give = milestones_to_reward * VIEW_REWARD_AMOUNT - - if reward_to_give > 0: - data['users'][uploader]['aducoin'] = data['users'][uploader].get('aducoin', 0) + reward_to_give - story['views_reward_milestone'] = current_views - - milestone_desc = f"{current_milestone * VIEW_REWARD_THRESHOLD} views" - log_transaction(data, uploader, 'earn_views', reward_to_give, f"Reached {milestone_desc} on story '{story.get('title', 'Untitled')}' (ID: {story_id})", related_story_id=story_id) - - reward_message = f"Awarded {reward_to_give} AduCoin to {uploader} for reaching {milestone_desc} views on story {story_id}" - logging.info(reward_message) - - - try: - save_data(data) - response_data = {'message': 'Success', 'views': story['views']} - if reward_message: - response_data['reward_message'] = reward_message - return jsonify(response_data), 200 - except Exception as e: - logging.error(f"Error saving view count or reward for story {story_id}: {e}") - story['views'] -= 1 - if reward_message and uploader and uploader in data.get('users', {}): - data['users'][uploader]['aducoin'] -= reward_to_give - story['views_reward_milestone'] = last_milestone_views - - return jsonify({'message': 'Failed to save view count', 'views': story['views']}), 503 - -@app.route('/messages') -def inbox(): - if 'username' not in session: - flash('Login to view your messages!', 'error') - return redirect(url_for('login')) - - username = session['username'] - data = load_data() - users_data = data.get('users', {}) - messages_data = data.get('direct_messages', {}) - - conversations = [] - for conv_key_str, messages in messages_data.items(): - try: - conv_key = eval(conv_key_str) - if not isinstance(conv_key, tuple) or len(conv_key) != 2: - logging.warning(f"Skipping invalid conversation key format: {conv_key_str}") - continue - - if username in conv_key: - other_user = next(user for user in conv_key if user != username) - if not messages: - last_message_text = "No messages yet" - last_message_time = "" - is_unread = False - else: - last_message = sorted(messages, key=lambda x: x['timestamp'])[-1] - last_message_text = last_message['text'] - last_message_time = last_message['timestamp'] - is_unread = any(msg['sender'] != username and not msg.get('read') for msg in messages) - - other_user_data = users_data.get(other_user, {}) - conversations.append({ - 'other_user': other_user, - 'avatar': other_user_data.get('avatar'), - 'last_message_text': last_message_text, - 'last_message_time': last_message_time, - 'is_unread': is_unread - }) - except Exception as e: - logging.error(f"Error processing conversation key {conv_key_str}: {e}") - continue - - conversations.sort(key=lambda x: (not x['is_unread'], x['last_message_time']), reverse=True) - - is_authenticated = True - user_count = len(users_data) - is_online = is_user_online(data, username) - unread_count = count_unread_messages(data, username) - - html_content = ''' - - - - - - Inbox - Adusis - - - - - - ''' + NAV_HTML + ''' - -
-

Messages

- {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} -
{{ message | escape }}
- {% endfor %} - {% endif %} - {% endwith %} - - {% if conversations %} - - {% else %} -

You have no conversations yet. Find a user on the Users page to start chatting!

- {% endif %} -
- - - -''' - return render_template_string(html_content, - conversations=conversations, - is_authenticated=is_authenticated, - username=username, - user_count=user_count, - is_online=is_online, - unread_count=unread_count, - repo_id=REPO_ID, - random=random, - logo_url=LOGO_URL, - html=html) - -@app.route('/messages/', methods=['GET', 'POST']) -def conversation(other_username): - if 'username' not in session: - flash('Login to view messages!', 'error') - return redirect(url_for('login')) - - username = session['username'] - data = load_data() - users_data = data.get('users', {}) - messages_data = data.get('direct_messages', {}) - - if other_username not in users_data: - flash(f"User '{html.escape(other_username)}' not found.", 'error') - return redirect(url_for('inbox')) - - if username == other_username: - flash("You cannot message yourself.", 'warning') - return redirect(url_for('inbox')) - - conv_key_tuple = get_conversation_key(username, other_username) - conv_key = str(conv_key_tuple) - - if request.method == 'POST': - message_text = request.form.get('message_text', '').strip() - if not message_text: - flash("Message cannot be empty.", 'error') - return redirect(url_for('conversation', other_username=other_username)) - if len(message_text) > 2000: - flash('Message too long (max 2000 characters).', 'error') - return redirect(url_for('conversation', other_username=other_username)) - - new_message = { - 'message_id': str(uuid.uuid4()), - 'sender': username, - 'text': message_text, - 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), - 'read': False - } - - current_data = load_data() - current_messages_data = current_data.setdefault('direct_messages', {}) - - if conv_key not in current_messages_data: - current_messages_data[conv_key] = [] - elif not isinstance(current_messages_data[conv_key], list): - logging.warning(f"Correcting non-list conversation data for key {conv_key}") - current_messages_data[conv_key] = [] - - current_messages_data[conv_key].append(new_message) - - try: - save_data(current_data) - return redirect(url_for('conversation', other_username=other_username)) - except Exception as e: - flash("Failed to send message.", 'error') - logging.error(f"Error saving message from {username} to {other_username}: {e}") - if conv_key in current_messages_data and new_message in current_messages_data[conv_key]: - current_messages_data[conv_key].remove(new_message) - return redirect(url_for('conversation', other_username=other_username)) - - data = load_data() - messages_data = data.get('direct_messages', {}) - messages = messages_data.get(conv_key, []) - if not isinstance(messages, list): messages = [] - - marked_read = False - for msg in messages: - if isinstance(msg, dict) and msg.get('sender') == other_username and not msg.get('read'): - msg['read'] = True - marked_read = True - - if marked_read: - try: - save_data(data) - logging.info(f"Marked messages as read in conversation {conv_key} for user {username}") - except Exception as e: - logging.error(f"Failed to save read status for conversation {conv_key}: {e}") - - messages.sort(key=lambda x: x.get('timestamp', '0')) - - is_authenticated = True - user_count = len(users_data) - is_online = is_user_online(data, username) - other_user_is_online = is_user_online(data, other_username) - unread_count = count_unread_messages(data, username) - other_user_data = users_data.get(other_username, {}) - - - html_content = ''' - - - - - - Chat with {{ other_username | escape }} - Adusis - - - - - - ''' + NAV_HTML + ''' - -
-
- {% if other_user_data.avatar %} - {{ other_username | escape }} - {% else %} -
{{ other_username[0]|upper }}
- {% endif %} -
-

{{ other_username | escape }}

-
- - {{ 'Online' if other_user_is_online else 'Offline' }} -
-
- View Profile -
- - {% with messages_flash = get_flashed_messages(with_categories=true) %} - {% if messages_flash %} - {% for category, message in messages_flash %} -
{{ message | escape }}
- {% endfor %} - {% endif %} - {% endwith %} - -
- {% if messages %} - {% for msg in messages %} - {% if msg is mapping %} -
-
{{ msg.text | escape }}
- {{ msg.timestamp }} -
- {% endif %} - {% endfor %} - {% else %} -

Start the conversation!

- {% endif %} -
- -
- - -
- - Back to Inbox - -
- - - -''' - return render_template_string(html_content, - messages=messages, - other_username=other_username, - other_user_data=other_user_data, - other_user_is_online=other_user_is_online, - is_authenticated=is_authenticated, - username=username, - user_count=user_count, - is_online=is_online, - unread_count=unread_count, - repo_id=REPO_ID, - random=random, - logo_url=LOGO_URL, - html=html) - -if __name__ == '__main__': - if not os.path.exists(DATA_FILE): - logging.info(f"{DATA_FILE} not found locally, attempting download from HF.") - download_db_from_hf() - if not os.path.exists(DATA_FILE): - logging.info(f"Data file {DATA_FILE} still not found after download attempt, creating empty file.") - with open(DATA_FILE, 'w', encoding='utf-8') as f: - json.dump(initialize_data_structure({}), f) - else: - logging.info(f"Successfully downloaded {DATA_FILE}.") - else: - logging.info(f"Using existing local file {DATA_FILE}.") - - try: - current_data = load_data() - initialized_data = initialize_data_structure(current_data) - needs_save = False - if 'direct_messages' not in current_data: needs_save = True - if 'transactions' not in current_data: needs_save = True - - if needs_save: - logging.info("Applying initial data structure updates...") - save_data(initialized_data) - else: - if json.dumps(initialized_data, sort_keys=True) != json.dumps(current_data, sort_keys=True): - logging.info("Ensuring data structure consistency...") - save_data(initialized_data) - - except Exception as e: - logging.error(f"Error during initial data structure check/update: {e}") - - backup_thread = threading.Thread(target=periodic_backup, daemon=True) - backup_thread.start() - logging.info("Starting Flask application...") - app.run(debug=False, host='0.0.0.0', port=7860) -# --- END OF FILE app.py ---