Files
website/app.py
SimolZimol 9e0a1abb53 modified: app.py
new file:   templates/itemeditor_command_storage.html
	modified:   versions/version.json
2026-01-07 03:45:06 +01:00

326 lines
11 KiB
Python

import os
import json
import uuid
from pathlib import Path
from datetime import datetime, timedelta
from threading import Lock
from flask import Flask, render_template, jsonify, send_from_directory, request
app = Flask(__name__)
# In-memory storage for commands (thread-safe)
command_storage = {}
storage_lock = Lock()
def cleanup_expired_commands():
"""Remove expired commands from storage"""
with storage_lock:
now = datetime.now()
expired_keys = [key for key, value in command_storage.items()
if datetime.fromisoformat(value['expires_at']) < now]
for key in expired_keys:
del command_storage[key]
# Load projects from version.json
def load_projects():
"""Load all projects from version.json"""
json_path = Path(__file__).parent / 'versions' / 'version.json'
with open(json_path, 'r', encoding='utf-8') as f:
return json.load(f)
def get_project_data(project_key, project_info):
"""Convert JSON project data to template format"""
# Determine project type and compatibility
project_type = project_info.get('project_type', 'minecraft')
# Get compatibility info
compat = None
if 'mc_compat' in project_info:
compat = project_info['mc_compat'].get('stable', '')
elif 'velocity_compat' in project_info:
compat = project_info['velocity_compat'].get('stable', '')
# Determine display type
if project_type in ['minecraft', 'spigot', 'paper', 'bukkit']:
display_type = 'Minecraft Plugin'
elif project_type == 'velocity':
display_type = 'Velocity Plugin'
elif project_type == 'discord':
display_type = 'Discord Bot'
else:
display_type = project_type.title()
# Get project status (global project status, not version status)
project_status = project_info.get('project_status', 'active')
# Get version info (prefer beta if specified, otherwise stable)
stable_version = project_info.get('stable', 'N/A')
beta_version = project_info.get('beta')
# Build project dict
return {
'id': project_key,
'name': project_info.get('name', project_key.title()),
'tagline': project_info.get('tagline', ''),
'type': display_type,
'description': project_info.get('description', ''),
'long_description': project_info.get('long_description', ''),
'icon': project_info.get('icon', 'fas fa-cube'),
'color': project_type,
'status': project_status.title(),
'version': stable_version,
'beta_version': beta_version,
'compatibility': compat,
'server_types': project_info.get('server_types', []),
'technologies': project_info.get('technologies', []),
'features': project_info.get('features', []),
'commands': project_info.get('commands', []),
'installation': project_info.get('installation', []),
'technical_highlights': project_info.get('technical_highlights', []),
'links': project_info.get('links', {}),
'download': project_info.get('download', {}),
'downloads': project_info.get('stats', {}).get('downloads', 0) if project_info.get('stats', {}).get('downloads') else None,
'users': None,
'url': f'/projects/{project_key}'
}
# Main routes
@app.route('/')
def home():
"""Homepage with overview of Devanturas and featured projects"""
all_projects = load_projects()
# Convert to template format - show featured projects (first 6)
projects = []
for key, info in list(all_projects.items())[:6]:
if info.get('name'): # Only show complete projects
projects.append(get_project_data(key, info))
return render_template('index.html', projects=projects)
@app.route('/projects')
def projects():
"""Overview of all projects"""
all_projects = load_projects()
# Convert all projects to template format
projects_list = []
for key, info in all_projects.items():
if info.get('name'): # Only show complete projects
projects_list.append(get_project_data(key, info))
return render_template('projects.html', projects=projects_list)
@app.route('/minecraft')
def minecraft():
"""Dedicated Minecraft development page"""
all_projects = load_projects()
# Filter minecraft plugins only
minecraft_projects = []
for key, info in all_projects.items():
if info.get('project_type') == 'minecraft' and info.get('name'):
proj = {
'name': info.get('name'),
'description': info.get('description', ''),
'version': info.get('stable', 'N/A'),
'supported_versions': info.get('mc_compat', {}).get('stable', 'N/A'),
'downloads': info.get('stats', {}).get('downloads', 'Available'),
'url': f'/projects/{key}'
}
minecraft_projects.append(proj)
minecraft_info = {
'experience': '7+ Years',
'preferred_apis': ['Spigot API', 'Paper API', 'Bukkit API', 'Velocity API'],
'specialties': ['Plugin Development', 'Server Optimization', 'Custom Commands', 'Permission Systems'],
'server_types': ['Spigot', 'Paper', 'Bukkit', 'Velocity']
}
return render_template('minecraft.html', projects=minecraft_projects, info=minecraft_info)
@app.route('/about')
def about():
"""About SimolZimol and Devanturas"""
skills = {
'Minecraft Development': [
'Spigot & Paper Plugin Development',
'Velocity Proxy Plugin Development',
'Java Programming',
'Server Administration',
'Performance Optimization',
'Custom Commands & GUIs',
'Permission System Integration'
],
'Discord Development': [
'Discord.py Bot Development',
'Database Integration (MySQL)',
'Asynchronous Programming',
'Community Management Tools',
'Slash Commands & Interactions',
'Docker Deployment'
],
'General Programming': [
'Java, Python',
'Database Design',
'RESTful APIs',
'Git Version Control',
'Docker & Containerization',
'Web Development (Flask)'
]
}
achievements = [
{
'title': 'Published Plugins',
'description': 'Active on Modrinth, Hangar & SpigotMC',
'icon': 'fas fa-download'
},
{
'title': 'Open Source',
'description': 'Contributing to GitHub repositories',
'icon': 'fab fa-github'
},
{
'title': 'Community',
'description': 'Supporting Discord communities',
'icon': 'fab fa-discord'
}
]
return render_template('about.html', skills=skills, achievements=achievements)
@app.route('/contact')
def contact():
"""Contact information and links"""
contact_links = [
{
'name': 'GitHub',
'url': 'https://github.com/SimolZimol/',
'description': 'Open source projects and code repositories',
'icon': 'fab fa-github',
'color': 'github'
},
{
'name': 'Discord',
'url': 'https://discordapp.com/users/253922739709018114',
'description': 'Direct contact for questions and support',
'icon': 'fab fa-discord',
'color': 'discord'
},
{
'name': 'Modrinth',
'url': 'https://modrinth.com/user/SimolZimol',
'description': 'Minecraft plugins on Modrinth platform',
'icon': 'fas fa-cube',
'color': 'modrinth'
},
{
'name': 'SpigotMC',
'url': 'https://www.spigotmc.org/members/simolzimol.123456/',
'description': 'Minecraft plugins and resources',
'icon': 'fas fa-plug',
'color': 'spigot'
}
]
return render_template('contact.html', links=contact_links)
# Healthcheck endpoint for platforms like Coolify
@app.route('/health')
def health():
return jsonify(status='ok'), 200
# Dynamic project detail pages
@app.route('/projects/<project_id>')
def project_detail(project_id):
"""Dynamic project detail page"""
all_projects = load_projects()
if project_id not in all_projects:
return "Project not found", 404
project_info = all_projects[project_id]
project = get_project_data(project_id, project_info)
return render_template('project_detail.html', project=project)
# Item Editor Command Storage
@app.route('/projects/itemeditor/storage')
def itemeditor_command_storage():
"""Item Editor Command Storage page"""
return render_template('itemeditor_command_storage.html')
@app.route('/projects/itemeditor/storage', methods=['POST'])
def store_command():
"""Store a command and return UUID link"""
cleanup_expired_commands()
try:
data = request.get_json()
command = data.get('command', '').strip()
if not command:
return jsonify({'success': False, 'error': 'Command is required'}), 400
if len(command) > 10000:
return jsonify({'success': False, 'error': 'Command exceeds maximum length of 10,000 characters'}), 400
# Generate UUID
command_uuid = str(uuid.uuid4())
# Calculate expiry (30 minutes)
created_at = datetime.now()
expires_at = created_at + timedelta(minutes=30)
# Store command
with storage_lock:
command_storage[command_uuid] = {
'command': command,
'created_at': created_at.isoformat(),
'expires_at': expires_at.isoformat()
}
# Generate URL
base_url = request.host_url.rstrip('/')
retrieval_url = f"{base_url}/projects/itemeditor/storage/{command_uuid}"
return jsonify({
'success': True,
'uuid': command_uuid,
'url': retrieval_url,
'expires_at': expires_at.isoformat()
}), 201
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/projects/itemeditor/storage/<command_uuid>')
def retrieve_command(command_uuid):
"""Retrieve stored command by UUID (JSON response for plugin)"""
cleanup_expired_commands()
with storage_lock:
command_data = command_storage.get(command_uuid)
if not command_data:
return jsonify({'error': 'Command not found or expired'}), 404
# Check if expired
if datetime.fromisoformat(command_data['expires_at']) < datetime.now():
with storage_lock:
del command_storage[command_uuid]
return jsonify({'error': 'Command expired'}), 410
return jsonify(command_data), 200
# Serve /versions as JSON
@app.route('/versions')
def versions():
return send_from_directory('versions', 'version.json', mimetype='application/json')
if __name__ == '__main__':
# Allow overriding via environment (e.g., Coolify sets PORT)
port = int(os.getenv('PORT', '5000'))
debug = os.getenv('FLASK_DEBUG', 'false').lower() == 'true'
app.run(debug=debug, host='0.0.0.0', port=port)