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 { --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; --primary-hue: 330; --secondary-hue: 270; --accent-hue: 50; --primary-color: hsl(var(--primary-hue), 100%, 55%); --secondary-color: hsl(var(--secondary-hue), 85%, 60%); --accent-color: hsl(var(--accent-hue), 100%, 50%); --text-light: #f0f0f5; --text-dark: #a0a0b0; --bg-dark: #0D0C13; --bg-light: #161422; --glass-bg: rgba(22, 20, 34, 0.6); --glass-border: rgba(255, 255, 255, 0.1); --glow-primary: 0 0 20px 0px hsla(var(--primary-hue), 100%, 55%, 0.5); --glow-secondary: 0 0 20px 0px hsla(var(--secondary-hue), 85%, 60%, 0.5); --glow-accent: 0 0 20px 0px hsla(var(--accent-hue), 100%, 50%, 0.4); --online-color: #00FF7F; --offline-color: #FF4500; --transition-fast: all 0.2s ease-in-out; --transition-medium: all 0.4s ease-in-out; } @keyframes fadeIn { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } } @keyframes modalZoomIn { from { opacity: 0; transform: scale(0.8); } to { opacity: 1; transform: scale(1); } } * { margin: 0; padding: 0; box-sizing: border-box; } html { scroll-behavior: smooth; } body { font-family: var(--font-sans); background-color: var(--bg-dark); color: var(--text-light); line-height: 1.6; overflow-x: hidden; background-image: radial-gradient(circle at 10% 15%, hsla(var(--primary-hue), 80%, 30%, 0.3) 0%, transparent 30%), radial-gradient(circle at 90% 85%, hsla(var(--secondary-hue), 80%, 30%, 0.35) 0%, transparent 40%); background-attachment: fixed; } .sidebar { position: fixed; top: 0; left: 0; width: 280px; height: 100%; background: var(--glass-bg); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border-right: 1px solid var(--glass-border); padding: 25px; z-index: 1000; transition: transform 0.4s ease; display: flex; flex-direction: column; } .sidebar.hidden { transform: translateX(-100%); } .sidebar-header { display: flex; align-items: center; gap: 15px; margin-bottom: 40px; padding-bottom: 20px; border-bottom: 1px solid var(--glass-border); } .logo { width: 50px; height: 50px; border-radius: 14px; border: 2px solid var(--primary-color); box-shadow: var(--glow-primary); } .nav-brand { font-size: 1.9em; font-weight: 700; color: var(--primary-color); text-shadow: 0 0 10px var(--primary-color); } .nav-links { display: flex; flex-direction: column; gap: 10px; flex-grow: 1; } .nav-link { display: flex; align-items: center; gap: 15px; padding: 14px 20px; color: var(--text-dark); text-decoration: none; border-radius: 10px; font-size: 1.1em; font-weight: 500; transition: var(--transition-fast); position: relative; border: 1px solid transparent; } .nav-link:hover { color: var(--text-light); background: rgba(255,255,255,0.05); } .nav-link.active { color: var(--primary-color); background: hsla(var(--primary-hue), 100%, 55%, 0.1); border: 1px solid hsla(var(--primary-hue), 100%, 55%, 0.3); font-weight: 600; } .nav-link span:first-child { font-size: 1.3em; } .logout-btn:hover { color: var(--offline-color); } .menu-btn { display: none; font-size: 28px; background: var(--glass-bg); border: 1px solid var(--glass-border); color: var(--primary-color); cursor: pointer; position: fixed; top: 20px; left: 20px; z-index: 1001; padding: 12px; border-radius: 50%; box-shadow: 0 0 15px rgba(0,0,0,0.5); transition: var(--transition-medium); backdrop-filter: blur(8px); } .menu-btn:hover { background: var(--primary-color); color: white; box-shadow: var(--glow-primary); } .container { margin-left: 300px; padding: 40px; transition: margin-left var(--transition-medium); max-width: 1200px; } .container.animated { animation: fadeIn 0.6s ease forwards; } body:not(.sidebar-active-on-pc) .container { margin-left: auto; margin-right: auto; } .btn { padding: 12px 28px; background: var(--primary-color); color: white; border: none; border-radius: 10px; cursor: pointer; font-size: 1.05em; font-weight: 600; transition: var(--transition-fast); display: inline-flex; align-items: center; justify-content: center; gap: 10px; box-shadow: 0 4px 15px -5px hsla(var(--primary-hue), 100%, 55%, 0.6); text-decoration: none; position: relative; overflow: hidden; } .btn:hover { transform: translateY(-3px); box-shadow: 0 7px 20px -5px hsla(var(--primary-hue), 100%, 55%, 0.8); } .btn-secondary { background: var(--secondary-color); box-shadow: 0 4px 15px -5px hsla(var(--secondary-hue), 85%, 60%, 0.6); } .btn-secondary:hover { box-shadow: 0 7px 20px -5px hsla(var(--secondary-hue), 85%, 60%, 0.8); } .btn-accent { background: var(--accent-color); color: var(--bg-dark); box-shadow: 0 4px 15px -5px hsla(var(--accent-hue), 100%, 50%, 0.6); } .btn-accent:hover { box-shadow: 0 7px 20px -5px hsla(var(--accent-hue), 100%, 50%, 0.8); } input, textarea, select { width: 100%; padding: 14px 18px; margin: 10px 0; border: 1px solid var(--glass-border); border-radius: 10px; background: var(--glass-bg); color: var(--text-light); font-size: 1.05em; transition: var(--transition-fast); font-family: inherit; } input:focus, textarea:focus, select:focus { outline: none; border-color: var(--primary-color); box-shadow: 0 0 0 3px hsla(var(--primary-hue), 100%, 55%, 0.3); } textarea { min-height: 120px; 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; backdrop-filter: blur(5px); } .modal img, .modal video { max-width: 90vw; max-height: 90vh; object-fit: contain; border-radius: 15px; box-shadow: 0 0 40px rgba(0,0,0,0.8); animation: modalZoomIn 0.4s ease; } .status-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; vertical-align: middle; border: 1px solid rgba(0,0,0,0.2); } .online { background: var(--online-color); box-shadow: 0 0 8px var(--online-color); } .offline { background: var(--offline-color); } .badge { background-color: var(--accent-color); color: var(--bg-dark); padding: 2px 9px; border-radius: 20px; font-size: 0.8em; font-weight: bold; } .nav-link .badge { position: absolute; right: 20px; top: 50%; transform: translateY(-50%); } .flash { padding: 18px; margin-bottom: 25px; border-radius: 10px; font-weight: 600; text-align: center; border: 1px solid; } .flash.success { background-color: hsla(150, 100%, 40%, 0.2); color: hsl(150, 100%, 70%); border-color: hsl(150, 100%, 40%); } .flash.error { background-color: hsla(var(--primary-hue), 100%, 55%, 0.2); color: hsl(var(--primary-hue), 100%, 80%); border-color: hsl(var(--primary-hue), 100%, 55%); } .flash.warning { background-color: hsla(var(--accent-hue), 100%, 50%, 0.2); color: hsl(var(--accent-hue), 100%, 70%); border-color: hsl(var(--accent-hue), 100%, 50%); } .story-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 30px; } .story-item { background: var(--glass-bg); border-radius: 15px; box-shadow: 0 8px 30px rgba(0,0,0,0.3); transition: var(--transition-medium); border: 1px solid var(--glass-border); overflow: hidden; display: flex; flex-direction: column; } .story-item:hover { transform: translateY(-8px); box-shadow: var(--glow-primary); border-color: var(--primary-color); } .story-preview-link { display: block; text-decoration: none; color: inherit; position: relative; background-color: #000; height: 200px; overflow: hidden; } .story-preview { width: 100%; height: 100%; object-fit: cover; 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: 600; margin-bottom: 10px; 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-color); font-weight: 500; text-decoration: none; margin-right: 5px; transition: color var(--transition-fast); } .uploader-link:hover { color: var(--primary-color); text-decoration: underline; } .story-item p.stats { font-size: 0.9em; 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: 5px; } .aducoin-display { display: inline-flex; align-items: center; gap: 8px; background-color: hsla(var(--accent-hue), 100%, 50%, 0.1); padding: 6px 12px; border-radius: 20px; border: 1px solid hsl(var(--accent-hue), 100%, 50%); color: hsl(var(--accent-hue), 100%, 65%); font-weight: bold; font-size: 1em; text-decoration: none; cursor: pointer; transition: var(--transition-fast); } .aducoin-display:hover { background-color: hsla(var(--accent-hue), 100%, 50%, 0.2); box-shadow: var(--glow-accent); } .aducoin-icon { width: 20px; height: 20px; vertical-align: middle; border-radius: 50%; } .form-section { margin-top: 30px; padding: 25px; background: var(--glass-bg); border-radius: 12px; border: 1px solid var(--glass-border); } .form-section h3 { font-size: 1.3em; color: var(--secondary-color); margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid var(--glass-border); } .form-input-container { display: flex; gap: 12px; align-items: center; margin-top: 15px; } .form-section input { flex-grow: 1; margin: 0; } .form-section .btn { flex-shrink: 0; } .form-section .info-text { font-size: 0.9em; color: var(--text-dark); margin-top: 8px; } .transaction-table { width: 100%; border-collapse: collapse; margin-top: 25px; } .transaction-table th, .transaction-table td { border-bottom: 1px solid var(--glass-border); padding: 15px; text-align: left; font-size: 0.95em; } .transaction-table th { background-color: rgba(255,255,255, 0.05); color: var(--text-light); font-weight: 600; } .transaction-table td { color: var(--text-dark); } .transaction-table tr:hover { background-color: rgba(255, 255, 255, 0.03); } .transaction-table td.amount-positive { color: var(--online-color); font-weight: bold; } .transaction-table td.amount-negative { color: var(--offline-color); font-weight: bold; } .conversation-item { display: flex; align-items: center; gap: 18px; padding: 18px; background: var(--glass-bg); border-radius: 12px; margin-bottom: 15px; transition: var(--transition-medium); text-decoration: none; color: inherit; border: 1px solid var(--glass-border); } .conversation-item:hover { transform: translateX(5px); box-shadow: var(--glow-secondary); border-color: var(--secondary-color); } .conversation-item.unread { border-left: 4px solid var(--accent-color); background: hsla(var(--accent-hue), 100%, 50%, 0.1); } .conversation-avatar { width: 50px; height: 50px; border-radius: 50%; object-fit: cover; flex-shrink: 0; border: 3px solid var(--secondary-color); } .conversation-avatar-placeholder { width: 50px; height: 50px; border-radius: 50%; background: linear-gradient(45deg, var(--primary-color), var(--secondary-color)); display: flex; align-items: center; justify-content: center; font-size: 24px; color: white; font-weight: bold; flex-shrink: 0; } .conversation-details { flex-grow: 1; overflow: hidden; } .conversation-username { font-weight: 600; color: var(--text-light); 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: rgba(0,0,0,0.2); 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; word-wrap: break-word; font-size: 1em; } .message-bubble.sent { background: hsl(var(--secondary-hue), 85%, 60%); border-bottom-right-radius: 8px; align-self: flex-end; color: white; } .message-bubble.received { background: var(--bg-light); border-bottom-left-radius: 8px; align-self: flex-start; color: var(--text-light); } .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; } @media (max-width: 900px) { .sidebar { transform: translateX(-100%); } .sidebar.active { transform: translateX(0); box-shadow: 10px 0 40px rgba(0,0,0,0.5); } .menu-btn { display: block; } .container { margin: 80px 20px 20px 20px; padding: 20px; max-width: none; } body:not(.sidebar-active-on-pc) .container, body.sidebar-active-on-pc .container { margin-left: auto; margin-right: auto; } } @media (max-width: 480px) { .story-grid { grid-template-columns: 1fr; } .form-input-container { flex-direction: column; align-items: stretch; } } ''' 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 ''' + NAV_HTML + '''

Join Adusis

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

Using referral code: {{ referral_code | escape }}

{% endif %}
Already a member? Login
''' 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 ''' + 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? Create 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 ''' + 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!{% else %}No stories yet!{% endif %}

{% if mode == 'hot' %}Promote a story from your profile to feature it here.{% else %}Be the first one to upload something.{% 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 ''' + 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 }}

