Compare commits

...

13 commits

12 changed files with 911 additions and 909 deletions

View file

@ -50,7 +50,7 @@ msgstr "Ver la fuente del artículo"
#: src/snapshots/templates/article-template.html.j2:138
msgid "You are leaving this page"
msgstr "Estas abandonando esta pagina"
msgstr "Estás abandonando esta pagina"
#: src/snapshots/templates/article-template.html.j2:157
msgid ""

1746
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,7 @@ license = "BSD-2"
package-mode = false
[tool.poetry.dependencies]
python = "^3.14"
python = "^3.13"
alembic = "^1.18.4"
babel = "^2.17"
beautifulsoup4 = "^4.13"
@ -28,12 +28,12 @@ tldextract = "^5"
uvicorn = {extras = ["standard"], version = "^0.30.6"}
[tool.poetry.group.dev.dependencies]
black = "^25.1.0"
black = "^26"
ruff = "^0.12"
pytest = "^8.4"
[tool.poetry.group.prod.dependencies]
gunicorn = "^22.0.0"
gunicorn = "^26"
python-json-logger = "^2.0.7"
prometheus-client = "^0.20.0"

View file

@ -39,7 +39,7 @@ def get_kaleidoscope_mirror(origin: str) -> str | None:
"TE": "trailers",
}
response = requests.post(
"https://kldscpe.info/api/v2/resolve", json=payload, headers=headers
"https://kldscpe.info/api/v2/resolve", json=payload, headers=headers, timeout=10
)
if response.status_code == 200:
try:

View file

@ -14,7 +14,13 @@ router = APIRouter()
@router.get("/api/v1/link")
def get_link(background_tasks: BackgroundTasks, db: DbSession, auth: ApiKey, url: str, type_: str = Query(default="auto", alias="type")):
def get_link(
background_tasks: BackgroundTasks,
db: DbSession,
auth: ApiKey,
url: str,
type_: str = Query(default="auto", alias="type"),
):
if auth and type_ in ["auto", "live", "live-short"]:
s = db.query(Link).filter(Link.url == url, Link.pool == 0).first()
if not s and resolve_mirror(db, url):
@ -50,8 +56,11 @@ def resolve_hash(db: DbSession, hash_: str, host: str = Header(settings.LINK_DOM
headers={"Referrer-Policy": "no-referrer"},
)
if host.lower().strip() != settings.API_DOMAIN:
target = resolve_mirror(db, link.url)
if not target:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
return RedirectResponse(
resolve_mirror(db, link.url),
target,
status_code=status.HTTP_302_FOUND,
headers={"Referrer-Policy": "no-referrer"},
)

View file

@ -1,3 +1,4 @@
import secrets
from typing import Annotated
from fastapi import Depends, Header, HTTPException
@ -10,10 +11,10 @@ def api_key(host: str = Header(), authorization: str | None = Header(None)) -> b
if host.lower().strip() != settings.API_DOMAIN.strip():
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
try:
if authorization.split()[1] == settings.API_KEY:
if secrets.compare_digest(authorization.split()[1], settings.API_KEY):
return True
return False
except AttributeError, TypeError, IndexError:
except (AttributeError, TypeError, IndexError):
return False

View file

@ -248,9 +248,9 @@ class SnapshotCamera:
article_description=self.get_attribute_value(
'meta[name="description"]', "content", optional=True
),
article_image=fetch_url(self.url, article_image_source)
if article_image_source
else None,
article_image=(
fetch_url(self.url, article_image_source) if article_image_source else None
),
article_image_caption=self.get_element_content(
self.config.article_image_caption_selector, optional=True
),
@ -286,7 +286,7 @@ class SnapshotCamera:
package_path="templates",
),
extensions=["jinja2.ext.i18n"],
autoescape=select_autoescape(),
autoescape=True,
trim_blocks=True,
lstrip_blocks=True,
)

View file

