Skip to content

Instantly share code, notes, and snippets.

@HoughIO
Created March 11, 2026 16:59
Show Gist options
  • Select an option

  • Save HoughIO/ec5a2926e7bbfd02e477c067b5f15fab to your computer and use it in GitHub Desktop.

Select an option

Save HoughIO/ec5a2926e7bbfd02e477c067b5f15fab to your computer and use it in GitHub Desktop.
KittyCam Gacha Server
#!/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