Backend (new apps.follows): - FollowedTag/FollowedPool/FollowedPost models; per-user follows with unseen tracking, plus FollowCloud for the cached blacklist cloud. - Two periodic commands sharing one fetch path: sync_followed_tags and sync_followed_pools fetch each followed tag/pool's newest posts (one e621 search per unique follow), store unseen feed rows, refresh covers and pool metadata; both fall back to anonymous e621 access. - API: /api/follows/tags|pools (follow, unfollow, mark seen), a merged feed with per-follow filtering, and /api/follows/cloud/ which rebuilds the blacklisted-tag cloud in a daemon thread when its 10 min cache is stale (polling returns building/ready). - e621 client now supports anonymous reads; trimmed posts carry preview URLs for covers and feed tiles. Frontend: - /followed page: follow forms, cover cards with unseen badges and Mark seen, merged feed with filter/unseen toggle, and a blacklist cloud panel that polls while building. Followed nav entry added.
63 lines
2.1 KiB
Python
63 lines
2.1 KiB
Python
from django.core.management.base import BaseCommand, CommandError
|
|
|
|
from apps.follows import sync
|
|
from apps.follows.models import FollowedPool
|
|
from apps.library import e621
|
|
|
|
|
|
class Command(BaseCommand):
|
|
help = (
|
|
"Fetch new posts for every followed pool: fills each feed, refreshes "
|
|
"the card cover (latest image), pool name/count and unseen badges."
|
|
)
|
|
|
|
def add_arguments(self, parser):
|
|
parser.add_argument(
|
|
"--user",
|
|
help="Only sync follows belonging to this username "
|
|
"(default: every user's follows).",
|
|
)
|
|
parser.add_argument(
|
|
"--limit",
|
|
type=int,
|
|
default=sync.FEED_LIMIT,
|
|
help=f"Posts fetched per pool (default {sync.FEED_LIMIT}).",
|
|
)
|
|
|
|
def handle(self, *args, **options):
|
|
username = options.get("user")
|
|
if username and sync.preferred_fetch_user(username) is None:
|
|
raise CommandError(f"No such user: {username}")
|
|
fetch_user = sync.preferred_fetch_user(username)
|
|
|
|
follows = FollowedPool.objects.select_related("user").order_by(
|
|
"pool_id", "user_id"
|
|
)
|
|
if username:
|
|
follows = follows.filter(user__username=username)
|
|
follows = list(follows)
|
|
grouped = {}
|
|
for follow in follows:
|
|
grouped.setdefault(follow.pool_id, []).append(follow)
|
|
|
|
created = 0
|
|
for pool_id, group in grouped.items():
|
|
try:
|
|
posts = sync.search_posts(
|
|
fetch_user, f"pool:{pool_id}", options["limit"]
|
|
)
|
|
except e621.E621Error as exc:
|
|
raise CommandError(f"Aborted at pool #{pool_id}: {exc}")
|
|
for follow in group:
|
|
created += sync.sync_pool_follow(
|
|
follow, fetch_user, posts=posts
|
|
)
|
|
|
|
identity = getattr(fetch_user, "username", None) or "anonymous"
|
|
self.stdout.write(
|
|
self.style.SUCCESS(
|
|
f"Synced {len(grouped)} pool(s) across {len(follows)} follow(s) "
|
|
f"as {identity}: {created} new post(s)."
|
|
)
|
|
)
|