@ -21,7 +21,12 @@ router = APIRouter()
)
def context(auth: ApiKey, url: str = "https://www.bbc.com/russian/articles/ckgeey4dqgxo"):
if settings.ENVIRONMENT.is_debug or auth:
return SnapshotCamera(url).get_context()
ctx = SnapshotCamera(url).get_context()
if ctx is None:
raise HTTPException(
status.HTTP_404_NOT_FOUND, detail="No configuration for URL"
)
return ctx
raise HTTPException(status.HTTP_404_NOT_FOUND)
@ -33,7 +38,7 @@ def context(auth: ApiKey, url: str = "https://www.bbc.com/russian/articles/ckgee
def parse(auth: ApiKey, url: str = "https://www.bbc.com/russian/articles/ckgeey4dqgxo"):
if settings.ENVIRONMENT.is_debug or auth:
return SnapshotCamera(url).render()
raise HTTPException(status.HTTP_404_NOT_FOUND)
raise HTTPException(status.HTTP_403_FORBIDDEN)
@router.get(
@ -48,10 +53,15 @@ def snap(
):
s = db.query(Snapshot).filter(Snapshot.url == url, Snapshot.pool == 0).first()
if not s and config_for_url(url):
s = Snapshot(url=url, pool=0, snapshot_state=SnapshotState.PENDING, provider=SnapshotProvider.GOOGLE)
s = Snapshot(
url=url,
pool=0,
snapshot_state=SnapshotState.PENDING,
provider=SnapshotProvider.GOOGLE,
)
db.add(s)
db.commit()
background_tasks.add_task(generate_snapshot, s.id)
if s:
return {"url": s.link}
return status.HTTP_403_FORBIDDEN
raise HTTPException(status.HTTP_404_NOT_FOUND)

View file

@ -17,7 +17,7 @@ class SnapshotContext(BaseModel):
page_direction: str | None = None
page_language: str | None = None
site_favicon: str | None = None
site_logo: str = None
site_logo: str | None = None
site_title: str
site_mirror_url: str | None = None
site_url: str

View file

@ -19,11 +19,13 @@ def generate_snapshot(id_: int) -> None:
return
try:
content = SnapshotCamera(snapshot.url).render()
upload_blob(hashids.encode(snapshot.id) + ".html", content.encode("utf-8"), "text/html")
upload_blob(
hashids.encode(snapshot.id) + ".html", content.encode("utf-8"), "text/html"
)
snapshot.snapshot_state = SnapshotState.UPDATING
snapshot.snapshot_published_at = datetime.now()
db.commit()
except Exception as e:
logging.error(e)
logging.exception("Failed to generate snapshot %s", id_)
snapshot.snapshot_state = SnapshotState.FAILED
db.commit()

View file

@ -24,7 +24,7 @@
<link rel="icon" href="{{ site_favicon }}" />
{% endif %}
<style>
{{ article_css() }}
{{ article_css() | safe }}
</style>
<script type="text/javascript">
@ -44,9 +44,9 @@
let target = e.target.closest("a");
if (target) {
// if the click was on or within an <a>
if (!target.className.includes("snap-skip-link") &&
!target.className.includes("snap-link--mirror") &&
!target.className.includes("snap-link--snapshot")) {
if (!target.classList.contains("snap-skip-link") &&
!target.classList.contains("snap-link--mirror") &&
!target.classList.contains("snap-link--snapshot")) {
e.preventDefault();
document.body.dataset.currentLink = target.href;
}
@ -124,7 +124,7 @@
</figure>
{% endif %}
<div class="snap-content">
{{ article_body }}
{{ article_body | safe }}
{% if article_mirror_url %}
<p>
<a href="{{ article_mirror_url }}" class="snap-footer-link snap-link--mirror">

View file

@ -171,7 +171,7 @@ figcaption {
margin-top: 3px;
}
.snap-footer-link svg:dir(ltr) {
.snap-footer-link:dir(ltr) svg {
float: right;
margin-left: 10px;
}