Created
March 11, 2026 16:59
-
-
Save HoughIO/ec5a2926e7bbfd02e477c067b5f15fab to your computer and use it in GitHub Desktop.
KittyCam Gacha Server
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Kevin & Todd Cam - Gacha System | |
| # Run on Pi: bash upgrade-gacha.sh | |
| echo "🎰 Installing gacha system..." | |
| # Install Flask if needed | |
| pip3 install flask --quiet 2>/dev/null || sudo pip3 install flask --quiet | |
| # Create gacha server | |
| sudo tee /opt/kittycam/gacha_server.py > /dev/null << 'PYEOF' | |
| #!/usr/bin/env python3 | |
| from flask import Flask, jsonify, request | |
| import json, os, random | |
| from datetime import date | |
| app = Flask(__name__) | |
| GACHA_FILE = '/opt/kittycam/gacha.json' | |
| # All cards: (pet, theme) pairs | |
| CARDS = [ | |
| # Kevin cards | |
| ('kevin', 'samurai'), ('kevin', 'space-captain'), ('kevin', 'vampire'), | |
| ('kevin', 'chef'), ('kevin', 'disco'), ('kevin', 'sopranos'), ('kevin', 'classical'), | |
| # Todd cards | |
| ('todd', 'samurai'), ('todd', 'space-captain'), ('todd', 'vampire'), | |
| ('todd', 'chef'), ('todd', 'disco'), ('todd', 'sopranos'), ('todd', 'classical'), | |
| # Simba cards (ultra rare) | |
| ('simba', 'samurai'), ('simba', 'space-captain'), ('simba', 'vampire'), | |
| ('simba', 'chef'), ('simba', 'disco'), ('simba', 'sopranos'), ('simba', 'classical'), | |
| ] | |
| # Weights: kevin/todd = 1.0, simba = 0.1 (10x rarer) | |
| def get_weight(card): | |
| return 0.1 if card[0] == 'simba' else 1.0 | |
| def load_state(): | |
| if os.path.exists(GACHA_FILE): | |
| try: | |
| with open(GACHA_FILE) as f: | |
| return json.load(f) | |
| except: | |
| pass | |
| return {'collected': [], 'last_pull': None} | |
| def save_state(state): | |
| with open(GACHA_FILE, 'w') as f: | |
| json.dump(state, f, indent=2) | |
| @app.route('/api/gacha/state') | |
| def get_state(): | |
| state = load_state() | |
| today = date.today().isoformat() | |
| can_pull = state.get('last_pull') != today | |
| return jsonify({ | |
| 'collected': state['collected'], | |
| 'canPull': can_pull, | |
| 'totalCards': len(CARDS) | |
| }) | |
| @app.route('/api/gacha/pull', methods=['POST']) | |
| def pull(): | |
| state = load_state() | |
| today = date.today().isoformat() | |
| if state.get('last_pull') == today: | |
| return jsonify({'error': 'Already pulled today', 'canPull': False}), 400 | |
| # Get cards they don't have yet | |
| collected_set = set(tuple(c) for c in state['collected']) | |
| available = [c for c in CARDS if tuple(c) not in collected_set] | |
| if not available: | |
| return jsonify({'error': 'All cards collected!', 'complete': True}), 400 | |
| # Weighted random selection | |
| weights = [get_weight(c) for c in available] | |
| total = sum(weights) | |
| r = random.random() * total | |
| cumulative = 0 | |
| chosen = available[0] | |
| for card, weight in zip(available, weights): | |
| cumulative += weight | |
| if r <= cumulative: | |
| chosen = card | |
| break | |
| state['collected'].append(list(chosen)) | |
| state['last_pull'] = today | |
| save_state(state) | |
| return jsonify({ | |
| 'card': {'pet': chosen[0], 'theme': chosen[1]}, | |
| 'isSimba': chosen[0] == 'simba', | |
| 'collected': len(state['collected']), | |
| 'total': len(CARDS) | |
| }) | |
| @app.route('/api/gacha/reset', methods=['POST']) | |
| def reset(): | |
| # Emergency reset - remove this in production if you want | |
| save_state({'collected': [], 'last_pull': None}) | |
| return jsonify({'reset': True}) | |
| if __name__ == '__main__': | |
| app.run(host='127.0.0.1', port=8082) | |
| PYEOF | |
| sudo chmod +x /opt/kittycam/gacha_server.py | |
| # Create systemd service | |
| sudo tee /etc/systemd/system/gacha.service > /dev/null << 'SVCEOF' | |
| [Unit] | |
| Description=KittyCam Gacha Server | |
| After=network.target | |
| [Service] | |
| Type=simple | |
| User=andy | |
| WorkingDirectory=/opt/kittycam | |
| ExecStart=/usr/bin/python3 /opt/kittycam/gacha_server.py | |
| Restart=always | |
| RestartSec=5 | |
| [Install] | |
| WantedBy=multi-user.target | |
| SVCEOF | |
| # Initialize gacha state if not exists | |
| if [ ! -f /opt/kittycam/gacha.json ]; then | |
| echo '{"collected": [], "last_pull": null}' | sudo tee /opt/kittycam/gacha.json > /dev/null | |
| sudo chown andy:andy /opt/kittycam/gacha.json | |
| fi | |
| # Update nginx config to proxy gacha API | |
| sudo tee /etc/nginx/sites-enabled/kittycam > /dev/null << 'NGINXEOF' | |
| server { | |
| listen 8080; | |
| server_name _; | |
| root /opt/kittycam/web; | |
| index index.html; | |
| proxy_buffering off; | |
| proxy_request_buffering off; | |
| location = /auth { | |
| if ($arg_key != "7jYVuk7F3cv0djjAQeNqURK8") { return 403; } | |
| return 200; | |
| } | |
| location /stream { | |
| proxy_pass http://127.0.0.1:8081/; | |
| proxy_http_version 1.1; | |
| proxy_set_header Connection ""; | |
| proxy_buffering off; | |
| proxy_cache off; | |
| proxy_read_timeout 3600s; | |
| proxy_send_timeout 3600s; | |
| chunked_transfer_encoding off; | |
| add_header Cache-Control "no-cache, no-store, must-revalidate"; | |
| add_header Pragma "no-cache"; | |
| } | |
| location = /api/events { | |
| alias /opt/kittycam/events.json; | |
| default_type application/json; | |
| add_header Cache-Control "no-cache"; | |
| } | |
| # Gacha API - proxy to Python server | |
| location /api/gacha/ { | |
| proxy_pass http://127.0.0.1:8082; | |
| proxy_http_version 1.1; | |
| proxy_set_header Host $host; | |
| add_header Cache-Control "no-cache"; | |
| } | |
| location /clips/ { | |
| alias /opt/kittycam/clips/; | |
| autoindex on; | |
| autoindex_format json; | |
| add_header Cache-Control "no-cache"; | |
| } | |
| location /static/ { | |
| alias /opt/kittycam/web/static/; | |
| add_header Cache-Control "public, max-age=31536000"; | |
| } | |
| location ~* (sw\.js|manifest\.json)$ { | |
| add_header Cache-Control "no-cache, no-store, must-revalidate"; | |
| } | |
| location / { | |
| try_files $uri $uri/ =404; | |
| add_header Cache-Control "no-cache"; | |
| } | |
| } | |
| NGINXEOF | |
| # Enable and start services | |
| sudo systemctl daemon-reload | |
| sudo systemctl enable gacha.service | |
| sudo systemctl restart gacha.service | |
| sudo systemctl reload nginx | |
| echo "✅ Gacha server installed!" | |
| echo "" | |
| echo "Next: Run the trading cards UI update" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment