Add delete all watchlist functionality for various streaming providers

This commit is contained in:
mhdzumair
2024-04-03 16:43:32 +05:30
parent 591b3566cb
commit 28028adb68
17 changed files with 264 additions and 72 deletions
+49 -5
View File
@@ -23,6 +23,7 @@ from api.scheduler import setup_scheduler
from db import database, crud, schemas
from db.config import settings
from scrapers.routes import router as scrapers_router
from streaming_providers import mapper
from streaming_providers.routes import router as streaming_provider_router
from utils import crypto, torrent, poster, const, wrappers
from utils.parser import generate_manifest, get_json_data
@@ -49,6 +50,15 @@ app.add_middleware(middleware.SecureLoggingMiddleware)
app.add_middleware(middleware.UserDataMiddleware)
TEMPLATES = Jinja2Templates(directory="resources")
DELETE_ALL_META = schemas.Meta(
**const.DELETE_ALL_WATCHLIST_META,
poster=f"{settings.host_url}/static/images/delete_all_poster.jpg",
background=f"{settings.host_url}/static/images/delete_all_background.png",
)
DELETE_ALL_META_ITEM = {
"meta": DELETE_ALL_META.model_dump(by_alias=True, exclude_none=True)
}
def get_user_data(request: Request) -> schemas.UserData:
@@ -226,11 +236,13 @@ async def get_catalog(
skip = int(skip) if skip and skip.isdigit() else 0
cache_key = f"{catalog_type}_{catalog_id}_{skip}_{genre}_catalog"
is_watchlist_catalog = False
if user_data.streaming_provider and catalog_id.startswith(
user_data.streaming_provider.service
):
response.headers.update(const.NO_CACHE_HEADERS)
cache_key = None
is_watchlist_catalog = True
elif catalog_type == "events":
response.headers.update(const.NO_CACHE_HEADERS)
cache_key = None
@@ -249,8 +261,23 @@ async def get_catalog(
)
else:
metas.metas.extend(
await crud.get_meta_list(user_data, catalog_type, catalog_id, skip)
await crud.get_meta_list(
user_data, catalog_type, catalog_id, is_watchlist_catalog, skip
)
)
if (
is_watchlist_catalog
and catalog_type == "movie"
and metas.metas
and mapper.DELETE_ALL_WATCHLIST_FUNCTIONS.get(
user_data.streaming_provider.service
)
):
delete_all_meta = DELETE_ALL_META.model_copy()
delete_all_meta.id = delete_all_meta.id.format(
user_data.streaming_provider.service
)
metas.metas.insert(0, delete_all_meta)
if cache_key:
await request.app.state.redis.set(
@@ -326,7 +353,12 @@ async def get_meta(
return meta_data
if catalog_type == "movie":
data = await crud.get_movie_meta(meta_id)
if meta_id.startswith("dl"):
delete_all_meta_item = DELETE_ALL_META_ITEM.copy()
delete_all_meta_item["meta"]["_id"] = meta_id
data = delete_all_meta_item
else:
data = await crud.get_movie_meta(meta_id)
elif catalog_type == "series":
data = await crud.get_series_meta(meta_id)
elif catalog_type == "events":
@@ -383,9 +415,21 @@ async def get_streams(
response.headers.update(const.DEFAULT_HEADERS)
if catalog_type == "movie":
fetched_streams = await crud.get_movie_streams(
user_data, secret_str, request.app.state.redis, video_id
)
if video_id.startswith("dl"):
if video_id == f"dl{user_data.streaming_provider.service}":
fetched_streams = [
schemas.Stream(
name=f"MediaFusion {user_data.streaming_provider.service.title()} 🗑️💩🚨",
description=f"🚨💀⚠ Delete all files in {user_data.streaming_provider.service} watchlist.",
url=f"{settings.host_url}/streaming_provider/{secret_str}/delete_all_watchlist",
)
]
else:
raise HTTPException(status_code=404, detail="Meta ID not found.")
else:
fetched_streams = await crud.get_movie_streams(
user_data, secret_str, request.app.state.redis, video_id
)
elif catalog_type == "series":
fetched_streams = await crud.get_series_streams(
user_data, secret_str, request.app.state.redis, video_id, season, episode
+12 -3
View File
@@ -41,6 +41,7 @@ async def get_meta_list(
user_data: schemas.UserData,
catalog_type: str,
catalog: str,
is_watchlist_catalog: bool,
skip: int = 0,
limit: int = 25,
) -> list[schemas.Meta]:
@@ -51,9 +52,7 @@ async def get_meta_list(
query_conditions = []
if user_data.streaming_provider and catalog.startswith(
user_data.streaming_provider.service
):
if is_watchlist_catalog:
downloaded_info_hashes = await fetch_downloaded_info_hashes(user_data)
if not downloaded_info_hashes:
return []
@@ -179,6 +178,16 @@ async def get_cached_torrent_streams(
async def get_movie_streams(
user_data, secret_str: str, redis: Redis, video_id: str
) -> list[Stream]:
if video_id.startswith("dl"):
if not video_id.endswith(user_data.streaming_provider.service):
return []
return [
schemas.Stream(
name=f"MediaFusion {user_data.streaming_provider.service.title()} 🗑️💩",
description="🚨💀⚠️\nDelete all files in streaming provider",
url=f"{settings.host_url}/streaming_provider/{secret_str}/delete_all",
)
]
streams = await get_cached_torrent_streams(redis, video_id)
if video_id.startswith("tt"):
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

+8
View File
@@ -86,3 +86,11 @@ def fetch_downloaded_info_hashes_from_ad(user_data: UserData) -> list[str]:
except ProviderException:
return []
def delete_all_torrents_from_ad(user_data: UserData):
"""Deletes all torrents from the AllDebrid account."""
ad_client = AllDebrid(token=user_data.streaming_provider.token)
torrents = ad_client.get_user_torrent_list()
for torrent in torrents["data"]["magnets"]:
ad_client.delete_torrent(torrent["id"])
+8
View File
@@ -95,3 +95,11 @@ def fetch_downloaded_info_hashes_from_dl(user_data: UserData) -> list[str]:
except ProviderException:
return []
def delete_all_torrents_from_dl(user_data: UserData):
"""Deletes all torrents from the DebridLink account."""
dl_client = DebridLink(token=user_data.streaming_provider.token)
torrents = dl_client.get_user_torrent_list()
for torrent in torrents["value"]:
dl_client.delete_torrent(torrent["id"])
+80
View File
@@ -0,0 +1,80 @@
from streaming_providers.alldebrid.utils import (
update_ad_cache_status,
fetch_downloaded_info_hashes_from_ad,
delete_all_torrents_from_ad,
)
from streaming_providers.debridlink.utils import (
update_dl_cache_status,
fetch_downloaded_info_hashes_from_dl,
delete_all_torrents_from_dl,
)
from streaming_providers.offcloud.utils import (
update_oc_cache_status,
fetch_downloaded_info_hashes_from_oc,
)
from streaming_providers.pikpak.utils import (
update_pikpak_cache_status,
fetch_downloaded_info_hashes_from_pikpak,
delete_all_torrents_from_pikpak,
)
from streaming_providers.premiumize.utils import (
update_pm_cache_status,
fetch_downloaded_info_hashes_from_premiumize,
delete_all_torrents_from_pm,
)
from streaming_providers.qbittorrent.utils import (
update_qbittorrent_cache_status,
fetch_info_hashes_from_webdav,
delete_all_torrents_from_qbittorrent,
)
from streaming_providers.realdebrid.utils import (
update_rd_cache_status,
fetch_downloaded_info_hashes_from_rd,
delete_all_watchlist_rd,
)
from streaming_providers.seedr.utils import (
update_seedr_cache_status,
fetch_downloaded_info_hashes_from_seedr,
delete_all_torrents_from_seedr,
)
from streaming_providers.torbox.utils import (
update_torbox_cache_status,
fetch_downloaded_info_hashes_from_torbox,
)
# Define provider-specific cache update functions
CACHE_UPDATE_FUNCTIONS = {
"alldebrid": update_ad_cache_status,
"debridlink": update_dl_cache_status,
"offcloud": update_oc_cache_status,
"pikpak": update_pikpak_cache_status,
"realdebrid": update_rd_cache_status,
"seedr": update_seedr_cache_status,
"torbox": update_torbox_cache_status,
"premiumize": update_pm_cache_status,
"qbittorrent": update_qbittorrent_cache_status,
}
# Define provider-specific downloaded info hashes fetch functions
FETCH_DOWNLOADED_INFO_HASHES_FUNCTIONS = {
"alldebrid": fetch_downloaded_info_hashes_from_ad,
"debridlink": fetch_downloaded_info_hashes_from_dl,
"offcloud": fetch_downloaded_info_hashes_from_oc,
"pikpak": fetch_downloaded_info_hashes_from_pikpak,
"realdebrid": fetch_downloaded_info_hashes_from_rd,
"seedr": fetch_downloaded_info_hashes_from_seedr,
"torbox": fetch_downloaded_info_hashes_from_torbox,
"premiumize": fetch_downloaded_info_hashes_from_premiumize,
"qbittorrent": fetch_info_hashes_from_webdav,
}
DELETE_ALL_WATCHLIST_FUNCTIONS = {
"alldebrid": delete_all_torrents_from_ad,
"debridlink": delete_all_torrents_from_dl,
"pikpak": delete_all_torrents_from_pikpak,
"premiumize": delete_all_torrents_from_pm,
"qbittorrent": delete_all_torrents_from_qbittorrent,
"realdebrid": delete_all_watchlist_rd,
"seedr": delete_all_torrents_from_seedr,
}
+14
View File
@@ -274,3 +274,17 @@ async def fetch_downloaded_info_hashes_from_pikpak(user_data: UserData) -> list[
return [
file["name"] for file in file_list_content["files"] if file["name"] != "My Pack"
]
async def delete_all_torrents_from_pikpak(user_data: UserData):
"""Deletes all torrents from the PikPak account."""
try:
pikpak = await initialize_pikpak(user_data)
except ProviderException:
return
file_list_content = await pikpak.file_list()
file_ids = [
file["id"] for file in file_list_content["files"] if file["name"] != "My Pack"
]
await pikpak.delete_forever(file_ids)
+5
View File
@@ -93,6 +93,11 @@ class Premiumize(DebridClient):
params={"id": folder_id} if folder_id else None,
)
def delete_folder(self, folder_id: str):
return self._make_request(
"POST", f"{self.BASE_URL}/folder/delete", data={"id": folder_id}
)
def delete_torrent(self, torrent_id):
return self._make_request(
"POST", f"{self.BASE_URL}/transfer/delete", data={"id": torrent_id}
+8
View File
@@ -125,3 +125,11 @@ def fetch_downloaded_info_hashes_from_premiumize(user_data: UserData) -> list[st
except ProviderException:
return []
def delete_all_torrents_from_pm(user_data: UserData):
"""Deletes all torrents from the Premiumize account."""
pm_client = Premiumize(token=user_data.streaming_provider.token)
folders = pm_client.get_folder_list()
for folder in folders["content"]:
pm_client.delete_folder(folder["id"])
+10 -1
View File
@@ -5,7 +5,7 @@ from os import path
from urllib.parse import urljoin, urlparse, quote
from aiohttp import ClientConnectorError
from aioqbt.api import AddFormBuilder, TorrentInfo
from aioqbt.api import AddFormBuilder, TorrentInfo, InfoFilter
from aioqbt.client import create_client, APIClient
from aioqbt.exc import LoginError, AddTorrentError
from aiowebdav.client import Client as WebDavClient
@@ -333,3 +333,12 @@ async def fetch_info_hashes_from_webdav(
]
return info_hashes
async def delete_all_torrents_from_qbittorrent(user_data: UserData):
"""Deletes all torrents from the qBittorrent server."""
async with initialize_qbittorrent(user_data) as qbittorrent:
torrents = await qbittorrent.torrents.info(filter=InfoFilter.COMPLETED)
await qbittorrent.torrents.delete(
hashes=[torrent.hash for torrent in torrents], delete_files=True
)
+8
View File
@@ -104,3 +104,11 @@ def fetch_downloaded_info_hashes_from_rd(user_data: UserData) -> list[str]:
except ProviderException:
return []
def delete_all_watchlist_rd(user_data: UserData):
"""Deletes all torrents from the RealDebrid watchlist."""
rd_client = RealDebrid(token=user_data.streaming_provider.token)
torrents = rd_client.get_user_torrent_list()
for torrent in torrents:
rd_client.delete_torrent(torrent["id"])
+42
View File
@@ -1,3 +1,4 @@
import asyncio
import logging
from fastapi import (
@@ -10,6 +11,7 @@ from fastapi.responses import RedirectResponse
from db import crud
from db.config import settings
from streaming_providers import mapper
from streaming_providers.alldebrid.utils import get_direct_link_from_alldebrid
from streaming_providers.debridlink.api import router as debridlink_router
from streaming_providers.debridlink.utils import get_direct_link_from_debridlink
@@ -109,6 +111,46 @@ async def streaming_provider_endpoint(
return RedirectResponse(url=video_url, headers=response.headers)
@router.get("/{secret_str}/delete_all_watchlist", tags=["streaming_provider"])
@wrappers.exclude_rate_limit
@wrappers.auth_required
async def delete_all_watchlist(request: Request, response: Response, secret_str: str):
response.headers.update(const.NO_CACHE_HEADERS)
user_data = request.scope.get("user", crypto.decrypt_user_data(secret_str))
if not user_data.streaming_provider:
raise HTTPException(status_code=400, detail="No streaming provider set.")
if delete_all_watchlist_function := mapper.DELETE_ALL_WATCHLIST_FUNCTIONS.get(
user_data.streaming_provider.service
):
try:
if asyncio.iscoroutinefunction(delete_all_watchlist_function):
await delete_all_watchlist_function(user_data)
else:
delete_all_watchlist_function(user_data)
video_url = f"{settings.host_url}/static/exceptions/watchlist_deleted.mp4"
except ProviderException as error:
logging.error(
"Exception occurred while deleting watchlist: %s",
error.message,
exc_info=True,
)
video_url = f"{settings.host_url}/static/exceptions/{error.video_file_name}"
except Exception as e:
logging.error(
"Exception occurred while deleting watchlist: %s", e, exc_info=True
)
video_url = f"{settings.host_url}/static/exceptions/api_error.mp4"
else:
raise HTTPException(
status_code=400, detail="Provider does not support this action."
)
return RedirectResponse(url=video_url, headers=response.headers)
router.include_router(seedr_router, prefix="/seedr", tags=["seedr"])
router.include_router(realdebrid_router, prefix="/realdebrid", tags=["realdebrid"])
router.include_router(debridlink_router, prefix="/debridlink", tags=["debridlink"])
+10
View File
@@ -229,3 +229,13 @@ def fetch_downloaded_info_hashes_from_seedr(user_data: UserData) -> list[str]:
return []
return [folder["name"] for folder in seedr.listContents()["folders"]]
def delete_all_torrents_from_seedr(user_data: UserData):
"""Deletes all torrents from the user's Seedr account."""
seedr = get_seedr_client(user_data)
for folder in seedr.listContents()["folders"]:
seedr.deleteFolder(folder["id"])
for torrent in seedr.listContents()["torrents"]:
seedr.deleteTorrent(torrent["id"])
+7
View File
@@ -129,6 +129,13 @@ STREAMING_SERVICE_REQUIREMENTS = {
"default": ["token"],
}
DELETE_ALL_WATCHLIST_META = {
"_id": "dl{}",
"title": "🗑️💩 Delete all files",
"type": "movie",
"description": "🚨💀⚠ Delete all files in streaming provider",
}
UA_HEADER = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36"
}
+3 -63
View File
@@ -10,42 +10,7 @@ from redis.asyncio import Redis
from db.config import settings
from db.models import TorrentStreams, TVStreams
from db.schemas import Stream, UserData
from streaming_providers.alldebrid.utils import (
update_ad_cache_status,
fetch_downloaded_info_hashes_from_ad,
)
from streaming_providers.debridlink.utils import (
update_dl_cache_status,
fetch_downloaded_info_hashes_from_dl,
)
from streaming_providers.offcloud.utils import (
update_oc_cache_status,
fetch_downloaded_info_hashes_from_oc,
)
from streaming_providers.pikpak.utils import (
update_pikpak_cache_status,
fetch_downloaded_info_hashes_from_pikpak,
)
from streaming_providers.premiumize.utils import (
update_pm_cache_status,
fetch_downloaded_info_hashes_from_premiumize,
)
from streaming_providers.qbittorrent.utils import (
update_qbittorrent_cache_status,
fetch_info_hashes_from_webdav,
)
from streaming_providers.realdebrid.utils import (
update_rd_cache_status,
fetch_downloaded_info_hashes_from_rd,
)
from streaming_providers.seedr.utils import (
update_seedr_cache_status,
fetch_downloaded_info_hashes_from_seedr,
)
from streaming_providers.torbox.utils import (
update_torbox_cache_status,
fetch_downloaded_info_hashes_from_torbox,
)
from streaming_providers import mapper
from utils import const
from utils.network import get_redirector_url
from utils.validation_helper import validate_m3u8_url_with_cache
@@ -55,31 +20,6 @@ ADULT_CONTENT_KEYWORDS = re.compile(
settings.adult_content_regex_keywords,
re.IGNORECASE,
)
# Define provider-specific cache update functions
CACHE_UPDATE_FUNCTIONS = {
"alldebrid": update_ad_cache_status,
"debridlink": update_dl_cache_status,
"offcloud": update_oc_cache_status,
"pikpak": update_pikpak_cache_status,
"realdebrid": update_rd_cache_status,
"seedr": update_seedr_cache_status,
"torbox": update_torbox_cache_status,
"premiumize": update_pm_cache_status,
"qbittorrent": update_qbittorrent_cache_status,
}
# Define provider-specific downloaded info hashes fetch functions
FETCH_DOWNLOADED_INFO_HASHES_FUNCTIONS = {
"alldebrid": fetch_downloaded_info_hashes_from_ad,
"debridlink": fetch_downloaded_info_hashes_from_dl,
"offcloud": fetch_downloaded_info_hashes_from_oc,
"pikpak": fetch_downloaded_info_hashes_from_pikpak,
"realdebrid": fetch_downloaded_info_hashes_from_rd,
"seedr": fetch_downloaded_info_hashes_from_seedr,
"torbox": fetch_downloaded_info_hashes_from_torbox,
"premiumize": fetch_downloaded_info_hashes_from_premiumize,
"qbittorrent": fetch_info_hashes_from_webdav,
}
async def filter_and_sort_streams(
@@ -102,7 +42,7 @@ async def filter_and_sort_streams(
return []
# Step 2: Update cache status based on provider
cache_update_function = CACHE_UPDATE_FUNCTIONS.get(
cache_update_function = mapper.CACHE_UPDATE_FUNCTIONS.get(
user_data.streaming_provider.service
if user_data.streaming_provider
else "torrent"
@@ -361,7 +301,7 @@ async def parse_tv_stream_data(
async def fetch_downloaded_info_hashes(user_data: UserData) -> list[str]:
if fetch_downloaded_info_hashes_function := FETCH_DOWNLOADED_INFO_HASHES_FUNCTIONS.get(
if fetch_downloaded_info_hashes_function := mapper.FETCH_DOWNLOADED_INFO_HASHES_FUNCTIONS.get(
user_data.streaming_provider.service
):
if asyncio.iscoroutinefunction(fetch_downloaded_info_hashes_function):