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
import atexit
import json
import os
import re
import tempfile
from time import time
from urllib.parse import urlsplit
import fcntl
import requests
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.interval import IntervalTrigger
def _cache_dir() -> str:
path = os.environ.get("LAIR_CACHE_DIR", "/tmp/lair-cache")
os.makedirs(path, exist_ok=True)
return path
from blueprints.risdeveau.modules.api.scheduler_guard import should_start_scheduler
from blueprints.risdeveau.modules.api.shared_cache import (
atomic_write_json,
cache_file,
load_json_if_newer,
)
def _cache_file(name: str) -> str:
return os.path.join(_cache_dir(), f"{name}.json")
def _atomic_write_json(path: str, payload: dict) -> None:
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_SOURCE_URL = os.environ.get(
"EURORING_SOURCE_URL",
"https://euroring.neocities.org/scripts/onionring-variables.js",
)
EURORING_SITE_URL = os.environ.get("EURORING_SITE_URL", "https://lair.moe")
EURORING_TIMEOUT = float(os.environ.get("EURORING_TIMEOUT", "10"))
@@ -102,8 +39,9 @@ data = {
"last_updated": 0,
}
_IS_WRITER = _should_start_scheduler()
_CACHE_PATH = _cache_file("webring")
_IS_WRITER = should_start_scheduler() if data["enabled"] else False
_CACHE_PATH = cache_file("webring")
_CACHE_MTIME = 0.0
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()
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]:
match = re.search(r"var\s+sites\s*=\s*\[(.*?)\]\s*;", text, re.S)
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}")
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") if use_index else None
index_url = _parse_js_string_value(text, "indexPage")
return {
"enabled": True,
@@ -195,7 +125,7 @@ def refresh_cache() -> None:
if _IS_WRITER:
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:
return
@@ -203,10 +133,12 @@ def refresh_cache() -> None:
_CACHE_MTIME = mtime
def _persist_cache() -> None:
if not _IS_WRITER:
return
_atomic_write_json(_CACHE_PATH, data)
atomic_write_json(_CACHE_PATH, data)
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="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")
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: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
include root/templates/header.pug
+14 -9
View File
@@ -67,7 +67,16 @@ block content
p
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
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/lair/next" rel="external next") &rarr;
if euroring.prev_url and euroring.next_url
.webring
a.block(href=euroring.prev_url rel="external prev") &larr;
if euroring.index_url
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;
.webring
a.block(href=euroring.prev_url rel="external prev") &larr;
a.block(href=euroring.index_url rel="external") Euroring
a.block(href=euroring.next_url rel="external next") &rarr;