mirror of
https://github.com/Arrowar/StreamingCommunity.git
synced 2025-06-07 20:15:24 +00:00
Merge branch 'Arrowar:main' into main
This commit is contained in:
commit
b3a89b19cd
13
.github/workflows/build.yml
vendored
13
.github/workflows/build.yml
vendored
@ -13,11 +13,11 @@ on:
|
|||||||
- 'false'
|
- 'false'
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- "*"
|
- "v*.*"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
if: github.event.inputs.publish_pypi == 'true'
|
if: startsWith(github.ref_name, 'v') && github.event.inputs.publish_pypi == 'true'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -39,6 +39,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
python -m pip install setuptools wheel twine
|
python -m pip install setuptools wheel twine
|
||||||
|
|
||||||
- name: Build package
|
- name: Build package
|
||||||
run: python setup.py sdist bdist_wheel
|
run: python setup.py sdist bdist_wheel
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ jobs:
|
|||||||
run: twine upload dist/*
|
run: twine upload dist/*
|
||||||
|
|
||||||
build:
|
build:
|
||||||
if: github.event.inputs.publish_pypi == 'false'
|
if: startsWith(github.ref_name, 'v') && github.event.inputs.publish_pypi == 'false'
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||||
@ -87,7 +88,7 @@ jobs:
|
|||||||
--hidden-import=qbittorrentapi --hidden-import=qbittorrent `
|
--hidden-import=qbittorrentapi --hidden-import=qbittorrent `
|
||||||
--hidden-import=bs4 --hidden-import=httpx --hidden-import=rich --hidden-import=tqdm `
|
--hidden-import=bs4 --hidden-import=httpx --hidden-import=rich --hidden-import=tqdm `
|
||||||
--hidden-import=m3u8 --hidden-import=psutil --hidden-import=unidecode `
|
--hidden-import=m3u8 --hidden-import=psutil --hidden-import=unidecode `
|
||||||
--hidden-import=jsbeautifier --hidden-import=pathvalidate `
|
--hidden-import=jsbeautifier --hidden-import=six --hidden-import=pathvalidate `
|
||||||
--hidden-import=Cryptodome.Cipher --hidden-import=Cryptodome.Cipher.AES `
|
--hidden-import=Cryptodome.Cipher --hidden-import=Cryptodome.Cipher.AES `
|
||||||
--hidden-import=Cryptodome.Util --hidden-import=Cryptodome.Util.Padding `
|
--hidden-import=Cryptodome.Util --hidden-import=Cryptodome.Util.Padding `
|
||||||
--hidden-import=Cryptodome.Random --hidden-import=Pillow `
|
--hidden-import=Cryptodome.Random --hidden-import=Pillow `
|
||||||
@ -102,7 +103,7 @@ jobs:
|
|||||||
--hidden-import=qbittorrentapi --hidden-import=qbittorrent \
|
--hidden-import=qbittorrentapi --hidden-import=qbittorrent \
|
||||||
--hidden-import=bs4 --hidden-import=httpx --hidden-import=rich --hidden-import=tqdm \
|
--hidden-import=bs4 --hidden-import=httpx --hidden-import=rich --hidden-import=tqdm \
|
||||||
--hidden-import=m3u8 --hidden-import=psutil --hidden-import=unidecode \
|
--hidden-import=m3u8 --hidden-import=psutil --hidden-import=unidecode \
|
||||||
--hidden-import=jsbeautifier --hidden-import=pathvalidate \
|
--hidden-import=jsbeautifier --hidden-import=six --hidden-import=pathvalidate \
|
||||||
--hidden-import=Cryptodome.Cipher --hidden-import=Cryptodome.Cipher.AES \
|
--hidden-import=Cryptodome.Cipher --hidden-import=Cryptodome.Cipher.AES \
|
||||||
--hidden-import=Cryptodome.Util --hidden-import=Cryptodome.Util.Padding \
|
--hidden-import=Cryptodome.Util --hidden-import=Cryptodome.Util.Padding \
|
||||||
--hidden-import=Cryptodome.Random --hidden-import=Pillow \
|
--hidden-import=Cryptodome.Random --hidden-import=Pillow \
|
||||||
@ -117,7 +118,7 @@ jobs:
|
|||||||
--hidden-import=qbittorrentapi --hidden-import=qbittorrent \
|
--hidden-import=qbittorrentapi --hidden-import=qbittorrent \
|
||||||
--hidden-import=bs4 --hidden-import=httpx --hidden-import=rich --hidden-import=tqdm \
|
--hidden-import=bs4 --hidden-import=httpx --hidden-import=rich --hidden-import=tqdm \
|
||||||
--hidden-import=m3u8 --hidden-import=psutil --hidden-import=unidecode \
|
--hidden-import=m3u8 --hidden-import=psutil --hidden-import=unidecode \
|
||||||
--hidden-import=jsbeautifier --hidden-import=pathvalidate \
|
--hidden-import=jsbeautifier --hidden-import=six --hidden-import=pathvalidate \
|
||||||
--hidden-import=Cryptodome.Cipher --hidden-import=Cryptodome.Cipher.AES \
|
--hidden-import=Cryptodome.Cipher --hidden-import=Cryptodome.Cipher.AES \
|
||||||
--hidden-import=Cryptodome.Util --hidden-import=Cryptodome.Util.Padding \
|
--hidden-import=Cryptodome.Util --hidden-import=Cryptodome.Util.Padding \
|
||||||
--hidden-import=Cryptodome.Random --hidden-import=Pillow \
|
--hidden-import=Cryptodome.Random --hidden-import=Pillow \
|
||||||
|
@ -51,13 +51,16 @@ def title_search(word_to_search: str) -> int:
|
|||||||
console.print("[yellow]The service might be temporarily unavailable or the domain may have changed.[/yellow]")
|
console.print("[yellow]The service might be temporarily unavailable or the domain may have changed.[/yellow]")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Construct the full site URL and load the search page
|
search_url = f"{site_constant.FULL_URL}/search/{word_to_search}/1/"
|
||||||
|
console.print(f"[cyan]Search url: [yellow]{search_url}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = httpx.get(
|
response = httpx.get(
|
||||||
url=f"{site_constant.FULL_URL}/search/{word_to_search}/1/",
|
url=search_url,
|
||||||
headers={'user-agent': get_userAgent()},
|
headers={'user-agent': get_userAgent()},
|
||||||
follow_redirects=True,
|
timeout=max_timeout,
|
||||||
timeout=max_timeout
|
verify=site_constant.VERIFY,
|
||||||
|
follow_redirects=True
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
|
@ -148,7 +148,8 @@ def title_search(title: str) -> int:
|
|||||||
cookies=cookies,
|
cookies=cookies,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
json=json_data,
|
json=json_data,
|
||||||
timeout=max_timeout
|
timeout=max_timeout,
|
||||||
|
verify=site_constant.VERIFY
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
|
@ -51,13 +51,22 @@ def title_search(word_to_search: str) -> int:
|
|||||||
console.print("[yellow]The service might be temporarily unavailable or the domain may have changed.[/yellow]")
|
console.print("[yellow]The service might be temporarily unavailable or the domain may have changed.[/yellow]")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
search_url = f"{site_constant.FULL_URL}/?s={word_to_search}"
|
||||||
|
console.print(f"[cyan]Search url: [yellow]{search_url}")
|
||||||
|
|
||||||
|
try:
|
||||||
response = httpx.get(
|
response = httpx.get(
|
||||||
url=f"{site_constant.FULL_URL}/?s={word_to_search}",
|
url=search_url,
|
||||||
headers={'user-agent': get_userAgent()},
|
headers={'user-agent': get_userAgent()},
|
||||||
timeout=max_timeout
|
timeout=max_timeout,
|
||||||
|
verify=site_constant.VERIFY,
|
||||||
|
follow_redirects=True
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
console.print(f"Site: {site_constant.SITE_NAME}, request search error: {e}")
|
||||||
|
|
||||||
# Create soup and find table
|
# Create soup and find table
|
||||||
soup = BeautifulSoup(response.text, "html.parser")
|
soup = BeautifulSoup(response.text, "html.parser")
|
||||||
|
|
||||||
|
@ -53,12 +53,16 @@ def title_search(word_to_search: str) -> int:
|
|||||||
console.print("[yellow]The service might be temporarily unavailable or the domain may have changed.[/yellow]")
|
console.print("[yellow]The service might be temporarily unavailable or the domain may have changed.[/yellow]")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Send request to search for titles
|
search_url = f"{site_constant.FULL_URL}/search/?&q={word_to_search}&quick=1&type=videobox_video&nodes=11"
|
||||||
|
console.print(f"[cyan]Search url: [yellow]{search_url}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = httpx.get(
|
response = httpx.get(
|
||||||
url=f"{site_constant.FULL_URL}/search/?&q={word_to_search}&quick=1&type=videobox_video&nodes=11",
|
url=search_url,
|
||||||
headers={'user-agent': get_userAgent()},
|
headers={'user-agent': get_userAgent()},
|
||||||
timeout=max_timeout
|
timeout=max_timeout,
|
||||||
|
verify=site_constant.VERIFY,
|
||||||
|
follow_redirects=True
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
|
@ -51,13 +51,16 @@ def title_search(word_to_search: str) -> int:
|
|||||||
console.print("[yellow]The service might be temporarily unavailable or the domain may have changed.[/yellow]")
|
console.print("[yellow]The service might be temporarily unavailable or the domain may have changed.[/yellow]")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Send request to search for titles
|
search_url = f"{site_constant.FULL_URL}/?story={word_to_search}&do=search&subaction=search"
|
||||||
print(f"{site_constant.FULL_URL}/?story={word_to_search}&do=search&subaction=search")
|
console.print(f"[cyan]Search url: [yellow]{search_url}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = httpx.get(
|
response = httpx.get(
|
||||||
url=f"{site_constant.FULL_URL}/?story={word_to_search}&do=search&subaction=search",
|
url=search_url,
|
||||||
headers={'user-agent': get_userAgent()},
|
headers={'user-agent': get_userAgent()},
|
||||||
timeout=max_timeout
|
timeout=max_timeout,
|
||||||
|
verify=site_constant.VERIFY,
|
||||||
|
follow_redirects=True
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
|
@ -55,11 +55,16 @@ def title_search(title_search: str) -> int:
|
|||||||
media_search_manager.clear()
|
media_search_manager.clear()
|
||||||
table_show_manager.clear()
|
table_show_manager.clear()
|
||||||
|
|
||||||
|
search_url = f"{site_constant.FULL_URL}/api/search?q={title_search}"
|
||||||
|
console.print(f"[cyan]Search url: [yellow]{search_url}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = httpx.get(
|
response = httpx.get(
|
||||||
url=f"{site_constant.FULL_URL}/api/search?q={title_search.replace(' ', '+')}",
|
url=search_url,
|
||||||
headers={'user-agent': get_userAgent()},
|
headers={'user-agent': get_userAgent()},
|
||||||
timeout=max_timeout
|
timeout=max_timeout,
|
||||||
|
verify=site_constant.VERIFY,
|
||||||
|
follow_redirects=True
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
|
@ -15,6 +15,9 @@ from StreamingCommunity.Util.console import console
|
|||||||
from StreamingCommunity.Util._jsonConfig import config_manager
|
from StreamingCommunity.Util._jsonConfig import config_manager
|
||||||
|
|
||||||
|
|
||||||
|
# Variable
|
||||||
|
VERIFY = config_manager.get("REQUESTS", "verify")
|
||||||
|
|
||||||
|
|
||||||
def get_tld(url_str):
|
def get_tld(url_str):
|
||||||
"""Extract the TLD (Top-Level Domain) from the URL."""
|
"""Extract the TLD (Top-Level Domain) from the URL."""
|
||||||
@ -79,7 +82,7 @@ def validate_url(url, base_url, max_timeout, max_retries=2, sleep=1):
|
|||||||
return False, None
|
return False, None
|
||||||
|
|
||||||
client = httpx.Client(
|
client = httpx.Client(
|
||||||
verify=False,
|
verify=VERIFY,
|
||||||
headers=get_headers(),
|
headers=get_headers(),
|
||||||
timeout=max_timeout
|
timeout=max_timeout
|
||||||
)
|
)
|
||||||
|
@ -31,6 +31,10 @@ class SiteConstant:
|
|||||||
def ROOT_PATH(self):
|
def ROOT_PATH(self):
|
||||||
return config_manager.get('DEFAULT', 'root_path')
|
return config_manager.get('DEFAULT', 'root_path')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def VERIFY(self):
|
||||||
|
return config_manager.get('REQUESTS', 'verify')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def DOMAIN_NOW(self):
|
def DOMAIN_NOW(self):
|
||||||
return config_manager.get_site(self.SITE_NAME, 'domain')
|
return config_manager.get_site(self.SITE_NAME, 'domain')
|
||||||
|
@ -18,7 +18,7 @@ from StreamingCommunity.Util._jsonConfig import config_manager
|
|||||||
|
|
||||||
# External libraries
|
# External libraries
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
from qbittorrent import Client
|
import qbittorrentapi
|
||||||
|
|
||||||
|
|
||||||
# Tor config
|
# Tor config
|
||||||
@ -41,15 +41,21 @@ class TOR_downloader:
|
|||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- host (str): IP address or hostname of the qBittorrent Web UI.
|
- host (str): IP address or hostname of the qBittorrent Web UI.
|
||||||
- port (int): Port number of the qBittorrent Web UI.
|
- port (int): Port of the qBittorrent Web UI.
|
||||||
- username (str): Username for logging into qBittorrent.
|
- username (str): Username for accessing qBittorrent.
|
||||||
- password (str): Password for logging into qBittorrent.
|
- password (str): Password for accessing qBittorrent.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
console.print(f"[cyan]Connect to: [green]{HOST}:{PORT}")
|
console.print(f"[cyan]Connect to: [green]{HOST}:{PORT}")
|
||||||
self.qb = Client(f'http://{HOST}:{PORT}/')
|
self.qb = qbittorrentapi.Client(
|
||||||
|
host=HOST,
|
||||||
|
port=PORT,
|
||||||
|
username=USERNAME,
|
||||||
|
password=PASSWORD
|
||||||
|
)
|
||||||
|
|
||||||
except:
|
except:
|
||||||
logging.error("Start qbitorrent first.")
|
logging.error("Start qbittorrent first.")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
self.username = USERNAME
|
self.username = USERNAME
|
||||||
@ -65,7 +71,7 @@ class TOR_downloader:
|
|||||||
Logs into the qBittorrent Web UI.
|
Logs into the qBittorrent Web UI.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.qb.login(self.username, self.password)
|
self.qb.auth_log_in()
|
||||||
self.logged_in = True
|
self.logged_in = True
|
||||||
logging.info("Successfully logged in to qBittorrent.")
|
logging.info("Successfully logged in to qBittorrent.")
|
||||||
|
|
||||||
@ -74,95 +80,86 @@ class TOR_downloader:
|
|||||||
self.logged_in = False
|
self.logged_in = False
|
||||||
|
|
||||||
def delete_magnet(self, torrent_info):
|
def delete_magnet(self, torrent_info):
|
||||||
|
"""
|
||||||
|
Deletes a torrent if it is not downloadable (no seeds/peers).
|
||||||
|
|
||||||
if (int(torrent_info.get('dl_speed')) == 0 and
|
Parameters:
|
||||||
int(torrent_info.get('peers')) == 0 and
|
- torrent_info: Object containing torrent information obtained from the qBittorrent API.
|
||||||
int(torrent_info.get('seeds')) == 0):
|
"""
|
||||||
|
if (int(torrent_info.dlspeed) == 0 and
|
||||||
# Elimina il torrent appena aggiunto
|
int(torrent_info.num_leechs) == 0 and
|
||||||
console.print(f"[bold red]⚠️ Torrent non scaricabile. Rimozione in corso...[/bold red]")
|
int(torrent_info.num_seeds) == 0):
|
||||||
|
|
||||||
|
console.print(f"[bold red]⚠️ Torrent not downloadable. Removing...[/bold red]")
|
||||||
try:
|
try:
|
||||||
# Rimuovi il torrent
|
self.qb.torrents_delete(delete_files=True, torrent_hashes=torrent_info.hash)
|
||||||
self.qb.delete_permanently(torrent_info['hash'])
|
|
||||||
|
|
||||||
except Exception as delete_error:
|
except Exception as delete_error:
|
||||||
logging.error(f"Errore durante la rimozione del torrent: {delete_error}")
|
logging.error(f"Error while removing torrent: {delete_error}")
|
||||||
|
|
||||||
# Resetta l'ultimo hash
|
|
||||||
self.latest_torrent_hash = None
|
self.latest_torrent_hash = None
|
||||||
|
|
||||||
def add_magnet_link(self, magnet_link):
|
def add_magnet_link(self, magnet_link):
|
||||||
"""
|
"""
|
||||||
Aggiunge un magnet link e recupera le informazioni dettagliate.
|
Adds a magnet link and retrieves detailed torrent information.
|
||||||
|
|
||||||
Args:
|
Arguments:
|
||||||
magnet_link (str): Magnet link da aggiungere
|
magnet_link (str): Magnet link to add.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: Informazioni del torrent aggiunto, o None se fallisce
|
dict: Information about the added torrent, or None in case of error.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Estrai l'hash dal magnet link
|
|
||||||
magnet_hash_match = re.search(r'urn:btih:([0-9a-fA-F]+)', magnet_link)
|
magnet_hash_match = re.search(r'urn:btih:([0-9a-fA-F]+)', magnet_link)
|
||||||
|
|
||||||
if not magnet_hash_match:
|
if not magnet_hash_match:
|
||||||
raise ValueError("Hash del magnet link non trovato")
|
raise ValueError("Magnet link hash not found")
|
||||||
|
|
||||||
magnet_hash = magnet_hash_match.group(1).lower()
|
magnet_hash = magnet_hash_match.group(1).lower()
|
||||||
|
|
||||||
# Estrai il nome del file dal magnet link (se presente)
|
# Extract the torrent name, if available
|
||||||
name_match = re.search(r'dn=([^&]+)', magnet_link)
|
name_match = re.search(r'dn=([^&]+)', magnet_link)
|
||||||
torrent_name = name_match.group(1).replace('+', ' ') if name_match else "Nome non disponibile"
|
torrent_name = name_match.group(1).replace('+', ' ') if name_match else "Name not available"
|
||||||
|
|
||||||
# Salva il timestamp prima di aggiungere il torrent
|
# Save the timestamp before adding the torrent
|
||||||
before_add_time = time.time()
|
before_add_time = time.time()
|
||||||
|
|
||||||
# Aggiungi il magnet link
|
console.print(f"[cyan]Adding magnet link ...")
|
||||||
console.print(f"[cyan]Aggiunta magnet link[/cyan]: [red]{magnet_link}")
|
self.qb.torrents_add(urls=magnet_link)
|
||||||
self.qb.download_from_link(magnet_link)
|
|
||||||
|
|
||||||
# Aspetta un attimo per essere sicuri che il torrent sia stato aggiunto
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
# Cerca il torrent
|
torrents = self.qb.torrents_info()
|
||||||
torrents = self.qb.torrents()
|
|
||||||
matching_torrents = [
|
matching_torrents = [
|
||||||
t for t in torrents
|
t for t in torrents
|
||||||
if (t['hash'].lower() == magnet_hash) or (t.get('added_on', 0) > before_add_time)
|
if (t.hash.lower() == magnet_hash) or (getattr(t, 'added_on', 0) > before_add_time)
|
||||||
]
|
]
|
||||||
|
|
||||||
if not matching_torrents:
|
if not matching_torrents:
|
||||||
raise ValueError("Nessun torrent corrispondente trovato")
|
raise ValueError("No matching torrent found")
|
||||||
|
|
||||||
# Prendi il primo torrent corrispondente
|
|
||||||
torrent_info = matching_torrents[0]
|
torrent_info = matching_torrents[0]
|
||||||
|
|
||||||
# Formatta e stampa le informazioni
|
console.print("\n[bold green]🔗 Added Torrent Details:[/bold green]")
|
||||||
console.print("\n[bold green]🔗 Dettagli Torrent Aggiunto:[/bold green]")
|
console.print(f"[yellow]Name:[/yellow] {torrent_info.name or torrent_name}")
|
||||||
console.print(f"[yellow]Name:[/yellow] {torrent_info.get('name', torrent_name)}")
|
console.print(f"[yellow]Hash:[/yellow] {torrent_info.hash}")
|
||||||
console.print(f"[yellow]Hash:[/yellow] {torrent_info['hash']}")
|
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# Salva l'hash per usi successivi e il path
|
self.latest_torrent_hash = torrent_info.hash
|
||||||
self.latest_torrent_hash = torrent_info['hash']
|
self.output_file = torrent_info.content_path
|
||||||
self.output_file = torrent_info['content_path']
|
self.file_name = torrent_info.name
|
||||||
self.file_name = torrent_info['name']
|
|
||||||
|
|
||||||
# Controlla che sia possibile il download
|
# Wait and verify if the download is possible
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
self.delete_magnet(self.qb.get_torrent(self.latest_torrent_hash))
|
self.delete_magnet(self.qb.torrents_info(torrent_hashes=self.latest_torrent_hash)[0])
|
||||||
|
|
||||||
return torrent_info
|
return torrent_info
|
||||||
|
|
||||||
def start_download(self):
|
def start_download(self):
|
||||||
"""
|
"""
|
||||||
Starts downloading the latest added torrent and monitors progress.
|
Starts downloading the added torrent and monitors its progress.
|
||||||
"""
|
"""
|
||||||
if self.latest_torrent_hash is not None:
|
if self.latest_torrent_hash is not None:
|
||||||
try:
|
try:
|
||||||
|
|
||||||
# Custom bar for mobile and pc
|
# Custom progress bar for mobile and PC
|
||||||
if USE_LARGE_BAR:
|
if USE_LARGE_BAR:
|
||||||
bar_format = (
|
bar_format = (
|
||||||
f"{Colors.YELLOW}[TOR] {Colors.WHITE}({Colors.CYAN}video{Colors.WHITE}): "
|
f"{Colors.YELLOW}[TOR] {Colors.WHITE}({Colors.CYAN}video{Colors.WHITE}): "
|
||||||
@ -189,34 +186,41 @@ class TOR_downloader:
|
|||||||
with progress_bar as pbar:
|
with progress_bar as pbar:
|
||||||
while True:
|
while True:
|
||||||
|
|
||||||
# Get variable from qtorrent
|
torrent_info = self.qb.torrents_info(torrent_hashes=self.latest_torrent_hash)[0]
|
||||||
torrent_info = self.qb.get_torrent(self.latest_torrent_hash)
|
self.save_path = torrent_info.save_path
|
||||||
self.save_path = torrent_info['save_path']
|
self.torrent_name = torrent_info.name
|
||||||
self.torrent_name = torrent_info['name']
|
|
||||||
|
|
||||||
# Fetch important variable
|
progress = torrent_info.progress * 100
|
||||||
pieces_have = torrent_info['pieces_have']
|
|
||||||
pieces_num = torrent_info['pieces_num']
|
|
||||||
progress = (pieces_have / pieces_num) * 100 if pieces_num else 0
|
|
||||||
pbar.n = progress
|
pbar.n = progress
|
||||||
|
|
||||||
download_speed = torrent_info['dl_speed']
|
download_speed = torrent_info.dlspeed
|
||||||
total_size = torrent_info['total_size']
|
total_size = torrent_info.size
|
||||||
downloaded_size = torrent_info['total_downloaded']
|
downloaded_size = torrent_info.downloaded
|
||||||
|
|
||||||
# Format variable
|
# Format the downloaded size
|
||||||
downloaded_size_str = internet_manager.format_file_size(downloaded_size)
|
downloaded_size_str = internet_manager.format_file_size(downloaded_size)
|
||||||
downloaded_size = downloaded_size_str.split(' ')[0]
|
downloaded_size = downloaded_size_str.split(' ')[0]
|
||||||
|
|
||||||
|
# Safely format the total size
|
||||||
total_size_str = internet_manager.format_file_size(total_size)
|
total_size_str = internet_manager.format_file_size(total_size)
|
||||||
total_size = total_size_str.split(' ')[0]
|
total_size_parts = total_size_str.split(' ')
|
||||||
total_size_unit = total_size_str.split(' ')[1]
|
if len(total_size_parts) >= 2:
|
||||||
|
total_size = total_size_parts[0]
|
||||||
|
total_size_unit = total_size_parts[1]
|
||||||
|
else:
|
||||||
|
total_size = total_size_str
|
||||||
|
total_size_unit = ""
|
||||||
|
|
||||||
|
# Safely format the average download speed
|
||||||
average_internet_str = internet_manager.format_transfer_speed(download_speed)
|
average_internet_str = internet_manager.format_transfer_speed(download_speed)
|
||||||
average_internet = average_internet_str.split(' ')[0]
|
average_internet_parts = average_internet_str.split(' ')
|
||||||
average_internet_unit = average_internet_str.split(' ')[1]
|
if len(average_internet_parts) >= 2:
|
||||||
|
average_internet = average_internet_parts[0]
|
||||||
|
average_internet_unit = average_internet_parts[1]
|
||||||
|
else:
|
||||||
|
average_internet = average_internet_str
|
||||||
|
average_internet_unit = ""
|
||||||
|
|
||||||
# Update the progress bar's postfix
|
|
||||||
if USE_LARGE_BAR:
|
if USE_LARGE_BAR:
|
||||||
pbar.set_postfix_str(
|
pbar.set_postfix_str(
|
||||||
f"{Colors.WHITE}[ {Colors.GREEN}{downloaded_size} {Colors.WHITE}< {Colors.GREEN}{total_size} {Colors.RED}{total_size_unit} "
|
f"{Colors.WHITE}[ {Colors.GREEN}{downloaded_size} {Colors.WHITE}< {Colors.GREEN}{total_size} {Colors.RED}{total_size_unit} "
|
||||||
@ -231,7 +235,6 @@ class TOR_downloader:
|
|||||||
pbar.refresh()
|
pbar.refresh()
|
||||||
time.sleep(0.2)
|
time.sleep(0.2)
|
||||||
|
|
||||||
# Break at the end
|
|
||||||
if int(progress) == 100:
|
if int(progress) == 100:
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -239,7 +242,15 @@ class TOR_downloader:
|
|||||||
logging.info("Download process interrupted.")
|
logging.info("Download process interrupted.")
|
||||||
|
|
||||||
def is_file_in_use(self, file_path: str) -> bool:
|
def is_file_in_use(self, file_path: str) -> bool:
|
||||||
"""Check if a file is in use by any process."""
|
"""
|
||||||
|
Checks if a file is being used by any process.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- file_path (str): The file path to check.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- bool: True if the file is in use, False otherwise.
|
||||||
|
"""
|
||||||
for proc in psutil.process_iter(['open_files']):
|
for proc in psutil.process_iter(['open_files']):
|
||||||
try:
|
try:
|
||||||
if any(file_path == f.path for f in proc.info['open_files'] or []):
|
if any(file_path == f.path for f in proc.info['open_files'] or []):
|
||||||
@ -251,21 +262,20 @@ class TOR_downloader:
|
|||||||
|
|
||||||
def move_downloaded_files(self, destination: str):
|
def move_downloaded_files(self, destination: str):
|
||||||
"""
|
"""
|
||||||
Moves downloaded files of the latest torrent to another location.
|
Moves the downloaded files of the most recent torrent to a new location.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- destination (str): Destination directory to move files.
|
- destination (str): Destination folder.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
- bool: True if files are moved successfully, False otherwise.
|
- bool: True if the move was successful, False otherwise.
|
||||||
"""
|
"""
|
||||||
console.print(f"[cyan]Destination folder: [red]{destination}")
|
console.print(f"[cyan]Destination folder: [red]{destination}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
timeout = 5
|
||||||
# Ensure the file is not in use
|
|
||||||
timeout = 3
|
|
||||||
elapsed = 0
|
elapsed = 0
|
||||||
|
|
||||||
while self.is_file_in_use(self.output_file) and elapsed < timeout:
|
while self.is_file_in_use(self.output_file) and elapsed < timeout:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
elapsed += 1
|
elapsed += 1
|
||||||
@ -273,24 +283,20 @@ class TOR_downloader:
|
|||||||
if elapsed == timeout:
|
if elapsed == timeout:
|
||||||
raise Exception(f"File '{self.output_file}' is in use and could not be moved.")
|
raise Exception(f"File '{self.output_file}' is in use and could not be moved.")
|
||||||
|
|
||||||
# Ensure destination directory exists
|
|
||||||
os.makedirs(destination, exist_ok=True)
|
os.makedirs(destination, exist_ok=True)
|
||||||
|
|
||||||
# Perform the move operation
|
|
||||||
try:
|
try:
|
||||||
shutil.move(self.output_file, destination)
|
shutil.move(self.output_file, destination)
|
||||||
|
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if e.errno == 17: # Cross-disk move error
|
if e.errno == 17: # Error when moving between different disks
|
||||||
# Perform copy and delete manually
|
|
||||||
shutil.copy2(self.output_file, destination)
|
shutil.copy2(self.output_file, destination)
|
||||||
os.remove(self.output_file)
|
os.remove(self.output_file)
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# Delete the torrent data
|
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
self.qb.delete_permanently(self.qb.torrents()[-1]['hash'])
|
last_torrent = self.qb.torrents_info()[-1]
|
||||||
|
self.qb.torrents_delete(delete_files=True, torrent_hashes=last_torrent.hash)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
__title__ = 'StreamingCommunity'
|
__title__ = 'StreamingCommunity'
|
||||||
__version__ = '2.6.0'
|
__version__ = '2.7.0'
|
||||||
__author__ = 'Arrowar'
|
__author__ = 'Arrowar'
|
||||||
__description__ = 'A command-line program to download film'
|
__description__ = 'A command-line program to download film'
|
||||||
__copyright__ = 'Copyright 2024'
|
__copyright__ = 'Copyright 2024'
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
"telegram_bot": false
|
"telegram_bot": false
|
||||||
},
|
},
|
||||||
"REQUESTS": {
|
"REQUESTS": {
|
||||||
|
"verify": false,
|
||||||
"timeout": 20,
|
"timeout": 20,
|
||||||
"max_retry": 8,
|
"max_retry": 8,
|
||||||
"proxy_start_min": 0.1,
|
"proxy_start_min": 0.1,
|
||||||
|
@ -3,6 +3,7 @@ bs4
|
|||||||
rich
|
rich
|
||||||
tqdm
|
tqdm
|
||||||
m3u8
|
m3u8
|
||||||
|
certifi
|
||||||
psutil
|
psutil
|
||||||
unidecode
|
unidecode
|
||||||
jsbeautifier
|
jsbeautifier
|
||||||
@ -10,6 +11,4 @@ pathvalidate
|
|||||||
pycryptodomex
|
pycryptodomex
|
||||||
ua-generator
|
ua-generator
|
||||||
qbittorrent-api
|
qbittorrent-api
|
||||||
python-qbittorrent
|
|
||||||
Pillow
|
|
||||||
pyTelegramBotAPI
|
pyTelegramBotAPI
|
2
setup.py
2
setup.py
@ -10,7 +10,7 @@ with open("requirements.txt", "r", encoding="utf-8-sig") as f:
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="StreamingCommunity",
|
name="StreamingCommunity",
|
||||||
version="2.6.1",
|
version="2.7.0",
|
||||||
long_description=read_readme(),
|
long_description=read_readme(),
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
author="Lovi-0",
|
author="Lovi-0",
|
||||||
|
34
update.py
34
update.py
@ -46,33 +46,37 @@ def move_content(source: str, destination: str):
|
|||||||
|
|
||||||
def keep_specific_items(directory: str, keep_folder: str, keep_file: str):
|
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.
|
Deletes all items in the given directory except for the specified folder,
|
||||||
|
the specified file, and the '.git' directory.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- directory (str): The path to the directory.
|
- directory (str): The path to the directory.
|
||||||
- keep_folder (str): The name of the folder to keep.
|
- keep_folder (str): The name of the folder to keep.
|
||||||
- keep_file (str): The name of the file to keep.
|
- keep_file (str): The name of the file to keep.
|
||||||
"""
|
"""
|
||||||
try:
|
|
||||||
if not os.path.exists(directory) or not os.path.isdir(directory):
|
if not os.path.exists(directory) or not os.path.isdir(directory):
|
||||||
raise ValueError(f"Error: '{directory}' is not a valid directory.")
|
console.print(f"[red]Error: '{directory}' is not a valid directory.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Define folders and files to skip
|
||||||
|
skip_folders = {keep_folder, ".git"}
|
||||||
|
skip_files = {keep_file}
|
||||||
|
|
||||||
# Iterate through items in the directory
|
# Iterate through items in the directory
|
||||||
for item in os.listdir(directory):
|
for item in os.listdir(directory):
|
||||||
|
if item in skip_folders or item in skip_files:
|
||||||
|
continue
|
||||||
|
|
||||||
item_path = os.path.join(directory, item)
|
item_path = os.path.join(directory, item)
|
||||||
|
try:
|
||||||
# Check if the item is the specified folder or file
|
if os.path.isdir(item_path):
|
||||||
if os.path.isdir(item_path) and item != keep_folder:
|
|
||||||
shutil.rmtree(item_path)
|
shutil.rmtree(item_path)
|
||||||
|
console.log(f"[green]Removed directory: {item_path}")
|
||||||
elif os.path.isfile(item_path) and item != keep_file:
|
elif os.path.isfile(item_path):
|
||||||
os.remove(item_path)
|
os.remove(item_path)
|
||||||
|
console.log(f"[green]Removed file: {item_path}")
|
||||||
except PermissionError as pe:
|
|
||||||
console.print(f"[red]PermissionError: {pe}. Check permissions and try again.")
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
console.print(f"[red]Error: {e}")
|
console.log(f"[yellow]Skipping {item_path} due to error: {e}")
|
||||||
|
|
||||||
|
|
||||||
def print_commit_info(commit_info: dict):
|
def print_commit_info(commit_info: dict):
|
||||||
@ -177,14 +181,14 @@ def main_upload():
|
|||||||
Main function to upload the latest commit of a GitHub repository.
|
Main function to upload the latest commit of a GitHub repository.
|
||||||
"""
|
"""
|
||||||
cmd_insert = Prompt.ask(
|
cmd_insert = Prompt.ask(
|
||||||
"[bold red]Are you sure you want to delete all files? (Only 'Video' folder and 'update_version.py' will remain)",
|
"[bold red]Are you sure you want to delete all files? (Only 'Video' folder and 'update.py' will remain)",
|
||||||
choices=['y', 'n'],
|
choices=['y', 'n'],
|
||||||
default='y',
|
default='y',
|
||||||
show_choices=True
|
show_choices=True
|
||||||
)
|
)
|
||||||
|
|
||||||
if cmd_insert.lower().strip() == 'y' or cmd_insert.lower().strip() == 'yes':
|
if cmd_insert.lower().strip() == 'y' or cmd_insert.lower().strip() == 'yes':
|
||||||
console.print("[red]Deleting all files except 'Video' folder and 'update_version.py'...")
|
console.print("[red]Deleting all files except 'Video' folder and 'update.py'...")
|
||||||
keep_specific_items(".", "Video", "upload.py")
|
keep_specific_items(".", "Video", "upload.py")
|
||||||
download_and_extract_latest_commit()
|
download_and_extract_latest_commit()
|
||||||
else:
|
else:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user