3 Commits

Author SHA1 Message Date
Sweetbread fdbb4c6a26 fixup! fixup! Add some webrings
Docker Build and Push / build-and-push (push) Successful in 53s
2026-04-13 22:07:41 +03:00
Sweetbread 007815dec1 Update ygg info
Docker Build and Push / build-and-push (push) Successful in 27s
2026-04-13 08:53:12 +03:00
Sweetbread 7080d30510 fixup! Add some webrings
Docker Build and Push / build-and-push (push) Successful in 21s
2026-04-09 05:37:08 +03:00
3 changed files with 62 additions and 96 deletions
+19 -87
View File
@@ -1,91 +1,28 @@
from __future__ import annotations from __future__ import annotations
import atexit import atexit
import json
import os import os
import re import re
import tempfile
from time import time from time import time
from urllib.parse import urlsplit from urllib.parse import urlsplit
import fcntl
import requests import requests
from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.interval import IntervalTrigger from apscheduler.triggers.interval import IntervalTrigger
from blueprints.risdeveau.modules.api.scheduler_guard import should_start_scheduler
def _cache_dir() -> str: from blueprints.risdeveau.modules.api.shared_cache import (
path = os.environ.get("LAIR_CACHE_DIR", "/tmp/lair-cache") atomic_write_json,
os.makedirs(path, exist_ok=True) cache_file,
return path load_json_if_newer,
)
def _cache_file(name: str) -> str: EURORING_SOURCE_URL = os.environ.get(
return os.path.join(_cache_dir(), f"{name}.json") "EURORING_SOURCE_URL",
"https://euroring.neocities.org/scripts/onionring-variables.js",
)
def _atomic_write_json(path: str, payload: dict) -> None: EURORING_SITE_URL = os.environ.get("EURORING_SITE_URL", "https://lair.moe")
parent = os.path.dirname(path) or "."
fd, tmp = tempfile.mkstemp(prefix=".tmp-", dir=parent)
try:
with os.fdopen(fd, "w", encoding="utf-8") as f:
json.dump(payload, f, ensure_ascii=False, separators=(",", ":"))
os.replace(tmp, path)
finally:
try:
if os.path.exists(tmp):
os.unlink(tmp)
except Exception:
pass
_CACHE_MTIME = 0.0
_LOCK_FD: int | None = None
def _load_json_if_newer(path: str, last_mtime: float) -> tuple[dict | None, float]:
try:
stat = os.stat(path)
except FileNotFoundError:
return None, last_mtime
except Exception:
return None, last_mtime
mtime = float(stat.st_mtime)
if mtime <= float(last_mtime):
return None, last_mtime
try:
with open(path, "r", encoding="utf-8") as f:
return json.load(f), mtime
except Exception:
return None, last_mtime
def _should_start_scheduler() -> bool:
global _LOCK_FD
if _LOCK_FD is not None:
return True
lock_path = os.environ.get("LAIR_SCHED_LOCK", "/tmp/lair-scheduler.lock")
fd = os.open(lock_path, os.O_CREAT | os.O_RDWR, 0o644)
try:
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
_LOCK_FD = fd
return True
except BlockingIOError:
os.close(fd)
return False
except Exception:
try:
os.close(fd)
finally:
return False
EURORING_SOURCE_URL = "https://euroring.neocities.org/scripts/onionring-variables.js"
EURORING_SITE_URL = "https://lair.moe"
EURORING_TIMEOUT = float(os.environ.get("EURORING_TIMEOUT", "10")) EURORING_TIMEOUT = float(os.environ.get("EURORING_TIMEOUT", "10"))
@@ -102,8 +39,9 @@ data = {
"last_updated": 0, "last_updated": 0,
} }
_IS_WRITER = _should_start_scheduler() _IS_WRITER = should_start_scheduler() if data["enabled"] else False
_CACHE_PATH = _cache_file("webring") _CACHE_PATH = cache_file("webring")
_CACHE_MTIME = 0.0
def _site_key(url: str) -> tuple[str, int | None, str, str]: def _site_key(url: str) -> tuple[str, int | None, str, str]:
@@ -126,13 +64,6 @@ def _parse_js_string_value(text: str, variable: str) -> str | None:
return match.group(2).strip() return match.group(2).strip()
def _parse_bool_value(text: str, variable: str) -> bool | None:
match = re.search(rf"var\s+{re.escape(variable)}\s*=\s*(true|false)\s*;", text)
if not match:
return None
return match.group(1) == "true"
def _parse_sites(text: str) -> list[str]: def _parse_sites(text: str) -> list[str]:
match = re.search(r"var\s+sites\s*=\s*\[(.*?)\]\s*;", text, re.S) match = re.search(r"var\s+sites\s*=\s*\[(.*?)\]\s*;", text, re.S)
if not match: if not match:
@@ -173,8 +104,7 @@ def _compute_ring_payload(text: str) -> dict:
raise ValueError(f"site not found in ring: {EURORING_SITE_URL}") raise ValueError(f"site not found in ring: {EURORING_SITE_URL}")
ring_name = _parse_js_string_value(text, "ringName") or "Webring" ring_name = _parse_js_string_value(text, "ringName") or "Webring"
use_index = _parse_bool_value(text, "useIndex") index_url = _parse_js_string_value(text, "indexPage")
index_url = _parse_js_string_value(text, "indexPage") if use_index else None
return { return {
"enabled": True, "enabled": True,
@@ -195,7 +125,7 @@ def refresh_cache() -> None:
if _IS_WRITER: if _IS_WRITER:
return return
payload, mtime = _load_json_if_newer(_CACHE_PATH, _CACHE_MTIME) payload, mtime = load_json_if_newer(_CACHE_PATH, _CACHE_MTIME)
if payload is None: if payload is None:
return return
@@ -203,10 +133,12 @@ def refresh_cache() -> None:
_CACHE_MTIME = mtime _CACHE_MTIME = mtime
def _persist_cache() -> None: def _persist_cache() -> None:
if not _IS_WRITER: if not _IS_WRITER:
return return
_atomic_write_json(_CACHE_PATH, data) atomic_write_json(_CACHE_PATH, data)
def fetch_webring() -> None: def fetch_webring() -> None:
+29
View File
@@ -6,6 +6,25 @@ html(lang=g.locale)
link(rel="stylesheet" href="/static/style/main.css") link(rel="stylesheet" href="/static/style/main.css")
link(rel="icon" type="image/webp" href="/static/img/lair.webp") link(rel="icon" type="image/webp" href="/static/img/lair.webp")
//- Yggdrasil links
link(rel="ygg-banner" href="/static/img/88x31/lair.gif")
link(
rel="alternate"
data-ygg-type="clearnet"
href="https://lair.moe"
title="Clearnet address")
link(
rel="alternate"
data-ygg-type="alfis"
href="http://lair.ygg"
title="Alfis domain")
link(
rel="alternate"
data-ygg-type="ygg-ipv6"
href="http://[201:96:5188::a690:7908:da7a]"
title="Direct IPv6")
script(src="/static/script/copy-mono.js") script(src="/static/script/copy-mono.js")
if request.headers.get('DNT') != "1": if request.headers.get('DNT') != "1":
@@ -24,6 +43,16 @@ html(lang=g.locale)
meta(property="og:image" value="https://lair.moe/static/img/lair.webp") meta(property="og:image" value="https://lair.moe/static/img/lair.webp")
meta(property="og:description" value=_("description")) meta(property="og:description" value=_("description"))
//- Yggdrasil meta
meta(name="ygg-category" content="service")
meta(name="ygg-topic" content="federation")
meta(name="ygg-topic" content="tools")
meta(name="ygg-language" content="ru")
meta(name="ygg-language" content="en")
meta(name="ygg-language" content="de")
meta(name="ygg-language" content="fr")
meta(name="ygg-language" content="jp")
body body
include root/templates/header.pug include root/templates/header.pug
+14 -9
View File
@@ -67,7 +67,16 @@ block content
p p
strong Yggdrasil strong Yggdrasil
| : | :
span.mono 200:ee1:bad2:1732:4b91:c3e3:2f08:29b3 ul
li
= "IPv6: "
span.mono 201:96:5188::a690:7908:da7a
li
= "DNS: "
span.mono ygg.lair.moe
li
= "Alfis: "
span.mono lair.ygg
.webring .webring
a.block(href="https://nixwebr.ing/prev/lair" rel="external prev") &larr; a.block(href="https://nixwebr.ing/prev/lair" rel="external prev") &larr;
@@ -79,11 +88,7 @@ block content
a.block(href="https://ctp-webr.ing/" rel="external") Catppuccin webring a.block(href="https://ctp-webr.ing/" rel="external") Catppuccin webring
a.block(href="https://ctp-webr.ing/lair/next" rel="external next") &rarr; a.block(href="https://ctp-webr.ing/lair/next" rel="external next") &rarr;
if euroring.prev_url and euroring.next_url .webring
.webring a.block(href=euroring.prev_url rel="external prev") &larr;
a.block(href=euroring.prev_url rel="external prev") &larr; a.block(href=euroring.index_url rel="external") Euroring
if euroring.index_url a.block(href=euroring.next_url rel="external next") &rarr;
a.block(href=euroring.index_url rel="external") Euroring
else
span.block= euroring.ring_name
a.block(href=euroring.next_url rel="external next") &rarr;