mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
hot fix #1
This commit is contained in:
@@ -0,0 +1,479 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Browser-based Terminal Tamagotchi
|
||||
Terminal interface in browser with real-time updates
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import random
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
from flask import Flask, render_template, jsonify
|
||||
from flask_socketio import SocketIO, emit
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SECRET_KEY'] = 'terminal-tamagotchi'
|
||||
socketio = SocketIO(app, cors_allowed_origins="*")
|
||||
|
||||
class BrowserTerminalTamagotchi:
|
||||
def __init__(self, project_name="MyClub"):
|
||||
self.project_name = project_name
|
||||
self.stats_file = os.path.join(os.path.dirname(__file__), 'stats.md')
|
||||
self.stats = {}
|
||||
self.last_check = time.time()
|
||||
self.typing_detected = False
|
||||
self.recent_changes = 0
|
||||
|
||||
# Pet state
|
||||
self.pet = {
|
||||
'name': project_name.lower() + '-chi',
|
||||
'mood': 'content',
|
||||
'evolution': 'baby',
|
||||
'level': 1,
|
||||
'energy': 100,
|
||||
'hunger': 0,
|
||||
'coding': 0,
|
||||
'face': '(o.o)',
|
||||
'activity': 'idle'
|
||||
}
|
||||
|
||||
# ASCII faces
|
||||
self.faces = {
|
||||
'baby': {
|
||||
'content': ['(o.o)', '(O.o)', '(o.O)'],
|
||||
'coding': ['(>.<)', '(>_<)', '(>.>_)'],
|
||||
'happy': ['(^.^)', '(^.^)', '(^o^)'],
|
||||
'tired': ['(-_-)', '(=_=)', '(x_x)']
|
||||
},
|
||||
'child': {
|
||||
'content': ['(o.o)', '(^.^)', '(•.•)'],
|
||||
'coding': ['(>_<)', '(>o<)', '(x.x)'],
|
||||
'happy': ['(^.^)', '(◕‿◕)', '(´•ᴗ•`)'],
|
||||
'tired': ['(-_-)', '(¬_¬)', '(=_=)']
|
||||
},
|
||||
'teen': {
|
||||
'content': ['(o.o)', '(^.^)', '(•_•)'],
|
||||
'coding': ['(>_<)', '(x_x)', '(>.<)'],
|
||||
'happy': ['(^.^)', '(◕‿◕)', '(´•ᴗ•`)'],
|
||||
'tired': ['(-_-)', '(¬_¬)', '(=_=)']
|
||||
},
|
||||
'adult': {
|
||||
'content': ['(o.o)', '(^.^)', '(•_•)'],
|
||||
'coding': ['(>_<)', '(x_x)', '(>.<)'],
|
||||
'happy': ['(^.^)', '(◕‿◕)', '(´•ᴗ•`)'],
|
||||
'tired': ['(-_-)', '(¬_¬)', '(=_=)']
|
||||
},
|
||||
'master': {
|
||||
'content': ['(o.o)', '(^.^)', '(•_•)'],
|
||||
'coding': ['(>_<)', '(x_x)', '(>.<)'],
|
||||
'happy': ['(^.^)', '(◕‿◕)', '(´•ᴗ•`)'],
|
||||
'tired': ['(-_-)', '(¬_¬)', '(=_=)']
|
||||
}
|
||||
}
|
||||
|
||||
def get_git_status(self):
|
||||
"""Get git repository status"""
|
||||
try:
|
||||
project_root = os.path.dirname(os.path.dirname(self.stats_file))
|
||||
|
||||
# Check if we're in a git repository
|
||||
git_dir = os.path.join(project_root, '.git')
|
||||
if not os.path.exists(git_dir):
|
||||
return {'status': 'no_repo', 'branch': None, 'changes': []}
|
||||
|
||||
# Try different git status commands to get all files
|
||||
commands = [
|
||||
['git', 'status', '--porcelain', '--ignored=traditional'],
|
||||
['git', 'status', '--porcelain', '--ignored'],
|
||||
['git', 'status', '--porcelain']
|
||||
]
|
||||
|
||||
result = None
|
||||
for cmd in commands:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
cwd=project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
)
|
||||
if result.returncode == 0:
|
||||
break
|
||||
except:
|
||||
continue
|
||||
|
||||
if not result or result.returncode != 0:
|
||||
return {'status': 'error', 'branch': None, 'changes': []}
|
||||
|
||||
# Get current branch
|
||||
branch_result = subprocess.run(
|
||||
['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
|
||||
cwd=project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5
|
||||
)
|
||||
|
||||
current_branch = branch_result.stdout.strip() if branch_result.returncode == 0 else 'unknown'
|
||||
|
||||
# Parse git status output
|
||||
changes = []
|
||||
for line in result.stdout.strip().split('\n'):
|
||||
if line and len(line) >= 3:
|
||||
status_code = line[:2]
|
||||
file_path = line[3:]
|
||||
|
||||
if status_code == '??':
|
||||
change_type = 'untracked'
|
||||
elif status_code == '!!':
|
||||
change_type = 'ignored'
|
||||
elif status_code == ' M':
|
||||
change_type = 'modified'
|
||||
elif status_code == 'A':
|
||||
change_type = 'added'
|
||||
elif status_code == 'D':
|
||||
change_type = 'deleted'
|
||||
elif status_code[0] in ['M', 'A', 'D']:
|
||||
change_type = 'staged'
|
||||
else:
|
||||
change_type = 'changed'
|
||||
|
||||
changes.append({
|
||||
'file': file_path,
|
||||
'type': change_type,
|
||||
'status': status_code
|
||||
})
|
||||
|
||||
# Debug: print what we found
|
||||
print(f"Git status found {len(changes)} changes")
|
||||
|
||||
return {
|
||||
'status': 'clean' if not changes else 'dirty',
|
||||
'branch': current_branch,
|
||||
'changes': changes,
|
||||
'total_changes': len(changes)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"Git status error: {e}") # Debug output
|
||||
return {'status': 'error', 'branch': None, 'changes': []}
|
||||
|
||||
def scan_project_files(self):
|
||||
"""Scan project files - using exact same logic as project_stats.py"""
|
||||
try:
|
||||
project_root = os.path.dirname(os.path.dirname(self.stats_file))
|
||||
current_time = time.time()
|
||||
|
||||
# Use exact same constants as project_stats.py + tamagotchi_env
|
||||
IGNORED_DIRS = {
|
||||
'.git',
|
||||
'.git_backup',
|
||||
'__pycache__',
|
||||
'node_modules',
|
||||
'dist',
|
||||
'build',
|
||||
'.next',
|
||||
'.cache',
|
||||
'.venv',
|
||||
'venv',
|
||||
'tamagotchi_env', # Exclude tamagotchi virtual environment
|
||||
}
|
||||
|
||||
ALLOWED_EXTS = {
|
||||
'.tsx',
|
||||
'.css',
|
||||
'.go',
|
||||
'.ts',
|
||||
'.js',
|
||||
'.html',
|
||||
'.sql',
|
||||
'.py',
|
||||
}
|
||||
|
||||
file_stats = {
|
||||
'directories': 0,
|
||||
'files': 0,
|
||||
'lines': 0,
|
||||
'extensions': {},
|
||||
'recent_changes': 0,
|
||||
'current_file': None, # Track most recently modified file
|
||||
'recent_files': [] # List of recently modified files
|
||||
}
|
||||
|
||||
for current_dir, dirs, files in os.walk(project_root):
|
||||
dirs[:] = [d for d in dirs if d not in IGNORED_DIRS]
|
||||
|
||||
# Only count directories that are NOT the root directory (same as project_stats.py)
|
||||
if current_dir != project_root:
|
||||
file_stats['directories'] += 1
|
||||
|
||||
for name in files:
|
||||
ext = os.path.splitext(name)[1]
|
||||
if ext not in ALLOWED_EXTS:
|
||||
continue
|
||||
|
||||
file_stats['files'] += 1
|
||||
path = os.path.join(current_dir, name)
|
||||
ext_key = ext or '<no_ext>'
|
||||
line_count = 0
|
||||
|
||||
try:
|
||||
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
for _ in f:
|
||||
line_count += 1
|
||||
except Exception:
|
||||
line_count = 0
|
||||
|
||||
file_stats['lines'] += line_count
|
||||
|
||||
if ext_key not in file_stats['extensions']:
|
||||
file_stats['extensions'][ext_key] = {'files': 0, 'lines': 0}
|
||||
file_stats['extensions'][ext_key]['files'] += 1
|
||||
file_stats['extensions'][ext_key]['lines'] += line_count
|
||||
|
||||
# Check for recent changes (last 30 seconds)
|
||||
try:
|
||||
mtime = os.path.getmtime(path)
|
||||
if current_time - mtime < 30:
|
||||
file_stats['recent_changes'] += 1
|
||||
|
||||
# Track the most recently modified file
|
||||
rel_path = os.path.relpath(path, project_root)
|
||||
file_info = {
|
||||
'path': rel_path,
|
||||
'name': name,
|
||||
'ext': ext,
|
||||
'mtime': mtime,
|
||||
'size': os.path.getsize(path)
|
||||
}
|
||||
|
||||
# Update current file (most recent)
|
||||
if not file_stats['current_file'] or mtime > file_stats['current_file']['mtime']:
|
||||
file_stats['current_file'] = file_info
|
||||
|
||||
# Add to recent files list (keep last 5)
|
||||
file_stats['recent_files'].append(file_info)
|
||||
file_stats['recent_files'] = sorted(
|
||||
file_stats['recent_files'],
|
||||
key=lambda x: x['mtime'],
|
||||
reverse=True
|
||||
)[:5]
|
||||
|
||||
# Debug output
|
||||
print(f"Recent change detected: {rel_path} ({ext})")
|
||||
except:
|
||||
pass
|
||||
|
||||
return file_stats
|
||||
|
||||
except Exception as e:
|
||||
return {'directories': 0, 'files': 0, 'lines': 0, 'extensions': {}, 'recent_changes': 0}
|
||||
|
||||
def update_pet_state(self, file_stats):
|
||||
"""Update pet based on file stats"""
|
||||
self.recent_changes = file_stats['recent_changes']
|
||||
self.typing_detected = self.recent_changes > 0
|
||||
|
||||
lines = file_stats['lines']
|
||||
if lines > 200000:
|
||||
self.pet['evolution'] = 'master'
|
||||
elif lines > 100000:
|
||||
self.pet['evolution'] = 'adult'
|
||||
elif lines > 50000:
|
||||
self.pet['evolution'] = 'teen'
|
||||
elif lines > 10000:
|
||||
self.pet['evolution'] = 'child'
|
||||
else:
|
||||
self.pet['evolution'] = 'baby'
|
||||
|
||||
self.pet['level'] = 1 + (file_stats['files'] // 50) + (lines // 10000)
|
||||
|
||||
if self.typing_detected:
|
||||
self.pet['mood'] = 'coding'
|
||||
self.pet['activity'] = 'coding'
|
||||
self.pet['coding'] = min(100, self.pet['coding'] + self.recent_changes * 10)
|
||||
self.pet['energy'] = min(100, self.pet['energy'] + 5)
|
||||
else:
|
||||
self.pet['activity'] = 'idle'
|
||||
self.pet['coding'] = max(0, self.pet['coding'] - 2)
|
||||
|
||||
if self.pet['energy'] > 70:
|
||||
self.pet['mood'] = 'content'
|
||||
elif self.pet['energy'] > 40:
|
||||
self.pet['mood'] = 'tired'
|
||||
else:
|
||||
self.pet['mood'] = 'tired'
|
||||
|
||||
self.pet['hunger'] = min(100, self.pet['hunger'] + 1)
|
||||
self.pet['energy'] = max(0, self.pet['energy'] - 1)
|
||||
|
||||
evolution_faces = self.faces.get(self.pet['evolution'], self.faces['baby'])
|
||||
mood_faces = evolution_faces.get(self.pet['mood'], evolution_faces['content'])
|
||||
self.pet['face'] = random.choice(mood_faces)
|
||||
|
||||
def get_terminal_output(self):
|
||||
"""Generate ultra-compact terminal-style output"""
|
||||
lines = []
|
||||
|
||||
# Header
|
||||
lines.append(f"[{datetime.now().strftime('%H:%M:%S')}] {self.pet['name'].upper()} TERMINAL")
|
||||
lines.append("=" * 50)
|
||||
|
||||
# Pet and status on one line
|
||||
lines.append(f"[{self.pet['face']}] {self.pet['mood'].upper()} | LVL:{self.pet['level']} | {self.pet['evolution'].upper()}")
|
||||
|
||||
# Status bars on one line
|
||||
energy_bar = "#" * int(self.pet['energy']//10) + " " * (10 - int(self.pet['energy']//10))
|
||||
hunger_bar = "#" * int(self.pet['hunger']//10) + " " * (10 - int(self.pet['hunger']//10))
|
||||
coding_bar = "#" * int(self.pet['coding']//10) + " " * (10 - int(self.pet['coding']//10))
|
||||
lines.append(f"E:[{energy_bar}] H:[{hunger_bar}] C:[{coding_bar}]")
|
||||
|
||||
# Current activity (compact)
|
||||
if self.typing_detected and self.stats.get('current_file'):
|
||||
current_file = self.stats['current_file']
|
||||
file_name = current_file['path'].split('/')[-1]
|
||||
lines.append(f"EDITING: {file_name} ({current_file['ext']})")
|
||||
elif self.typing_detected:
|
||||
lines.append(f"ACTIVITY: {self.recent_changes} files changed")
|
||||
else:
|
||||
lines.append(f"Activity: {self.pet['activity'].upper()}")
|
||||
|
||||
# Git status (compact)
|
||||
git_status = self.get_git_status()
|
||||
if git_status['status'] == 'no_repo':
|
||||
lines.append("GIT: Not a git repository")
|
||||
elif git_status['status'] == 'error':
|
||||
lines.append("GIT: Error checking git")
|
||||
elif git_status['status'] == 'clean':
|
||||
lines.append(f"GIT: Clean ({git_status['branch']})")
|
||||
else:
|
||||
untracked = len([c for c in git_status['changes'] if c['type'] == 'untracked'])
|
||||
modified = len([c for c in git_status['changes'] if c['type'] == 'modified'])
|
||||
staged = len([c for c in git_status['changes'] if c['type'] == 'staged'])
|
||||
deleted = len([c for c in git_status['changes'] if c['type'] == 'deleted'])
|
||||
ignored = len([c for c in git_status['changes'] if c['type'] == 'ignored'])
|
||||
|
||||
lines.append(f"GIT: {git_status['total_changes']} files ({git_status['branch']})")
|
||||
lines.append(f" U:{untracked} M:{modified} S:{staged} D:{deleted} I:{ignored}")
|
||||
|
||||
# Recent files (compact)
|
||||
if self.stats.get('recent_files'):
|
||||
recent_names = [f['path'].split('/')[-1] for f in self.stats['recent_files'][:2]]
|
||||
lines.append(f"RECENT: {', '.join(recent_names)}")
|
||||
|
||||
# Project stats (compact)
|
||||
lines.append(f"PROJECT: {self.stats.get('files', 0)} files | {self.stats.get('lines', 0):,} lines")
|
||||
|
||||
# Top languages (compact)
|
||||
extensions = self.stats.get('extensions', {})
|
||||
if extensions:
|
||||
sorted_exts = sorted(extensions.items(), key=lambda x: x[1]['lines'], reverse=True)[:3]
|
||||
top_langs = [f"{ext}({data['files']})" for ext, data in sorted_exts]
|
||||
lines.append(f"LANGUAGES: {', '.join(top_langs)}")
|
||||
|
||||
lines.append("CONTROLS: [f]eed [p]lay [r]efresh [q]uit")
|
||||
lines.append("=" * 50)
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def feed_pet(self):
|
||||
"""Feed the pet"""
|
||||
self.pet['hunger'] = max(0, self.pet['hunger'] - 30)
|
||||
self.pet['energy'] = min(100, self.pet['energy'] + 10)
|
||||
self.pet['mood'] = 'content'
|
||||
|
||||
def play_with_pet(self):
|
||||
"""Play with pet"""
|
||||
self.pet['energy'] = max(0, self.pet['energy'] - 10)
|
||||
self.pet['mood'] = 'content'
|
||||
self.pet['activity'] = 'playing'
|
||||
|
||||
# Global instance
|
||||
tamagotchi = None
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""Main terminal page"""
|
||||
return render_template('terminal.html')
|
||||
|
||||
@app.route('/api/terminal-data')
|
||||
def get_terminal_data():
|
||||
"""Get terminal output as JSON"""
|
||||
return jsonify({
|
||||
'output': tamagotchi.get_terminal_output(),
|
||||
'typing_detected': tamagotchi.typing_detected,
|
||||
'recent_changes': tamagotchi.recent_changes
|
||||
})
|
||||
|
||||
@socketio.on('connect')
|
||||
def handle_connect():
|
||||
"""Handle client connection"""
|
||||
emit('terminal_update', {
|
||||
'output': tamagotchi.get_terminal_output(),
|
||||
'typing_detected': tamagotchi.typing_detected,
|
||||
'recent_changes': tamagotchi.recent_changes
|
||||
})
|
||||
|
||||
@socketio.on('command')
|
||||
def handle_command(data):
|
||||
"""Handle terminal commands"""
|
||||
cmd = data.get('command', '').lower().strip()
|
||||
|
||||
if cmd == 'f':
|
||||
tamagotchi.feed_pet()
|
||||
print(f"Feed command received - Hunger: {tamagotchi.pet['hunger']}%")
|
||||
elif cmd == 'p':
|
||||
tamagotchi.play_with_pet()
|
||||
print(f"Play command received - Energy: {tamagotchi.pet['energy']}%")
|
||||
elif cmd == 'r':
|
||||
print("Refresh command received - stats refresh automatically")
|
||||
elif cmd == 'q':
|
||||
emit('quit', {'message': 'Terminal session ended'})
|
||||
return
|
||||
|
||||
# Send updated state
|
||||
emit('terminal_update', {
|
||||
'output': tamagotchi.get_terminal_output(),
|
||||
'typing_detected': tamagotchi.typing_detected,
|
||||
'recent_changes': tamagotchi.recent_changes
|
||||
})
|
||||
|
||||
def background_monitor():
|
||||
"""Background thread to monitor files and broadcast updates"""
|
||||
while True:
|
||||
try:
|
||||
# Scan files and update pet
|
||||
file_stats = tamagotchi.scan_project_files()
|
||||
tamagotchi.stats = file_stats
|
||||
tamagotchi.update_pet_state(file_stats)
|
||||
|
||||
# Broadcast update to all clients
|
||||
socketio.emit('terminal_update', {
|
||||
'output': tamagotchi.get_terminal_output(),
|
||||
'typing_detected': tamagotchi.typing_detected,
|
||||
'recent_changes': tamagotchi.recent_changes
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Monitor error: {e}")
|
||||
|
||||
time.sleep(2) # Update every 2 seconds
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Initialize tamagotchi
|
||||
project_name = "MyClub"
|
||||
tamagotchi = BrowserTerminalTamagotchi(project_name)
|
||||
|
||||
# Start background monitoring
|
||||
import threading
|
||||
monitor_thread = threading.Thread(target=background_monitor, daemon=True)
|
||||
monitor_thread.start()
|
||||
|
||||
print("🖥️ Browser Terminal Tamagotchi Starting...")
|
||||
print("🌐 Open your browser to: http://localhost:5000")
|
||||
print("⌨️ Terminal interface in browser!")
|
||||
print("🔥 Real-time file monitoring!")
|
||||
|
||||
socketio.run(app, host='0.0.0.0', port=5000, debug=False)
|
||||
Reference in New Issue
Block a user