Moved the Agents into their own repo. Kept the agent.pl just for reference
This commit is contained in:
parent
22381be29a
commit
8680a02b13
18132 changed files with 0 additions and 2569420 deletions
|
|
@ -1,292 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
FAQ RSS Generator
|
||||
Generates FAQ.RSS from YAML game documentation files.
|
||||
Produces clean HTML-escaped content without CDATA, using <br> for line breaks.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
import html
|
||||
import xml.etree.ElementTree as ET
|
||||
from datetime import datetime
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
class FAQRSSGenerator:
|
||||
def __init__(self, data_dir="data/games", output_file="FAQ.RSS"):
|
||||
self.data_dir = Path(data_dir)
|
||||
self.output_file = Path(output_file)
|
||||
self.games = []
|
||||
|
||||
def load_games(self):
|
||||
"""Load all YAML game files from data directory"""
|
||||
self.games = []
|
||||
if not self.data_dir.exists():
|
||||
print(f"Error: Data directory {self.data_dir} does not exist")
|
||||
return False
|
||||
|
||||
yaml_files = list(self.data_dir.glob("*.yml")) + list(self.data_dir.glob("*.yaml"))
|
||||
if not yaml_files:
|
||||
print(f"Warning: No YAML files found in {self.data_dir}")
|
||||
return True
|
||||
|
||||
for yaml_file in yaml_files:
|
||||
try:
|
||||
with open(yaml_file, 'r', encoding='utf-8') as f:
|
||||
game_data = yaml.safe_load(f)
|
||||
if self.validate_game_data(game_data, yaml_file):
|
||||
self.games.append(game_data)
|
||||
print(f"Loaded: {game_data['name']}")
|
||||
except Exception as e:
|
||||
print(f"Error loading {yaml_file}: {e}")
|
||||
|
||||
return True
|
||||
|
||||
def validate_game_data(self, data, filename):
|
||||
"""Validate game YAML data structure"""
|
||||
required_fields = ['name', 'supports_workshop', 'startup', 'configs', 'troubleshooting']
|
||||
|
||||
for field in required_fields:
|
||||
if field not in data:
|
||||
print(f"Error in {filename}: Missing required field '{field}'")
|
||||
return False
|
||||
|
||||
# Validate startup section
|
||||
startup = data['startup']
|
||||
if 'default_command' not in startup:
|
||||
print(f"Error in {filename}: Missing 'default_command' in startup section")
|
||||
return False
|
||||
|
||||
# Validate workshop section if supported
|
||||
if data['supports_workshop'] and 'workshop' not in data:
|
||||
print(f"Error in {filename}: supports_workshop is true but 'workshop' section missing")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def escape_html_content(self, text):
|
||||
"""Escape HTML content and convert newlines to <br> tags"""
|
||||
if not text:
|
||||
return ""
|
||||
# Escape HTML entities
|
||||
escaped = html.escape(str(text))
|
||||
# Convert newlines to <br> tags
|
||||
escaped = escaped.replace('\n', '<br>')
|
||||
return escaped
|
||||
|
||||
def generate_config_files_content(self, game):
|
||||
"""Generate Config Files section content"""
|
||||
content = "<strong>Configuration Files</strong><br>"
|
||||
|
||||
for config in game['configs']:
|
||||
file_name = config['file']
|
||||
paths = config.get('paths', [])
|
||||
desc = config.get('desc', '')
|
||||
|
||||
content += "<br>- " + self.escape_html_content(file_name)
|
||||
if paths:
|
||||
path_str = ", ".join(paths)
|
||||
content += " — " + self.escape_html_content(desc) + ". Paths: " + self.escape_html_content(path_str)
|
||||
elif desc:
|
||||
content += " — " + self.escape_html_content(desc)
|
||||
content += "<br>"
|
||||
|
||||
return content
|
||||
|
||||
def generate_startup_parameters_content(self, game):
|
||||
"""Generate Startup Parameters section content"""
|
||||
startup = game['startup']
|
||||
content = "<strong>Default Command Line</strong><br>"
|
||||
content += "<br>" + self.escape_html_content(startup['default_command']) + "<br>"
|
||||
|
||||
# Port scheme
|
||||
if 'ports' in startup and startup['ports']:
|
||||
content += "<br><strong>Port Scheme</strong><br>"
|
||||
for port in startup['ports']:
|
||||
label = port.get('label', '')
|
||||
port_num = port.get('port', '')
|
||||
proto = port.get('proto', '')
|
||||
relative = port.get('relative', '')
|
||||
content += "<br>- " + self.escape_html_content(f"{label} ({proto}) — {relative} (default {port_num})") + "<br>"
|
||||
|
||||
# Command line flags
|
||||
if 'flags' in startup and startup['flags']:
|
||||
content += "<br><strong>Command Line Flags</strong><br>"
|
||||
for flag in startup['flags']:
|
||||
flag_name = flag.get('flag', '')
|
||||
default = flag.get('default', '')
|
||||
flag_type = flag.get('type', '')
|
||||
desc = flag.get('desc', '')
|
||||
content += "<br>" + self.escape_html_content(f"{flag_name} — {desc}. Type: {flag_type}, Default: {default}") + "<br>"
|
||||
|
||||
return content
|
||||
|
||||
def generate_troubleshooting_content(self, game):
|
||||
"""Generate Troubleshooting section content"""
|
||||
content = "<strong>Common Issues and Solutions</strong><br>"
|
||||
|
||||
for issue in game['troubleshooting']:
|
||||
content += "<br>- " + self.escape_html_content(issue) + "<br>"
|
||||
|
||||
return content
|
||||
|
||||
def generate_workshop_content(self, game):
|
||||
"""Generate Steam Workshop section content"""
|
||||
if not game.get('supports_workshop', False):
|
||||
return None
|
||||
|
||||
workshop = game.get('workshop', {})
|
||||
content = "<strong>Steam Workshop Configuration</strong><br>"
|
||||
|
||||
notes = workshop.get('notes', [])
|
||||
for note in notes:
|
||||
content += "<br>- " + self.escape_html_content(note) + "<br>"
|
||||
|
||||
return content
|
||||
|
||||
def create_rss_item(self, title, category, content):
|
||||
"""Create RSS item element"""
|
||||
item = ET.Element('item')
|
||||
|
||||
title_elem = ET.SubElement(item, 'title')
|
||||
title_elem.text = title
|
||||
|
||||
category_elem = ET.SubElement(item, 'category')
|
||||
category_elem.text = category
|
||||
|
||||
# Handle namespaced element properly
|
||||
content_elem = ET.SubElement(item, '{http://purl.org/rss/1.0/modules/content/}encoded')
|
||||
content_elem.text = content
|
||||
|
||||
return item
|
||||
|
||||
def generate_rss(self):
|
||||
"""Generate the complete RSS file"""
|
||||
# Create backup if file exists
|
||||
if self.output_file.exists():
|
||||
backup_file = self.output_file.with_suffix('.bak')
|
||||
shutil.copy2(self.output_file, backup_file)
|
||||
print(f"Created backup: {backup_file}")
|
||||
|
||||
# Create RSS root
|
||||
rss = ET.Element('rss', version='2.0')
|
||||
rss.set('xmlns:content', 'http://purl.org/rss/1.0/modules/content/')
|
||||
rss.set('xmlns:dc', 'http://purl.org/dc/elements/1.1/')
|
||||
|
||||
channel = ET.SubElement(rss, 'channel')
|
||||
|
||||
# Channel metadata
|
||||
title = ET.SubElement(channel, 'title')
|
||||
title.text = 'Game Server FAQ'
|
||||
|
||||
link = ET.SubElement(channel, 'link')
|
||||
link.text = 'https://gameservers.world/faq'
|
||||
|
||||
description = ET.SubElement(channel, 'description')
|
||||
description.text = 'Comprehensive game server configuration and troubleshooting guide'
|
||||
|
||||
language = ET.SubElement(channel, 'dc:language')
|
||||
language.text = 'en'
|
||||
|
||||
pubdate = ET.SubElement(channel, 'pubDate')
|
||||
# Fix datetime deprecation warning
|
||||
pubdate.text = datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT')
|
||||
|
||||
# Sort games alphabetically by name, then items by section order
|
||||
sorted_games = sorted(self.games, key=lambda x: x['name'])
|
||||
|
||||
# Define section order
|
||||
section_order = ['Config Files', 'Startup Parameters', 'Troubleshooting', 'Steam Workshop']
|
||||
|
||||
# Generate items for each game
|
||||
all_items = []
|
||||
for game in sorted_games:
|
||||
game_name = game['name']
|
||||
|
||||
# Required sections in order
|
||||
sections = [
|
||||
('Config Files', self.generate_config_files_content(game)),
|
||||
('Startup Parameters', self.generate_startup_parameters_content(game)),
|
||||
('Troubleshooting', self.generate_troubleshooting_content(game))
|
||||
]
|
||||
|
||||
# Optional workshop section
|
||||
workshop_content = self.generate_workshop_content(game)
|
||||
if workshop_content:
|
||||
sections.append(('Steam Workshop', workshop_content))
|
||||
|
||||
# Create RSS items
|
||||
for title, content in sections:
|
||||
item = self.create_rss_item(title, game_name, content)
|
||||
all_items.append((game_name, title, item))
|
||||
|
||||
# Sort all items by category (game name) then by section order
|
||||
all_items.sort(key=lambda x: (x[0], section_order.index(x[1]) if x[1] in section_order else 999))
|
||||
|
||||
# Add all items to channel (no longer needed since we're writing manually)
|
||||
# for game_name, title, item in all_items:
|
||||
# channel.append(item)
|
||||
|
||||
# Write RSS file manually to avoid namespace issues
|
||||
with open(self.output_file, 'w', encoding='utf-8') as f:
|
||||
f.write('<?xml version="1.0" encoding="utf-8" ?>\n')
|
||||
f.write('<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">\n')
|
||||
f.write(' <channel>\n')
|
||||
f.write(' <title>Game Server FAQ</title>\n')
|
||||
f.write(' <link>https://gameservers.world/faq</link>\n')
|
||||
f.write(' <description>Comprehensive game server configuration and troubleshooting guide</description>\n')
|
||||
f.write(' <dc:language>en</dc:language>\n')
|
||||
f.write(f' <pubDate>{datetime.now().strftime("%a, %d %b %Y %H:%M:%S GMT")}</pubDate>\n')
|
||||
|
||||
# Write all items
|
||||
for game_name, title, item in all_items:
|
||||
title_text = item.find('title').text
|
||||
category_text = item.find('category').text
|
||||
content_elem = item.find('{http://purl.org/rss/1.0/modules/content/}encoded')
|
||||
content = content_elem.text if content_elem is not None else ""
|
||||
|
||||
f.write(' <item>\n')
|
||||
f.write(f' <title>{html.escape(title_text)}</title>\n')
|
||||
f.write(f' <category>{html.escape(category_text)}</category>\n')
|
||||
f.write(f' <content:encoded>{content}</content:encoded>\n')
|
||||
f.write(' </item>\n')
|
||||
|
||||
f.write(' </channel>\n')
|
||||
f.write('</rss>\n')
|
||||
|
||||
print(f"Generated RSS with {len(all_items)} items for {len(sorted_games)} games")
|
||||
print(f"Output: {self.output_file}")
|
||||
|
||||
def run(self):
|
||||
"""Main execution method"""
|
||||
print("FAQ RSS Generator")
|
||||
print("================")
|
||||
|
||||
if not self.load_games():
|
||||
return False
|
||||
|
||||
if not self.games:
|
||||
print("No valid games found. RSS file will be empty.")
|
||||
|
||||
self.generate_rss()
|
||||
return True
|
||||
|
||||
def main():
|
||||
if len(sys.argv) > 1:
|
||||
data_dir = sys.argv[1]
|
||||
else:
|
||||
data_dir = "data/games"
|
||||
|
||||
if len(sys.argv) > 2:
|
||||
output_file = sys.argv[2]
|
||||
else:
|
||||
output_file = "FAQ.RSS"
|
||||
|
||||
generator = FAQRSSGenerator(data_dir, output_file)
|
||||
success = generator.run()
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue