diff options
| -rw-r--r-- | middleware/authentication.py | 10 | ||||
| -rw-r--r-- | static/css/main.css | 4 | ||||
| -rw-r--r-- | templates/watch/watch.html | 41 | ||||
| -rw-r--r-- | user_profile/migrations/0004_remove_userhistory_timestamp_and_more.py | 21 | ||||
| -rw-r--r-- | user_profile/models.py | 2 | ||||
| -rw-r--r-- | watch/urls.py | 1 | ||||
| -rw-r--r-- | watch/utils.py | 12 | ||||
| -rw-r--r-- | watch/views.py | 39 |
8 files changed, 120 insertions, 10 deletions
diff --git a/middleware/authentication.py b/middleware/authentication.py index 5845036..7bd8a93 100644 --- a/middleware/authentication.py +++ b/middleware/authentication.py @@ -43,7 +43,7 @@ class AuthMiddleware: ) if timezone.now() > verified_at + timedelta(hours=24): # Verification expired, need to re-check - raise ValueError("Verification expired") + pass except (json.JSONDecodeError, ValueError): # Cookie is invalid or expired, need to re-check pass @@ -57,6 +57,14 @@ class AuthMiddleware: token_type=request.user.discord_token_type, ) + # update user object + request.user.usrname = user["username"] + request.user.discord_global_name = user["global_name"] + request.user.discord_guild_name = user["guild_name"] + request.user.save() + + print(user, "user") + if not user["is_authorized"]: logout(request) request.session["next"] = request.get_full_path() diff --git a/static/css/main.css b/static/css/main.css index c50c0fc..d235d15 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -1366,6 +1366,10 @@ video { --tw-bg-opacity: 0.4; } +.bg-opacity-30 { + --tw-bg-opacity: 0.3; +} + .bg-cover { background-size: cover; } diff --git a/templates/watch/watch.html b/templates/watch/watch.html index 309566f..2574a96 100644 --- a/templates/watch/watch.html +++ b/templates/watch/watch.html @@ -45,7 +45,7 @@ </a> {% else %} - <a href="{% url "watch:watch_episode" anime_id episode.number %}" class="flex flex-row justify-between w-full gap-4 bg-white bg-opacity-10 p-2 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30"> + <a href="{% url "watch:watch_episode" anime_id episode.number %}" class="flex flex-row justify-between w-full gap-4 {% if episode.number in watched_episodes %}bg-{{ user.preferences.accent_colour }}-600 bg-opacity-20{% else %}bg-white bg-opacity-10{% endif %} p-2 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30"> <span class="truncate max-w-full overflow-hidden text-ellipsis whitespace-nowrap">{{ episode.number }}. {{ episode.title }}</span> <span class="flex flex-row item-center gap-2"> {% if anime_selected.episodes.sub >= episode.number %} @@ -88,10 +88,10 @@ const introOverlay = document.getElementById('intro-overlay'); const outroOverlay = document.getElementById('outro-overlay'); const autoSkipIntro = {% if user.preferences.auto_skip_intro %}true{% else %}false{% endif %}; - console.log(introStart, introEnd, outroStart, outroEnd); - + const currentWatchTime = {{ current_watched_time }}; + import { VidstackPlayer, VidstackPlayerLayout, TextTrack } from 'https://cdn.vidstack.io/player'; - + const layout = new VidstackPlayerLayout({ {% for track in episode_data.tracks %} {% if track.kind == 'thumbnails' %} @@ -161,12 +161,15 @@ if (chapter.startTime === chapter.endTime || chapter.endTime === 0) { continue; } - console.log(`Adding chapter at ${chapter.startTime} for ${chapter.title}`); chaptersTrack.addCue(new VTTCue(chapter.startTime, chapter.endTime, chapter.title)); } player.textTracks.add(chaptersTrack); chaptersLoaded = true; + + if (currentWatchTime > 0) { + player.currentTime = currentWatchTime; + } }); player.addEventListener('time-update', (event) => { @@ -233,5 +236,33 @@ }); {% endif %} + let lastTime = 0; + setInterval(() => { + const { + paused, + playing, + waiting, + currentTime, + } = player.state; + if (paused || waiting) { + lastTime = currentTime; + } + if (paused || waiting || currentTime === lastTime) { + return; + } + fetch('{% url "watch:update_watch_history" %}', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': '{{ csrf_token }}' + }, + body: JSON.stringify({ + 'anime_id': {{ anime_id }}, + 'episode': {{ current_episode }}, + 'time_watched': parseInt(player.currentTime) + }) + }); + }, 30000); + </script> {% endblock scripts %} diff --git a/user_profile/migrations/0004_remove_userhistory_timestamp_and_more.py b/user_profile/migrations/0004_remove_userhistory_timestamp_and_more.py new file mode 100644 index 0000000..e5455be --- /dev/null +++ b/user_profile/migrations/0004_remove_userhistory_timestamp_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 5.1 on 2024-08-30 23:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("user_profile", "0003_userpreferences_accent_colour"), + ] + + operations = [ + migrations.RemoveField( + model_name="userhistory", + name="timestamp", + ), + migrations.AddField( + model_name="userhistory", + name="time_watched", + field=models.IntegerField(default=0), + ), + ] diff --git a/user_profile/models.py b/user_profile/models.py index 205a46c..554ac49 100644 --- a/user_profile/models.py +++ b/user_profile/models.py @@ -28,7 +28,7 @@ class UserHistory(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) anime_id = models.IntegerField() episode = models.IntegerField() - timestamp = models.DateTimeField(auto_now=True) + time_watched = models.IntegerField(default=0) def __str__(self): return f"{self.user.username} watched episode {self.episode} of anime {self.anime_id}" diff --git a/watch/urls.py b/watch/urls.py index ab7d14e..4e0f66d 100644 --- a/watch/urls.py +++ b/watch/urls.py @@ -6,4 +6,5 @@ app_name = "watch" urlpatterns = [ path('<int:anime_id>/', views.watch, name='watch'), path('<int:anime_id>/<int:episode>/', views.watch, name='watch_episode'), + path('update_watch_history/', views.update_episode_watch_time, name='update_watch_history'), ] diff --git a/watch/utils.py b/watch/utils.py index 83888f6..857601a 100644 --- a/watch/utils.py +++ b/watch/utils.py @@ -1,6 +1,7 @@ import redis import os import dotenv +from user_profile.models import UserHistory dotenv.load_dotenv() @@ -11,6 +12,17 @@ r = redis.Redis( ssl=True, ) +def update_anime_user_history(user, anime_id, episode, time_watched): + # per episode history + history, created = UserHistory.objects.get_or_create(user=user, anime_id=anime_id, episode=episode) + history.time_watched = time_watched + history.save() + +def get_anime_user_history(user, anime_id): + history = UserHistory.objects.filter(user=user, anime_id=anime_id).order_by("-episode") + return history + + def store_in_redis_cache(anime_id, data): r.set(anime_id, data, ex=60*60*24*30) # 30 days print("data cached", anime_id) diff --git a/watch/views.py b/watch/views.py index 570082c..7ebd472 100644 --- a/watch/views.py +++ b/watch/views.py @@ -2,7 +2,9 @@ import os from django.http import JsonResponse import dotenv from django.shortcuts import render, redirect +from watch.utils import update_anime_user_history, get_anime_user_history import requests +import json dotenv.load_dotenv() @@ -50,9 +52,23 @@ def watch(request, anime_id, episode=None): response = requests.get(base_url) episode_data = response.json() - # search for the current episode name in anime_episodes.episodes where episode.number == episode current_episode_name = [e["title"] for e in anime_episodes["episodes"] if e["number"] == episode][0] + # store anime history + if request.user.is_authenticated: + anime_history = get_anime_user_history(request.user, anime_id) + + # if current episode is not in history, add it + if not any(h.episode == episode for h in anime_history): + update_anime_user_history(request.user, anime_id, episode, 0) + + watched_episodes = [h.episode for h in anime_history] + current_watched_time = [h.time_watched for h in anime_history if h.episode == episode][0] + else: + anime_history = None + watched_episodes = [] + current_watched_time = 0 + context = { "anime_data": anime_data, "anime_selected": anime_selected, @@ -62,10 +78,27 @@ def watch(request, anime_id, episode=None): "stream_url": episode_data["sources"][0]["url"], "anime_id": anime_id, "current_episode_name": current_episode_name, + "anime_history": anime_history, + "watched_episodes": watched_episodes, + "current_watched_time": current_watched_time, "mode": mode, } - # print(anime_search_result) - # print(anime_data) + print(context) return render(request, "watch/watch.html", context) + +def update_episode_watch_time(request): + if request.method != "POST": + return JsonResponse({"status": "error", "message": "Invalid request"}) + + data = json.loads(request.body) + anime_id =data.get("anime_id") + episode =data.get("episode") + time_watched =data.get("time_watched") + + if request.user.is_authenticated: + update_anime_user_history(request.user, anime_id, episode, time_watched) + return JsonResponse({"status": "success"}) + else: + return JsonResponse({"status": "error", "message": "User not authenticated"}) |
