206 lines
6.1 KiB
Python
206 lines
6.1 KiB
Python
import logging
|
|
from datetime import timedelta
|
|
|
|
from celery import shared_task
|
|
from charts.utils import (
|
|
build_charts_since,
|
|
build_daily_charts,
|
|
build_monthly_charts,
|
|
build_weekly_charts,
|
|
build_yearly_charts,
|
|
)
|
|
from django.apps import apps
|
|
from django.contrib.auth import get_user_model
|
|
from django.utils import timezone
|
|
|
|
logger = logging.getLogger(__name__)
|
|
User = get_user_model()
|
|
|
|
MEDIA_TYPES = [
|
|
"artist",
|
|
"album",
|
|
"track",
|
|
"tv_series",
|
|
"video",
|
|
"podcast",
|
|
"board_game",
|
|
"trail",
|
|
"food",
|
|
"book",
|
|
]
|
|
|
|
|
|
@shared_task
|
|
def check_twitch_channels_for_vods():
|
|
"""Check recent Twitch channel scrobbles for matching VODs."""
|
|
from scrobbles.models import Scrobble
|
|
|
|
cutoff = timezone.now() - timedelta(hours=48)
|
|
|
|
channel_scrobbles = Scrobble.objects.filter(
|
|
media_type=Scrobble.MediaType.CHANNEL,
|
|
timestamp__gte=cutoff,
|
|
)
|
|
|
|
logger.info(f"[twitch_vods] Checking {channel_scrobbles.count()} channel scrobbles")
|
|
|
|
matched_count = 0
|
|
|
|
for scrobble in channel_scrobbles:
|
|
if not scrobble.channel or not scrobble.channel.twitch_id:
|
|
continue
|
|
|
|
try:
|
|
from videos.sources.twitch import get_channel_vods
|
|
|
|
vods = get_channel_vods(scrobble.channel.twitch_id)
|
|
scrobble_time = scrobble.timestamp
|
|
|
|
for vod in vods:
|
|
if not vod.get("published_at"):
|
|
continue
|
|
|
|
from videos.sources import twitch as twitch_source
|
|
|
|
vod_time = twitch_source.parse_twitch_datetime(vod["published_at"])
|
|
|
|
if not vod_time:
|
|
continue
|
|
|
|
time_diff = (vod_time - scrobble_time).total_seconds()
|
|
|
|
if 0 < time_diff <= 86400:
|
|
from videos.models import Video
|
|
|
|
video = Video.get_from_twitch_id(vod["id"], overwrite=True)
|
|
if video:
|
|
video.scrobble_for_user(
|
|
scrobble.user_id,
|
|
status="stopped",
|
|
source="Twitch VOD",
|
|
)
|
|
matched_count += 1
|
|
logger.info(
|
|
f"[twitch_vods] Matched VOD {vod['id']} for channel scrobble {scrobble.id}"
|
|
)
|
|
break
|
|
except Exception as e:
|
|
logger.warning(
|
|
f"[twitch_vods] Error processing scrobble {scrobble.id}: {e}"
|
|
)
|
|
|
|
logger.info(f"[twitch_vods] Matched {matched_count} VODs")
|
|
|
|
|
|
@shared_task
|
|
def process_retroarch_import(import_id):
|
|
RetroarchImport = apps.get_model("scrobbles", "RetroarchImport")
|
|
retroarch_import = RetroarchImport.objects.filter(id=import_id).first()
|
|
if not retroarch_import:
|
|
logger.warn(f"RetroarchImport not found with id {import_id}")
|
|
|
|
retroarch_import.process()
|
|
|
|
|
|
@shared_task
|
|
def process_lastfm_import(import_id):
|
|
LastFmImport = apps.get_model("scrobbles", "LastFMImport")
|
|
lastfm_import = LastFmImport.objects.filter(id=import_id).first()
|
|
if not lastfm_import:
|
|
logger.warn(f"LastFmImport not found with id {import_id}")
|
|
|
|
lastfm_import.process()
|
|
|
|
|
|
@shared_task
|
|
def process_tsv_import(import_id):
|
|
model_path = "scrobbles.AudioscrobblerTSVImport"
|
|
AudioScrobblerTSVImport = apps.get_model(model_path)
|
|
tsv_import = AudioScrobblerTSVImport.objects.filter(id=import_id).first()
|
|
if not tsv_import:
|
|
logger.warn(f"AudioScrobblerTSVImport not found with id {import_id}")
|
|
|
|
tsv_import.process()
|
|
|
|
|
|
@shared_task
|
|
def process_koreader_import(import_id):
|
|
KoReaderImport = apps.get_model("scrobbles", "KoReaderImport")
|
|
koreader_import = KoReaderImport.objects.filter(id=import_id).first()
|
|
if not koreader_import:
|
|
logger.warn(f"KOReaderImport not found with id {import_id}")
|
|
|
|
koreader_import.process()
|
|
|
|
|
|
@shared_task
|
|
def create_yesterdays_charts():
|
|
"""Build/update charts for all users starting from last known record."""
|
|
for user in User.objects.all():
|
|
build_charts_since(user)
|
|
|
|
|
|
@shared_task
|
|
def build_charts_for_user(user_id):
|
|
"""Build charts for a specific user starting from last known record."""
|
|
user = User.objects.filter(id=user_id).first()
|
|
if not user:
|
|
logger.error(f"User with id {user_id} not found")
|
|
return
|
|
logger.info(f"Building charts for {user}")
|
|
build_charts_since(user)
|
|
|
|
|
|
@shared_task
|
|
def rebuild_weekly_charts():
|
|
"""Rebuild weekly charts for all users for the just-completed week."""
|
|
now = timezone.now()
|
|
year, week, _ = now.isocalendar()
|
|
for user in User.objects.all():
|
|
build_weekly_charts(user, year, week, MEDIA_TYPES)
|
|
logger.info(f"Rebuilt weekly charts for week {week}, {year}")
|
|
|
|
|
|
@shared_task
|
|
def rebuild_monthly_charts():
|
|
"""Rebuild monthly charts for all users for the just-completed month."""
|
|
now = timezone.now()
|
|
year, month = now.year, now.month
|
|
if month == 1:
|
|
month = 12
|
|
year -= 1
|
|
else:
|
|
month -= 1
|
|
for user in User.objects.all():
|
|
build_monthly_charts(user, year, month, MEDIA_TYPES)
|
|
logger.info(f"Rebuilt monthly charts for {month}/{year}")
|
|
|
|
|
|
@shared_task
|
|
def rebuild_yearly_charts():
|
|
"""Rebuild yearly charts for all users for the just-completed year."""
|
|
now = timezone.now()
|
|
year = now.year - 1
|
|
for user in User.objects.all():
|
|
build_yearly_charts(user, year, MEDIA_TYPES)
|
|
logger.info(f"Rebuilt yearly charts for {year}")
|
|
|
|
|
|
@shared_task
|
|
def update_charts_for_timestamp(user_id, year, month, day, week):
|
|
"""Update charts for a specific time period."""
|
|
user = User.objects.filter(id=user_id).first()
|
|
if not user:
|
|
logger.error(f"User with id {user_id} not found")
|
|
return
|
|
|
|
try:
|
|
build_daily_charts(user, year, month, day, MEDIA_TYPES)
|
|
build_weekly_charts(user, year, week, MEDIA_TYPES)
|
|
build_monthly_charts(user, year, month, MEDIA_TYPES)
|
|
build_yearly_charts(user, year, MEDIA_TYPES)
|
|
date_str = f"{year}-{month:02d}-{day:02d}"
|
|
logger.info(f"[charts] Updated charts for {user} on {date_str}")
|
|
except Exception as e:
|
|
logger.error(f"[charts] Failed to update charts: {e}")
|