CI and images / lint (push) Successful in 4s
CI and images / extension-version (push) Successful in 3s
CI and images / frontend-build (push) Successful in 21s
CI and images / extension-test (push) Successful in 20s
CI and images / backend-lint-and-test (push) Successful in 34s
CI and images / integration (push) Successful in 2m24s
CI and images / sign-extension (push) Successful in 3s
CI and images / build-agent (push) Successful in 7s
CI and images / build-web (push) Successful in 1m42s
CI and images / smoke-web (push) Successful in 59s
CI and images / promote (push) Successful in 2s
- A message with no image or video attachment is chat: extract_media returns nothing for it, so nothing downloads and no post record is written. A message that passes keeps every file, numbered as gallery-dl numbers them. - `discord_authors` in a source's config limits the walk to those posters (id, username or display name); the edit dialog has a field for it and no longer drops config keys it has no field for. - source.display_name (migration 0115), refreshed by every Discord walk, shows on Subscriptions in place of the two-id URL; a Discord post card names its channel from the record's `channel`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
75 lines
3.3 KiB
Python
75 lines
3.3 KiB
Python
"""Source — a platform-specific URL owned by an Artist (e.g., a Patreon URL).
|
|
|
|
Multiple sources per artist support creators with cross-platform presence.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import (
|
|
JSON,
|
|
Boolean,
|
|
DateTime,
|
|
ForeignKey,
|
|
Integer,
|
|
String,
|
|
Text,
|
|
UniqueConstraint,
|
|
)
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from .base import Base
|
|
|
|
|
|
class Source(Base):
|
|
__tablename__ = "source"
|
|
|
|
__table_args__ = (
|
|
# alembic 0010. One row per (artist, platform, url): re-adding a source
|
|
# the artist already has is an update, not a second row. The model had
|
|
# never declared it (#3275), so autogenerate would have proposed
|
|
# DROPPING it — the guarantee existed only in the migration chain.
|
|
#
|
|
# Named explicitly because the naming convention would render this
|
|
# `uq_source_artist_id` (uq keys off column_0_name), which is both
|
|
# wrong about the shape and not what the database actually has.
|
|
UniqueConstraint(
|
|
"artist_id", "platform", "url", name="uq_source_artist_platform_url"
|
|
),
|
|
)
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
artist_id: Mapped[int] = mapped_column(
|
|
ForeignKey("artist.id", ondelete="CASCADE"), nullable=False, index=True
|
|
)
|
|
platform: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
url: Mapped[str] = mapped_column(Text, nullable=False)
|
|
enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True, server_default="true")
|
|
|
|
config_overrides: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
# alembic 0115: what the source is called on its platform, where the URL
|
|
# alone doesn't say — a Discord link is two numbers. The ingester that
|
|
# walks it refreshes it every walk, so a rename reads as the new name.
|
|
# NULL until a walk has read it, and on platforms that don't write it.
|
|
display_name: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
|
|
last_checked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
last_error: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
# alembic 0032: last ErrorType category (auth_error, rate_limited,
|
|
# not_found, ...). Lets FailingSourcesCard surface the taxonomy as
|
|
# a colored chip so operators can bulk-triage by error class. Set
|
|
# by _update_source_health alongside last_error; cleared on 'ok'.
|
|
error_type: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True)
|
|
check_interval_override: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
consecutive_failures: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
|
|
|
|
# alembic 0031: sticky deep-scan budget. When > 0, the next N download
|
|
# runs use gallery-dl's full-walk config (skip: True + 1800s timeout);
|
|
# when 0, runs use tick mode (skip: "exit:20" + 870s, exits early once
|
|
# 20 contiguous archived items are seen). Auto-decrements per run, with
|
|
# an auto-reset to 0 on clean exit + zero downloads (queue drained).
|
|
backfill_runs_remaining: Mapped[int] = mapped_column(
|
|
Integer, nullable=False, default=0, server_default="0",
|
|
)
|
|
|
|
artist = relationship("Artist", back_populates="sources")
|