Compare commits
13 commits
9b52e821aa
...
e0a74dc23d
| Author | SHA1 | Date | |
|---|---|---|---|
| e0a74dc23d | |||
| a939032ebc | |||
| 1a5e3a2aba | |||
| bbf5f3d98b | |||
| 838425f62e | |||
| 27ce5fc4a8 | |||
| e1362cea4b | |||
| 3914c29390 | |||
| 36d6a61107 | |||
| f3698dc8a6 | |||
| 656d6e5dd2 | |||
| 6f8aed8fd9 | |||
| d6d675a2be |
12 changed files with 911 additions and 909 deletions
|
|
@ -50,7 +50,7 @@ msgstr "Ver la fuente del artículo"
|
||||||
|
|
||||||
#: src/snapshots/templates/article-template.html.j2:138
|
#: src/snapshots/templates/article-template.html.j2:138
|
||||||
msgid "You are leaving this page"
|
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
|
#: src/snapshots/templates/article-template.html.j2:157
|
||||||
msgid ""
|
msgid ""
|
||||||
|
|
|
||||||
1746
poetry.lock
generated
1746
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -8,7 +8,7 @@ license = "BSD-2"
|
||||||
package-mode = false
|
package-mode = false
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.14"
|
python = "^3.13"
|
||||||
alembic = "^1.18.4"
|
alembic = "^1.18.4"
|
||||||
babel = "^2.17"
|
babel = "^2.17"
|
||||||
beautifulsoup4 = "^4.13"
|
beautifulsoup4 = "^4.13"
|
||||||
|
|
@ -28,12 +28,12 @@ tldextract = "^5"
|
||||||
uvicorn = {extras = ["standard"], version = "^0.30.6"}
|
uvicorn = {extras = ["standard"], version = "^0.30.6"}
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
black = "^25.1.0"
|
black = "^26"
|
||||||
ruff = "^0.12"
|
ruff = "^0.12"
|
||||||
pytest = "^8.4"
|
pytest = "^8.4"
|
||||||
|
|
||||||
[tool.poetry.group.prod.dependencies]
|
[tool.poetry.group.prod.dependencies]
|
||||||
gunicorn = "^22.0.0"
|
gunicorn = "^26"
|
||||||
python-json-logger = "^2.0.7"
|
python-json-logger = "^2.0.7"
|
||||||
prometheus-client = "^0.20.0"
|
prometheus-client = "^0.20.0"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ def get_kaleidoscope_mirror(origin: str) -> str | None:
|
||||||
"TE": "trailers",
|
"TE": "trailers",
|
||||||
}
|
}
|
||||||
response = requests.post(
|
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:
|
if response.status_code == 200:
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,13 @@ router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/v1/link")
|
@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"]:
|
if auth and type_ in ["auto", "live", "live-short"]:
|
||||||
s = db.query(Link).filter(Link.url == url, Link.pool == 0).first()
|
s = db.query(Link).filter(Link.url == url, Link.pool == 0).first()
|
||||||
if not s and resolve_mirror(db, url):
|
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"},
|
headers={"Referrer-Policy": "no-referrer"},
|
||||||
)
|
)
|
||||||
if host.lower().strip() != settings.API_DOMAIN:
|
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(
|
return RedirectResponse(
|
||||||
resolve_mirror(db, link.url),
|
target,
|
||||||
status_code=status.HTTP_302_FOUND,
|
status_code=status.HTTP_302_FOUND,
|
||||||
headers={"Referrer-Policy": "no-referrer"},
|
headers={"Referrer-Policy": "no-referrer"},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import secrets
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
from fastapi import Depends, Header, HTTPException
|
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():
|
if host.lower().strip() != settings.API_DOMAIN.strip():
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||||
try:
|
try:
|
||||||
if authorization.split()[1] == settings.API_KEY:
|
if secrets.compare_digest(authorization.split()[1], settings.API_KEY):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
except AttributeError, TypeError, IndexError:
|
except (AttributeError, TypeError, IndexError):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -248,9 +248,9 @@ class SnapshotCamera:
|
||||||
article_description=self.get_attribute_value(
|
article_description=self.get_attribute_value(
|
||||||
'meta[name="description"]', "content", optional=True
|
'meta[name="description"]', "content", optional=True
|
||||||
),
|
),
|
||||||
article_image=fetch_url(self.url, article_image_source)
|
article_image=(
|
||||||
if article_image_source
|
fetch_url(self.url, article_image_source) if article_image_source else None
|
||||||
else None,
|
),
|
||||||
article_image_caption=self.get_element_content(
|
article_image_caption=self.get_element_content(
|
||||||
self.config.article_image_caption_selector, optional=True
|
self.config.article_image_caption_selector, optional=True
|
||||||
),
|
),
|
||||||
|
|
@ -286,7 +286,7 @@ class SnapshotCamera:
|
||||||
package_path="templates",
|
package_path="templates",
|
||||||
),
|
),
|
||||||
extensions=["jinja2.ext.i18n"],
|
extensions=["jinja2.ext.i18n"],
|
||||||
autoescape=select_autoescape(),
|
autoescape=True,
|
||||||
trim_blocks=True,
|
trim_blocks=True,
|
||||||
lstrip_blocks=True,
|
lstrip_blocks=True,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,12 @@ router = APIRouter()
|
||||||
)
|
)
|
||||||
def context(auth: ApiKey, url: str = "https://www.bbc.com/russian/articles/ckgeey4dqgxo"):
|
def context(auth: ApiKey, url: str = "https://www.bbc.com/russian/articles/ckgeey4dqgxo"):
|
||||||
if settings.ENVIRONMENT.is_debug or auth:
|
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)
|
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"):
|
def parse(auth: ApiKey, url: str = "https://www.bbc.com/russian/articles/ckgeey4dqgxo"):
|
||||||
if settings.ENVIRONMENT.is_debug or auth:
|
if settings.ENVIRONMENT.is_debug or auth:
|
||||||
return SnapshotCamera(url).render()
|
return SnapshotCamera(url).render()
|
||||||
raise HTTPException(status.HTTP_404_NOT_FOUND)
|
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
|
|
@ -48,10 +53,15 @@ def snap(
|
||||||
):
|
):
|
||||||
s = db.query(Snapshot).filter(Snapshot.url == url, Snapshot.pool == 0).first()
|
s = db.query(Snapshot).filter(Snapshot.url == url, Snapshot.pool == 0).first()
|
||||||
if not s and config_for_url(url):
|
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.add(s)
|
||||||
db.commit()
|
db.commit()
|
||||||
background_tasks.add_task(generate_snapshot, s.id)
|
background_tasks.add_task(generate_snapshot, s.id)
|
||||||
if s:
|
if s:
|
||||||
return {"url": s.link}
|
return {"url": s.link}
|
||||||
return status.HTTP_403_FORBIDDEN
|
raise HTTPException(status.HTTP_404_NOT_FOUND)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ class SnapshotContext(BaseModel):
|
||||||
page_direction: str | None = None
|
page_direction: str | None = None
|
||||||
page_language: str | None = None
|
page_language: str | None = None
|
||||||
site_favicon: str | None = None
|
site_favicon: str | None = None
|
||||||
site_logo: str = None
|
site_logo: str | None = None
|
||||||
site_title: str
|
site_title: str
|
||||||
site_mirror_url: str | None = None
|
site_mirror_url: str | None = None
|
||||||
site_url: str
|
site_url: str
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,13 @@ def generate_snapshot(id_: int) -> None:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
content = SnapshotCamera(snapshot.url).render()
|
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_state = SnapshotState.UPDATING
|
||||||
snapshot.snapshot_published_at = datetime.now()
|
snapshot.snapshot_published_at = datetime.now()
|
||||||
db.commit()
|
db.commit()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(e)
|
logging.exception("Failed to generate snapshot %s", id_)
|
||||||
snapshot.snapshot_state = SnapshotState.FAILED
|
snapshot.snapshot_state = SnapshotState.FAILED
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
<link rel="icon" href="{{ site_favicon }}" />
|
<link rel="icon" href="{{ site_favicon }}" />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<style>
|
<style>
|
||||||
{{ article_css() }}
|
{{ article_css() | safe }}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
@ -44,9 +44,9 @@
|
||||||
let target = e.target.closest("a");
|
let target = e.target.closest("a");
|
||||||
if (target) {
|
if (target) {
|
||||||
// if the click was on or within an <a>
|
// if the click was on or within an <a>
|
||||||
if (!target.className.includes("snap-skip-link") &&
|
if (!target.classList.contains("snap-skip-link") &&
|
||||||
!target.className.includes("snap-link--mirror") &&
|
!target.classList.contains("snap-link--mirror") &&
|
||||||
!target.className.includes("snap-link--snapshot")) {
|
!target.classList.contains("snap-link--snapshot")) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
document.body.dataset.currentLink = target.href;
|
document.body.dataset.currentLink = target.href;
|
||||||
}
|
}
|
||||||
|
|
@ -124,7 +124,7 @@
|
||||||
</figure>
|
</figure>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="snap-content">
|
<div class="snap-content">
|
||||||
{{ article_body }}
|
{{ article_body | safe }}
|
||||||
{% if article_mirror_url %}
|
{% if article_mirror_url %}
|
||||||
<p>
|
<p>
|
||||||
<a href="{{ article_mirror_url }}" class="snap-footer-link snap-link--mirror">
|
<a href="{{ article_mirror_url }}" class="snap-footer-link snap-link--mirror">
|
||||||
|
|
|
||||||
|
|
@ -171,7 +171,7 @@ figcaption {
|
||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.snap-footer-link svg:dir(ltr) {
|
.snap-footer-link:dir(ltr) svg {
|
||||||
float: right;
|
float: right;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue