Added: Example .env
This commit is contained in:
		
							
								
								
									
										27
									
								
								.env_example
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								.env_example
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
# THIS IS AN EXAMPLE
 | 
			
		||||
# CHANGE IT IN PRODUCTION
 | 
			
		||||
 | 
			
		||||
# Postgres Setup
 | 
			
		||||
POSTGRES_DB=jupyter
 | 
			
		||||
POSTGRES_USER=jupyter
 | 
			
		||||
POSTGRES_PASSWORD=1234
 | 
			
		||||
 | 
			
		||||
# Jupyter Setup (Run openssl rand -hex 32)
 | 
			
		||||
JUPYTERHUB_CRYPT_KEY=5bb3affb5036422b0ac201e9cadde6ab783c7742861006c04e65e46d2bf3564b
 | 
			
		||||
JUPYTERHUB_AUTH_TOKEN=c546bb0e1dbcb73b41092b2e7e46fa69f8c031c14ea0cd94cc6696349ffacceb
 | 
			
		||||
LOG_LEVEL=INFO
 | 
			
		||||
JUPYTERHUB_BASE_URL=/jupyter/
 | 
			
		||||
NATIVE_AUTH_MIN_PASSWORD_LENGTH=8 
 | 
			
		||||
JUPYTERHUB_ADMIN_USER=admin
 | 
			
		||||
ENABLE_LAB=1
 | 
			
		||||
SPAWNER_DEFAULT_URL=/lab
 | 
			
		||||
NOTEBOOK_MEMORY_LIMIT=500M 
 | 
			
		||||
NOTEBOOK_CPU_LIMIT=1.0
 | 
			
		||||
HUB_IP=jupyterhub # Name of the Docker container
 | 
			
		||||
 | 
			
		||||
# Docker
 | 
			
		||||
DOCKER_NETWORK_NAME=jupyter_network
 | 
			
		||||
POSTGRES_PORT=5432
 | 
			
		||||
JUPYTERHUB_PORT=8000
 | 
			
		||||
NGINX_HTTP_PORT=8080 
 | 
			
		||||
NGINX_HTTPS_PORT=8443
 | 
			
		||||
@@ -3,7 +3,9 @@ import sys
 | 
			
		||||
from dockerspawner import DockerSpawner
 | 
			
		||||
from jupyterhub.utils import random_port
 | 
			
		||||
from nativeauthenticator import NativeAuthenticator
 | 
			
		||||
#from nbgrader.auth import JupyterHubAuthPlugin
 | 
			
		||||
import subprocess
 | 
			
		||||
 | 
			
		||||
# from nbgrader.auth import JupyterHubAuthPlugin
 | 
			
		||||
from traitlets import Unicode, Bool, Int, Float
 | 
			
		||||
 | 
			
		||||
import secrets
 | 
			
		||||
@@ -11,86 +13,93 @@ import string
 | 
			
		||||
from datetime import datetime, timedelta
 | 
			
		||||
from typing import Optional
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def generate_api_token(
 | 
			
		||||
    length: int = 32,
 | 
			
		||||
    prefix: Optional[str] = None,
 | 
			
		||||
) -> dict:
 | 
			
		||||
    """
 | 
			
		||||
    Generate a secure random API token with optional prefix and expiration.
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        length: Length of the token (default: 32)
 | 
			
		||||
        prefix: Optional prefix for the token (e.g., 'hub_')
 | 
			
		||||
        expiration_days: Optional expiration in days from now
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
        - token: The generated token
 | 
			
		||||
    """
 | 
			
		||||
    # Generate cryptographically secure random string
 | 
			
		||||
    alphabet = string.ascii_letters + string.digits
 | 
			
		||||
    token = ''.join(secrets.choice(alphabet) for _ in range(length))
 | 
			
		||||
    
 | 
			
		||||
    token = "".join(secrets.choice(alphabet) for _ in range(length))
 | 
			
		||||
 | 
			
		||||
    # Add prefix if specified
 | 
			
		||||
    if prefix:
 | 
			
		||||
        token = f"{prefix}{token}"
 | 
			
		||||
    
 | 
			
		||||
    return token 
 | 
			
		||||
 | 
			
		||||
