Skip to content
This repository has been archived by the owner on Nov 26, 2024. It is now read-only.

Commit

Permalink
Merge pull request #56 from LimeDrive:limedrive-TorBox
Browse files Browse the repository at this point in the history
feat: 🔥 TorBox
  • Loading branch information
LimeDrive authored Oct 20, 2024
2 parents 3153ac4 + 83824c4 commit 852c88b
Show file tree
Hide file tree
Showing 40 changed files with 1,692 additions and 659 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<p align="center" style="font-size: 1.5em; font-weight: bold;">Optimisé pour le contenu francophone</p>

<p align="center">
<img src="stream_fusion/static/logo-stream-fusion.png" alt="StreamFusion Logo"/>
<img src="stream_fusion/static/logo_anim_stream-fusion-3.gif" alt="StreamFusion Logo"/>
</p>

<p align="center">
Expand All @@ -20,6 +20,7 @@ StreamFusion est un addon avancé pour Stremio, spécialement conçu pour améli
- **Intégration de Zilean** : Indexe les hashlist de DébridMediaManager pour accéder aux contenus en cache chez les débrideurs.
- **Intégration de Real-Debrid** : Permet la redistribution des liens de streaming en direct et l'ajout de torrents depuis Stremio.
- **Intégration de AllDebrid** : Offre un accès aux liens de streaming et aux torrents via AllDebrid.
- **Intégration de TorBox** : Offre un accès aux liens de streaming et aux torrents avec TorBox.
- **Tri optimisé pour le contenu français** : Offre des résultats ciblés et de qualité, avec reconnaissance des langues et des teams.
- **Sécurité renforcée** : Protège l'application avec une clé API via une interface de gestion.

Expand Down
1 change: 1 addition & 0 deletions docs/en/StreamFusion/streamfusion.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ StreamFusion is an advanced addon for Stremio, specially designed to enhance the
- **Zilean integration**: Indexes DébridMediaManager hashlists to access cached content from debridders.
- **Real-Debrid integration**: Allows direct redistribution of streaming links and torrent addition from Stremio.
- **AllDebrid integration**: Provides access to streaming links and torrents via AllDebrid.
- **TorBox integration**: Provides access to streaming links and torrents via TorBox.
- **Optimized sorting for French content**: Offers targeted and quality results, with language and team recognition.
- **Enhanced security**: Protects the application with an API key via a management interface.

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion docs/fr/StreamFusion/streamfusion.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<p align="center">
<img src="../images/logo-stream-fusion.png" alt="StreamFusion">
<img src="../images/dark_logo_streamfusion.png" alt="StreamFusion">
</p>

<h1 align="center">Présentation de StreamFusion</h1>
Expand All @@ -11,6 +11,7 @@ StreamFusion est un addon avancé pour Stremio, spécialement conçu pour améli
- **Intégration de Zilean** : Indexe les hashlist de DébridMediaManager pour accéder aux contenus en cache chez les débrideurs.
- **Intégration de Real-Debrid** : Permet la redistribution des liens de streaming en direct et l'ajout de torrents depuis Stremio.
- **Intégration de AllDebrid** : Offre un accès aux liens de streaming et aux torrents via AllDebrid.
- **Intégration de TorBox** : Offre un accès aux liens de streaming et aux torrents avec TorBox.
- **Tri optimisé pour le contenu français** : Offre des résultats ciblés et de qualité, avec reconnaissance des langues et des teams.
- **Sécurité renforcée** : Protège l'application avec une clé API via une interface de gestion.

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "stream-fusion"
version = "2.1.1"
version = "2.2.0"
description = "StreamFusion is an advanced plugin for Stremio that significantly enhances its streaming capabilities with debrid service."
authors = ["LimeDrive <[email protected]>"]
readme = "README.md"
Expand Down
7 changes: 0 additions & 7 deletions stream_fusion/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,12 @@
NO_CONFIG = {'streams': [{'url': "#", 'title': "No configuration found"}]}
JACKETT_ERROR = {'streams': [{'url': "#", 'title': "An error occured"}]}

# CACHER_URL = "https://stremio-jackett-cacher.elfhosted.com/"

NO_CACHE_HEADERS = {
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
"Pragma": "no-cache",
"Expires": "0",
}

NO_CACHE_VIDEO_URL = "https://github.com/aymene69/stremio-jackett/raw/main/source/videos/nocache.mp4"

EXCLUDED_TRACKERS = ['0day.kiev', '1ptbar', '2 Fast 4 You', '2xFree', '3ChangTrai', '3D Torrents', '3Wmg', '4thD',
'52PT', '720pier', 'Abnormal', 'ABtorrents', 'Acid-Lounge', 'Across The Tasman', 'Aftershock',
'AGSVPT', 'Aidoru!Online', 'Aither (API)', 'AlphaRatio', 'Amigos Share Club', 'AniDUB',
Expand Down Expand Up @@ -122,9 +118,6 @@
"FRENCH": r"\b(?:FRENCH|FR)\b",
}

# REDIS_HOST = 'redis'
# REDIS_PORT = 6379

class CustomException(Exception):
def __init__(self, status_code: int, message: Any):
self.status_code = status_code
Expand Down
70 changes: 24 additions & 46 deletions stream_fusion/services/postgresql/dao/torrentitem_dao.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,21 @@ async def create_torrent_item(self, torrent_item: TorrentItem, id: str) -> Torre
await self.session.flush()
await self.session.refresh(new_item)

logger.success(f"Created new TorrentItem: {new_item.id}")
logger.debug(f"TorrentItemDAO: Created new TorrentItem: {new_item.id}")
return new_item
except Exception as e:
logger.error(f"Error creating TorrentItem: {str(e)}")
logger.error(f"TorrentItemDAO: Error creating TorrentItem: {str(e)}")

async def get_all_torrent_items(self, limit: int, offset: int) -> List[TorrentItemModel]:
async with self.session.begin():
try:
query = select(TorrentItemModel).limit(limit).offset(offset)
result = await self.session.execute(query)
items = result.scalars().all()
logger.info(f"Retrieved {len(items)} TorrentItems")
logger.debug(f"TorrentItemDAO: Retrieved {len(items)} TorrentItems")
return items
except Exception as e:
logger.error(f"Error retrieving TorrentItems: {str(e)}")

logger.error(f"TorrentItemDAO: Error retrieving TorrentItems: {str(e)}")

async def get_torrent_item_by_id(self, item_id: str) -> Optional[TorrentItemModel]:
async with self.session.begin():
Expand All @@ -48,13 +47,13 @@ async def get_torrent_item_by_id(self, item_id: str) -> Optional[TorrentItemMode
result = await self.session.execute(query)
db_item = result.scalar_one_or_none()
if db_item:
logger.info(f"Retrieved TorrentItem: {item_id}")
logger.debug(f"TorrentItemDAO: Retrieved TorrentItem: {item_id}")
return db_item
else:
logger.info(f"TorrentItem not found: {item_id}")
logger.debug(f"TorrentItemDAO: TorrentItem not found: {item_id}")
return None
except Exception as e:
logger.error(f"Error retrieving TorrentItem {item_id}: {str(e)}")
logger.error(f"TorrentItemDAO: Error retrieving TorrentItem {item_id}: {str(e)}")
return None

async def update_torrent_item(self, item_id: str, torrent_item: TorrentItem) -> TorrentItemModel:
Expand All @@ -65,7 +64,7 @@ async def update_torrent_item(self, item_id: str, torrent_item: TorrentItem) ->
db_item = result.scalar_one_or_none()

if not db_item:
logger.warning(f"TorrentItem not found for update: {item_id}")
logger.warning(f"TorrentItemDAO: TorrentItem not found for update: {item_id}")
return None

# Update fields
Expand All @@ -76,10 +75,10 @@ async def update_torrent_item(self, item_id: str, torrent_item: TorrentItem) ->
await self.session.flush()
await self.session.refresh(db_item)

logger.info(f"Updated TorrentItem: {item_id}")
logger.debug(f"TorrentItemDAO: Updated TorrentItem: {item_id}")
return db_item
except Exception as e:
logger.error(f"Error updating TorrentItem {item_id}: {str(e)}")
logger.error(f"TorrentItemDAO: Error updating TorrentItem {item_id}: {str(e)}")
return None

async def delete_torrent_item(self, item_id: str) -> bool:
Expand All @@ -91,13 +90,13 @@ async def delete_torrent_item(self, item_id: str) -> bool:

if db_item:
await self.session.delete(db_item)
logger.info(f"Deleted TorrentItem: {item_id}")
logger.debug(f"TorrentItemDAO: Deleted TorrentItem: {item_id}")
return True
else:
logger.warning(f"TorrentItem not found for deletion: {item_id}")
logger.warning(f"TorrentItemDAO: TorrentItem not found for deletion: {item_id}")
return False
except Exception as e:
logger.error(f"Error deleting TorrentItem {item_id}: {str(e)}")
logger.error(f"TorrentItemDAO: Error deleting TorrentItem {item_id}: {str(e)}")
return False

async def get_torrent_items_by_info_hash(self, info_hash: str) -> List[TorrentItemModel]:
Expand All @@ -106,10 +105,10 @@ async def get_torrent_items_by_info_hash(self, info_hash: str) -> List[TorrentIt
query = select(TorrentItemModel).where(TorrentItemModel.info_hash == info_hash)
result = await self.session.execute(query)
items = result.scalars().all()
logger.info(f"Retrieved {len(items)} TorrentItems with info_hash: {info_hash}")
logger.debug(f"TorrentItemDAO: Retrieved {len(items)} TorrentItems with info_hash: {info_hash}")
return items
except Exception as e:
logger.error(f"Error retrieving TorrentItems by info_hash {info_hash}: {str(e)}")
logger.error(f"TorrentItemDAO: Error retrieving TorrentItems by info_hash {info_hash}: {str(e)}")
return None

async def get_torrent_items_by_indexer(self, indexer: str) -> List[TorrentItemModel]:
Expand All @@ -118,10 +117,10 @@ async def get_torrent_items_by_indexer(self, indexer: str) -> List[TorrentItemMo
query = select(TorrentItemModel).where(TorrentItemModel.indexer == indexer)
result = await self.session.execute(query)
items = result.scalars().all()
logger.info(f"Retrieved {len(items)} TorrentItems from indexer: {indexer}")
logger.debug(f"TorrentItemDAO: Retrieved {len(items)} TorrentItems from indexer: {indexer}")
return items
except Exception as e:
logger.error(f"Error retrieving TorrentItems by indexer {indexer}: {str(e)}")
logger.error(f"TorrentItemDAO: Error retrieving TorrentItems by indexer {indexer}: {str(e)}")
return None

async def is_torrent_item_cached(self, item_id: str) -> bool:
Expand All @@ -131,10 +130,10 @@ async def is_torrent_item_cached(self, item_id: str) -> bool:
result = await self.session.execute(query)
count = result.scalar_one()
is_cached = count > 0
logger.info(f"TorrentItem {item_id} {'is' if is_cached else 'is not'} in cache")
logger.debug(f"TorrentItemDAO: TorrentItem {item_id} {'is' if is_cached else 'is not'} in cache")
return is_cached
except Exception as e:
logger.error(f"Error checking if TorrentItem {item_id} is cached: {str(e)}")
logger.error(f"TorrentItemDAO: Error checking if TorrentItem {item_id} is cached: {str(e)}")
return None

async def get_torrent_items_by_type(self, item_type: str) -> List[TorrentItemModel]:
Expand All @@ -143,10 +142,10 @@ async def get_torrent_items_by_type(self, item_type: str) -> List[TorrentItemMod
query = select(TorrentItemModel).where(TorrentItemModel.type == item_type)
result = await self.session.execute(query)
items = result.scalars().all()
logger.info(f"Retrieved {len(items)} TorrentItems of type: {item_type}")
logger.debug(f"TorrentItemDAO: Retrieved {len(items)} TorrentItems of type: {item_type}")
return items
except Exception as e:
logger.error(f"Error retrieving TorrentItems by type {item_type}: {str(e)}")
logger.error(f"TorrentItemDAO: Error retrieving TorrentItems by type {item_type}: {str(e)}")
return None

async def get_torrent_items_by_availability(self, available: bool) -> List[TorrentItemModel]:
Expand All @@ -155,29 +154,8 @@ async def get_torrent_items_by_availability(self, available: bool) -> List[Torre
query = select(TorrentItemModel).where(TorrentItemModel.availability == available)
result = await self.session.execute(query)
items = result.scalars().all()
logger.info(f"Retrieved {len(items)} TorrentItems with availability: {available}")
logger.debug(f"TorrentItemDAO: Retrieved {len(items)} TorrentItems with availability: {available}")
return items
except Exception as e:
logger.error(f"Error retrieving TorrentItems by availability {available}: {str(e)}")
return None

# async def update_torrent_item_availability(self, item_id: str, available: bool) -> bool:
# async with self.session.begin():
# try:
# query = select(TorrentItemModel).where(TorrentItemModel.id == item_id)
# result = await self.session.execute(query)
# db_item = result.scalar_one_or_none()

# if not db_item:
# logger.warning(f"TorrentItem not found for availability update: {item_id}")
# return False

# db_item.availability = available
# db_item.updated_at = int(datetime.now(timezone.utc).timestamp())
# await self.session.flush()

# logger.info(f"Updated availability for TorrentItem {item_id}: {available}")
# return True
# except Exception as e:
# logger.error(f"Error updating availability for TorrentItem {item_id}: {str(e)}")
# raise HTTPException(status_code=500, detail="Internal server error")
logger.error(f"TorrentItemDAO: Error retrieving TorrentItems by availability {available}: {str(e)}")
return None
61 changes: 50 additions & 11 deletions stream_fusion/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,26 @@ class LogLevel(str, enum.Enum):
ERROR = "ERROR"
FATAL = "FATAL"


class DebridService(str, enum.Enum):
"""Possible debrid services."""

RD = "RD"
AD = "AD"
TB = "TB"


class NoCacheVideoLanguages(str, enum.Enum):
"""Possible languages for which to not cache video results."""

FR = "https://github.com/LimeDrive/stream-fusion/raw/refs/heads/limedrive-TorBox/stream_fusion/static/videos/fr_download_video.mp4"
EN = "https://github.com/LimeDrive/stream-fusion/raw/refs/heads/limedrive-TorBox/stream_fusion/static/videos/en_download_video.mp4"

@classmethod
def get_url(cls, language):
"""Get the video URL for a given language."""
return cls[language.upper()].value


def get_default_worker_count():
"""
Expand All @@ -29,13 +44,15 @@ def get_default_worker_count():
"""
return min(max(multiprocessing.cpu_count() * 2, 2), 6)


def check_env_variable(var_name):
value = os.getenv(var_name.upper())

if value and isinstance(value, str) and len(value.strip()) >= 10:
return True
return False


class Settings(BaseSettings):
"""Settings for the application"""

Expand All @@ -53,25 +70,39 @@ class Settings(BaseSettings):
)
)
use_https: bool = False
default_debrid_service: DebridService = DebridService.RD
download_service: DebridService = DebridService.TB
no_cache_video_language: NoCacheVideoLanguages = NoCacheVideoLanguages.FR

# PROXY
proxied_link: bool = check_env_variable("RD_TOKEN") or check_env_variable("AD_TOKEN")
proxied_link: bool = check_env_variable("RD_TOKEN") or check_env_variable(
"AD_TOKEN"
)
proxy_url: str | URL | None = None
playback_proxy: bool | None = (
None # If set, the link will be proxied through the given proxy.
)
proxy_buffer_size: int = 1024 * 1024

# REALDEBRID
rd_token: str | None = None
rd_unique_account: bool = check_env_variable("RD_TOKEN")
rd_base_url: str = "https://api.real-debrid.com/rest"
rd_api_version: str = "1.0"

# ALLDEBRID
ad_token: str | None = None
ad_unique_account: bool = check_env_variable("AD_TOKEN")
ad_user_app: str = "streamfusion"
ad_user_ip: str | None = None
ad_use_proxy: bool = check_env_variable("PROXY_URL")
ad_base_url: str = "https://api.alldebrid.com"
ad_api_version: str = "v4"

# TORBOX
tb_token: str | None = None
tb_unique_account: bool = check_env_variable("TB_TOKEN")
tb_base_url: str = "https://api.torbox.app"
tb_api_version: str = "v1"

# LOGGING
log_level: LogLevel = LogLevel.INFO
Expand All @@ -82,12 +113,12 @@ class Settings(BaseSettings):
secret_api_key: str | None = None
security_hide_docs: bool = True

# POSTGRESQL_DB
# POSTGRESQL_DB
# TODO: Change the values, but break dev environment
pg_host: str = "stremio-postgres"
pg_port: int = 5432
pg_user: str = "streamfusion" #"stremio"
pg_pass: str = "streamfusion" #"stremio"
pg_user: str = "streamfusion" # "stremio"
pg_pass: str = "streamfusion" # "stremio"
pg_base: str = "streamfusion"
pg_echo: bool = False

Expand Down Expand Up @@ -139,15 +170,15 @@ class Settings(BaseSettings):
develop: bool = False
reload: bool = False

@field_validator('proxy_url')
@field_validator("proxy_url")
@classmethod
def validate_and_create_proxy_url(cls, v: str | None) -> URL | None:
if v is None:
return None
v = v.strip('"\'')
if not v.startswith(('http://', 'https://')):
v = 'http://' + v

v = v.strip("\"'")
if not v.startswith(("http://", "https://")):
v = "http://" + v
try:
return URL(v)
except ValueError as e:
Expand All @@ -168,6 +199,7 @@ def pg_url(self) -> URL:
password=self.pg_pass,
path=f"/{self.pg_base}",
)

@property
def jackett_url(self) -> URL:
"""
Expand Down Expand Up @@ -214,6 +246,13 @@ def redis_url(self) -> URL:
env_file=".env", secrets_dir="/run/secrets", env_file_encoding="utf-8"
)

@property
def no_cache_video_url(self) -> str:
"""
Get the URL for the no-cache video based on the selected language.
"""
return self.no_cache_video_language.value


try:
settings = Settings()
Expand Down
Loading

0 comments on commit 852c88b

Please sign in to comment.