import re import threading import time from os import path, listdir from datetime import date from configparser import ConfigParser from sass import compile as compile_sass from htmlmin import minify as minify_html from flask import Flask, redirect, render_template, request, url_for, g, Response # ---------- ENV VARS -------------------------------------------------------- STATUS_URL = "https://builds.kolibrios.org/status.html" STATUS_FETCH_DELAY_SEC = 300 # 5 minutes # ---------- APP CONFIG ------------------------------------------------------ app = Flask(__name__) if app.debug: # CSS Compilation and minification css = compile_sass(filename="static/style.scss", output_style="compressed") with open("static/style.css", "w", encoding="utf-8") as f: f.write(css) # JS minification with open("static/script.js", encoding="utf-8") as f: js = f.read() js = re.sub(r"/\*.*?\*/", "", js, flags=re.S) js = re.sub(r"//.*", "", js) js = re.sub(r"\s+", " ", js).strip() with open("static/script.min.js", "w", encoding="utf-8") as f: f.write(js) # ---------- LATEST COMMIT DATE (MINIMAL ADD-ON) ----------------------------- autobuild_date = "DD.MM.YYYY" autobuild_vers = "0.0.0.0+0000-0000000" def _refresh_build_date_once(): global autobuild_date, autobuild_vers try: from urllib.request import Request, urlopen import re req = Request( STATUS_URL, headers={ "User-Agent": "Mozilla/5.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", }, ) with urlopen(req, timeout=10) as r: html = r.read().decode( r.headers.get_content_charset() or "utf-8", "replace" ) rows = re.findall(r"(]*>.*?)", html, flags=re.I | re.S) if not rows: return last_commit_ver = None for row in rows: cls = re.search(r'class\s*=\s*"([^"]*)"', row, flags=re.I) classes = cls.group(1).lower() if cls else "" text = re.sub(r"<[^>]+>", " ", row) if "commit" in classes: mver = re.search( r"\b(\d+\.\d+\.\d+\.\d+\+\d{3,8}-[0-9a-fA-F]{7,40})\b", row ) if mver: last_commit_ver = mver.group(1) elif "success" in classes: mts = re.search( r"\b(\d{4})\.(\d{2})\.(\d{2})\s+\d{2}:\d{2}:\d{2}\b", text ) if not mts: mds = re.search(r"\b(\d{2})\.(\d{2})\.(\d{4})\b", text) if mds: autobuild_date = f"{mds.group(1)}.{mds.group(2)}.{mds.group(3)}" else: return else: y, mo, d = mts.groups() autobuild_date = f"{d}.{mo}.{y}" if last_commit_ver: autobuild_vers = last_commit_ver return except Exception: pass def _updater_loop(): while True: _refresh_build_date_once() time.sleep(STATUS_FETCH_DELAY_SEC) _started = False _refresh_build_date_once() _updater_lock = threading.Lock() @app.before_request def _ensure_updater_started(): global _started if not _started: with _updater_lock: if not _started: _started = True threading.Thread(target=_updater_loop, daemon=True).start() @app.context_processor def _inject_autobuild_vers(): return {'autobuild_vers': autobuild_vers} @app.context_processor def _inject_autobuild_date(): return {'autobuild_date': autobuild_date} # ---------- LOCALES FUNCTIONS ----------------------------------------------- def load_all_locales(): translations = {} locales_dir = "locales" locales_code_default = ('en', 'ru', 'es') locales_code_extra = [] locales_code = () for filename in listdir(locales_dir): if filename.endswith(".ini"): cp = ConfigParser() lang = path.splitext(filename)[0] with open(path.join(locales_dir, filename), encoding="utf-8") as f: cp.read_file(f) if lang not in locales_code_default: locales_code_extra.append(lang) translations[lang] = { section: dict(cp[section]) for section in cp.sections() } locales_code = locales_code_default + tuple(sorted(locales_code_extra)) locales_name = {l: translations[l]['title']['language'] for l in locales_code} return translations, locales_name, locales_code translations, locales_name, locales_code = load_all_locales() # ---------- HELPER FUNCTIONS ------------------------------------------------ def get_best_lang(): return request.accept_languages.best_match(locales_code) or "en" def render_localized_template(lang, template_name): if lang not in locales_code: return redirect(url_for("index", lang=get_best_lang())) return minify_html( render_template( template_name, year=date.today().year, ), remove_empty_space=True, ) @app.before_request def before_request(): if args := request.view_args: g.locale = args.get("lang", "en") g.translations = translations.get(g.locale, get_best_lang()) g.locales_name = locales_name @app.context_processor def inject_translations(): def translate(text, **kwargs): section, key = text.split(":", 1) template = g.translations \ .get(section, {}) \ .get(key, f"${section}: {key}$") try: return template.format(**kwargs) except Exception: return template return {'_': translate} # ---------- MAIN PAGES ------------------------------------------------------ @app.route("/") def home(): return redirect(url_for("index", lang=get_best_lang())) @app.route("/") def index(lang): return render_localized_template(lang, "index.html") @app.route("//download") def download(lang): return render_localized_template(lang, "download.html") # ---------- ROBOTS.TXT + SITEMAP.XML ---------------------------------------- @app.route("/robots.txt") def robots_txt(): base_url = request.url_root.rstrip("/") content = [ "User-agent: *", "Disallow:", f"Sitemap: {base_url}/sitemap.xml", ] return Response("\n".join(content), mimetype="text/plain") @app.route("/sitemap.xml") def sitemap_xml(): base_url = request.url_root.rstrip("/") today = date.today().isoformat() urls = [] for lang in locales_code: urls.append(f"{base_url}/{lang}") urls.append(f"{base_url}/{lang}/download") xml_lines = [ '', '', ] for loc in urls: xml_lines.extend( [ f" ", f" {loc}", f" {today}", f" monthly", f" 0.8", f" ", ] ) xml_lines.append("") return Response("\n".join(xml_lines), mimetype="application/xml") # ---------- APP ENTRY ------------------------------------------------------- if __name__ == "__main__": app.run(host="0.0.0.0", debug=True)