import subprocess
 | 
			
		||||
    return token
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def is_service_available_cmd(host, port):
 | 
			
		||||
    """Check service using system commands"""
 | 
			
		||||
    try:
 | 
			
		||||
        subprocess.run(
 | 
			
		||||
            ['nc', '-z', host, str(port)],
 | 
			
		||||
            ["nc", "-z", host, str(port)],
 | 
			
		||||
            check=True,
 | 
			
		||||
            timeout=3,
 | 
			
		||||
            stdout=subprocess.DEVNULL,
 | 
			
		||||
            stderr=subprocess.DEVNULL
 | 
			
		||||
            stderr=subprocess.DEVNULL,
 | 
			
		||||
        )
 | 
			
		||||
        return True
 | 
			
		||||
    except (subprocess.TimeoutExpired, subprocess.CalledProcessError):
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Configuration file for JupyterHub
 | 
			
		||||
c = get_config()
 | 
			
		||||
 | 
			
		||||
# Base URL configuration (The new way, dont use ip/port/bind_url attributes it throws a warning and is deprecated)
 | 
			
		||||
port = int(os.environ.get('JUPYTERHUB_PORT', '8000'))
 | 
			
		||||
base_url = os.environ.get('JUPYTERHUB_BASE_URL', '/jupyter/')
 | 
			
		||||
# Base URL configuration
 | 
			
		||||
# The new way, dont use ip/port/bind_url
 | 
			
		||||
# attributes it throws a warning and is deprecated
 | 
			
		||||
port = int(os.environ.get("JUPYTERHUB_PORT", "8000"))
 | 
			
		||||
base_url = os.environ.get("JUPYTERHUB_BASE_URL", "/jupyter/")
 | 
			
		||||
c.JupyterHub.bind_url = f"http://0.0.0.0:{port}{base_url}"
 | 
			
		||||
 | 
			
		||||
# Database configuration
 | 
			
		||||
c.JupyterHub.db_url = f"postgresql://{os.environ['POSTGRES_USER']}:{os.environ['POSTGRES_PASSWORD']}@stack-{os.environ['POSTGRES_HOST']}:5432/{os.environ['POSTGRES_DB']}"
 | 
			
		||||
#c.JupyterHub.db_url = 'sqlite:///jupyterhub.sqlite' 
 | 
			
		||||
# c.JupyterHub.db_url = 'sqlite:///jupyterhub.sqlite'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Authenticator configuration - NativeAuthenticator
 | 
			
		||||
c.JupyterHub.authenticator_class = NativeAuthenticator
 | 
			
		||||
 | 
			
		||||
# Native Authenticator settings
 | 
			
		||||
#c.NativeAuthenticator.create_system_users = True
 | 
			
		||||
c.NativeAuthenticator.minimum_password_length = int(os.environ.get('NATIVE_AUTH_MIN_PASSWORD_LENGTH', '8'))
 | 
			
		||||
# c.NativeAuthenticator.create_system_users = True
 | 
			
		||||
c.NativeAuthenticator.minimum_password_length = int(
 | 
			
		||||
    os.environ.get("NATIVE_AUTH_MIN_PASSWORD_LENGTH", "8")
 | 
			
		||||
)
 | 
			
		||||
c.NativeAuthenticator.check_common_password = True
 | 
			
		||||
c.NativeAuthenticator.enable_signup = True
 | 
			
		||||
c.NativeAuthenticator.ask_email_on_signup = False
 | 
			
		||||
c.NativeAuthenticator.allow_2fa = False
 | 
			
		||||
 | 
			
		||||
# Admin configuration
 | 
			
		||||
admin_user = os.environ.get('JUPYTERHUB_ADMIN_USER', 'admin')
 | 
			
		||||
c.Authenticator.admin_users = {admin_user, 'DerGrumpf'}
 | 
			
		||||
admin_user = os.environ.get("JUPYTERHUB_ADMIN_USER", "admin")
 | 
			
		||||
c.Authenticator.admin_users = {admin_user}
 | 
			
		||||
c.Authenticator.any_allow_config = True
 | 
			
		||||
c.Authenticator.allow_all = True
 | 
			
		||||
 | 
			
		||||
# Custom logo and templates
 | 
			
		||||
c.JupyterHub.logo_file = '/srv/jupyterhub/templates/logo.png'
 | 
			
		||||
