mirror of
https://github.com/Arrowar/StreamingCommunity.git
synced 2025-06-08 12:35:27 +00:00
Restore ...
This commit is contained in:
parent
306377a7be
commit
2abfe82815
11
.gitignore
vendored
11
.gitignore
vendored
@ -42,14 +42,6 @@ local_settings.py
|
|||||||
db.sqlite3
|
db.sqlite3
|
||||||
db.sqlite3-journal
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
|
||||||
# Vue stuff:
|
|
||||||
frontend/dist/
|
|
||||||
frontend/node_modules/
|
|
||||||
frontend/npm-debug.log
|
|
||||||
frontend/.vite/
|
|
||||||
frontend/.vscode/
|
|
||||||
|
|
||||||
# Jupyter Notebook
|
# Jupyter Notebook
|
||||||
.ipynb_checkpoints
|
.ipynb_checkpoints
|
||||||
|
|
||||||
@ -64,6 +56,3 @@ venv.bak/
|
|||||||
|
|
||||||
# Other
|
# Other
|
||||||
Video
|
Video
|
||||||
.idea/
|
|
||||||
.vscode/
|
|
||||||
.DS_Store
|
|
||||||
|
@ -46,21 +46,6 @@ class Episode:
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Episode(id={self.id}, number={self.number}, name='{self.name}', plot='{self.plot}', duration={self.duration} sec)"
|
return f"Episode(id={self.id}, number={self.number}, name='{self.name}', plot='{self.plot}', duration={self.duration} sec)"
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
|
||||||
return {
|
|
||||||
"id": self.id,
|
|
||||||
"number": self.number,
|
|
||||||
"name": self.name,
|
|
||||||
"plot": self.plot,
|
|
||||||
"duration": self.duration,
|
|
||||||
"scws_id": self.scws_id,
|
|
||||||
"season_id": self.season_id,
|
|
||||||
"created_by": self.created_by,
|
|
||||||
"created_at": self.created_at,
|
|
||||||
"updated_at": self.updated_at,
|
|
||||||
"images": [image.__dict__ for image in self.images]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class EpisodeManager:
|
class EpisodeManager:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -60,17 +60,4 @@ class PreviewManager:
|
|||||||
images_str = "\n".join(str(image) for image in self.images)
|
images_str = "\n".join(str(image) for image in self.images)
|
||||||
return f"Title: ID={self.id}, Type={self.type}, Runtime={self.runtime}, Release Date={self.release_date}, Quality={self.quality}, Plot={self.plot}, Seasons Count={self.seasons_count}\nGenres:\n{genres_str}\nPreview:\n{self.preview}\nImages:\n{images_str}"
|
return f"Title: ID={self.id}, Type={self.type}, Runtime={self.runtime}, Release Date={self.release_date}, Quality={self.quality}, Plot={self.plot}, Seasons Count={self.seasons_count}\nGenres:\n{genres_str}\nPreview:\n{self.preview}\nImages:\n{images_str}"
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return {
|
|
||||||
"id": self.id,
|
|
||||||
"type": self.type,
|
|
||||||
"runtime": self.runtime,
|
|
||||||
"release_date": self.release_date,
|
|
||||||
"quality": self.quality,
|
|
||||||
"plot": self.plot,
|
|
||||||
"seasons_count": self.seasons_count,
|
|
||||||
"genres": [genre.__dict__ for genre in self.genres],
|
|
||||||
"preview": self.preview.__dict__,
|
|
||||||
"images": [image.__dict__ for image in self.images]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -37,33 +37,10 @@ class MediaItem:
|
|||||||
self.last_air_date: str = data.get('last_air_date')
|
self.last_air_date: str = data.get('last_air_date')
|
||||||
self.seasons_count: int = data.get('seasons_count')
|
self.seasons_count: int = data.get('seasons_count')
|
||||||
self.images: List[Image] = [Image(image_data) for image_data in data.get('images', [])]
|
self.images: List[Image] = [Image(image_data) for image_data in data.get('images', [])]
|
||||||
self.comment: str = data.get('comment')
|
|
||||||
self.plot: str = data.get('plot')
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"MediaItem(id={self.id}, slug='{self.slug}', name='{self.name}', type='{self.type}', score='{self.score}', sub_ita={self.sub_ita}, last_air_date='{self.last_air_date}', seasons_count={self.seasons_count}, images={self.images})"
|
return f"MediaItem(id={self.id}, slug='{self.slug}', name='{self.name}', type='{self.type}', score='{self.score}', sub_ita={self.sub_ita}, last_air_date='{self.last_air_date}', seasons_count={self.seasons_count}, images={self.images})"
|
||||||
|
|
||||||
@property
|
|
||||||
def to_dict(self) -> dict:
|
|
||||||
"""
|
|
||||||
Convert the MediaItem to a dictionary.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: The MediaItem as a dictionary.
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
"id": self.id,
|
|
||||||
"slug": self.slug,
|
|
||||||
"name": self.name,
|
|
||||||
"type": self.type.upper(),
|
|
||||||
"score": self.score,
|
|
||||||
"sub_ita": self.sub_ita,
|
|
||||||
"last_air_date": self.last_air_date,
|
|
||||||
"seasons_count": self.seasons_count,
|
|
||||||
"images": [image.__dict__ for image in self.images],
|
|
||||||
"comment": self.comment,
|
|
||||||
"plot": self.plot
|
|
||||||
}
|
|
||||||
|
|
||||||
class MediaManager:
|
class MediaManager:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -28,19 +28,6 @@ class Image:
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Image(id={self.id}, filename='{self.filename}', type='{self.type}', imageable_type='{self.imageable_type}', url='{self.url}')"
|
return f"Image(id={self.id}, filename='{self.filename}', type='{self.type}', imageable_type='{self.imageable_type}', url='{self.url}')"
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
|
||||||
return {
|
|
||||||
'id': self.id,
|
|
||||||
'filename': self.filename,
|
|
||||||
'type': self.type,
|
|
||||||
'imageable_type': self.imageable_type,
|
|
||||||
'imageable_id': self.imageable_id,
|
|
||||||
'created_at': self.created_at,
|
|
||||||
'updated_at': self.updated_at,
|
|
||||||
'original_url_field': self.original_url_field,
|
|
||||||
'url': self.url
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Episode:
|
class Episode:
|
||||||
def __init__(self, data: Dict[str, Any]):
|
def __init__(self, data: Dict[str, Any]):
|
||||||
@ -59,21 +46,6 @@ class Episode:
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Episode(id={self.id}, number={self.number}, name='{self.name}', plot='{self.plot}', duration={self.duration} sec)"
|
return f"Episode(id={self.id}, number={self.number}, name='{self.name}', plot='{self.plot}', duration={self.duration} sec)"
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
|
||||||
return {
|
|
||||||
'id': self.id,
|
|
||||||
'number': self.number,
|
|
||||||
'name': self.name,
|
|
||||||
'plot': self.plot,
|
|
||||||
'duration': self.duration,
|
|
||||||
'scws_id': self.scws_id,
|
|
||||||
'season_id': self.season_id,
|
|
||||||
'created_by': self.created_by,
|
|
||||||
'created_at': self.created_at,
|
|
||||||
'updated_at': self.updated_at,
|
|
||||||
'images': [image.to_dict() for image in self.images]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class EpisodeManager:
|
class EpisodeManager:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -60,17 +60,4 @@ class PreviewManager:
|
|||||||
images_str = "\n".join(str(image) for image in self.images)
|
images_str = "\n".join(str(image) for image in self.images)
|
||||||
return f"Title: ID={self.id}, Type={self.type}, Runtime={self.runtime}, Release Date={self.release_date}, Quality={self.quality}, Plot={self.plot}, Seasons Count={self.seasons_count}\nGenres:\n{genres_str}\nPreview:\n{self.preview}\nImages:\n{images_str}"
|
return f"Title: ID={self.id}, Type={self.type}, Runtime={self.runtime}, Release Date={self.release_date}, Quality={self.quality}, Plot={self.plot}, Seasons Count={self.seasons_count}\nGenres:\n{genres_str}\nPreview:\n{self.preview}\nImages:\n{images_str}"
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return {
|
|
||||||
"id": self.id,
|
|
||||||
"type": self.type,
|
|
||||||
"runtime": self.runtime,
|
|
||||||
"release_date": self.release_date,
|
|
||||||
"quality": self.quality,
|
|
||||||
"plot": self.plot,
|
|
||||||
"seasons_count": self.seasons_count,
|
|
||||||
"genres": [genre.__dict__ for genre in self.genres],
|
|
||||||
"preview": self.preview.__dict__,
|
|
||||||
"images": [image.__dict__ for image in self.images]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -37,44 +37,10 @@ class MediaItem:
|
|||||||
self.last_air_date: str = data.get('last_air_date')
|
self.last_air_date: str = data.get('last_air_date')
|
||||||
self.seasons_count: int = data.get('seasons_count')
|
self.seasons_count: int = data.get('seasons_count')
|
||||||
self.images: List[Image] = [Image(image_data) for image_data in data.get('images', [])]
|
self.images: List[Image] = [Image(image_data) for image_data in data.get('images', [])]
|
||||||
self.comment: str = data.get('comment')
|
|
||||||
self.plot: str = data.get('plot')
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"MediaItem(id={self.id}, slug='{self.slug}', name='{self.name}', type='{self.type}', score='{self.score}', sub_ita={self.sub_ita}, last_air_date='{self.last_air_date}', seasons_count={self.seasons_count}, images={self.images})"
|
return f"MediaItem(id={self.id}, slug='{self.slug}', name='{self.name}', type='{self.type}', score='{self.score}', sub_ita={self.sub_ita}, last_air_date='{self.last_air_date}', seasons_count={self.seasons_count}, images={self.images})"
|
||||||
|
|
||||||
@property
|
|
||||||
def to_dict(self) -> dict:
|
|
||||||
"""
|
|
||||||
Convert the MediaItem to a dictionary.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: The MediaItem as a dictionary.
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
"id": self.id,
|
|
||||||
"slug": self.slug,
|
|
||||||
"name": self.name,
|
|
||||||
"type": self.type.upper(),
|
|
||||||
"score": self.score,
|
|
||||||
"sub_ita": self.sub_ita,
|
|
||||||
"last_air_date": self.last_air_date,
|
|
||||||
"seasons_count": self.seasons_count,
|
|
||||||
"images": [image.__dict__ for image in self.images],
|
|
||||||
"comment": self.comment,
|
|
||||||
"plot": self.plot
|
|
||||||
}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def get_site_id(self) -> str:
|
|
||||||
"""
|
|
||||||
Get the site ID of the media item.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
int: The site ID of the media item.
|
|
||||||
"""
|
|
||||||
return f"{self.id}-{self.slug}"
|
|
||||||
|
|
||||||
|
|
||||||
class MediaManager:
|
class MediaManager:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
"""
|
|
||||||
ASGI config for api project.
|
|
||||||
|
|
||||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from django.core.asgi import get_asgi_application
|
|
||||||
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "api.settings")
|
|
||||||
|
|
||||||
application = get_asgi_application()
|
|
@ -1,140 +0,0 @@
|
|||||||
"""
|
|
||||||
Django settings for api project.
|
|
||||||
|
|
||||||
Generated by 'django-admin startproject' using Django 4.2.7.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/4.2/topics/settings/
|
|
||||||
|
|
||||||
For the full list of settings and their values, see
|
|
||||||
https://docs.djangoproject.com/en/4.2/ref/settings/
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
||||||
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
|
||||||
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
|
||||||
SECRET_KEY = "django-insecure-7u!h-#6b--%h8()19so$s+t9cjh5y1+ljnqum*@gm))0(a_qka"
|
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
|
||||||
DEBUG = True
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
|
||||||
"django.contrib.admin",
|
|
||||||
"django.contrib.auth",
|
|
||||||
"django.contrib.contenttypes",
|
|
||||||
"django.contrib.sessions",
|
|
||||||
"django.contrib.messages",
|
|
||||||
"django.contrib.staticfiles",
|
|
||||||
"corsheaders",
|
|
||||||
"endpoints",
|
|
||||||
"rest_framework",
|
|
||||||
]
|
|
||||||
|
|
||||||
MIDDLEWARE = [
|
|
||||||
"corsheaders.middleware.CorsMiddleware",
|
|
||||||
"django.middleware.security.SecurityMiddleware",
|
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
|
||||||
"django.middleware.common.CommonMiddleware",
|
|
||||||
"django.middleware.csrf.CsrfViewMiddleware",
|
|
||||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
|
||||||
"django.contrib.messages.middleware.MessageMiddleware",
|
|
||||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
|
||||||
]
|
|
||||||
|
|
||||||
ROOT_URLCONF = "api.urls"
|
|
||||||
|
|
||||||
TEMPLATES = [
|
|
||||||
{
|
|
||||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
|
||||||
"DIRS": [],
|
|
||||||
"APP_DIRS": True,
|
|
||||||
"OPTIONS": {
|
|
||||||
"context_processors": [
|
|
||||||
"django.template.context_processors.debug",
|
|
||||||
"django.template.context_processors.request",
|
|
||||||
"django.contrib.auth.context_processors.auth",
|
|
||||||
"django.contrib.messages.context_processors.messages",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
WSGI_APPLICATION = "api.wsgi.application"
|
|
||||||
|
|
||||||
|
|
||||||
# Database
|
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
"default": {
|
|
||||||
"ENGINE": "django.db.backends.sqlite3",
|
|
||||||
"NAME": BASE_DIR / "db.sqlite3",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Password validation
|
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
|
||||||
{
|
|
||||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# Internationalization
|
|
||||||
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
|
||||||
|
|
||||||
LANGUAGE_CODE = "en-us"
|
|
||||||
|
|
||||||
TIME_ZONE = "UTC"
|
|
||||||
|
|
||||||
USE_I18N = True
|
|
||||||
|
|
||||||
USE_TZ = True
|
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
|
||||||
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
|
||||||
|
|
||||||
STATIC_URL = "static/"
|
|
||||||
|
|
||||||
# Default primary key field type
|
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
|
||||||
|
|
||||||
CORS_ALLOWED_ORIGINS = [
|
|
||||||
"http://localhost:5173",
|
|
||||||
]
|
|
||||||
|
|
||||||
CORS_ALLOWED_METHODS = (
|
|
||||||
"DELETE",
|
|
||||||
"GET",
|
|
||||||
"OPTIONS",
|
|
||||||
"PATCH",
|
|
||||||
"POST",
|
|
||||||
"PUT",
|
|
||||||
)
|
|
@ -1,24 +0,0 @@
|
|||||||
"""
|
|
||||||
URL configuration for api project.
|
|
||||||
|
|
||||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
|
||||||
https://docs.djangoproject.com/en/4.2/topics/http/urls/
|
|
||||||
Examples:
|
|
||||||
Function views
|
|
||||||
1. Add an import: from my_app import views
|
|
||||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
|
||||||
Class-based views
|
|
||||||
1. Add an import: from other_app.views import Home
|
|
||||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
|
||||||
Including another URLconf
|
|
||||||
1. Import the include() function: from django.urls import include, path
|
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
|
||||||
"""
|
|
||||||
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.urls import path, include
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path("admin/", admin.site.urls),
|
|
||||||
path("api/", include("endpoints.urls")),
|
|
||||||
]
|
|
@ -1,16 +0,0 @@
|
|||||||
"""
|
|
||||||
WSGI config for api project.
|
|
||||||
|
|
||||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
|
||||||
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "api.settings")
|
|
||||||
|
|
||||||
application = get_wsgi_application()
|
|
@ -1,3 +0,0 @@
|
|||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
# Register your models here.
|
|
@ -1,6 +0,0 @@
|
|||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class EndpointsConfig(AppConfig):
|
|
||||||
default_auto_field = "django.db.models.BigAutoField"
|
|
||||||
name = "endpoints"
|
|
@ -1,3 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
|
|
||||||
# Create your models here.
|
|
@ -1,3 +0,0 @@
|
|||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
@ -1,10 +0,0 @@
|
|||||||
from rest_framework import routers
|
|
||||||
|
|
||||||
from .views import SearchView#, DownloadView
|
|
||||||
|
|
||||||
router = routers.DefaultRouter()
|
|
||||||
|
|
||||||
router.register(r"search", SearchView, basename="search")
|
|
||||||
#router.register(r"download", DownloadView, basename="download")
|
|
||||||
|
|
||||||
urlpatterns = router.urls
|
|
@ -1,241 +0,0 @@
|
|||||||
import json
|
|
||||||
import os
|
|
||||||
|
|
||||||
from django.http import StreamingHttpResponse
|
|
||||||
|
|
||||||
from rest_framework import viewsets
|
|
||||||
from rest_framework.decorators import action
|
|
||||||
from rest_framework.response import Response
|
|
||||||
|
|
||||||
from Src.Api.Animeunity import title_search as anime_search
|
|
||||||
from Src.Api.Animeunity.Core.Vix_player.player import VideoSource as anime_source
|
|
||||||
from Src.Api.Animeunity.site import media_search_manager as anime_media_manager
|
|
||||||
|
|
||||||
from Src.Api.Streamingcommunity import title_search as sc_search, get_version_and_domain
|
|
||||||
from Src.Api.Streamingcommunity.Core.Vix_player.player import VideoSource as film_video_source
|
|
||||||
from Src.Api.Streamingcommunity.site import media_search_manager as film_media_manager
|
|
||||||
|
|
||||||
|
|
||||||
class SearchView(viewsets.ViewSet):
|
|
||||||
|
|
||||||
def list(self, request):
|
|
||||||
self.search_query = request.query_params.get("search_terms")
|
|
||||||
self.type_search = request.query_params.get("type")
|
|
||||||
|
|
||||||
media_manager = anime_media_manager if self.type_search == "anime" else film_media_manager
|
|
||||||
media_manager.media_list = []
|
|
||||||
self.len_database = 0
|
|
||||||
if self.type_search == "film":
|
|
||||||
_, self.domain = get_version_and_domain()
|
|
||||||
self.len_database = sc_search(self.search_query, self.domain)
|
|
||||||
elif self.type_search == "anime":
|
|
||||||
self.len_database = anime_search(self.search_query)
|
|
||||||
|
|
||||||
media_list = media_manager.media_list
|
|
||||||
|
|
||||||
if self.len_database != 0:
|
|
||||||
data_to_return = []
|
|
||||||
for _, media in enumerate(media_list):
|
|
||||||
if self.type_search == "anime":
|
|
||||||
if media.type == "TV":
|
|
||||||
media.type = "TV_ANIME"
|
|
||||||
if media.type == "Movie":
|
|
||||||
media.type = "OVA"
|
|
||||||
data_to_return.append(media.to_dict)
|
|
||||||
|
|
||||||
return Response({"media": data_to_return})
|
|
||||||
|
|
||||||
return Response({"error": "No media found with that search query"})
|
|
||||||
|
|
||||||
@action(detail=False, methods=["get"])
|
|
||||||
def get_episodes_info(self, request):
|
|
||||||
self.media_id = request.query_params.get("media_id")
|
|
||||||
self.media_slug = request.query_params.get("media_slug")
|
|
||||||
self.type_media = request.query_params.get("type_media")
|
|
||||||
|
|
||||||
try:
|
|
||||||
match self.type_media:
|
|
||||||
case "TV":
|
|
||||||
|
|
||||||
def stream_episodes():
|
|
||||||
self.version, self.domain = get_version_and_domain()
|
|
||||||
|
|
||||||
video_source = film_video_source()
|
|
||||||
video_source.setup(
|
|
||||||
version=self.version,
|
|
||||||
domain=self.domain,
|
|
||||||
media_id=self.media_id,
|
|
||||||
series_name=self.media_slug
|
|
||||||
)
|
|
||||||
video_source.collect_info_seasons()
|
|
||||||
seasons_count = video_source.obj_title_manager.get_length()
|
|
||||||
|
|
||||||
episodes = {}
|
|
||||||
for i_season in range(1, seasons_count + 1):
|
|
||||||
video_source.obj_episode_manager.clear()
|
|
||||||
video_source.collect_title_season(i_season)
|
|
||||||
episodes_count = (
|
|
||||||
video_source.obj_episode_manager.get_length()
|
|
||||||
)
|
|
||||||
episodes[i_season] = {}
|
|
||||||
for i_episode in range(1, episodes_count + 1):
|
|
||||||
episode = video_source.obj_episode_manager.episodes[
|
|
||||||
i_episode - 1
|
|
||||||
]
|
|
||||||
episodes[i_season][i_episode] = episode.to_dict()
|
|
||||||
|
|
||||||
yield f'{json.dumps({"episodes": episodes})}\n\n'
|
|
||||||
|
|
||||||
response = StreamingHttpResponse(
|
|
||||||
stream_episodes(), content_type="text/event-stream"
|
|
||||||
)
|
|
||||||
return response
|
|
||||||
|
|
||||||
case "TV_ANIME":
|
|
||||||
def stream_episodes():
|
|
||||||
video_source = anime_source()
|
|
||||||
video_source.setup(
|
|
||||||
media_id = self.media_id,
|
|
||||||
series_name = self.media_slug
|
|
||||||
)
|
|
||||||
episoded_count = video_source.get_count_episodes()
|
|
||||||
|
|
||||||
for i in range(0, episoded_count):
|
|
||||||
episode_info = video_source.get_info_episode(i).to_dict()
|
|
||||||
episode_info["episode_id"] = i
|
|
||||||
episode_info["episode_total"] = episoded_count
|
|
||||||
print(f"Getting episode {i} of {episoded_count} info...")
|
|
||||||
yield f"{json.dumps(episode_info)}\n\n"
|
|
||||||
|
|
||||||
response = StreamingHttpResponse(
|
|
||||||
stream_episodes(), content_type="text/event-stream"
|
|
||||||
)
|
|
||||||
return response
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return Response(
|
|
||||||
{
|
|
||||||
"error": "Error while getting episodes info",
|
|
||||||
"message": str(e),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return Response({"error": "No media found with that search query"})
|
|
||||||
|
|
||||||
@action(detail=False, methods=["get"])
|
|
||||||
def get_preview(self, request):
|
|
||||||
self.media_id = request.query_params.get("media_id")
|
|
||||||
self.media_slug = request.query_params.get("media_slug")
|
|
||||||
self.type_media = request.query_params.get("type_media")
|
|
||||||
|
|
||||||
try:
|
|
||||||
if self.type_media in ["TV", "MOVIE"]:
|
|
||||||
version, domain = get_version_and_domain()
|
|
||||||
video_source = film_video_source()
|
|
||||||
video_source.setup(media_id=self.media_id, version=version, domain=domain, series_name=self.media_slug)
|
|
||||||
video_source.get_preview()
|
|
||||||
return Response(video_source.obj_preview.to_dict())
|
|
||||||
if self.type_media in ["TV_ANIME", "OVA", "SPECIAL"]:
|
|
||||||
video_source = anime_source()
|
|
||||||
video_source.setup(media_id=self.media_id, series_name=self.media_slug)
|
|
||||||
video_source.get_preview()
|
|
||||||
return Response(video_source.obj_preview.to_dict())
|
|
||||||
except Exception as e:
|
|
||||||
return Response(
|
|
||||||
{
|
|
||||||
"error": "Error while getting preview info",
|
|
||||||
"message": str(e),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return Response({"error": "No media found with that search query"})
|
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
class DownloadView(viewsets.ViewSet):
|
|
||||||
|
|
||||||
def create(self, request):
|
|
||||||
self.media_id = request.data.get("media_id")
|
|
||||||
self.media_slug = request.data.get("media_slug")
|
|
||||||
self.type_media = request.data.get("type_media").upper()
|
|
||||||
self.download_id = request.data.get("download_id")
|
|
||||||
self.tv_series_episode_id = request.data.get("tv_series_episode_id")
|
|
||||||
|
|
||||||
if self.type_media in ["TV", "MOVIE"]:
|
|
||||||
self.site_version, self.domain = get_version_and_domain()
|
|
||||||
|
|
||||||
response_dict = {"error": "No media found with that search query"}
|
|
||||||
|
|
||||||
try:
|
|
||||||
match self.type_media:
|
|
||||||
case "MOVIE":
|
|
||||||
download_film(self.media_id, self.media_slug, self.domain)
|
|
||||||
case "TV":
|
|
||||||
video_source = VideoSource()
|
|
||||||
video_source.set_url_base_name(STREAM_SITE_NAME)
|
|
||||||
video_source.set_version(self.site_version)
|
|
||||||
video_source.set_domain(self.domain)
|
|
||||||
video_source.set_series_name(self.media_slug)
|
|
||||||
video_source.set_media_id(self.media_id)
|
|
||||||
|
|
||||||
video_source.collect_info_seasons()
|
|
||||||
video_source.obj_episode_manager.clear()
|
|
||||||
|
|
||||||
video_source.collect_title_season(self.download_id)
|
|
||||||
episodes_count = video_source.obj_episode_manager.get_length()
|
|
||||||
for i_episode in range(1, episodes_count + 1):
|
|
||||||
episode_id = video_source.obj_episode_manager.episodes[
|
|
||||||
i_episode - 1
|
|
||||||
].id
|
|
||||||
|
|
||||||
# Define filename and path for the downloaded video
|
|
||||||
mp4_name = remove_special_characters(
|
|
||||||
f"{map_episode_title(self.media_slug,video_source.obj_episode_manager.episodes[i_episode - 1],self.download_id)}.mp4"
|
|
||||||
)
|
|
||||||
mp4_path = remove_special_characters(
|
|
||||||
os.path.join(
|
|
||||||
ROOT_PATH,
|
|
||||||
SERIES_FOLDER,
|
|
||||||
self.media_slug,
|
|
||||||
f"S{self.download_id}",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
os.makedirs(mp4_path, exist_ok=True)
|
|
||||||
|
|
||||||
# Get iframe and content for the episode
|
|
||||||
video_source.get_iframe(episode_id)
|
|
||||||
video_source.get_content()
|
|
||||||
video_source.set_url_base_name(STREAM_SITE_NAME)
|
|
||||||
|
|
||||||
# Download the episode
|
|
||||||
obj_download = Downloader(
|
|
||||||
m3u8_playlist=video_source.get_playlist(),
|
|
||||||
key=video_source.get_key(),
|
|
||||||
output_filename=os.path.join(mp4_path, mp4_name),
|
|
||||||
)
|
|
||||||
|
|
||||||
obj_download.download_m3u8()
|
|
||||||
|
|
||||||
case "TV_ANIME":
|
|
||||||
episodes_downloader = EpisodeDownloader(
|
|
||||||
self.media_id, self.media_slug
|
|
||||||
)
|
|
||||||
episodes_downloader.download_episode(self.download_id)
|
|
||||||
case "OVA" | "SPECIAL":
|
|
||||||
anime_download_film(
|
|
||||||
id_film=self.media_id, title_name=self.media_slug
|
|
||||||
)
|
|
||||||
case _:
|
|
||||||
raise Exception("Type media not supported")
|
|
||||||
|
|
||||||
response_dict = {
|
|
||||||
"message": "Download done, it is saved in Video folder inside project root"
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
response_dict = {
|
|
||||||
"error": "Error while downloading the media",
|
|
||||||
"message": str(e),
|
|
||||||
}
|
|
||||||
|
|
||||||
return Response(response_dict)
|
|
||||||
'''
|
|
@ -1,24 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
"""Django's command-line utility for administrative tasks."""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Run administrative tasks."""
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "api.settings")
|
|
||||||
try:
|
|
||||||
from django.core.management import execute_from_command_line
|
|
||||||
except ImportError as exc:
|
|
||||||
raise ImportError(
|
|
||||||
"Couldn't import Django. Are you sure it's installed and "
|
|
||||||
"available on your PYTHONPATH environment variable? Did you "
|
|
||||||
"forget to activate a virtual environment?"
|
|
||||||
) from exc
|
|
||||||
execute_from_command_line(sys.argv)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,15 +0,0 @@
|
|||||||
/* eslint-env node */
|
|
||||||
require('@rushstack/eslint-patch/modern-module-resolution')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
'extends': [
|
|
||||||
'plugin:vue/vue3-essential',
|
|
||||||
'eslint:recommended',
|
|
||||||
'@vue/eslint-config-typescript',
|
|
||||||
'@vue/eslint-config-prettier/skip-formatting'
|
|
||||||
],
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 'latest'
|
|
||||||
}
|
|
||||||
}
|
|
30
frontend/.gitignore
vendored
30
frontend/.gitignore
vendored
@ -1,30 +0,0 @@
|
|||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
pnpm-debug.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
|
|
||||||
node_modules
|
|
||||||
.DS_Store
|
|
||||||
dist
|
|
||||||
dist-ssr
|
|
||||||
coverage
|
|
||||||
*.local
|
|
||||||
|
|
||||||
/cypress/videos/
|
|
||||||
/cypress/screenshots/
|
|
||||||
|
|
||||||
# Editor directories and files
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/extensions.json
|
|
||||||
.idea
|
|
||||||
*.suo
|
|
||||||
*.ntvs*
|
|
||||||
*.njsproj
|
|
||||||
*.sln
|
|
||||||
*.sw?
|
|
||||||
|
|
||||||
*.tsbuildinfo
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/prettierrc",
|
|
||||||
"semi": false,
|
|
||||||
"tabWidth": 2,
|
|
||||||
"singleQuote": true,
|
|
||||||
"printWidth": 100,
|
|
||||||
"trailingComma": "none"
|
|
||||||
}
|
|
7
frontend/.vscode/extensions.json
vendored
7
frontend/.vscode/extensions.json
vendored
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"recommendations": [
|
|
||||||
"Vue.volar",
|
|
||||||
"dbaeumer.vscode-eslint",
|
|
||||||
"esbenp.prettier-vscode"
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
# frontend
|
|
||||||
|
|
||||||
This template should help get you started developing with Vue 3 in Vite.
|
|
||||||
|
|
||||||
## Recommended IDE Setup
|
|
||||||
|
|
||||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
|
||||||
|
|
||||||
## Type Support for `.vue` Imports in TS
|
|
||||||
|
|
||||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
|
||||||
|
|
||||||
## Customize configuration
|
|
||||||
|
|
||||||
See [Vite Configuration Reference](https://vitejs.dev/config/).
|
|
||||||
|
|
||||||
## Project Setup
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
### Compile and Hot-Reload for Development
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Type-Check, Compile and Minify for Production
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Lint with [ESLint](https://eslint.org/)
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm run lint
|
|
||||||
```
|
|
1
frontend/env.d.ts
vendored
1
frontend/env.d.ts
vendored
@ -1 +0,0 @@
|
|||||||
/// <reference types="vite/client" />
|
|
@ -1,13 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<link rel="icon" href="/favicon.ico">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Vite App</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app"></div>
|
|
||||||
<script type="module" src="/src/main.ts"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
3225
frontend/package-lock.json
generated
3225
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,36 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "frontend",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite",
|
|
||||||
"build": "run-p type-check \"build-only {@}\" --",
|
|
||||||
"preview": "vite preview",
|
|
||||||
"build-only": "vite build",
|
|
||||||
"type-check": "vue-tsc --build --force",
|
|
||||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
|
||||||
"format": "prettier --write src/"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"axios": "^1.6.8",
|
|
||||||
"vue": "^3.4.21",
|
|
||||||
"vue-router": "^4.3.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@rushstack/eslint-patch": "^1.8.0",
|
|
||||||
"@tsconfig/node20": "^20.1.4",
|
|
||||||
"@types/node": "^20.12.5",
|
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
|
||||||
"@vue/eslint-config-prettier": "^9.0.0",
|
|
||||||
"@vue/eslint-config-typescript": "^13.0.0",
|
|
||||||
"@vue/tsconfig": "^0.5.1",
|
|
||||||
"eslint": "^8.57.0",
|
|
||||||
"eslint-plugin-vue": "^9.23.0",
|
|
||||||
"npm-run-all2": "^6.1.2",
|
|
||||||
"prettier": "^3.2.5",
|
|
||||||
"typescript": "~5.4.0",
|
|
||||||
"vite": "^5.2.8",
|
|
||||||
"vue-tsc": "^2.0.11"
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 4.2 KiB |
@ -1,36 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { RouterLink, RouterView } from 'vue-router'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<RouterView />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
#app {
|
|
||||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
margin-top: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
background-color: #42b883;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 8px 16px;
|
|
||||||
font-size: 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
background-color: #3a9f74;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:disabled {
|
|
||||||
background-color: #d3d3d3;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,81 +0,0 @@
|
|||||||
import axios from "axios";
|
|
||||||
import type { AxiosResponse } from "axios";
|
|
||||||
import type { DownloadResponse, MediaItemResponse } from "@/api/interfaces";
|
|
||||||
|
|
||||||
const BASE_URL = "http://localhost:8000/api";
|
|
||||||
|
|
||||||
const api = axios.create({
|
|
||||||
baseURL: BASE_URL,
|
|
||||||
});
|
|
||||||
|
|
||||||
async function get<T>(url: string): Promise<AxiosResponse<T>> {
|
|
||||||
return api.get(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function post<T>(url: string, data: any): Promise<AxiosResponse<T>> {
|
|
||||||
return api.post(url, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function search(
|
|
||||||
query: string,
|
|
||||||
type: string
|
|
||||||
): Promise<AxiosResponse<MediaItemResponse>> {
|
|
||||||
return get(`/search?search_terms=${query}&type=${type}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getEpisodesInfo(
|
|
||||||
mediaId: number,
|
|
||||||
mediaSlug: string,
|
|
||||||
mediaType: string
|
|
||||||
): Promise<Response> {
|
|
||||||
const url = `/search/get_episodes_info?media_id=${mediaId}&media_slug=${mediaSlug}&type_media=${mediaType}`;
|
|
||||||
return fetch(`${BASE_URL}${url}`, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "text/event-stream",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPreview(
|
|
||||||
mediaId: number,
|
|
||||||
mediaSlug: string,
|
|
||||||
mediaType: string
|
|
||||||
): Promise<AxiosResponse<any>> {
|
|
||||||
const url = `/search/get_preview?media_id=${mediaId}&media_slug=${mediaSlug}&type_media=${mediaType}`;
|
|
||||||
return get(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function downloadMedia(
|
|
||||||
mediaId: number,
|
|
||||||
mediaSlug: string,
|
|
||||||
mediaType: string,
|
|
||||||
downloadId?: number,
|
|
||||||
tvSeriesEpisodeId?: number
|
|
||||||
): Promise<AxiosResponse<DownloadResponse>> {
|
|
||||||
const url = `/download/`;
|
|
||||||
const data = {
|
|
||||||
media_id: mediaId,
|
|
||||||
media_slug: mediaSlug,
|
|
||||||
type_media: mediaType,
|
|
||||||
download_id: downloadId,
|
|
||||||
tv_series_episode_id: tvSeriesEpisodeId,
|
|
||||||
};
|
|
||||||
return post(url, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const downloadFilm = (mediaId: number, mediaSlug: string) =>
|
|
||||||
downloadMedia(mediaId, mediaSlug, "MOVIE");
|
|
||||||
export const downloadTvSeries = (
|
|
||||||
mediaId: number,
|
|
||||||
mediaSlug: string,
|
|
||||||
downloadId: number,
|
|
||||||
tvSeriesEpisodeId?: number
|
|
||||||
) => downloadMedia(mediaId, mediaSlug, "TV", downloadId, tvSeriesEpisodeId);
|
|
||||||
export const downloadAnimeFilm = (mediaId: number, mediaSlug: string) =>
|
|
||||||
downloadMedia(mediaId, mediaSlug, "OVA");
|
|
||||||
export const downloadAnimeSeries = (
|
|
||||||
mediaId: number,
|
|
||||||
mediaSlug: string,
|
|
||||||
downloadId: number
|
|
||||||
) => downloadMedia(mediaId, mediaSlug, "TV_ANIME", downloadId);
|
|
@ -1,64 +0,0 @@
|
|||||||
interface Image {
|
|
||||||
imageable_id: number;
|
|
||||||
imageable_type: string;
|
|
||||||
filename: string;
|
|
||||||
type: string;
|
|
||||||
original_url_field: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MediaItem {
|
|
||||||
id: number;
|
|
||||||
slug: string;
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
score: string;
|
|
||||||
sub_ita: number;
|
|
||||||
last_air_date: string;
|
|
||||||
seasons_count: number;
|
|
||||||
images: Image[];
|
|
||||||
comment: string;
|
|
||||||
plot: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MediaItemResponse {
|
|
||||||
media: MediaItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Episode {
|
|
||||||
id: number;
|
|
||||||
anime_id: number;
|
|
||||||
user_id: number | null;
|
|
||||||
number: string;
|
|
||||||
created_at: string;
|
|
||||||
link: string;
|
|
||||||
visite: number;
|
|
||||||
hidden: number;
|
|
||||||
public: number;
|
|
||||||
scws_id: number;
|
|
||||||
file_name: string;
|
|
||||||
tg_post: number;
|
|
||||||
episode_id: number;
|
|
||||||
episode_total: number;
|
|
||||||
name: string; // TV Show exclusive
|
|
||||||
plot: string; // TV Show exclusive
|
|
||||||
duration: number; // TV Show exclusive
|
|
||||||
season_id: number; // TV Show exclusive
|
|
||||||
created_by: any; // TV Show exclusive
|
|
||||||
updated_at: string; // TV Show exclusive
|
|
||||||
season_index: number; // TV Show exclusive
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Season {
|
|
||||||
[key: string]: {
|
|
||||||
[key: string]: Episode;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SeasonResponse {
|
|
||||||
episodes: Season;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DownloadResponse {
|
|
||||||
error: string;
|
|
||||||
message: string;
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
import {downloadAnimeFilm, downloadAnimeSeries, downloadFilm, downloadTvSeries} from "@/api/api";
|
|
||||||
import type {DownloadResponse, Episode, MediaItem, Season} from "@/api/interfaces";
|
|
||||||
|
|
||||||
export const handleTVDownload = async (tvShowEpisodes: any[], item: MediaItem) => {
|
|
||||||
alertDownload();
|
|
||||||
for (const season of tvShowEpisodes) {
|
|
||||||
const i = tvShowEpisodes.indexOf(season);
|
|
||||||
const res = (await downloadTvSeries(item.id, item.slug, i + 1)).data;
|
|
||||||
handleDownloadError(res);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleTVEpisodesDownload = async (episodes: Episode[], item: MediaItem) => {
|
|
||||||
alertDownload();
|
|
||||||
for (const episode of episodes) {
|
|
||||||
const i = episodes.indexOf(episode);
|
|
||||||
const res = (await downloadTvSeries(item.id, item.slug, episode.season_index + 1, i)).data;
|
|
||||||
handleDownloadError(res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const handleMovieDownload = async (item: MediaItem) => {
|
|
||||||
alertDownload();
|
|
||||||
const res = (await downloadFilm(item.id, item.slug)).data;
|
|
||||||
handleDownloadError(res);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleTVAnimeDownload = async (episodeCount: number, item: MediaItem) => {
|
|
||||||
alertDownload();
|
|
||||||
for (let i = 0; i < episodeCount; i++) {
|
|
||||||
const res = (await downloadAnimeSeries(item.id, item.slug, i)).data;
|
|
||||||
handleDownloadError(res);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleTvAnimeEpisodesDownload = async (episodes: Episode[], item: MediaItem) => {
|
|
||||||
alertDownload();
|
|
||||||
for (const episode of episodes) {
|
|
||||||
const res = (await downloadAnimeSeries(item.id, item.slug, episode.episode_id)).data;
|
|
||||||
handleDownloadError(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export const handleOVADownload = async (item: MediaItem) => {
|
|
||||||
alertDownload();
|
|
||||||
const res = (await downloadAnimeFilm(item.id, item.slug)).data;
|
|
||||||
handleDownloadError(res);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDownloadError = (res: DownloadResponse) => {
|
|
||||||
if (res.error) {
|
|
||||||
throw new Error(`${res.error} - ${res.message}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const alertDownload = (message?: any) => {
|
|
||||||
if (message) {
|
|
||||||
alert(message)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
alert('Il downlaod è iniziato, il file sarà disponibile tra qualche minuto nella cartella \'Video\' del progetto...')
|
|
||||||
}
|
|
@ -1,86 +0,0 @@
|
|||||||
/* color palette from <https://github.com/vuejs/theme> */
|
|
||||||
:root {
|
|
||||||
--vt-c-white: #ffffff;
|
|
||||||
--vt-c-white-soft: #f8f8f8;
|
|
||||||
--vt-c-white-mute: #f2f2f2;
|
|
||||||
|
|
||||||
--vt-c-black: #181818;
|
|
||||||
--vt-c-black-soft: #222222;
|
|
||||||
--vt-c-black-mute: #282828;
|
|
||||||
|
|
||||||
--vt-c-indigo: #2c3e50;
|
|
||||||
|
|
||||||
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
|
||||||
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
|
||||||
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
|
||||||
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
|
||||||
|
|
||||||
--vt-c-text-light-1: var(--vt-c-indigo);
|
|
||||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
|
||||||
--vt-c-text-dark-1: var(--vt-c-white);
|
|
||||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* semantic color variables for this project */
|
|
||||||
:root {
|
|
||||||
--color-background: var(--vt-c-white);
|
|
||||||
--color-background-soft: var(--vt-c-white-soft);
|
|
||||||
--color-background-mute: var(--vt-c-white-mute);
|
|
||||||
|
|
||||||
--color-border: var(--vt-c-divider-light-2);
|
|
||||||
--color-border-hover: var(--vt-c-divider-light-1);
|
|
||||||
|
|
||||||
--color-heading: var(--vt-c-text-light-1);
|
|
||||||
--color-text: var(--vt-c-text-light-1);
|
|
||||||
|
|
||||||
--section-gap: 160px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--color-background: var(--vt-c-black);
|
|
||||||
--color-background-soft: var(--vt-c-black-soft);
|
|
||||||
--color-background-mute: var(--vt-c-black-mute);
|
|
||||||
|
|
||||||
--color-border: var(--vt-c-divider-dark-2);
|
|
||||||
--color-border-hover: var(--vt-c-divider-dark-1);
|
|
||||||
|
|
||||||
--color-heading: var(--vt-c-text-dark-1);
|
|
||||||
--color-text: var(--vt-c-text-dark-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*,
|
|
||||||
*::before,
|
|
||||||
*::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
min-height: 100vh;
|
|
||||||
color: var(--color-text);
|
|
||||||
background: var(--color-background);
|
|
||||||
transition:
|
|
||||||
color 0.5s,
|
|
||||||
background-color 0.5s;
|
|
||||||
line-height: 1.6;
|
|
||||||
font-family:
|
|
||||||
Inter,
|
|
||||||
-apple-system,
|
|
||||||
BlinkMacSystemFont,
|
|
||||||
'Segoe UI',
|
|
||||||
Roboto,
|
|
||||||
Oxygen,
|
|
||||||
Ubuntu,
|
|
||||||
Cantarell,
|
|
||||||
'Fira Sans',
|
|
||||||
'Droid Sans',
|
|
||||||
'Helvetica Neue',
|
|
||||||
sans-serif;
|
|
||||||
font-size: 15px;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
|
Before Width: | Height: | Size: 276 B |
@ -1,35 +0,0 @@
|
|||||||
@import './base.css';
|
|
||||||
|
|
||||||
#app {
|
|
||||||
max-width: 1280px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2rem;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
a,
|
|
||||||
.green {
|
|
||||||
text-decoration: none;
|
|
||||||
color: hsla(160, 100%, 37%, 1);
|
|
||||||
transition: 0.4s;
|
|
||||||
padding: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (hover: hover) {
|
|
||||||
a:hover {
|
|
||||||
background-color: hsla(160, 100%, 37%, 0.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
body {
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#app {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
padding: 0 2rem;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,92 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref, onMounted } from 'vue';
|
|
||||||
import axios from 'axios';
|
|
||||||
import type {MediaItem} from "@/api/interfaces";
|
|
||||||
import {useRouter} from "vue-router";
|
|
||||||
import router from "@/router";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
item: MediaItem;
|
|
||||||
mediaType: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const imageUrl = ref('');
|
|
||||||
const movieApiUrl = 'https://api.themoviedb.org/3/search/movie?api_key=15d2ea6d0dc1d476efbca3eba2b9bbfb&query=';
|
|
||||||
const animeApiUrl = 'https://kitsu.io/api/edge/anime?filter[text]=';
|
|
||||||
|
|
||||||
const navigateToDetails = () => {
|
|
||||||
router.push({ name: 'details', params: { item: JSON.stringify(props.item), imageUrl: imageUrl.value } });
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
imageUrl.value = "https://eapp.org/wp-content/uploads/2018/05/poster_placeholder.jpg";
|
|
||||||
if (props.item.images && props.item.images.length > 0) {
|
|
||||||
for (const image of props.item.images) {
|
|
||||||
if (image.type === "poster") {
|
|
||||||
imageUrl.value = "https://cdn.streamingcommunity.marketing/images/" + image.filename;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (props.mediaType == "film") {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(movieApiUrl + props.item.name);
|
|
||||||
if (response.data.results.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
imageUrl.value = "http://image.tmdb.org/t/p/w500/" + response.data.results[0].poster_path;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching movie image:', error);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(animeApiUrl + props.item.name);
|
|
||||||
if (response.data.data && response.data.data.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
imageUrl.value = response.data.data[0].attributes.posterImage.small;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching anime image:', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="card" @click="navigateToDetails">
|
|
||||||
<img :src="imageUrl" :alt="item.name" class="card-image" />
|
|
||||||
<div class="card-title">
|
|
||||||
{{ item.name.slice(0, 25) + (item.name.length > 24 ? '...' : '') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.card {
|
|
||||||
background-color: #313131;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
overflow: hidden;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card:hover {
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
|
||||||
transform: translateY(-2px) scale(1.02);
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-image {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-title {
|
|
||||||
padding: 12px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,105 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
modelValue: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
|
||||||
|
|
||||||
const isAnimeSelected = computed(() => props.modelValue === 'anime')
|
|
||||||
|
|
||||||
const toggleOption = () => {
|
|
||||||
emit('update:modelValue', isAnimeSelected.value ? 'film' : 'anime')
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="switch-container">
|
|
||||||
<span :style="{'color':isAnimeSelected ? '':'green', 'font-weight': isAnimeSelected ? '':'bold'}" class="switch-label-left">Film/Serie TV</span>
|
|
||||||
<label class="switch">
|
|
||||||
<input type="checkbox" :checked="isAnimeSelected" @change="toggleOption">
|
|
||||||
<span class="slider round"></span>
|
|
||||||
</label>
|
|
||||||
<span :style="{'color':isAnimeSelected ? 'green':'', 'font-weight': isAnimeSelected ? 'bold':''}" class="switch-label-right">Anime</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.switch-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-label-left,
|
|
||||||
.switch-label-right {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
width: 60px;
|
|
||||||
height: 34px;
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hide default HTML checkbox */
|
|
||||||
.switch input {
|
|
||||||
opacity: 0;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The slider */
|
|
||||||
.slider {
|
|
||||||
position: absolute;
|
|
||||||
cursor: pointer;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: #42b883;
|
|
||||||
-webkit-transition: .4s;
|
|
||||||
transition: .4s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider:before {
|
|
||||||
position: absolute;
|
|
||||||
content: "";
|
|
||||||
height: 26px;
|
|
||||||
width: 26px;
|
|
||||||
left: 4px;
|
|
||||||
bottom: 4px;
|
|
||||||
background-color: white;
|
|
||||||
-webkit-transition: .4s;
|
|
||||||
transition: .4s;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:checked + .slider {
|
|
||||||
background-color: #42b883;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:focus + .slider {
|
|
||||||
box-shadow: 0 0 1px #42b883;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:checked + .slider:before {
|
|
||||||
-webkit-transform: translateX(26px);
|
|
||||||
-ms-transform: translateX(26px);
|
|
||||||
transform: translateX(26px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Rounded sliders */
|
|
||||||
.slider.round {
|
|
||||||
border-radius: 34px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider.round:before {
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,11 +0,0 @@
|
|||||||
import './assets/main.css'
|
|
||||||
|
|
||||||
import { createApp } from 'vue'
|
|
||||||
import App from './App.vue'
|
|
||||||
import router from './router'
|
|
||||||
|
|
||||||
const app = createApp(App)
|
|
||||||
|
|
||||||
app.use(router)
|
|
||||||
|
|
||||||
app.mount('#app')
|
|
@ -1,30 +0,0 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
|
||||||
import HomeView from '../views/HomeView.vue'
|
|
||||||
import Details from "../views/Details.vue";
|
|
||||||
|
|
||||||
const router = createRouter({
|
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
name: 'home',
|
|
||||||
component: HomeView
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/details:item:imageUrl',
|
|
||||||
name: 'details',
|
|
||||||
component: Details,
|
|
||||||
props: route => {
|
|
||||||
let item;
|
|
||||||
try {
|
|
||||||
item = JSON.parse(<string>route.params.item);
|
|
||||||
} catch (error) {
|
|
||||||
item = {}; // default value
|
|
||||||
}
|
|
||||||
return { item: item, imageUrl: route.params.imageUrl };
|
|
||||||
},
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
export default router
|
|
@ -1,335 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { useRoute } from 'vue-router'
|
|
||||||
import type {Episode, MediaItem, SeasonResponse} from "@/api/interfaces";
|
|
||||||
import { onBeforeMount, onMounted, ref } from "vue";
|
|
||||||
import { getEpisodesInfo, getPreview } from "@/api/api";
|
|
||||||
import {
|
|
||||||
alertDownload,
|
|
||||||
handleMovieDownload,
|
|
||||||
handleOVADownload,
|
|
||||||
handleTVAnimeDownload,
|
|
||||||
handleTvAnimeEpisodesDownload,
|
|
||||||
handleTVDownload, handleTVEpisodesDownload
|
|
||||||
} from "@/api/utils";
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
const item: MediaItem = JSON.parse(<string>route.params.item)
|
|
||||||
const imageUrl: string = <string>route.params.imageUrl
|
|
||||||
const animeEpisodes = ref<Episode[]>([])
|
|
||||||
const totalEpisodes = ref<number>(0)
|
|
||||||
const tvShowEpisodes = ref<any[]>([])
|
|
||||||
const loading = ref(false)
|
|
||||||
const selectingEpisodes = ref(false)
|
|
||||||
const selectedEpisodes = ref<Episode[]>([])
|
|
||||||
const previewItem = ref<MediaItem>(item)
|
|
||||||
|
|
||||||
onBeforeMount(async () => {
|
|
||||||
const res = await getPreview(item.id, item.slug, item.type)
|
|
||||||
if (res && res.data) {
|
|
||||||
previewItem.plot = res.data.plot
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
loading.value = true;
|
|
||||||
if (['MOVIE', 'OVA', 'SPECIAL'].includes(item.type)) {
|
|
||||||
loading.value = false;
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
const response = await getEpisodesInfo(item.id, item.slug, item.type)
|
|
||||||
if (response && response.body) {
|
|
||||||
loading.value = false;
|
|
||||||
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
||||||
while (true) {
|
|
||||||
const {value, done} = await reader.read();
|
|
||||||
if (done) {
|
|
||||||
window.scrollTo(0, 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (item.type === 'TV_ANIME') {
|
|
||||||
const episodesData:Episode = JSON.parse(value.trim());
|
|
||||||
animeEpisodes.value.push(episodesData);
|
|
||||||
totalEpisodes.value = episodesData.episode_total;
|
|
||||||
} else {
|
|
||||||
const episodesData:SeasonResponse = JSON.parse(value.trim());
|
|
||||||
for (const seasonKey in episodesData.episodes) {
|
|
||||||
const season = episodesData.episodes[seasonKey];
|
|
||||||
const episodes:Episode[] = [];
|
|
||||||
for (const episodeKey in season) {
|
|
||||||
const episode:Episode = season[episodeKey];
|
|
||||||
episodes.push(episode);
|
|
||||||
}
|
|
||||||
tvShowEpisodes.value.push(episodes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const toggleEpisodeSelection = () => {
|
|
||||||
selectingEpisodes.value = !selectingEpisodes.value
|
|
||||||
selectedEpisodes.value = []
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleEpisodeSelect = (episode: Episode, seasonNumber?: number) => {
|
|
||||||
if (selectedEpisodes.value.includes(episode)) {
|
|
||||||
selectedEpisodes.value = selectedEpisodes.value.filter(e => e !== episode)
|
|
||||||
} else {
|
|
||||||
if (seasonNumber) {
|
|
||||||
episode.season_index = seasonNumber
|
|
||||||
}
|
|
||||||
selectedEpisodes.value.push(episode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const downloadSelectedEpisodes = async () => {
|
|
||||||
try {
|
|
||||||
switch (item.type) {
|
|
||||||
case 'TV':
|
|
||||||
await handleTVEpisodesDownload(selectedEpisodes.value, item);
|
|
||||||
case 'TV_ANIME':
|
|
||||||
await handleTvAnimeEpisodesDownload(selectedEpisodes.value, item);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error('Tipo di media non supportato');
|
|
||||||
}
|
|
||||||
toggleEpisodeSelection();
|
|
||||||
} catch (error) {
|
|
||||||
alertDownload(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const downloadAllItems = async () => {
|
|
||||||
try {
|
|
||||||
switch (item.type) {
|
|
||||||
case 'TV':
|
|
||||||
await handleTVDownload(tvShowEpisodes.value, item);
|
|
||||||
case 'MOVIE':
|
|
||||||
await handleMovieDownload(item);
|
|
||||||
break;
|
|
||||||
case 'TV_ANIME':
|
|
||||||
await handleTVAnimeDownload(totalEpisodes.value, item);
|
|
||||||
break;
|
|
||||||
case 'OVA':
|
|
||||||
case 'SPECIAL':
|
|
||||||
await handleOVADownload(item);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error('Tipo di media non supportato');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
alertDownload(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="details-container">
|
|
||||||
<div class="details-card">
|
|
||||||
|
|
||||||
<!--HEADER SECTION-->
|
|
||||||
<div class="details-header">
|
|
||||||
<img :src="imageUrl" :alt="item.name" class="details-image" />
|
|
||||||
<div class="details-title-container">
|
|
||||||
<h1 class="details-title">{{ item.name }}</h1>
|
|
||||||
<h3>★ {{ item.score }}</h3>
|
|
||||||
<div class="details-description">
|
|
||||||
<p>{{ item.plot }}</p>
|
|
||||||
</div>
|
|
||||||
<h3 v-if="animeEpisodes.length > 0 && !loading">Numero episodi: {{ totalEpisodes }}</h3>
|
|
||||||
<h3 v-if="tvShowEpisodes.length > 0 && !loading">Numero stagioni: {{ tvShowEpisodes.length }}</h3>
|
|
||||||
<hr style="opacity: 0.2; margin-top: 10px"/>
|
|
||||||
|
|
||||||
<!--DOWNLOAD SECTION-->
|
|
||||||
<div class="download-section">
|
|
||||||
<button :disabled="loading || selectingEpisodes"
|
|
||||||
@click.prevent="downloadAllItems">
|
|
||||||
Scarica {{['TV_ANIME', 'TV'].includes(item.type)? 'tutto' : ''}}
|
|
||||||
</button>
|
|
||||||
<template v-if="!loading && ['TV_ANIME', 'TV'].includes(item.type)">
|
|
||||||
<button @click="toggleEpisodeSelection">
|
|
||||||
{{selectingEpisodes ? 'Disattiva' : 'Attiva'}} selezione episodi
|
|
||||||
</button>
|
|
||||||
<button :disabled="selectedEpisodes.length == 0"
|
|
||||||
@click="downloadSelectedEpisodes">
|
|
||||||
Download episodi selezionati
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--SERIES SECTION-->
|
|
||||||
<div v-if="!loading && ['TV_ANIME', 'TV'].includes(item.type)"
|
|
||||||
:class="item.type == 'TV_ANIME' ? 'episodes-container' : 'season-container'">
|
|
||||||
<div v-if="animeEpisodes.length == 0 && tvShowEpisodes.length == 0">
|
|
||||||
<p>Non ci sono episodi...</p>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="item.type == 'TV_ANIME'"
|
|
||||||
v-for="episode in animeEpisodes"
|
|
||||||
:key="episode.id"
|
|
||||||
class="episode-item"
|
|
||||||
:style="{ backgroundColor: selectedEpisodes.includes(episode) ? '#42b883' : '#333' }"
|
|
||||||
@click="selectingEpisodes ? toggleEpisodeSelect(episode) : null">
|
|
||||||
<div class="episode-title">Episodio {{ episode.number }}</div>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="item.type == 'TV'" v-for="(season, index) in tvShowEpisodes" v-bind:key="season.number" class="season-item">
|
|
||||||
<div class="season-title">Stagione {{ index + 1 }}</div>
|
|
||||||
<div class="episode-container">
|
|
||||||
<div v-for="episode in season" :key="episode.id">
|
|
||||||
<div class="episode-item"
|
|
||||||
:style="{ backgroundColor: selectedEpisodes.includes(episode) ? '#42b883' : '#333' }"
|
|
||||||
@click="selectingEpisodes ? toggleEpisodeSelect(episode, index) : null">
|
|
||||||
<div class="episode-title">
|
|
||||||
Episodio {{ episode.number }} -
|
|
||||||
{{episode.name.slice(0, 40) + (episode.name.length > 39 ? '...' : '')}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--MOVIES SECTION-->
|
|
||||||
<div v-else-if="!loading && ['MOVIE', 'OVA', 'SPECIAL'].includes(item.type)">
|
|
||||||
<p>Questo è un {{item.type}} (QUESTO TESTO E' A SCOPO DI TEST)</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!--LOADING SECTION-->
|
|
||||||
<div v-else-if="loading">
|
|
||||||
<p>Loading...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
h3 {
|
|
||||||
padding-top: 10px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.details-container {
|
|
||||||
padding-top: 10px;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
width: 200%;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.details-card {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 1200px;
|
|
||||||
background-color: #232323;
|
|
||||||
padding: 2rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.details-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.details-image {
|
|
||||||
width: 295px;
|
|
||||||
margin-right: 2rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1008px) {
|
|
||||||
.details-container {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.details-header {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.details-image {
|
|
||||||
max-width: 100%;
|
|
||||||
margin-right: 0;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.details-title-container {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.details-title {
|
|
||||||
font-size: 2rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.details-description {
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.episodes-container {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.episode-item {
|
|
||||||
background-color: #333;
|
|
||||||
padding: 1rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.season-item {
|
|
||||||
background-color: #2a2a2a;
|
|
||||||
padding: 1rem;
|
|
||||||
margin-top: 5px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.season-item div {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.season-title {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: bold;
|
|
||||||
padding-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.episode-item:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.episode-title {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-section {
|
|
||||||
margin-top: 1rem;
|
|
||||||
flex: fit-content;
|
|
||||||
flex-direction: row;
|
|
||||||
button {
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.episodes-container {
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.season-item div {
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,176 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import search from "@/api/api";
|
|
||||||
import Toggle from "@/components/Toggle.vue";
|
|
||||||
import { ref, watch } from 'vue'
|
|
||||||
import type { MediaItem } from "@/api/interfaces";
|
|
||||||
import Card from "@/components/Card.vue";
|
|
||||||
import { onBeforeRouteLeave } from 'vue-router'
|
|
||||||
|
|
||||||
const selectedOption = ref('film')
|
|
||||||
const searchedTitle = ref('')
|
|
||||||
const searchResults = ref<MediaItem[]>([])
|
|
||||||
const loading = ref(false)
|
|
||||||
|
|
||||||
const storeSearchResults = () => {
|
|
||||||
localStorage.setItem('searchResults', JSON.stringify(searchResults.value))
|
|
||||||
localStorage.setItem('selectedOption', selectedOption.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const retrieveSearchResults = () => {
|
|
||||||
const storedResults = localStorage.getItem('searchResults')
|
|
||||||
try {
|
|
||||||
if (!storedResults) return
|
|
||||||
searchResults.value = JSON.parse(storedResults)
|
|
||||||
selectedOption.value = localStorage.getItem('selectedOption') || 'film'
|
|
||||||
} catch (e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(searchResults, storeSearchResults, { deep: true })
|
|
||||||
|
|
||||||
retrieveSearchResults()
|
|
||||||
|
|
||||||
function searchTitle() {
|
|
||||||
loading.value = true
|
|
||||||
search(searchedTitle.value, selectedOption.value).then((res) => {
|
|
||||||
searchResults.value = res.data.media
|
|
||||||
loading.value = false
|
|
||||||
}).catch((err) => {
|
|
||||||
console.log(err)
|
|
||||||
})
|
|
||||||
storeSearchResults()
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="search-container">
|
|
||||||
<div class="search-bar">
|
|
||||||
<input
|
|
||||||
v-model="searchedTitle"
|
|
||||||
v-on:keyup.enter="() => searchTitle()"
|
|
||||||
class="search-input"
|
|
||||||
type="text"
|
|
||||||
placeholder="Cerca un titolo..."
|
|
||||||
/>
|
|
||||||
<div class="toggle-button-container">
|
|
||||||
<Toggle style="margin-right: 30px" v-model="selectedOption" class="search-toggle"></Toggle>
|
|
||||||
<button @click="searchTitle">
|
|
||||||
<span v-if="!loading">Cerca</span>
|
|
||||||
<span v-else class="loader"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="searchResults && searchResults.length > 0" class="card-container">
|
|
||||||
<div v-for="result in searchResults" :key="result.id" class="card-item">
|
|
||||||
<Card :item="result" :media-type="selectedOption" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<p style="text-align: center; margin-top: 100px;">Nessun risultato trovato</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.search-container {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
background-color: #313131;
|
|
||||||
padding: 16px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-bar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 8px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-input {
|
|
||||||
flex-grow: 1;
|
|
||||||
border: none;
|
|
||||||
background-color: transparent;
|
|
||||||
outline: none;
|
|
||||||
font-size: 16px;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-button-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-toggle {
|
|
||||||
margin: 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-container {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(4, 1fr);
|
|
||||||
gap: 20px;
|
|
||||||
padding: 100px 8% 20px;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-item {
|
|
||||||
width: 250px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border: 1px solid #FFF;
|
|
||||||
border-bottom-color: transparent;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: inline-block;
|
|
||||||
box-sizing: border-box;
|
|
||||||
animation: rotation 1s linear infinite;
|
|
||||||
margin-left: 15px;
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes rotation {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.card-container {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
padding: 120px 12% 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.search-bar {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-input {
|
|
||||||
flex-basis: 100%;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-button-container {
|
|
||||||
width: 100%;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-container {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
|
||||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
|
||||||
"exclude": ["src/**/__tests__/*"],
|
|
||||||
"compilerOptions": {
|
|
||||||
"composite": true,
|
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
||||||
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["./src/*"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"files": [],
|
|
||||||
"references": [
|
|
||||||
{
|
|
||||||
"path": "./tsconfig.node.json"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "./tsconfig.app.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@tsconfig/node20/tsconfig.json",
|
|
||||||
"include": [
|
|
||||||
"vite.config.*",
|
|
||||||
"vitest.config.*",
|
|
||||||
"cypress.config.*",
|
|
||||||
"nightwatch.conf.*",
|
|
||||||
"playwright.config.*"
|
|
||||||
],
|
|
||||||
"compilerOptions": {
|
|
||||||
"composite": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
||||||
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "Bundler",
|
|
||||||
"types": ["node"]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
import { fileURLToPath, URL } from 'node:url'
|
|
||||||
|
|
||||||
import { defineConfig } from 'vite'
|
|
||||||
import vue from '@vitejs/plugin-vue'
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [
|
|
||||||
vue(),
|
|
||||||
],
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
29
kill_gui.sh
29
kill_gui.sh
@ -1,29 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Trova i processi che utilizzano la porta 8000
|
|
||||||
PROCESSES=$(lsof -i :8000 | awk 'NR!=1 {print $2}')
|
|
||||||
|
|
||||||
# Termina i processi trovati
|
|
||||||
if [ -n "$PROCESSES" ]; then
|
|
||||||
echo "Terminating processes using port 8000:"
|
|
||||||
for PID in $PROCESSES; do
|
|
||||||
echo "Killing process with PID: $PID"
|
|
||||||
kill -9 $PID
|
|
||||||
done
|
|
||||||
else
|
|
||||||
echo "No processes found using port 8000"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Trova i processi che utilizzano la porta 5173
|
|
||||||
PROCESSES=$(lsof -i :5173 | awk 'NR!=1 {print $2}')
|
|
||||||
|
|
||||||
# Termina i processi trovati
|
|
||||||
if [ -n "$PROCESSES" ]; then
|
|
||||||
echo "Terminating processes using port 5173:"
|
|
||||||
for PID in $PROCESSES; do
|
|
||||||
echo "Killing process with PID: $PID"
|
|
||||||
kill -9 $PID
|
|
||||||
done
|
|
||||||
else
|
|
||||||
echo "No processes found using port 5173"
|
|
||||||
fi
|
|
@ -2,10 +2,3 @@
|
|||||||
tqdm
|
tqdm
|
||||||
rich
|
rich
|
||||||
unidecode
|
unidecode
|
||||||
ffmpeg-python
|
|
||||||
pycryptodome
|
|
||||||
m3u8
|
|
||||||
lxml
|
|
||||||
django==4.2.11
|
|
||||||
djangorestframework==3.15.1
|
|
||||||
django-cors-headers==4.3.1
|
|
23
start_gui.sh
23
start_gui.sh
@ -1,23 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Installa i pacchetti Python
|
|
||||||
echo "Installazione dei pacchetti Python..."
|
|
||||||
pip install -r requirements.txt
|
|
||||||
|
|
||||||
# Installa i pacchetti npm
|
|
||||||
echo "Installazione dei pacchetti npm..."
|
|
||||||
cd frontend
|
|
||||||
npm install
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
# Avvia il backend Django
|
|
||||||
echo "Avvio del backend Django..."
|
|
||||||
python3.11 api/manage.py runserver &
|
|
||||||
|
|
||||||
# Avvia il frontend Vue.js con Vite
|
|
||||||
echo "Avvio del frontend Vue.js con Vite..."
|
|
||||||
cd frontend
|
|
||||||
npm run dev &
|
|
||||||
|
|
||||||
# Attendi l'esecuzione dei processi
|
|
||||||
wait
|
|
Loading…
x
Reference in New Issue
Block a user