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 %}
-
-
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 %}
-
- {% 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 %}
-
-
-
-
-
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 %}
-
- {% endif %}
-
-
-
-
- {% 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 %}
-
🔥 Promoted until {{ story.promotion_expiry_formatted }}
- {% 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 %}
- 🔥 HOT
- {% 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
-
-
- {% 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 %}
-
-
-
-
-
-'''
- 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 %}
-
-
-
-
-
-
-
-
-'''
- 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 + '''
-
-
-
-
-
![]()
-
-
-
-
-
-'''
- 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 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 %}
-
-
-
-
- | Date |
- Type |
- Amount |
- Description |
- Balance After |
-
-
-
- {% for tx in transactions %}
-
- | {{ tx.timestamp }} |
- {{ tx.type.replace('_', ' ')|title }} |
- {{ '%+d'|format(tx.amount) if tx.amount != 0 else tx.amount }} |
- {{ tx.description | escape }} |
- {{ tx.balance_after }} |
-
- {% endfor %}
-
-
-
- {% 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 + '''
-
-
-
-
- {% 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 ---
Comments ({{ story.get('comments', []) | length }})
- {% if is_authenticated %} -- {% else %} -
Login to post a comment.
- {% endif %} - -Be the first to comment!
- {% endif %} -