c.JupyterHub.template_paths = ['/etc/jupyterhub/templates']
 | 
			
		||||
c.JupyterHub.logo_file = "/srv/jupyterhub/templates/logo.png"
 | 
			
		||||
c.JupyterHub.template_paths = ["/etc/jupyterhub/templates"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Hub environment for containers
 | 
			
		||||
c.JupyterHub.spawner_class = DockerSpawner
 | 
			
		||||
c.Spawner.ip = '0.0.0.0'
 | 
			
		||||
c.DockerSpawner.image = os.environ.get('NOTEBOOK_IMAGE', 'jupyter/scipy-notebook:latest')
 | 
			
		||||
c.Spawner.ip = "0.0.0.0"
 | 
			
		||||
c.DockerSpawner.image = os.environ.get(
 | 
			
		||||
    "NOTEBOOK_IMAGE", "jupyter/scipy-notebook:latest"
 | 
			
		||||
)
 | 
			
		||||
'''network_name = os.environ.get('DOCKER_NETWORK_NAME', 'stack_default')
 | 
			
		||||
c.DockerSpawner.network_name = network_name 
 | 
			
		||||
c.DockerSpawner.extra_host_config = {
 | 
			
		||||
@@ -142,7 +151,7 @@ def pre_spawn_hook(spawner):
 | 
			
		||||
c.DockerSpawner.pre_spawn_hook = pre_spawn_hook
 | 
			
		||||
'''
 | 
			
		||||
 | 
			
		||||
'''
 | 
			
		||||
"""
 | 
			
		||||
# Services configuration for NBGrader
 | 
			
		||||
c.JupyterHub.services = [
 | 
			
		||||
    {
 | 
			
		||||
@@ -162,15 +171,15 @@ c.JupyterHub.services = [
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
]
 | 
			
		||||
'''
 | 
			
		||||
'''
 | 
			
		||||
"""
 | 
			
		||||
"""
 | 
			
		||||
# NBGrader configuration
 | 
			
		||||
c.JupyterHub.load_groups = {
 | 
			
		||||
    'nbgrader-instructors': ['instructor'],
 | 
			
		||||
    'nbgrader-students': []
 | 
			
		||||
}
 | 
			
		||||
'''
 | 
			
		||||
'''
 | 
			
		||||
"""
 | 
			
		||||
"""
 | 
			
		||||
# NBGrader Config 
 | 
			
		||||
c.JupyterHub.services = [
 | 
			
		||||
    {
 | 
			
		||||
@@ -194,14 +203,14 @@ c.JupyterHub.services = [
 | 
			
		||||
        'user': 'root'
 | 
			
		||||
    }
 | 
			
		||||
]
 | 
			
		||||
'''
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
# Security settings
 | 
			
		||||
c.JupyterHub.cookie_secret_file = '/srv/jupyterhub/cookie_secret'
 | 
			
		||||
c.ConfigurableHTTPProxy.auth_token = os.environ.get('JUPYTERHUB_AUTH_TOKEN', '')
 | 
			
		||||
c.JupyterHub.cookie_secret_file = "/srv/jupyterhub/cookie_secret"
 | 
			
		||||
c.ConfigurableHTTPProxy.auth_token = os.environ.get("JUPYTERHUB_AUTH_TOKEN", "")
 | 
			
		||||
 | 
			
		||||
# Logging
 | 
			
		||||
c.JupyterHub.log_level = os.environ.get('LOG_LEVEL', 'INFO')
 | 
			
		||||
c.JupyterHub.log_level = os.environ.get("LOG_LEVEL", "INFO")
 | 
			
		||||
 | 
			
		||||
# Shutdown settings
 | 
			
		||||
c.JupyterHub.shutdown_on_logout = False
 | 
			
		||||
 
 | 
			
		||||
@@ -1,360 +0,0 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
    <title>Discord Mod Simulator 2025</title>
 | 
			
		||||
    <style>
 | 
			
		||||
        body {
 | 
			
		||||
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 | 
			
		||||
            background-color: #36393f;
 | 
			
		||||
            color: #dcddde;
 | 
			
		||||
            max-width: 800px;
 | 
			
		||||
            margin: 0 auto;
 | 
			
		||||
            padding: 20px;
 | 
			
		||||
        }
 | 
			
		||||
        #game-container {
 | 
			
		||||
            background-color: #2f3136;
 | 
			
		||||
            border-radius: 8px;
 | 
			
		||||
            padding: 20px;
 | 
			
		||||
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
 | 
			
		||||
        }
 | 
			
		||||
        #status-bar {
 | 
			
		||||
            display: flex;
 | 
			
		||||
            justify-content: space-between;
 | 
			
		||||
            margin-bottom: 20px;
 | 
			
		||||
            padding: 10px;
 | 
			
		||||
            background-color: #202225;
 | 
			
		||||
            border-radius: 5px;
 | 
			
		||||
        }
 | 
			
		||||
        .stat {
 | 
			
		||||
            display: flex;
 | 
			
		||||
            flex-direction: column;
 | 
			
		||||
            align-items: center;
 | 
			
		||||
        }
 | 
			
		||||
        #event-display {
 | 
			
		||||
            min-height: 150px;
 | 
			
		||||
            padding: 15px;
 | 
			
		||||
            background-color: #40444b;
 | 
			
		||||
            border-radius: 5px;
 | 
			
		||||
            margin-bottom: 20px;
 | 
			
		||||
            white-space: pre-wrap;
 | 
			
		||||
        }
 | 
			
		||||
        #actions {
 | 
			
		||||
            display: grid;
 | 
			
		||||
            grid-template-columns: 1fr 1fr;
 | 
			
		||||
            gap: 10px;
 | 
			
		||||
        }
 | 
			
		||||
        button {
 | 
			
		||||
            background-color: #5865f2;
 | 
			
		||||
            color: white;
 | 
			
		||||
            border: none;
 | 
			
		||||
            padding: 10px;
 | 
			
		||||
            border-radius: 5px;
 | 
			
		||||
            cursor: pointer;
 | 
			
		||||
            transition: background-color 0.2s;
 | 
			
		||||
        }
 | 
			
		||||
        button:hover {
 | 
			
		||||
            background-color: #4752c4;
 | 
			
		||||
        }
 | 
			
		||||
        button:disabled {
 | 
			
		||||
            background-color: #4f545c;
 | 
			
		||||
            cursor: not-allowed;
 | 
			
		||||
        }
 | 
			
		||||
        #log {
 | 
			
		||||
            margin-top: 20px;
 | 
			
		||||
            max-height: 200px;
 | 
			
		||||
            overflow-y: auto;
 | 
			
		||||
            padding: 10px;
 | 
			
		||||
            background-color: #40444b;
 | 
			
		||||
            border-radius: 5px;
 | 
			
		||||
            font-size: 0.9em;
 | 
			
		||||
        }
 | 
			
		||||
        .log-entry {
 | 
			
		||||
            margin-bottom: 5px;
 | 
			
		||||
            border-bottom: 1px solid #4f545c;
 | 
			
		||||
            padding-bottom: 5px;
 | 
			
		||||
        }
 | 
			
		||||
        .positive {
 | 
			
		||||
            color: #3ba55c;
 | 
			
		||||
        }
 | 
			
		||||
        .negative {
 | 
			
		||||
            color: #ed4245;
 | 
			
		||||
        }
 | 
			
		||||
        .warning {
 | 
			
		||||
            color: #faa61a;
 | 
			
		||||
        }
 | 
			
		||||
    </style>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <div id="game-container">
 | 
			
		||||
        <h1>Discord Mod Simulator 2025</h1>
 | 
			
		||||
        <div id="status-bar">
 | 
			
		||||
            <div class="stat">
 | 
			
		||||
                <span>Members</span>
 | 
			
		||||
                <span id="members">1000</span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="stat">
 | 
			
		||||
                <span>Activity</span>
 | 
			
		||||
                <span id="activity">50%</span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="stat">
 | 
			
		||||
                <span>Reputation</span>
 | 
			
		||||
                <span id="reputation">50%</span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="stat">
 | 
			
		||||
                <span>Day</span>
 | 
			
		||||
                <span id="day">1</span>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <div id="event-display">
 | 
			
		||||
            Welcome to Discord Mod Simulator 2025! As a moderator, you must balance keeping the server active while maintaining order. Make your decisions carefully!
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <div id="actions">
 | 
			
		||||
            <!-- Buttons will be generated by JavaScript -->
 | 
			
		||||
        </div>
 | 
			
		||||
        
 | 
			
		||||
        <div id="log">
 | 
			
		||||
            <div class="log-entry">Game started. Good luck!</div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <script>
 | 
			
		||||
        // Game state
 | 
			
		||||
        const gameState = {
 | 
			
		||||
            members: 1000,
 | 
			
		||||
            activity: 50,
 | 
			
		||||
            reputation: 50,
 | 
			
		||||
            day: 1,
 | 
			
		||||
            powerUps: {
 | 
			
		||||
                adminBackup: 3,
 | 
			
		||||
                muteAll: 2,
 | 
			
		||||
                customRole: 3
 | 
			
		||||
            },
 | 
			
		||||
            gameOver: false
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Event database
 | 
			
		||||
        const events = [
 | 
			
		||||
            {
 | 
			
		||||
                id: 'spam',
 | 
			
		||||
                text: "User xX_SpamLord_Xx is posting rapid-fire memes in #general. What do you do?",
 | 
			
		||||
                options: [
 | 
			
		||||
                    { text: "Delete messages and warn", members: 0, activity: -5, reputation: 5 },
 | 
			
		||||
                    { text: "Timeout for 1 hour", members: -10, activity: -10, reputation: 10 },
 | 
			
		||||
                    { text: "Ban the user", members: -30, activity: -15, reputation: 15 },
 | 
			
		||||
                    { text: "Ignore it", members: 5, activity: 10, reputation: -10 }
 | 
			
		||||
                ]
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                id: 'argument',
 | 
			
		||||
                text: "Two users are having a heated argument about which year had the best memes. It's getting personal.",
 | 
			
		||||
                options: [
 | 
			
		||||
                    { text: "Warn both users", members: 0, activity: -5, reputation: 5 },
 | 
			
		||||
                    { text: "Timeout both for 30 minutes", members: -15, activity: -15, reputation: 10 },
 | 
			
		||||
                    { text: "Create #debate channel and move them", members: 10, activity: 5, reputation: 15 },
 | 
			
		||||
                    { text: "Let them fight", members: -20, activity: 20, reputation: -20 }
 | 
			
		||||
                ]
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                id: 'raid',
 | 
			
		||||
                text: "OH NO! The server is being raided by another community! Spam and offensive content is flooding in!",
 | 
			
		||||
                options: [
 | 
			
		||||
                    { text: "Use Mute All power-up (stops raid but lowers activity)", powerUp: 'muteAll', members: -50, activity: -30, reputation: 20 },
 | 
			
		||||
                    { text: "Call Admin Backup", powerUp: 'adminBackup', members: -20, activity: -10, reputation: 10 },
 | 
			
		||||
                    { text: "Ban raiders manually", members: -100, activity: -40, reputation: 5 },
 | 
			
		||||
                    { text: "Do nothing (risky!)", members: -150, activity: -50, reputation: -30 }
 | 
			
		||||
                ]
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                id: 'feature',
 | 
			
		||||
                text: "A user suggests a great feature for the server that would require some moderator work to implement.",
 | 
			
		||||
                options: [
 | 
			
		||||
                    { text: "Implement the feature", members: 20, activity: 15, reputation: 10 },
 | 
			
		||||
                    { text: "Say you'll consider it", members: 5, activity: 5, reputation: 5 },
 | 
			
		||||
                    { text: "Reject the idea", members: -10, activity: -5, reputation: -5 },
 | 
			
		||||
                    { text: "Create a poll for the community", members: 10, activity: 20, reputation: 15 }
 | 
			
		||||
                ]
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                id: 'bot',
 | 
			
		||||
                text: "The moderation bot is malfunctioning! It's randomly timing out users and missing actual rule violations.",
 | 
			
		||||
                options: [
 | 
			
		||||
                    { text: "Fix the bot (takes time)", members: -10, activity: -10, reputation: 20 },
 | 
			
		||||
                    { text: "Disable the bot temporarily", members: 0, activity: 10, reputation: -10 },
 | 
			
		||||
                    { text: "Blame the bot developer", members: -20, activity: -20, reputation: -20 },
 | 
			
		||||
                    { text: "Use Custom Roles to help moderate", powerUp: 'customRole', members: 0, activity: 0, reputation: 15 }
 | 
			
		||||
                ]
 | 
			
		||||
            }
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        // DOM elements
 | 
			
		||||
        const membersEl = document.getElementById('members');
 | 
			
		||||
        const activityEl = document.getElementById('activity');
 | 
			
		||||
        const reputationEl = document.getElementById('reputation');
 | 
			
		||||
        const dayEl = document.getElementById('day');
 | 
			
		||||
        const eventDisplayEl = document.getElementById('event-display');
 | 
			
		||||
        const actionsEl = document.getElementById('actions');
 | 
			
		||||
        const logEl = document.getElementById('log');
 | 
			
		||||
 | 
			
		||||
        // Update the UI with current game state
 | 
			
		||||
        function updateUI() {
 | 
			
		||||
            membersEl.textContent = gameState.members;
 | 
			
		||||
            activityEl.textContent = `${gameState.activity}%`;
 | 
			
		||||
            reputationEl.textContent = `${gameState.reputation}%`;
 | 
			
		||||
            dayEl.textContent = gameState.day;
 | 
			
		||||
            
 | 
			
		||||
            // Update colors based on values
 | 
			
		||||
            activityEl.className = gameState.activity < 30 ? 'negative' : gameState.activity > 70 ? 'positive' : '';
 | 
			
		||||
            reputationEl.className = gameState.reputation < 30 ? 'negative' : gameState.reputation > 70 ? 'positive' : '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Add a message to the log
 | 
			
		||||
        function addLog(message, type = '') {
 | 
			
		||||
            const entry = document.createElement('div');
 | 
			
		||||
            entry.className = `log-entry ${type}`;
 | 
			
		||||
            entry.textContent = `Day ${gameState.day}: ${message}`;
 | 
			
		||||
            logEl.appendChild(entry);
 | 
			
		||||
            logEl.scrollTop = logEl.scrollHeight;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Present a random event to the player
 | 
			
		||||
        function presentEvent() {
 | 
			
		||||
            if (gameState.gameOver) return;
 | 
			
		||||
            
 | 
			
		||||
            const event = events[Math.floor(Math.random() * events.length)];
 | 
			
		||||
            eventDisplayEl.textContent = event.text;
 | 
			
		||||
            
 | 
			
		||||
            // Clear previous buttons
 | 
			
		||||
            actionsEl.innerHTML = '';
 | 
			
		||||
            
 | 
			
		||||
            // Create new buttons for each option
 | 
			
		||||
            event.options.forEach((option, index) => {
 | 
			
		||||
                const button = document.createElement('button');
 | 
			
		||||
                button.textContent = option.text;
 | 
			
		||||
                
 | 
			
		||||
                // Check if this option requires a power-up we don't have
 | 
			
		||||
                if (option.powerUp && gameState.powerUps[option.powerUp] <= 0) {
 | 
			
		||||
                    button.disabled = true;
 | 
			
		||||
                    button.title = "No more uses left!";
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                button.addEventListener('click', () => {
 | 
			
		||||
                    handleChoice(option, event);
 | 
			
		||||
                });
 | 
			
		||||
                
 | 
			
		||||
                actionsEl.appendChild(button);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Handle player's choice
 | 
			
		||||
        function handleChoice(option, event) {
 | 
			
		||||
            // Use power-up if needed
 | 
			
		||||
            if (option.powerUp) {
 | 
			
		||||
                gameState.powerUps[option.powerUp]--;
 | 
			
		||||
                addLog(`Used ${option.powerUp} power-up.`, 'warning');
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // Update game state
 | 
			
		||||
            gameState.members += option.members || 0;
 | 
			
		||||
            gameState.activity += option.activity || 0;
 | 
			
		||||
            gameState.reputation += option.reputation || 0;
 | 
			
		||||
            
 | 
			
		||||
            // Ensure values stay within bounds
 | 
			
		||||
            gameState.members = Math.max(0, gameState.members);
 | 
			
		||||
            gameState.activity = Math.max(0, Math.min(100, gameState.activity));
 | 
			
		||||
            gameState.reputation = Math.max(0, Math.min(100, gameState.reputation));
 | 
			
		||||
            
 | 
			
		||||
            // Log the result
 | 
			
		||||
            let resultMessage = `You chose: ${option.text}. `;
 | 
			
		||||
            if (option.members !== 0) {
 | 
			
		||||
                resultMessage += `Members ${option.members > 0 ? '+' : ''}${option.members}. `;
 | 
			
		||||
            }
 | 
			
		||||
            if (option.activity !== 0) {
 | 
			
		||||
                resultMessage += `Activity ${option.activity > 0 ? '+' : ''}${option.activity}%. `;
 | 
			
		||||
            }
 | 
			
		||||
            if (option.reputation !== 0) {
 | 
			
		||||
                resultMessage += `Reputation ${option.reputation > 0 ? '+' : ''}${option.reputation}%. `;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            addLog(resultMessage.trim());
 | 
			
		||||
            
 | 
			
		||||
            // Advance to next day
 | 
			
		||||
            gameState.day++;
 | 
			
		||||
            
 | 
			
		||||
            // Check win/lose conditions
 | 
			
		||||
            checkGameState();
 | 
			
		||||
            
 | 
			
		||||
            // Update UI and present new event
 | 
			
		||||
            updateUI();
 | 
			
		||||
            
 | 
			
		||||
            if (!gameState.gameOver) {
 | 
			
		||||
                setTimeout(presentEvent, 1000);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Check win/lose conditions
 | 
			
		||||
        function checkGameState() {
 | 
			
		||||
            if (gameState.members >= 10000 && gameState.reputation >= 75) {
 | 
			
		||||
                eventDisplayEl.textContent = "CONGRATULATIONS! You've grown the server to 10,000 members with great reputation! You win!";
 | 
			
		||||
                gameState.gameOver = true;
 | 
			
		||||
                actionsEl.innerHTML = '<button onclick="location.reload()">Play Again</button>';
 | 
			
		||||
                addLog("YOU WIN! Server is thriving!", 'positive');
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if (gameState.members <= 100) {
 | 
			
		||||
                eventDisplayEl.textContent = "GAME OVER! The server has died from lack of members. Maybe you were too strict?";
 | 
			
		||||
                gameState.gameOver = true;
 | 
			
		||||
                actionsEl.innerHTML = '<button onclick="location.reload()">Play Again</button>';
 | 
			
		||||
                addLog("Game over - server died.", 'negative');
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if (gameState.reputation <= 20) {
 | 
			
		||||
                eventDisplayEl.textContent = "GAME OVER! The community revolted against your moderation and you were demoted.";
 | 
			
		||||
                gameState.gameOver = true;
 | 
			
		||||
                actionsEl.innerHTML = '<button onclick="location.reload()">Play Again</button>';
 | 
			
		||||
                addLog("Game over - you were demoted.", 'negative');
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if (gameState.reputation >= 90 && Math.random() < 0.3) {
 | 
			
		||||
                addLog("The server owner promoted you to Admin for your great work!", 'positive');
 | 
			
		||||
                gameState.powerUps.adminBackup += 2;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // Random member growth based on reputation and activity
 | 
			
		||||
            const growthFactor = (gameState.reputation / 100) * (gameState.activity / 100);
 | 
			
		||||
            const randomGrowth = Math.floor(Math.random() * 20 * growthFactor);
 | 
			
		||||
            if (randomGrowth > 0) {
 | 
			
		||||
                gameState.members += randomGrowth;
 | 
			
		||||
                addLog(`Server grew by ${randomGrowth} members organically!`, 'positive');
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // Random events
 | 
			
		||||
            if (Math.random() < 0.2) {
 | 
			
		||||
                const randomEvent = Math.random();
 | 
			
		||||
                if (randomEvent < 0.3) {
 | 
			
		||||
                    // Good event
 | 
			
		||||
                    gameState.reputation += 5;
 | 
			
		||||
                    addLog("A user complimented your moderation style in #feedback!", 'positive');
 | 
			
		||||
                } else if (randomEvent < 0.6) {
 | 
			
		||||
                    // Bad event
 | 
			
		||||
                    gameState.activity -= 10;
 | 
			
		||||
                    addLog("A popular meme channel went quiet for a day.", 'negative');
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Neutral event
 | 
			
		||||
                    addLog("Nothing particularly interesting happened today.");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Initialize the game
 | 
			
		||||
        updateUI();
 | 
			
		||||
        presentEvent();
 | 
			
		||||
    </script>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
		Reference in New Issue
	
	Block a user