Improve organization

This commit is contained in:
Ghost 2024-04-06 15:14:18 +02:00
parent 92c35b489a
commit bf7153214f
32 changed files with 708 additions and 144 deletions

View File

@ -7,7 +7,7 @@
This repository provide a simple script designed to facilitate the downloading of films and series from a popular streaming community platform. The script allows users to download individual films, entire series, or specific episodes, providing a seamless experience for content consumers.
## Join us
You can chat, help improve this repo, or just hang around for some fun in the **Git_StreamingCommunity** Discord [Server](https://discord.com/invite/qP3nsCXV5z)
You can chat, help improve this repo, or just hang around for some fun in the **Git_StreamingCommunity** Discord [Server](https://discord.gg/we8n4tfxFs)
# Table of Contents
* [INSTALLATION](#installation)

View File

@ -21,6 +21,7 @@ class Episode:
self.created_at: str = data.get('created_at', '')
self.updated_at: str = data.get('updated_at', '')
class EpisodeManager:
def __init__(self):
"""

View File

@ -1,6 +1,5 @@
# 03.03.24
# Import
from typing import List
class Image:

View File

@ -2,6 +2,7 @@
from typing import List, Dict, Union
class Title:
def __init__(self, title_data: Dict[str, Union[int, str, None]]):
"""
@ -20,6 +21,7 @@ class Title:
self.updated_at: str = title_data.get('updated_at')
self.episodes_count: int = title_data.get('episodes_count')
class TitleManager:
def __init__(self):
"""

View File

@ -1,22 +1,26 @@
# 01.03.24
# Class import
from Src.Util.headers import get_headers
from .SeriesType import TitleManager
from .EpisodeType import EpisodeManager
from .WindowType import WindowVideo, WindowParameter
# Import
import requests
import re
import json
import binascii
import logging
import sys
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlencode, quote
# External libraries
import requests
from bs4 import BeautifulSoup
# Internal utilities
from Src.Util.headers import get_headers
from .SeriesType import TitleManager
from .EpisodeType import EpisodeManager
from .WindowType import WindowVideo, WindowParameter
class VideoSource:
def __init__(self):
"""

View File

@ -2,6 +2,7 @@
from typing import Dict, Any
class WindowVideo:
def __init__(self, data: Dict[str, Any]):
"""
@ -25,6 +26,7 @@ class WindowVideo:
self.folder_id: int = data.get('folder_id', '')
self.created_at_diff: str = data.get('created_at_diff', '')
class WindowParameter:
def __init__(self, data: Dict[str, Any]):
"""

View File

@ -1,16 +1,20 @@
# 11.03.24
# Class import
import os
import logging
# External libraries
import requests
# Internal utilities
from Src.Util.console import console, msg
from Src.Util.config import config_manager
from Src.Lib.FFmpeg.my_m3u8 import Downloader
from Src.Util.message import start_message
from .Class import VideoSource
# General import
import os
import logging
import requests
# Config
ROOT_PATH = config_manager.get('DEFAULT', 'root_path')
@ -20,6 +24,7 @@ SERIES_FOLDER = config_manager.get('DEFAULT', 'series_folder_name')
URL_SITE_NAME = config_manager.get('SITE', 'anime_site_name')
SITE_DOMAIN = config_manager.get('SITE', 'anime_domain')
# Variable
video_source = VideoSource()

View File

@ -1,21 +1,23 @@
# 3.12.23 -> 10.12.23
# Class import
import os
import logging
# Internal utilities
from Src.Util.console import console
from Src.Util.config import config_manager
from Src.Lib.FFmpeg.my_m3u8 import Downloader
from Src.Util.message import start_message
from .Class import VideoSource
# General import
import os
import logging
# Config
ROOT_PATH = config_manager.get('DEFAULT', 'root_path')
MOVIE_FOLDER = config_manager.get('DEFAULT', 'movies_folder_name')
STREAM_SITE_NAME = config_manager.get('SITE', 'streaming_site_name')
# Variable
video_source = VideoSource()
video_source.set_url_base_name(STREAM_SITE_NAME)

View File

@ -1,6 +1,11 @@
# 3.12.23 -> 10.12.23
# Class import
import os
import sys
import logging
# Internal utilities
from Src.Util.console import console, msg
from Src.Util.config import config_manager
from Src.Util.table import TVShowManager
@ -9,16 +14,13 @@ from Src.Lib.Unidecode import transliterate
from Src.Lib.FFmpeg.my_m3u8 import Downloader
from .Class import VideoSource
# General import
import os
import logging
import sys
# Config
ROOT_PATH = config_manager.get('DEFAULT', 'root_path')
SERIES_FOLDER = config_manager.get('DEFAULT', 'series_folder_name')
STREAM_SITE_NAME = config_manager.get('SITE', 'streaming_site_name')
# Variable
video_source = VideoSource()
video_source.set_url_base_name(STREAM_SITE_NAME)
@ -56,6 +58,7 @@ def manage_selection(cmd_insert: str, max_count: int) -> list[int]:
# Return list of selected seasons)
return list_season_select
def display_episodes_list() -> str:
"""
Display episodes list and handle user input.
@ -133,6 +136,7 @@ def donwload_video(tv_name: str, index_season_selected: int, index_episode_selec
logging.error(f"(donwload_video) Error downloading film: {e}")
pass
def donwload_episode(tv_name: str, index_season_selected: int, donwload_all: bool = False) -> None:
"""
Download all episodes of a season.
@ -174,6 +178,7 @@ def donwload_episode(tv_name: str, index_season_selected: int, donwload_all: boo
for i_episode in list_episode_select:
donwload_video(tv_name, index_season_selected, i_episode)
def download_series(tv_id: str, tv_name: str, version: str, domain: str) -> None:
"""
Download all episodes of a TV series.

View File

@ -1,26 +1,32 @@
# 10.12.23
# Class import
import sys
import json
import logging
# External libraries
import requests
from bs4 import BeautifulSoup
# Internal utilities
from Src.Util.table import TVShowManager
from Src.Util.headers import get_headers
from Src.Util.console import console
from Src.Util.config import config_manager
from .Class import MediaManager, MediaItem
# General import
import sys
import json
import logging
import requests
from bs4 import BeautifulSoup
# Config
GET_TITLES_OF_MOMENT = config_manager.get_bool('DEFAULT', 'get_moment_title')
# Variable
media_search_manager = MediaManager()
table_show_manager = TVShowManager()
def get_token(site_name: str, domain: str) -> dict:
"""
Function to retrieve session tokens from a specified website.
@ -61,6 +67,7 @@ def get_token(site_name: str, domain: str) -> dict:
'csrf_token': find_csrf_token
}
def get_moment_titles(domain: str, version: str, prefix: str):
"""
Retrieves the title name from a specified domain using the provided version and prefix.
@ -100,6 +107,7 @@ def get_moment_titles(domain: str, version: str, prefix: str):
logging.error("Error occurred: %s", str(e))
return None
def get_domain() -> str:
"""
Fetches the domain from a Telegra.ph API response.
@ -123,6 +131,7 @@ def get_domain() -> str:
logging.error(f"Error fetching domain: {e}")
sys.exit(0)
def test_site(domain: str) -> str:
"""
Tests the availability of a website.
@ -151,6 +160,7 @@ def test_site(domain: str) -> str:
logging.error(f"Error testing site: {e}")
return None
def get_version(text: str) -> str:
"""
Extracts the version from the HTML text of a webpage.
@ -174,6 +184,7 @@ def get_version(text: str) -> str:
logging.error(f"Error extracting version: {e}")
sys.exit(0)
def get_version_and_domain() -> tuple[str, str]:
"""
Retrieves the version and domain of a website.
@ -215,6 +226,7 @@ def get_version_and_domain() -> tuple[str, str]:
logging.error(f"Error getting version and domain: {e}")
sys.exit(0)
def search(title_search: str, domain: str) -> int:
"""
Search for titles based on a search query.
@ -237,6 +249,7 @@ def search(title_search: str, domain: str) -> int:
# Return the number of titles found
return media_search_manager.get_length()
def update_domain_anime():
"""
Update the domain for anime streaming site.
@ -268,6 +281,7 @@ def update_domain_anime():
# Extract the domain from the URL and update the config
config_manager.set_key('SITE', 'anime_domain', new_site_url.split(".")[-1])
def anime_search(title_search: str) -> int:
"""
Function to perform an anime search using a provided title.
@ -322,6 +336,7 @@ def anime_search(title_search: str) -> int:
# Return the length of media search manager
return media_search_manager.get_length()
def get_select_title() -> MediaItem:
"""
Display a selection of titles and prompt the user to choose one.

View File

@ -1,6 +1,5 @@
# 5.01.24 -> 7.01.24 -> 20.02.24 -> 29.03.24
# Importing modules
import os
import sys
import time
@ -8,16 +7,19 @@ import threading
import logging
import warnings
# Disable specific warnings
from tqdm import TqdmExperimentalWarning
warnings.filterwarnings("ignore", category=TqdmExperimentalWarning)
warnings.filterwarnings("ignore", category=UserWarning, module="cryptography")
# External libraries
import requests
from tqdm.rich import tqdm
from concurrent.futures import ThreadPoolExecutor, as_completed
# Internal utilities
from Src.Util.console import console
from Src.Util.headers import get_headers
@ -30,6 +32,7 @@ from Src.Util.os import (
convert_to_hex
)
# Logic class
from .util import (
print_duration_table,
@ -44,6 +47,7 @@ from .util import (
M3U8_UrlFix
)
# Config
Download_audio = config_manager.get_bool('M3U8_OPTIONS', 'download_audio')
Donwload_subtitles = config_manager.get_bool('M3U8_OPTIONS', 'download_subtitles')
@ -59,12 +63,12 @@ TQDM_SHOW_PROGRESS = config_manager.get_bool('M3U8', 'tqdm_show_progress')
MIN_TS_FILES_IN_FOLDER = config_manager.get_int('M3U8', 'minimum_ts_files_in_folder')
REMOVE_SEGMENTS_FOLDER = config_manager.get_bool('M3U8', 'cleanup_tmp_folder')
# Variable
config_headers = config_manager.get_dict('M3U8_OPTIONS', 'request')
failed_segments = []
class_urlFix = M3U8_UrlFix()
# [ main class ]
class M3U8_Segments:
def __init__(self, url, folder, key=None):

View File

@ -1,14 +1,15 @@
# 03.04.24
# Import
import subprocess
import logging
import os
# External library
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
class AES_ECB:
def __init__(self, key: bytes) -> None:
"""
@ -49,6 +50,7 @@ class AES_ECB:
decrypted_data = cipher.decrypt(ciphertext)
return unpad(decrypted_data, AES.block_size)
class AES_CBC:
def __init__(self, key: bytes, iv: bytes) -> None:
"""
@ -91,6 +93,7 @@ class AES_CBC:
decrypted_data = cipher.decrypt(ciphertext)
return unpad(decrypted_data, AES.block_size)
class AES_CTR:
def __init__(self, key: bytes, nonce: bytes) -> None:
"""
@ -132,6 +135,7 @@ class AES_CTR:
cipher = AES.new(self.key, AES.MODE_CTR, nonce=self.nonce)
return cipher.decrypt(ciphertext)
class M3U8_Decryption:
def __init__(self, key: bytes, iv: bytes = None) -> None:
"""

View File

@ -1,16 +1,20 @@
# 31.01.24
# Class
from Src.Util.console import console
# Import
import ffmpeg
import subprocess
import os
import json
import logging
import shutil
# External libraries
import ffmpeg
# Internal utilities
from Src.Util.console import console
def has_audio_stream(video_path: str) -> bool:
"""
Check if the input video has an audio stream.
@ -37,6 +41,7 @@ def has_audio_stream(video_path: str) -> bool:
logging.error(f"Error: {e.stderr}")
return None
def get_video_duration(file_path: str) -> (float):
"""
Get the duration of a video file.
@ -64,6 +69,7 @@ def get_video_duration(file_path: str) -> (float):
logging.error(f"Error: {e.stderr}")
return None
def format_duration(seconds: float) -> list[int, int, int]:
"""
Format duration in seconds into hours, minutes, and seconds.
@ -80,6 +86,7 @@ def format_duration(seconds: float) -> list[int, int, int]:
return int(hours), int(minutes), int(seconds)
def print_duration_table(file_path: str) -> None:
"""
Print duration of a video file in hours, minutes, and seconds.
@ -98,7 +105,7 @@ def print_duration_table(file_path: str) -> None:
# Print the formatted duration
console.log(f"[cyan]Info [green]'{file_path}': [purple]{int(hours)}[red]h [purple]{int(minutes)}[red]m [purple]{int(seconds)}[red]s")
# SINGLE SUBTITLE
def add_subtitle(input_video_path: str, input_subtitle_path: str, output_video_path: str, subtitle_language: str = 'ita', prefix: str = "single_sub") -> str:
"""
Convert a video with a single subtitle.
@ -153,7 +160,7 @@ def add_subtitle(input_video_path: str, input_subtitle_path: str, output_video_p
# Return
return output_video_path
# SEGMENTS
def concatenate_and_save(file_list_path: str, output_filename: str, video_decoding: str = None, audio_decoding: str = None, prefix: str = "segments", output_directory: str = None) -> str:
"""
Concatenate input files and save the output with specified decoding parameters.
@ -222,7 +229,7 @@ def concatenate_and_save(file_list_path: str, output_filename: str, video_decodi
# Return
return output_file_path
# AUDIOS
def join_audios(video_path: str, audio_tracks: list[dict[str, str]], prefix: str = "merged") -> str:
"""
Join video with multiple audio tracks and sync them if there are matching segments.
@ -300,7 +307,7 @@ def join_audios(video_path: str, audio_tracks: list[dict[str, str]], prefix: str
logging.error("[M3U8_Downloader] Ffmpeg error: %s", ffmpeg_error)
return ""
# SUBTITLES
def transcode_with_subtitles(video: str, subtitles_list: list[dict[str, str]], output_file: str, prefix: str = "transcoded") -> str:
"""

View File

@ -1,9 +1,5 @@
# 24.01.2023
# Class
from Src.Util.console import console
# Import
import subprocess
import os
import requests
@ -12,7 +8,10 @@ import sys
import ctypes
# [ func ]
# Internal utilities
from Src.Util.console import console
def isAdmin() -> (bool):
"""
Check if the current user has administrative privileges.
@ -29,6 +28,7 @@ def isAdmin() -> (bool):
return is_admin
def get_version():
"""
Get the version of FFmpeg installed on the system.
@ -55,6 +55,7 @@ def get_version():
print("Error executing FFmpeg command:", e.output.strip())
raise e
def download_ffmpeg():
"""
Download FFmpeg binary for Windows and add it to the system PATH.
@ -102,6 +103,7 @@ def download_ffmpeg():
print(f"Failed to extract FFmpeg zip file: {e}")
raise e
def check_ffmpeg():
"""
Check if FFmpeg is installed and available on the system PATH.

View File

@ -1,7 +1,9 @@
# 29.02.24
# Internal utilities
from Src.Util.os import format_size
class M3U8_Ts_Files:
def __init__(self):
"""

View File

@ -1,12 +1,16 @@
# 29.04.25
# Class import
import logging
# Internal utilities
from Src.Util.headers import get_headers
# Import
from m3u8 import M3U8
import logging
# External libraries
import requests
from m3u8 import M3U8
class M3U8_Parser:
def __init__(self, DOWNLOAD_SPECIFIC_SUBTITLE = None):

View File

@ -1,10 +1,10 @@
# 29.03.24
# Import
import logging
import sys
import logging
from urllib.parse import urlparse, urljoin
class M3U8_UrlFix:
def __init__(self) -> None:

View File

@ -0,0 +1,360 @@
# 04.4.24
import base64
import json
import logging
import ssl
import time
import re
import urllib.parse
import urllib.request
import urllib.error
from typing import Dict, Optional, Union, Unpack
# Constants
HTTP_TIMEOUT = 4
HTTP_RETRIES = 2
HTTP_DELAY = 1
class RequestError(Exception):
"""Custom exception class for request errors."""
def __init__(self, message: str, original_exception: Optional[Exception] = None) -> None:
"""
Initialize a RequestError instance.
Args:
message (str): The error message.
original_exception (Optional[Exception], optional): The original exception that occurred. Defaults to None.
"""
super().__init__(message)
self.original_exception = original_exception
def __str__(self) -> str:
"""Return a string representation of the exception."""
if self.original_exception:
return f"{super().__str__()} Original Exception: {type(self.original_exception).__name__}: {str(self.original_exception)}"
else:
return super().__str__()
class Response:
"""Class representing an HTTP response."""
def __init__(
self,
status: int,
text: str,
is_json: bool = False,
content: bytes = b"",
headers: Optional[Dict[str, str]] = None,
cookies: Optional[Dict[str, str]] = None,
redirect_url: Optional[str] = None,
response_time: Optional[float] = None,
timeout: Optional[float] = None,
):
"""
Initialize a Response object.
Args:
status (int): The HTTP status code of the response.
text (str): The response content as text.
is_json (bool, optional): Indicates if the response content is JSON. Defaults to False.
content (bytes, optional): The response content as bytes. Defaults to b"".
headers (Optional[Dict[str, str]], optional): The response headers. Defaults to None.
cookies (Optional[Dict[str, str]], optional): The cookies set in the response. Defaults to None.
redirect_url (Optional[str], optional): The URL if a redirection occurred. Defaults to None.
response_time (Optional[float], optional): The time taken to receive the response. Defaults to None.
timeout (Optional[float], optional): The request timeout. Defaults to None.
"""
self.status_code = status
self.text = text
self.is_json = is_json
self.content = content
self.headers = headers or {}
self.cookies = cookies or {}
self.redirect_url = redirect_url
self.response_time = response_time
self.timeout = timeout
self.ok = 200 <= status < 300
def raise_for_status(self):
"""Raise an error if the response status code is not in the 2xx range."""
if not self.ok:
raise RequestError(f"Request failed with status code {self.status_code}")
def json(self):
"""Return the response content as JSON if it is JSON."""
if self.is_json:
return json.loads(self.text)
else:
return None
class ManageRequests:
"""Class for managing HTTP requests."""
def __init__(
self,
url: str,
method: str = 'GET',
headers: Optional[Dict[str, str]] = None,
timeout: float = HTTP_TIMEOUT,
retries: int = HTTP_RETRIES,
params: Optional[Dict[str, str]] = None,
verify_ssl: bool = True,
auth: Optional[tuple] = None,
proxy: Optional[str] = None,
cookies: Optional[Dict[str, str]] = None,
redirection_handling: bool = True,
):
"""
Initialize a ManageRequests object.
Args:
url (str): The URL to which the request will be sent.
method (str, optional): The HTTP method to be used for the request. Defaults to 'GET'.
headers (Optional[Dict[str, str]], optional): The request headers. Defaults to None.
timeout (float, optional): The request timeout. Defaults to HTTP_TIMEOUT.
retries (int, optional): The number of retries in case of request failure. Defaults to HTTP_RETRIES.
params (Optional[Dict[str, str]], optional): The query parameters for the request. Defaults to None.
verify_ssl (bool, optional): Indicates whether SSL certificate verification should be performed. Defaults to True.
auth (Optional[tuple], optional): Tuple containing the username and password for basic authentication. Defaults to None.
proxy (Optional[str], optional): The proxy URL. Defaults to None.
cookies (Optional[Dict[str, str]], optional): The cookies to be included in the request. Defaults to None.
redirection_handling (bool, optional): Indicates whether redirections should be followed. Defaults to True.
"""
self.url = url
self.method = method
self.headers = headers or {'User-Agent': 'Mozilla/5.0'}
self.timeout = timeout
self.retries = retries
self.params = params
self.verify_ssl = verify_ssl
self.auth = auth
self.proxy = proxy
self.cookies = cookies
self.redirection_handling = redirection_handling
def add_header(self, key: str, value: str) -> None:
"""Add a header to the request."""
self.headers[key] = value
def send(self) -> Response:
"""Send the HTTP request."""
start_time = time.time()
self.attempt = 0
redirect_url = None
while self.attempt < self.retries:
try:
req = self._build_request()
response = self._perform_request(req)
return self._process_response(response, start_time, redirect_url)
except (urllib.error.URLError, urllib.error.HTTPError) as e:
self._handle_error(e)
attempt += 1
def _build_request(self) -> urllib.request.Request:
"""Build the urllib Request object."""
headers = self.headers.copy()
if self.params:
url = self.url + '?' + urllib.parse.urlencode(self.params)
else:
url = self.url
req = urllib.request.Request(url, headers=headers, method=self.method)
if self.auth:
req.add_header('Authorization', 'Basic ' + base64.b64encode(f"{self.auth[0]}:{self.auth[1]}".encode()).decode())
if self.cookies:
cookie_str = '; '.join([f"{name}={value}" for name, value in self.cookies.items()])
req.add_header('Cookie', cookie_str)
return req
def _perform_request(self, req: urllib.request.Request) -> urllib.response.addinfourl:
"""Perform the HTTP request."""
if self.proxy:
proxy_handler = urllib.request.ProxyHandler({'http': self.proxy, 'https': self.proxy})
opener = urllib.request.build_opener(proxy_handler)
urllib.request.install_opener(opener)
if not self.verify_ssl:
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
response = urllib.request.urlopen(req, timeout=self.timeout, context=ssl_context)
else:
response = urllib.request.urlopen(req, timeout=self.timeout)
return response
def _process_response(self, response: urllib.response.addinfourl, start_time: float, redirect_url: Optional[str]) -> Response:
"""Process the HTTP response."""
response_data = response.read()
content_type = response.headers.get('Content-Type', '').lower()
is_response_api = "json" in content_type
if self.redirection_handling and response.status in (301, 302, 303, 307, 308):
location = response.headers.get('Location')
logging.info(f"Redirecting to: {location}")
redirect_url = location
self.url = location
return self.send()
return self._build_response(response, response_data, start_time, redirect_url, content_type)
def _build_response(self, response: urllib.response.addinfourl, response_data: bytes, start_time: float, redirect_url: Optional[str], content_type: str) -> Response:
"""Build the Response object."""
response_time = time.time() - start_time
response_headers = dict(response.headers)
response_cookies = {}
for cookie in response.headers.get_all('Set-Cookie', []):
cookie_parts = cookie.split(';')
cookie_name, cookie_value = cookie_parts[0].split('=')
response_cookies[cookie_name.strip()] = cookie_value.strip()
return Response(
status=response.status,
text=response_data.decode('latin-1'),
is_json=("json" in content_type),
content=response_data,
headers=response_headers,
cookies=response_cookies,
redirect_url=redirect_url,
response_time=response_time,
timeout=self.timeout,
)
def _handle_error(self, e: Union[urllib.error.URLError, urllib.error.HTTPError]) -> None:
"""Handle request error."""
logging.error(f"Request failed for URL '{self.url}': {str(e)}")
if self.attempt < self.retries:
logging.info(f"Retrying request for URL '{self.url}' (attempt {self.attempt}/{self.retries})")
time.sleep(HTTP_DELAY)
else:
logging.error(f"Maximum retries reached for URL '{self.url}'")
raise RequestError(str(e))
class ValidateRequest:
"""Class for validating request inputs."""
@staticmethod
def validate_url(url: str) -> bool:
"""Validate URL format."""
url_regex = re.compile(
r'^(?:http|ftp)s?://' # http:// or https://
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' # domain...
r'localhost|' # localhost...
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or IP
r'(?::\d+)?' # optional port
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
return re.match(url_regex, url) is not None
@staticmethod
def validate_headers(headers: Dict[str, str]) -> bool:
"""Validate header values."""
for key, value in headers.items():
if not isinstance(key, str) or not isinstance(value, str):
return False
return True
class ValidateResponse:
"""Class for validating response data."""
@staticmethod
def is_valid_json(data: str) -> bool:
"""Check if response data is a valid JSON."""
try:
json.loads(data)
return True
except ValueError:
return False
class SSLHandler:
"""Class for handling SSL certificates."""
@staticmethod
def load_certificate(custom_cert_path: str) -> None:
"""Load custom SSL certificate."""
ssl_context = ssl.create_default_context(cafile=custom_cert_path)
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
class KwargsRequest():
"""Class representing keyword arguments for a request."""
url: str
headers: Optional[Dict[str, str]] = None
timeout: float = HTTP_TIMEOUT
retries: int = HTTP_RETRIES
params: Optional[Dict[str, str]] = None
cookies: Optional[Dict[str, str]] = None
class Request:
"""Class for making HTTP requests."""
def __init__(self) -> None:
pass
def get(self, url: str, **kwargs: Unpack[KwargsRequest]):
"""
Send a GET request.
Args:
url (str): The URL to which the request will be sent.
**kwargs: Additional keyword arguments for the request.
Returns:
Response: The response object.
"""
return self._send_request(url, 'GET', **kwargs)
def post(self, url: str, **kwargs: Unpack[KwargsRequest]):
"""
Send a POST request.
Args:
url (str): The URL to which the request will be sent.
**kwargs: Additional keyword arguments for the request.
Returns:
Response: The response object.
"""
return self._send_request(url, 'POST', **kwargs)
def put(self, url: str, **kwargs: Unpack[KwargsRequest]):
"""
Send a PUT request.
Args:
url (str): The URL to which the request will be sent.
**kwargs: Additional keyword arguments for the request.
Returns:
Response: The response object.
"""
return self._send_request(url, 'PUT', **kwargs)
def delete(self, url: str, **kwargs: Unpack[KwargsRequest]):
"""
Send a DELETE request.
Args:
url (str): The URL to which the request will be sent.
**kwargs: Additional keyword arguments for the request.
Returns:
Response: The response object.
"""
return self._send_request(url, 'DELETE', **kwargs)
def _send_request(self, url: str, method: str, **kwargs: Unpack[KwargsRequest]):
"""Send an HTTP request."""
# Add validation checks for URL and headers
if not ValidateRequest.validate_url(url):
raise ValueError("Invalid URL format")
if 'headers' in kwargs and not ValidateRequest.validate_headers(kwargs['headers']):
raise ValueError("Invalid header values")
return ManageRequests(url, method, **kwargs).send()
# Out
request = Request()

View File

@ -0,0 +1,105 @@
# 04.4.24
import logging
import re
import os
import random
import threading
import json
from typing import Dict, List
# Internal utilities
from .my_requests import request
def get_browser_user_agents_online(browser: str) -> List[str]:
"""
Retrieve browser user agent strings from a website.
Args:
browser (str): The name of the browser (e.g., 'chrome', 'firefox', 'safari').
Returns:
List[str]: List of user agent strings for the specified browser.
"""
url = f"https://useragentstring.com/pages/{browser}/"
try:
# Make request and find all user agents
html = request.get(url).text
browser_user_agents = re.findall(r"<a href=\'/.*?>(.+?)</a>", html, re.UNICODE)
return [ua for ua in browser_user_agents if "more" not in ua.lower()]
except Exception as e:
logging.error(f"Failed to fetch user agents for '{browser}': {str(e)}")
return []
def update_user_agents(browser_name: str, browser_user_agents: Dict[str, List[str]]) -> None:
"""
Update browser user agents dictionary with new requests.
Args:
browser_name (str): Name of the browser.
browser_user_agents (Dict[str, List[str]]): Dictionary to store browser user agents.
"""
browser_user_agents[browser_name] = get_browser_user_agents_online(browser_name)
def create_or_update_user_agent_file() -> None:
"""
Create or update the user agent file with browser user agents.
"""
user_agent_file = os.path.join(os.environ.get('TEMP'), 'fake_user_agent.json')
if not os.path.exists(user_agent_file):
browser_user_agents: Dict[str, List[str]] = {}
threads = []
for browser_name in ['chrome', 'firefox', 'safari']:
t = threading.Thread(target=update_user_agents, args=(browser_name, browser_user_agents))
threads.append(t)
t.start()
for t in threads:
t.join()
with open(user_agent_file, 'w') as f:
json.dump(browser_user_agents, f, indent=4)
logging.info(f"User agent file created at: {user_agent_file}")
else:
logging.info("User agent file already exists.")
class UserAgentManager:
"""
Manager class to access browser user agents from a file.
"""
def __init__(self):
# Get path to temp file where save all user agents
self.user_agent_file = os.path.join(os.environ.get('TEMP'), 'fake_user_agent.json')
# If file dont exist, creaet it
if not os.path.exists(self.user_agent_file):
create_or_update_user_agent_file()
def get_random_user_agent(self, browser: str) -> str:
"""
Get a random user agent for the specified browser.
Args:
browser (str): The name of the browser ('chrome', 'firefox', 'safari').
Returns:
Optional[str]: Random user agent string for the specified browser.
"""
with open(self.user_agent_file, 'r') as f:
browser_user_agents = json.load(f)
return random.choice(browser_user_agents.get(browser.lower(), []))
# Output
ua = UserAgentManager()

View File

@ -1,17 +1,18 @@
# 04.04.24
# Import
import os
import logging
import importlib.util
# Variable
Cache = {}
class UnidecodeError(ValueError):
pass
def transliterate_nonascii(string: str, errors: str = 'ignore', replace_str: str = '?') -> str:
"""Transliterates non-ASCII characters in a string to their ASCII counterparts.
@ -25,6 +26,7 @@ def transliterate_nonascii(string: str, errors: str = 'ignore', replace_str: str
"""
return _transliterate(string, errors, replace_str)
def _get_ascii_representation(char: str) -> str:
"""Obtains the ASCII representation of a Unicode character.
@ -79,6 +81,7 @@ def _get_ascii_representation(char: str) -> str:
else:
return None
def _transliterate(string: str, errors: str, replace_str: str) -> str:
"""Main transliteration function.
@ -116,6 +119,7 @@ def _transliterate(string: str, errors: str, replace_str: str) -> str:
return ''.join(retval)
def transliterate_expect_ascii(string: str, errors: str = 'ignore', replace_str: str = '?') -> str:
"""Transliterates non-ASCII characters in a string, expecting ASCII input.
@ -140,4 +144,6 @@ def transliterate_expect_ascii(string: str, errors: str = 'ignore', replace_str:
# Otherwise, transliterate non-ASCII characters
return _transliterate(string, errors, replace_str)
# Out
transliterate = transliterate_expect_ascii

View File

@ -1,14 +1,15 @@
# 01.03.2023
# Class import
from .version import __version__
from Src.Util.console import console
# General import
import os
import requests
import time
# Internal utilities
from .version import __version__
from Src.Util.console import console
# Variable
repo_name = "StreamingCommunity_api"
repo_user = "ghost6446"

View File

@ -4,6 +4,7 @@ import json
import os
from typing import Any, List
class ConfigManager:
def __init__(self, file_path: str = 'config.json') -> None:
"""Initialize the ConfigManager.

View File

@ -1,9 +1,9 @@
# 24.02.24
# Import
from rich.console import Console
from rich.prompt import Prompt
# Variable
msg = Prompt()
console = Console()

View File

@ -1,10 +1,11 @@
# 3.12.23 -> 10.12.23 -> 20.03.24
# 4.04.24
# Import
import fake_useragent
import logging
# Internal utilities
from Src.Lib.Request.user_agent import ua
# Variable
useragent = fake_useragent.UserAgent(use_external_data=True)
def get_headers() -> str:
"""
@ -15,4 +16,7 @@ def get_headers() -> str:
"""
# Get a random user agent string from the user agent rotator
return useragent.firefox
random_headers = ua.get_random_user_agent("firefox")
logging.info(f"Use headers: {random_headers}")
return random_headers

View File

@ -1,12 +1,13 @@
# 26.03.24
# Class import
from Src.Util.config import config_manager
# Import
import logging
from logging.handlers import RotatingFileHandler
# Internal utilities
from Src.Util.config import config_manager
class Logger:
def __init__(self):
"""

View File

@ -1,17 +1,22 @@
# 3.12.23 -> 19.07.24
# Class import
from .config import config_manager
from Src.Util.console import console
# Import
import os
import platform
# External libraries
from Src.Util.console import console
# Internal utilities
from .config import config_manager
# Variable
CLEAN = config_manager.get_bool('DEFAULT', 'clean_console')
SHOW = config_manager.get_bool('DEFAULT', 'show_message')
def get_os_system():
"""
This function returns the name of the operating system.
@ -19,6 +24,7 @@ def get_os_system():
os_system = platform.system()
return os_system
def start_message():
"""
Display a start message.

View File

@ -1,6 +1,5 @@
# 24.01.24
# Import
import shutil
import os
import time
@ -9,6 +8,7 @@ import hashlib
import logging
import re
def remove_folder(folder_path: str) -> None:
"""
Remove a folder if it exists.
@ -23,6 +23,7 @@ def remove_folder(folder_path: str) -> None:
except OSError as e:
print(f"Error removing folder '{folder_path}': {e}")
def remove_file(file_path: str) -> None:
"""
Remove a file if it exists
@ -38,8 +39,7 @@ def remove_file(file_path: str) -> None:
os.remove(file_path)
except OSError as e:
print(f"Error removing file '{file_path}': {e}")
#else:
# print(f"File '{file_path}' does not exist.")
def remove_special_characters(filename) -> str:
"""
@ -60,6 +60,7 @@ def remove_special_characters(filename) -> str:
return cleaned_filename
def move_file_one_folder_up(file_path) -> None:
"""
Move a file one folder up from its current location.
@ -83,6 +84,7 @@ def move_file_one_folder_up(file_path) -> None:
# Move the file
os.rename(file_path, new_path)
def read_json(path: str):
"""Reads JSON file and returns its content.
@ -98,6 +100,7 @@ def read_json(path: str):
return config
def save_json(json_obj, path: str) -> None:
"""Saves JSON object to the specified file path.
@ -109,6 +112,7 @@ def save_json(json_obj, path: str) -> None:
with open(path, 'w') as file:
json.dump(json_obj, file, indent=4) # Adjust the indentation as needed
def clean_json(path: str) -> None:
"""Reads JSON data from the file, cleans it, and saves it back.
@ -132,6 +136,7 @@ def clean_json(path: str) -> None:
# Save the modified JSON data back to the file
save_json(modified_data, path)
def format_size(size_bytes: float) -> str:
"""
Format the size in bytes into a human-readable format.
@ -154,6 +159,7 @@ def format_size(size_bytes: float) -> str:
# Round the size to two decimal places and return with the appropriate unit
return f"{size_bytes:.2f} {units[unit_index]}"
def compute_sha1_hash(input_string: str) -> str:
"""
Computes the SHA-1 hash of the input string.
@ -170,6 +176,7 @@ def compute_sha1_hash(input_string: str) -> str:
# Return the hashed string
return hashed_string
def decode_bytes(bytes_data: bytes, encodings_to_try: list[str] = None) -> str:
"""
Decode a byte sequence using a list of encodings and return the decoded string.
@ -200,6 +207,7 @@ def decode_bytes(bytes_data: bytes, encodings_to_try: list[str] = None) -> str:
logging.info("Raw byte data: %s", bytes_data)
return None
def convert_to_hex(bytes_data: bytes) -> str:
"""
Convert a byte sequence to its hexadecimal representation.

View File

@ -1,9 +1,5 @@
# 03.03.24
# Class import
from .message import start_message
# Import
from rich.console import Console
from rich.table import Table
from rich.text import Text
@ -11,6 +7,11 @@ from rich.prompt import Prompt
from rich.style import Style
from typing import Dict, List, Any
# Internal utilities
from .message import start_message
class TVShowManager:
def __init__(self):
"""

Binary file not shown.

12
run.py
View File

@ -1,6 +1,11 @@
# 10.12.23 -> 31.01.24
# Class
import sys
import logging
import platform
# Internal utilities
from Src.Api import (
get_version_and_domain,
download_series,
@ -19,10 +24,6 @@ from Src.Upload.update import update as git_update
from Src.Lib.FFmpeg import check_ffmpeg
from Src.Util.logger import Logger
# Import
import sys
import logging
import platform
# Variable
DEBUG_MODE = config_manager.get_bool("DEFAULT", "debug")
@ -31,7 +32,6 @@ SWITCH_TO = config_manager.get_bool('DEFAULT', 'swith_anime')
CLOSE_CONSOLE = config_manager.get_bool('DEFAULT', 'not_close')
# [ main ]
def initialize():
"""
Initialize the application.

View File

@ -1,16 +1,25 @@
# 10.12.24
# General imports
import requests
import os
import shutil
from zipfile import ZipFile
from io import BytesIO
from zipfile import ZipFile
# Internal utilities
from Src.Util.config import config_manager
# External libraries
import requests
from rich.console import Console
# Variable
console = Console()
local_path = os.path.join(".")
ROOT_PATH = config_manager.get('DEFAULT', 'root_path')
def move_content(source: str, destination: str) :
"""
@ -36,6 +45,7 @@ def move_content(source: str, destination: str) :
else:
shutil.move(source_path, destination_path)
def keep_specific_items(directory: str, keep_folder: str, keep_file: str):
"""
Delete all items in the directory except for the specified folder and file.
@ -66,6 +76,7 @@ def keep_specific_items(directory: str, keep_folder: str, keep_file: str):
except Exception as e:
print(f"Error: {e}")
def download_and_extract_latest_commit(author: str, repo_name: str):
"""
Download and extract the latest commit from a GitHub repository.
@ -115,6 +126,7 @@ def download_and_extract_latest_commit(author: str, repo_name: str):
else:
console.log(f"[red]Failed to fetch commit information. Status code: {response.status_code}")
def main_upload():
"""
Main function to upload the latest commit of a GitHub repository.
@ -128,10 +140,11 @@ def main_upload():
if cmd_insert == "yes":
# Remove all old file
keep_specific_items(".", "videos", "upload.py")
keep_specific_items(".", ROOT_PATH, "upload.py")
download_and_extract_latest_commit(repository_owner, repository_name)
main_upload()
# win