aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobby <[email protected]>2024-09-04 03:47:53 -0400
committerBobby <[email protected]>2024-09-04 03:47:53 -0400
commitf7b777fbca7569a98cb51aa983de9cf82c070ac9 (patch)
tree94ac7a3b862d7a594e34e12a798042f01f14e01a
parentb73b047452ba47141a96cf000333fc9a7211aa91 (diff)
downloadyugen-f7b777fbca7569a98cb51aa983de9cf82c070ac9.tar.xz
yugen-f7b777fbca7569a98cb51aa983de9cf82c070ac9.zip
seasons and mal and zoro id stream
-rw-r--r--static/css/input.css2
-rw-r--r--static/css/main.css86
-rw-r--r--templates/home/index.html8
-rw-r--r--templates/partials/colors.html24
-rw-r--r--templates/watch/watch.html61
-rw-r--r--watch/urls.py8
-rw-r--r--watch/views.py239
7 files changed, 370 insertions, 58 deletions
diff --git a/static/css/input.css b/static/css/input.css
index 3c6acc2..d40e436 100644
--- a/static/css/input.css
+++ b/static/css/input.css
@@ -46,7 +46,7 @@ main {
transition: transform 0.1s ease-in-out;
}
-.historySwiper .swiper-slide {
+.historySwiper .swiper-slide, .seasonSwiper .swiper-slide {
height: 12rem !important; /* equivalent to h-48 */
width: auto !important;
aspect-ratio: 16 / 9 !important;
diff --git a/static/css/main.css b/static/css/main.css
index d85407a..33882fe 100644
--- a/static/css/main.css
+++ b/static/css/main.css
@@ -858,10 +858,6 @@ video {
height: 100%;
}
-.h-64 {
- height: 16rem;
-}
-
.max-h-24 {
max-height: 6rem;
}
@@ -939,18 +935,10 @@ video {
width: max-content;
}
-.w-52 {
- width: 13rem;
-}
-
.min-w-32 {
min-width: 8rem;
}
-.min-w-56 {
- min-width: 14rem;
-}
-
.max-w-4xl {
max-width: 56rem;
}
@@ -980,10 +968,6 @@ video {
max-width: max-content;
}
-.max-w-56 {
- max-width: 14rem;
-}
-
.flex-1 {
flex: 1 1 0%;
}
@@ -1126,10 +1110,6 @@ video {
overflow: hidden;
}
-.overflow-scroll {
- overflow: scroll;
-}
-
.overflow-x-auto {
overflow-x: auto;
}
@@ -1184,10 +1164,44 @@ video {
border-width: 1px;
}
+.border-4 {
+ border-width: 4px;
+}
+
.border-b {
border-bottom-width: 1px;
}
+.border-amber-600 {
+ --tw-border-opacity: 1;
+ border-color: rgb(217 119 6 / var(--tw-border-opacity));
+}
+
+.border-blue-600 {
+ --tw-border-opacity: 1;
+ border-color: rgb(37 99 235 / var(--tw-border-opacity));
+}
+
+.border-cyan-600 {
+ --tw-border-opacity: 1;
+ border-color: rgb(8 145 178 / var(--tw-border-opacity));
+}
+
+.border-green-600 {
+ --tw-border-opacity: 1;
+ border-color: rgb(22 163 74 / var(--tw-border-opacity));
+}
+
+.border-indigo-600 {
+ --tw-border-opacity: 1;
+ border-color: rgb(79 70 229 / var(--tw-border-opacity));
+}
+
+.border-lime-600 {
+ --tw-border-opacity: 1;
+ border-color: rgb(101 163 13 / var(--tw-border-opacity));
+}
+
.border-neutral-700 {
--tw-border-opacity: 1;
border-color: rgb(64 64 64 / var(--tw-border-opacity));
@@ -1198,11 +1212,41 @@ video {
border-color: rgb(38 38 38 / var(--tw-border-opacity));
}
+.border-orange-600 {
+ --tw-border-opacity: 1;
+ border-color: rgb(234 88 12 / var(--tw-border-opacity));
+}
+
+.border-pink-600 {
+ --tw-border-opacity: 1;
+ border-color: rgb(219 39 119 / var(--tw-border-opacity));
+}
+
+.border-purple-600 {
+ --tw-border-opacity: 1;
+ border-color: rgb(147 51 234 / var(--tw-border-opacity));
+}
+
+.border-red-600 {
+ --tw-border-opacity: 1;
+ border-color: rgb(220 38 38 / var(--tw-border-opacity));
+}
+
+.border-teal-600 {
+ --tw-border-opacity: 1;
+ border-color: rgb(13 148 136 / var(--tw-border-opacity));
+}
+
.border-white {
--tw-border-opacity: 1;
border-color: rgb(255 255 255 / var(--tw-border-opacity));
}
+.border-yellow-600 {
+ --tw-border-opacity: 1;
+ border-color: rgb(202 138 4 / var(--tw-border-opacity));
+}
+
.border-opacity-10 {
--tw-border-opacity: 0.1;
}
@@ -2046,7 +2090,7 @@ main {
transition: transform 0.1s ease-in-out;
}
-.historySwiper .swiper-slide {
+.historySwiper .swiper-slide, .seasonSwiper .swiper-slide {
height: 12rem !important;
/* equivalent to h-48 */
width: auto !important;
diff --git a/templates/home/index.html b/templates/home/index.html
index e9d9093..275a36b 100644
--- a/templates/home/index.html
+++ b/templates/home/index.html
@@ -1,8 +1,10 @@
-{% extends "partials/base.html" %} {% block css %}
+{% extends "partials/base.html" %}
{% load custom_filters %}
+{% block css %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/>
-{% endblock css %} {% block content %}
+{% endblock css %}
+{% block content %}
<!-- Swiper: Trending Anime -->
<section class="swiper bigSwiper rounded mt-2">
<div class="swiper-wrapper">
@@ -108,7 +110,7 @@
<div class="swiper-wrapper">
{% for history in user_history_data %}
<a href="{% url "watch:watch_episode" history.anime.id history.episode.number %}" class="group rounded-lg aspect-video h-48 relative flex-shrink-0 overflow-hidden swiper-slide">
- <div class="absolute inset-0 bg-center bg-cover transition-transform group-hover:scale-110" style="background-image: url('{% if history.episode.image %}{{ history.episode.image }}'){% else %}{{ history.anime.cover }}{% endif %}"></div>
+ <div class="absolute inset-0 bg-center bg-cover transition-transform group-hover:scale-110" style="background-image: url('{% if history.episode.image %}{{ history.episode.image }}{% else %}{{ history.anime.cover }}{% endif %}')"></div>
<div class="absolute inset-0 bg-{{ user.preferences.accent_colour }}-600 opacity-0 group-hover:opacity-30 transition-opacity"></div>
<div class="absolute inset-0" style="background: linear-gradient(45deg, rgb(8, 8, 8) 15%, transparent 60%), linear-gradient(0deg, rgb(8, 8, 8) 0%, transparent 60%);"></div>
<div class="flex flex-col justify-end h-full p-2 relative z-10">
diff --git a/templates/partials/colors.html b/templates/partials/colors.html
index 20330b2..c4c18d5 100644
--- a/templates/partials/colors.html
+++ b/templates/partials/colors.html
@@ -15,18 +15,18 @@
<div class="bg-amber-400 hover:bg-amber-400 text-amber-400"></div>
<!-- 600 Colors + Checkboxes-->
- <div class="bg-purple-600 hover:bg-purple-600 text-purple-600 peer-checked:bg-purple-600"></div>
- <div class="bg-blue-600 hover:bg-blue-600 text-blue-600 peer-checked:bg-blue-600"></div>
- <div class="bg-green-600 hover:bg-green-600 text-green-600 peer-checked:bg-green-600"></div>
- <div class="bg-yellow-600 hover:bg-yellow-600 text-yellow-600 peer-checked:bg-yellow-600"></div>
- <div class="bg-red-600 hover:bg-red-600 text-red-600 peer-checked:bg-red-600"></div>
- <div class="bg-pink-600 hover:bg-pink-600 text-pink-600 peer-checked:bg-pink-600"></div>
- <div class="bg-indigo-600 hover:bg-indigo-600 text-indigo-600 peer-checked:bg-indigo-600"></div>
- <div class="bg-cyan-600 hover:bg-cyan-600 text-cyan-600 peer-checked:bg-cyan-600"></div>
- <div class="bg-orange-600 hover:bg-orange-600 text-orange-600 peer-checked:bg-orange-600"></div>
- <div class="bg-teal-600 hover:bg-teal-600 text-teal-600 peer-checked:bg-teal-600"></div>
- <div class="bg-lime-600 hover:bg-lime-600 text-lime-600 peer-checked:bg-lime-600"></div>
- <div class="bg-amber-600 hover:bg-amber-600 text-amber-600 peer-checked:bg-amber-600"></div>
+ <div class="bg-purple-600 hover:bg-purple-600 text-purple-600 peer-checked:bg-purple-600 border-purple-600"></div>
+ <div class="bg-blue-600 hover:bg-blue-600 text-blue-600 peer-checked:bg-blue-600 border-blue-600"></div>
+ <div class="bg-green-600 hover:bg-green-600 text-green-600 peer-checked:bg-green-600 border-green-600"></div>
+ <div class="bg-yellow-600 hover:bg-yellow-600 text-yellow-600 peer-checked:bg-yellow-600 border-yellow-600"></div>
+ <div class="bg-red-600 hover:bg-red-600 text-red-600 peer-checked:bg-red-600 border-red-600"></div>
+ <div class="bg-pink-600 hover:bg-pink-600 text-pink-600 peer-checked:bg-pink-600 border-pink-600"></div>
+ <div class="bg-indigo-600 hover:bg-indigo-600 text-indigo-600 peer-checked:bg-indigo-600 border-indigo-600"></div>
+ <div class="bg-cyan-600 hover:bg-cyan-600 text-cyan-600 peer-checked:bg-cyan-600 border-cyan-600"></div>
+ <div class="bg-orange-600 hover:bg-orange-600 text-orange-600 peer-checked:bg-orange-600 border-orange-600"></div>
+ <div class="bg-teal-600 hover:bg-teal-600 text-teal-600 peer-checked:bg-teal-600 border-teal-600"></div>
+ <div class="bg-lime-600 hover:bg-lime-600 text-lime-600 peer-checked:bg-lime-600 border-lime-600"></div>
+ <div class="bg-amber-600 hover:bg-amber-600 text-amber-600 peer-checked:bg-amber-600 border-amber-600"></div>
<!-- 700 Colors -->
<div class="bg-purple-700 hover:bg-purple-700 text-purple-700"></div>
diff --git a/templates/watch/watch.html b/templates/watch/watch.html
index 04e4f13..050b82e 100644
--- a/templates/watch/watch.html
+++ b/templates/watch/watch.html
@@ -5,6 +5,7 @@
<link rel="stylesheet" href="https://cdn.vidstack.io/player/audio.css" />
<link rel="stylesheet" href="https://cdn.vidstack.io/player/video.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"/>
+<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css"/>
{% endblock css %}
{% block content %}
<div class="flex flex-col lg:flex-row mt-4 gap-2">
@@ -26,8 +27,8 @@
<h2 class="text-white text-xl font-bold mb-4">Episodes</h2>
<div class="flex flex-col gap-2 h-full max-h-96 lg:h-[39vw] lg:max-h-[761px] overflow-y-auto">
{% for episode in all_episodes %}
- {% if episode.number == current_episode_number %}
- <a id="selected-episode" href="{% url "watch:watch_episode" anime.id episode.number %}{% if request.GET.mode %}?mode={{ request.GET.mode }}{% endif %}" class="flex flex-row gap-4 justify-between items-center w-full bg-{{ user.preferences.accent_colour }}-600 p-2 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30">
+ {% if not viaMal and episode.number == current_episode_number or viaMal and episode.episode.number == current_episode_number %}
+ <a id="selected-episode" href="{% if not viaMal %}{% url "watch:watch_episode" anime.id episode.number %}{% if request.GET.mode %}?mode={{ request.GET.mode }}{% endif %}{% else %}{% url "watch:watch_via_zid_mal_id" anime.id anime.z_anime_id %}?ep={{ episode.episode.identifier }}{% if request.GET.mode %}&mode={{ request.GET.mode }}{% endif %}{% endif %}" class="flex flex-row gap-4 justify-between items-center w-full bg-{{ user.preferences.accent_colour }}-600 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">
@@ -61,7 +62,7 @@
</a>
{% else %}
- <a href="{% url "watch:watch_episode" anime.id episode.number %}{% if request.GET.mode %}?mode={{ request.GET.mode }}{% endif %}" 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">
+ <a href="{% if not viaMal %}{% url "watch:watch_episode" anime.id episode.number %}{% if request.GET.mode %}?mode={{ request.GET.mode }}{% endif %}{% else %}{% url "watch:watch_via_zid_mal_id" anime.id anime.z_anime_id %}?ep={{ episode.episode.identifier }}{% if request.GET.mode %}&mode={{ request.GET.mode }}{% endif %}{% endif %}" 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 episode.filler %}
@@ -102,8 +103,8 @@
<div class="flex flex-row gap-2 items-center justify-between">
<h2 class="text-xl font-bold truncate max-w-full overflow-hidden text-ellipsis whitespace-nowrap">Episode {{ current_episode.number }} — {{ current_episode.title }}</h2>
<div class="flex flex-row gap-1 items-center">
- <a href="{% if current_episode.number %}{% url "watch:watch_episode" anime.id current_episode.number %}?mode=sub{% endif %}" class="{% if mode == "sub" %}bg-{{ user.preferences.accent_colour }}-600{% else %}bg-white bg-opacity-10{% endif %} text-white text-sm font-bold px-4 py-2 rounded">Sub</a>
- <a href="{% if current_episode.number %}{% url "watch:watch_episode" anime.id current_episode.number %}?mode=dub{% endif %}" class="{% if mode == "dub" %}bg-{{ user.preferences.accent_colour }}-600{% else %}bg-white bg-opacity-10{% endif %} text-white text-sm font-bold px-4 py-2 rounded">Dub</a>
+ <a href="{% if not viaMal %}{% url "watch:watch_episode" anime.id current_episode.number %}?mode=sub{% else %}{% url "watch:watch_via_zid_mal_id" anime.id anime.z_anime_id %}?ep={{ current_episode.episode.identifier }}&mode=sub{% endif %}" class="{% if mode == "sub" %}bg-{{ user.preferences.accent_colour }}-600{% else %}bg-white bg-opacity-10{% endif %} text-white text-sm font-bold px-4 py-2 rounded">Sub</a>
+ <a href="{% if not viaMal %}{% url "watch:watch_episode" anime.id current_episode.number %}?mode=dub{% else %}{% url "watch:watch_via_zid_mal_id" anime.id anime.z_anime_id %}?ep={{ current_episode.episode.identifier }}&mode=dub{% endif %}" class="{% if mode == "dub" %}bg-{{ user.preferences.accent_colour }}-600{% else %}bg-white bg-opacity-10{% endif %} text-white text-sm font-bold px-4 py-2 rounded">Dub</a>
</div>
</div>
<p class="my-4">
@@ -440,8 +441,34 @@
{% endif %}
</div>
</div>
+ {% if seasons %}
+ <div class="my-8">
+ <h2 class="text-xl font-bold text-white uppercase flex flex-row items-center gap-1 mb-4">
+ Seasons
+ </h2>
+ <div class="flex flex-row gap-2 mt-4 overflow-x-auto no-scrollbar swiper seasonSwiper">
+ <div class="swiper-wrapper">
+ {% for season in seasons %}
+ <a href="{% url "watch:watch_via_zid" season.id %}" class="group rounded-lg aspect-video h-48 relative flex-shrink-0 overflow-hidden swiper-slide
+ {% if season.isCurrent %}border-4 border-{{ user.preferences.accent_colour }}-600{% endif %}">
+ <div class="absolute inset-0 bg-center bg-cover transition-transform group-hover:scale-110" style="background-image: url('{{ season.poster }}')"></div>
+ <div class="absolute inset-0 bg-{{ user.preferences.accent_colour }}-600 opacity-0 group-hover:opacity-30 transition-opacity"></div>
+ <div class="absolute inset-0" style="background: linear-gradient(45deg, rgb(8, 8, 8) 15%, transparent 60%), linear-gradient(0deg, rgb(8, 8, 8) 0%, transparent 60%);"></div>
+ <div class="flex flex-col justify-end h-full p-2 relative z-10">
+ <h1 class="text-xl font-bold truncate max-w-full overflow-hidden text-ellipsis whitespace-nowrap">
+ {{ season.name }}
+ </h1>
+ <h2 class="font-bold truncate max-w-full overflow-hidden text-ellipsis whitespace-nowrap">{{ season.title }}</h2>
+ </div>
+ </a>
+ {% endfor %}
+ </div>
+ </div>
+ </div>
+ {% endif %}
+
{% if characters %}
- <div class="my-4">
+ <div class="my-8">
<h2 class="text-xl font-bold text-white uppercase flex flex-row items-center gap-1 mb-4">
Characters &amp; Voice Actors
</h2>
@@ -463,6 +490,7 @@
</div>
<div class="flex flex-col items-end">
{% for voice_actor in character.voiceActors|slice:":1" %}
+ {% if voice_actor.image %}
<div class="flex flex-row gap-2 items-center mb-2">
<div class="flex flex-col gap-2 text-right">
<span class="font-bold">
@@ -476,6 +504,7 @@
</div>
<img src="{{ voice_actor.image }}" alt="{{ voice_actor.name }}" class="rounded-full w-16 h-16 object-cover"/>
</div>
+ {% endif %}
{% endfor %}
</div>
</div>
@@ -554,7 +583,7 @@
</div>
{% endif %}
{% if related.type == "TV" or related.type == "MOVIE" or related.type == "OVA" or related.type == "ONA" or related.type == "SPECIAL" or related.type == "TV_SHORT" %}
- <a href="{% url 'watch:watch' related.id %}" class="flex flex-row 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="{% if not viaMal %}{% url 'watch:watch' related.id %}{% else %}{% url "watch:watch_via_zid" related.zid %}{% endif %}" class="flex flex-row w-full gap-4 bg-white bg-opacity-10 p-2 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30">
<img src="{{ related.image }}" alt="{{ related.title.english }}" class="rounded-lg w-12 h-16 object-cover"/>
<div class="flex flex-col gap-2">
<span class="font-bold flex gap-2 flex-row items-center">
@@ -604,12 +633,14 @@
{{ related.rating }}
</span>
{% endif %}
+ {% if related.relationType %}
<span class="text-xs font-bold bg-white bg-opacity-10 p-1 rounded flex items-center gap-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-4">
<path d="m11.645 20.91-.007-.003-.022-.012a15.247 15.247 0 0 1-.383-.218 25.18 25.18 0 0 1-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0 1 12 5.052 5.5 5.5 0 0 1 16.313 3c2.973 0 5.437 2.322 5.437 5.25 0 3.925-2.438 7.111-4.739 9.256a25.175 25.175 0 0 1-4.244 3.17 15.247 15.247 0 0 1-.383.219l-.022.012-.007.004-.003.001a.752.752 0 0 1-.704 0l-.003-.001Z" />
</svg>
{{ related.relationType }}
</span>
+ {% endif %}
{% if related.episodes %}
<span class="text-xs font-bold bg-white bg-opacity-10 p-1 rounded flex items-center gap-1">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
@@ -632,8 +663,8 @@
</div>
<div class="flex flex-col gap-2">
{% for recommendation in recommendations|slice:":10" %}
- {% if recommendation.id %}
- <a href="{% url 'watch:watch' recommendation.id %}" class="flex flex-row w-full gap-4 bg-white bg-opacity-10 p-2 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30">
+ {% if recommendation.id or recommendation.zid %}
+ <a href="{% if not viaMal %}{% url 'watch:watch' recommendation.id %}{% else %}{% url "watch:watch_via_zid" recommendation.zid %}{% endif %}" class="flex flex-row w-full gap-4 bg-white bg-opacity-10 p-2 rounded hover:bg-{{ user.preferences.accent_colour }}-600 hover:bg-opacity-30">
<img src="{{ recommendation.image }}" alt="{{ recommendation.title.english }}" class="rounded-lg w-12 h-16 object-cover"/>
<div class="flex flex-col gap-2 max-w-[calc(100%-4rem)]">
<span class="font-bold flex gap-2 flex-row items-center">
@@ -817,7 +848,14 @@
observer.observe(episodesSelect, {childList: true, characterData: true});
{% endif %}
</script>
-
+<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
+<script>
+ const seasonSwiper = new Swiper(".seasonSwiper", {
+ slidesPerView: 'auto',
+ spaceBetween: 8,
+ freeMode: true,
+ });
+ </script>
<script type="module">
const introStart = {{ streaming_data.intro.start }};
const introEnd = {{ streaming_data.intro.end }};
@@ -1000,6 +1038,7 @@
});
{% endif %}
+ {% if not viaMal %}
let lastTime = 0;
setInterval(() => {
const {
@@ -1029,6 +1068,6 @@
lastTime = currentTime;
}, 30000);
-
+ {% endif %}
</script>
{% endblock scripts %}
diff --git a/watch/urls.py b/watch/urls.py
index 4e0f66d..8df0660 100644
--- a/watch/urls.py
+++ b/watch/urls.py
@@ -4,7 +4,9 @@ from . import views
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'),
+ path('<int:anime_id>', views.watch, name='watch'),
+ path('<int:anime_id>/<int:episode>', views.watch, name='watch_episode'),
+ path('zid:<str:zid>', views.watch_via_zid, name='watch_via_zid'),
+ path('update_watch_history', views.update_episode_watch_time, name='update_watch_history'),
+ path('malId:<int:mal_id>$zid:<str:zid>', views.watch_via_zid_mal_id, name='watch_via_zid_mal_id'), # if anilist id is not available
]
diff --git a/watch/views.py b/watch/views.py
index a8a5232..b988402 100644
--- a/watch/views.py
+++ b/watch/views.py
@@ -2,15 +2,16 @@ import datetime
from datetime import datetime as dt
import os
from django.http import Http404, JsonResponse
+from django.urls import reverse
import dotenv
from django.shortcuts import get_object_or_404, render, redirect
import requests
from authentication.utils import get_single_anime_mal
+from watch.tmdbmapper import parse_title_and_season
from watch.utils import get_all_episode_metadata, get_from_redis_cache, store_in_redis_cache, update_anime_user_history, get_anime_user_history
from watch.models import Anime, AnimeEpisode, AnimeTitle, AnimeTrailer, AnimeGenre, AnimeStudio
from django.db import transaction
-from django.db.models import Q
-from collections import defaultdict
+from authentication.models import User
import json
dotenv.load_dotenv()
@@ -45,11 +46,33 @@ def get_info_by_zid(zid):
return anime_selected
-def get_episodes_by_zid(anime):
- base_url = f"{os.getenv('ZORO_URL')}/anime/episodes/{anime.z_anime_id}"
- response = requests.get(base_url)
- fetched_episodes = response.json()
+def get_seasons_by_zid(zid):
+ if not zid:
+ return []
+ fetched_info = get_info_by_zid(zid)
+ seasons = fetched_info["seasons"]
+
+ for season in seasons:
+ season["poster"] = season["poster"].replace("100x200/100", "400x800/100")
+
+ return seasons
+
+def get_episodes_by_zid(z_anime_id):
+ cache_key = f"anime_{z_anime_id}_episodes"
+ try:
+ fetched_episodes = get_from_redis_cache(cache_key)
+ fetched_episodes = json.loads(fetched_episodes)
+ except:
+ base_url = f"{os.getenv('ZORO_URL')}/anime/episodes/{z_anime_id}"
+ response = requests.get(base_url)
+ fetched_episodes = response.json()
+ store_in_redis_cache(cache_key, json.dumps(fetched_episodes), 3600 * 12)
+
+ return fetched_episodes
+
+def get_episodes_and_metadata(anime):
+ fetched_episodes = get_episodes_by_zid(anime.z_anime_id)
anime_data = {
"id": anime.id,
"title": {
@@ -198,7 +221,7 @@ def update_anime_episodes(anime):
if not anime.z_anime_id:
return anime
- fetched_episodes = get_episodes_by_zid(anime)
+ fetched_episodes = get_episodes_and_metadata(anime)
with transaction.atomic():
# Update anime's total episodes
@@ -299,6 +322,8 @@ def watch(request, anime_id, episode=None):
if anime and episode_data:
update_anime_user_history(request.user, anime, episode_data, current_watched_time)
+ seasons = get_seasons_by_zid(anime.z_anime_id)
+
context = {
"anime": anime,
"current_episode_number": episode,
@@ -314,6 +339,7 @@ def watch(request, anime_id, episode=None):
"current_watched_time": current_watched_time,
"mal_data": mal_data,
"mal_episode_range": range(1, mal_data["num_episodes"] + 1) if mal_data else None,
+ "seasons": seasons,
}
if "nextAiringEpisode" in anime_fetched:
@@ -337,3 +363,202 @@ def update_episode_watch_time(request):
return JsonResponse({"status": "success"})
else:
return JsonResponse({"status": "error", "message": "User not authenticated"})
+
+def watch_via_zid(request, zid):
+ cache_key = f"anime_{zid}_anilist_lookup"
+ anilist_id = get_from_redis_cache(cache_key)
+
+ # See if anilist id is present in the info from zoro
+ if not anilist_id:
+ anime_selected = get_info_by_zid(zid)
+ anilist_id = anime_selected["anime"]["info"]["anilistId"]
+ # store_in_redis_cache(cache_key, anilist_id, 3600 * 24 * 30)
+
+ # If not see if we can find the anilist id from the mal id using anilist graphql
+ # this will almost always work and we wont need to search for the anime by name
+ if not anilist_id:
+ mal_id = anime_selected["anime"]["info"]["malId"]
+ print("Searching using graphql. Mal id:", mal_id)
+ anilist_graphql_url = "https://graphql.anilist.co"
+ query = """
+ query {{
+ Media(idMal: {mal_id}, type: ANIME) {{
+ id
+ }}
+ }}
+ """.format(mal_id=mal_id)
+
+ response = requests.post(anilist_graphql_url, json={"query": query})
+ response = response.json()
+
+ if not "errors" in response:
+ anilist_id = response["data"]["Media"]["id"]
+ # store_in_redis_cache(cache_key, anilist_id, 3600 * 24 * 30)
+
+ if not anilist_id:
+ anime_name = anime_selected['anime']['info']['name']
+ anime_name = parse_title_and_season(anime_name)["show_name"]
+ consumet_search_url = f"{os.getenv('CONSUMET_URL')}/meta/anilist/advanced-search?query={anime_name}&provider=zoro"
+ response = requests.get(consumet_search_url)
+ anime_search_results = response.json()
+
+ # compare where mal id is same and return the anilist id
+ for result in anime_search_results["results"]:
+ if result["malId"] == mal_id:
+ anilist_id = result["id"]
+ # store_in_redis_cache(cache_key, anilist_id, 3600 * 24 * 30)
+ break
+
+ print(anilist_id)
+ if anilist_id:
+ return redirect("watch:watch", anime_id=anilist_id)
+ else:
+ return redirect("watch:watch_via_zid_mal_id", mal_id=mal_id, zid=zid)
+
+# same thing as watch, but with mal id and zid since anilist id is not available
+# context remains the same but data is not saved in database
+def watch_via_zid_mal_id(request, mal_id, zid):
+ anime_info = get_info_by_zid(zid)
+
+ mal_access_token = request.user.mal_access_token
+ if not mal_access_token:
+ u = User.objects.filter(mal_access_token__isnull=False).first()
+ mal_access_token = u.mal_access_token
+
+ anime_mal_info = get_single_anime_mal(mal_access_token, mal_id)
+ anime_episodes = get_episodes_by_zid(zid)
+ for index, episode in enumerate(anime_episodes["episodes"]):
+ episode_identifier = episode["episodeId"].split("?ep=")[1]
+ anime_episodes["episodes"][index]["episode"] = {
+ "identifier": episode_identifier,
+ "number": index + 1,
+ }
+ print(anime_episodes["episodes"][index]["episodeId"])
+
+ current_episode_number = 1
+ ep = request.GET.get("ep", None)
+
+ if ep:
+ current_episode_number = next((i + 1 for i, item in enumerate(anime_episodes["episodes"]) if item["episode"]["identifier"] == ep), 1)
+ else:
+ ep = anime_episodes["episodes"][0]["episode"]["identifier"]
+ return redirect(reverse("watch:watch_via_zid_mal_id", args=[mal_id, zid]) + f"?ep={ep}")
+
+ current_episode = anime_episodes["episodes"][int(current_episode_number) - 1]
+
+ mode = request.GET.get("mode", request.user.preferences.default_language)
+ if mode == "dub" and (not anime_info["anime"]["info"]["stats"]["episodes"]["dub"] or anime_info["anime"]["info"]["stats"]["episodes"]["dub"] < current_episode_number):
+ mode = "sub"
+
+ streaming_data = get_episode_streaming_data(current_episode["episodeId"], mode)
+
+ if streaming_data and "tracks" in streaming_data and not any(t["kind"] == "captions" for t in streaming_data["tracks"]) and mode == "dub" and request.user.preferences.ingrain_sub_subtitles_in_dub:
+ sub_streaming_data = get_episode_streaming_data(current_episode["episodeId"], "sub")
+ captions = [t for t in sub_streaming_data["tracks"] if t["kind"] == "captions"]
+ if captions:
+ streaming_data["tracks"].extend(captions)
+
+ anime = {
+ "id": mal_id,
+ "malId": mal_id,
+ "z_anime_id": zid,
+ "description": anime_info["anime"]["info"]["description"],
+ "image": anime_info["anime"]["info"]["poster"].replace("300x400/100", "600x800/100"),
+ "countryOfOrigin": "JP",
+ "titles": {
+ "english": anime_mal_info["alternative_titles"]["en"],
+ "romaji": anime_mal_info["title"],
+ "native": anime_mal_info["alternative_titles"]["ja"]
+ },
+ "type": anime_mal_info["media_type"].replace("_", " ").title(),
+ "popularity": anime_mal_info["popularity"],
+ "releaseDate": anime_mal_info["start_date"].split("-")[0],
+ "totalEpisodes": anime_mal_info["num_episodes"],
+ "currentEpisode": len(anime_episodes["episodes"]),
+ "rating": anime_mal_info["mean"],
+ "duration": anime_mal_info["average_episode_duration"] // 60 + 1,
+ "genres": {
+ "all": anime_mal_info["genres"]
+ },
+ "status": anime_mal_info["status"].replace("_", " ").title(),
+ "season": anime_mal_info["start_season"]["season"].title(),
+ "studios": {
+ "all": anime_mal_info["studios"]
+ },
+ "sub": anime_info["anime"]["info"]["stats"]["episodes"].get("sub", 0),
+ "dub": anime_info["anime"]["info"]["stats"]["episodes"].get("dub", 0),
+ }
+
+ related = []
+ for r in anime_info["relatedAnimes"]:
+ rd = {
+ "zid": r["id"],
+ "image": r["poster"].replace("300x400/100", "600x800/100"),
+ "title": {
+ "english": r["name"],
+ "romaji": r["jname"]
+ },
+ "episodes": r["episodes"]["sub"],
+ "type": r["type"],
+ }
+
+ related.append(rd)
+
+ recommended = []
+ for r in anime_info["recommendedAnimes"]:
+ rd = {
+ "zid": r["id"],
+ "image": r["poster"].replace("300x400/100", "600x800/100"),
+ "title": {
+ "english": r["name"],
+ "romaji": r["jname"]
+ },
+ "episodes": r["episodes"]["sub"],
+ "type": r["type"],
+ }
+
+ characters = []
+ for c in anime_info["anime"]["info"]["charactersVoiceActors"]:
+ cd = {
+ "name": {
+ "full": c["character"]["name"],
+ "natve": c["character"]["name"],
+ },
+ "image": c["character"]["poster"].replace("100x100/100", "200x200/100"),
+ "role": c["character"]["cast"],
+ "voiceActors": [
+ {
+ "name": {
+ "full": c["voiceActor"]["name"],
+ "native": c["voiceActor"]["name"],
+ },
+ "image": c["voiceActor"]["poster"].replace("100x100/100", "200x200/100"),
+ "language": "Japanese",
+ }
+ ],
+ }
+
+ characters.append(cd)
+
+ context = {
+ "anime": anime,
+ "current_episode_number": current_episode_number,
+ "current_episode": current_episode,
+ "all_episodes": anime_episodes["episodes"],
+ "streaming_data": streaming_data,
+ "stream_url": streaming_data["sources"][0]["url"] if streaming_data and "sources" in streaming_data else None,
+ "watched_episodes": [],
+ "current_watched_time": 0,
+ "mode": mode,
+ "seasons": get_seasons_by_zid(zid),
+ "viaMal": True,
+ "related": related,
+ "recommendations": recommended,
+ "characters": characters,
+ }
+
+ if request.user.mal_access_token:
+ context["mal_data"] = anime_mal_info
+ context["mal_episode_range"] = range(1, anime_mal_info["num_episodes"] + 1)
+
+ return render(request, "watch/watch.html", context)