{% endif %}

Last Seen: {{ last_seen }}

Edit Profile

Your Referral Link

Share this link! Earn 100 AduCoin for each 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'] %}

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

{% 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 ''' + 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 %}

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

By: {{ story['uploader'] | escape }}

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

👁️ Views {{ story.get('views', 0) }}
{% if can_donate %}

Support {{ story.uploader | escape }}!

Your balance: {{ sender_aducoin }} AduCoin

{% endif %}

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

{% if is_authenticated %}
{% else %}

Login to comment.

{% endif %}
{% for comment in story['comments'] | reverse %}
{{ comment['username'] | escape }} {{ comment['timestamp'] }}
{{ comment['text'] | escape }}
{% endfor %}
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 ''' + 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 %}
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 ''' + NAV_HTML + ''' ''' 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(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 at %H:%M') else: last_seen = last_seen_dt.strftime('%b %d, %Y') else: last_seen = 'Never' except ValueError: last_seen = 'Invalid Date' html_content = ''' Profile - {{ profile_username | escape }} - Adusis ''' + NAV_HTML + '''
{% if avatar %} 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 }}

{% 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 %}
{% set media_url = "https://huggingface.co/datasets/" + repo_id + "/resolve/main/" + story['type'] + "s/" + story['filename'] %}

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

❤️ {{ 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 has no 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 - Adusis ''' + NAV_HTML + '''

AduCoin AduCoin History

Your current balance: {{ current_aducoin }} 🪙
{% 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 %}

No transaction history yet.

{% endif %}
''' 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 someone 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 %} Avatar {% 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 %}
''' 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)