1464 lines
66 KiB
Python
1464 lines
66 KiB
Python
import os
|
|
import time
|
|
from tqdm import tqdm
|
|
import torf
|
|
from dotenv import load_dotenv
|
|
# import requests
|
|
from bs4 import BeautifulSoup
|
|
import subprocess
|
|
import json
|
|
from lib.ScreenShot import ScreenShot
|
|
from urllib.parse import urljoin
|
|
import asyncio
|
|
import aiohttp
|
|
from urllib.parse import urlparse,parse_qs
|
|
|
|
from lib.logging_data import logger
|
|
import discord
|
|
|
|
from themoviedb import aioTMDb as TMDb
|
|
from themoviedb import Movie, TV, SizeType
|
|
|
|
class qBittorrent:
|
|
|
|
def __init__(self, url='http://media-server.lan:8080',console:logger=None):
|
|
self.url = url
|
|
load_dotenv(".env")
|
|
self.username = os.getenv('qbit_username')
|
|
self.password = os.getenv('qbit_password')
|
|
# self.session = requests.Session()
|
|
self.session = aiohttp.ClientSession()
|
|
self.console = console or logger(app_name="qBittorrent",log_dir="./log")
|
|
|
|
async def login(self):
|
|
"""Login to qBittorrent WebUI"""
|
|
payload = {
|
|
'username': self.username,
|
|
'password': self.password
|
|
}
|
|
# response = await self.session.post(f"{self.url}/api/v2/auth/login", data=payload)
|
|
async with self.session.post(f"{self.url}/api/v2/auth/login", data=payload) as response:
|
|
if response.status == 200:
|
|
await response.text()
|
|
if response.status == 200:
|
|
self.console.log("✅ Successfully logged in to qBittorrent")
|
|
else:
|
|
self.console.error("❌ Failed to log in to qBittorrent")
|
|
|
|
async def add_torrent(self, torrent_file_path, save_path=None, category="SeFree",rename=None):
|
|
"""Add a torrent file to qBittorrent"""
|
|
await self.login()
|
|
# files = {'torrents': (os.path.basename(torrent_file_path), open(torrent_file_path, 'rb'), 'application/x-bittorrent')}
|
|
form = aiohttp.FormData()
|
|
form.add_field('torrents', open(torrent_file_path, 'rb'), filename=os.path.basename(torrent_file_path), content_type='application/x-bittorrent')
|
|
|
|
# payload = {'contentLayout':"Original"}
|
|
form.add_field('contentLayout', 'Original')
|
|
|
|
if save_path:
|
|
form.add_field('savepath', save_path)
|
|
if category:
|
|
form.add_field('category', category)
|
|
if rename:
|
|
form.add_field('rename', rename)
|
|
|
|
# response = self.session.post(f"{self.url}/api/v2/torrents/add", files=files, data=payload)
|
|
count = 0
|
|
while True:
|
|
async with self.session.post(f"{self.url}/api/v2/torrents/add", data=form) as response:
|
|
# print(response.status_code)
|
|
# print(response.text)
|
|
|
|
if response.status == 200:
|
|
self.console.log(f"✅ Successfully added torrent: {torrent_file_path}")
|
|
break
|
|
else:
|
|
self.console.warn(f"❌ Failed to add torrent: {torrent_file_path} - {await response.text()}")
|
|
count += 1
|
|
if count >= 5:
|
|
self.console.error("❌ Max retry attempts reached. Exiting.")
|
|
exit(1)
|
|
await asyncio.sleep(5)
|
|
|
|
async def stop_torrent(self,search_string,tmdb_id=None,tmdb_type='tv') -> aiohttp.ClientResponse | None:
|
|
# self.session.post
|
|
async with self.session.get(f"{self.url}/api/v2/torrents/info") as torrents:
|
|
torrents = await torrents.json()
|
|
|
|
if tmdb_id:
|
|
info=await tmdb_info(tmdb_id,tmdb_type,'en-US')
|
|
search_string=info.name
|
|
|
|
torrent = next((t for t in torrents if t['name'] in search_string or search_string in t['name']),None)
|
|
|
|
# print(f"Removing: {t['name']}")
|
|
async with self.session.post(f"{self.url}/api/v2/torrents/stop", data={"hashes": torrent["hash"],}) as response:
|
|
if response.status == 200:
|
|
self.console.log("✅ Successfully stop torrent")
|
|
return response
|
|
else:
|
|
self.console.error("❌ Failed to stop torrent")
|
|
return
|
|
|
|
async def stop_previous_episode_torrent(self,tmdb_id,entry,title_config,prv_count=1) -> None:
|
|
season=int(entry['season'])
|
|
if title_config['absolute_season']:
|
|
season=int(title_config['season'])+int(title_config['absolute_season'])
|
|
|
|
episode=int(entry['episode'])
|
|
if title_config['absolute']:
|
|
episode=int(entry['episode'])+int(title_config['absolute'])
|
|
|
|
previous_episode=episode-prv_count
|
|
|
|
ssep="S{:02d}E{:02d}".format(season, episode)
|
|
pv_ssep="S{:02d}E{:02d}".format(season, previous_episode)
|
|
|
|
info=await tmdb_info(tmdb_id,'tv','en-US')
|
|
|
|
async with self.session.get(f"{self.url}/api/v2/torrents/info") as torrents:
|
|
torrents = await torrents.json()
|
|
|
|
# torrent = next((t for t in torrents if info.name in t['name'] and ssep in t['name'] ),None)
|
|
result=[]
|
|
for torrent in torrents:
|
|
if not( info.name in torrent['name'] and (ssep in torrent['name'] or pv_ssep in torrent['name'])):
|
|
continue
|
|
async with self.session.post(f"{self.url}/api/v2/torrents/stop", data={"hashes": torrent["hash"],}) as response:
|
|
if response.status == 200:
|
|
self.console.log("✅ Successfully stop torrent :", torrent['name'])
|
|
result.append(True)
|
|
else:
|
|
self.console.error("❌ Failed to stop torrent :" , torrent['name'])
|
|
result.append(False)
|
|
# return
|
|
return all(result)
|
|
async def set_super_seed(self,rename):
|
|
async with self.session.get(f"{self.url}/api/v2/torrents/info") as torrents:
|
|
torrents = await torrents.json()
|
|
result=[]
|
|
for torrent in torrents:
|
|
if rename not in torrent['name']:
|
|
continue
|
|
# print(torrent["hash"])
|
|
|
|
async with self.session.post(f"{self.url}/api/v2/torrents/setSuperSeeding", data={"hashes": torrent["hash"],"value":True}) as response:
|
|
if response.status == 200:
|
|
self.console.log("✅ Successfully stop torrent :", torrent['name'])
|
|
result.append(True)
|
|
else:
|
|
self.console.error("❌ Failed to stop torrent :" , torrent['name'])
|
|
result.append(False)
|
|
return all(result)
|
|
|
|
class BearBit:
|
|
"""Class to interact with BearBit API"""
|
|
|
|
def __init__(self,console:logger=None):
|
|
load_dotenv(".env")
|
|
|
|
self.id = os.getenv('bb_id')
|
|
self.password = os.getenv('bb_pass')
|
|
self.baseurl = "https://bearbit.org"
|
|
self.user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0"
|
|
# self.session = requests.Session()
|
|
self.session = aiohttp.ClientSession()
|
|
self.TRACKER_BEARBIT =None
|
|
|
|
self.console = console or logger(app_name="BearBit",log_dir="./log")
|
|
|
|
|
|
async def login(self):
|
|
"""Login to BearBit"""
|
|
|
|
url = f"{self.baseurl}/takelogin.php"
|
|
|
|
payload = f'username={self.id}&password={self.password}&returnto=%2F'
|
|
|
|
headers = {
|
|
'User-Agent': self.user_agent,
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
'Referer': f'{self.baseurl}/login.php?returnto=%2F'
|
|
}
|
|
|
|
# response = await self.session.post(url, headers=headers, data=payload, verify=False)
|
|
async with self.session.post(url, headers=headers, data=payload, verify_ssl=False) as response:
|
|
# await response.text()
|
|
if 300 <= response.status < 400:
|
|
return False # Login Failed
|
|
else:
|
|
return True
|
|
|
|
async def check_login(self):
|
|
"""Check login status"""
|
|
url = f"{self.baseurl}/"
|
|
|
|
headers = {
|
|
'User-Agent': self.user_agent
|
|
}
|
|
|
|
response = None
|
|
try:
|
|
# response = await self.retry(self.session.get, url, headers=headers, verify=False)
|
|
|
|
async with self.session.get(url, headers=headers, verify_ssl=False) as response:
|
|
await response.text()
|
|
if response.status == 200:
|
|
pass
|
|
else:
|
|
return False # Not logged in
|
|
except Exception:
|
|
return True
|
|
|
|
soup = BeautifulSoup(response.text, 'html.parser')
|
|
title = soup.find('title').text
|
|
|
|
return 'login' not in title.lower()
|
|
|
|
async def get_tracker(self):
|
|
"""get current tracker url"""
|
|
if not await self.check_login():
|
|
await self.login()
|
|
|
|
url = f"{self.baseurl}/toserv.php"
|
|
|
|
headers = {
|
|
'User-Agent': self.user_agent
|
|
}
|
|
|
|
# response = await self.retry(self.session.get, url, headers=headers, verify=False)
|
|
async with self.session.get(url, headers=headers, verify_ssl=False) as response:
|
|
# await response.text()
|
|
|
|
if response.status != 200:
|
|
raise Exception(f"Failed to get tracker: {response.status}")
|
|
text = await response.read()
|
|
|
|
soup = BeautifulSoup(text, 'html.parser')
|
|
passkey_field = soup.find('input', {'name': 'passk'})
|
|
self.TRACKER_BEARBIT = passkey_field.get('value')
|
|
|
|
async def upload_torrent(self,name:str,sdescr:str,poster_url:str,category:str,imdburl:str,source_type:str,codec:str,standard:str,country:str,source:str,original_platform:int,tracktype:str,torrent_file_path,description:str):
|
|
"""Upload a torrent file to BearBit"""
|
|
CATEGORY_MAP = {
|
|
"Anime": 10,
|
|
"Western Series": 52,
|
|
"Korean Series": 5,
|
|
"Japanese Series": 51,
|
|
"Chinese Series": 54,
|
|
"Thai Series": 53,
|
|
"Other Series": 57,
|
|
"Hi-Def Movie": 92,
|
|
"4K": 90,
|
|
"สารคดี": 17,
|
|
"Documentary": 17,
|
|
}
|
|
SOURCE_TYPE_MAP = {
|
|
'4K': 42,
|
|
'Blu-ray': 1,
|
|
'CD': 9,
|
|
'DVD5': 7,
|
|
'DVD9': 8,
|
|
'Encode': 4,
|
|
'HD DVD': 2,
|
|
'HDTV': 6,
|
|
'MiniBD': 5,
|
|
'Remux': 3,
|
|
'Track': 10,
|
|
'WEB-DL': 31,
|
|
'Image': 27
|
|
}
|
|
CODEC_MAP = {
|
|
'AC-3': 19,
|
|
'APE': 17,
|
|
'DTS': 18,
|
|
'FLAC': 16,
|
|
'H264': 11,
|
|
'HEVC': 30,
|
|
'MPEG-1': 14,
|
|
'MPEG-2': 15,
|
|
'Other': 20,
|
|
'VC-1': 12,
|
|
'Xvid': 13,
|
|
'Image/PDF': 28
|
|
}
|
|
STANDARD_MAP = {
|
|
'1080i': 22,
|
|
'1080p': 21,
|
|
'720p': 23,
|
|
'DVD': 25,
|
|
'480p': 24,
|
|
'Other': 29
|
|
}
|
|
COUNTRY_MAP = {
|
|
'Thai': 'ไทย',
|
|
'Western': 'ฝรั่ง',
|
|
'Korean': 'เกาหลี',
|
|
'Japanese': 'ญี่ปุ่น',
|
|
'Chinese': 'จีน',
|
|
'Other': 'อื่นๆ'
|
|
}
|
|
SOURCE_MAP = {
|
|
'Master': 'Master',
|
|
'Zoom': 'หนังซูม',
|
|
'V2D': 'V2D From Master',
|
|
'TV': 'From TV',
|
|
'HD-TV': 'From HD-TV',
|
|
'Hi-def': 'Hi-def rip from Master',
|
|
##for Anime
|
|
'From Master/DVD': 'V2D From Master/DVD Modified',
|
|
'Rip TV': 'อัดจาก TV',
|
|
'Rip Master': 'rip from Master',
|
|
'scan': 'Scan'
|
|
}
|
|
ORIGINAL_PLATFORM_MAP = {
|
|
'DVD': 1,
|
|
'Hi-def': 5,
|
|
'TV': 2,
|
|
'Books': 3,
|
|
'Other': 4,
|
|
'Netflix': 6
|
|
}
|
|
TRACKTYPE_MAP = {
|
|
'Thai Audio': 'พากย์ไทย',
|
|
'Thai Audio Thai Sub': 'พากย์ไทย บรรยายไทย',
|
|
'Thai Sub': 'Soundtrack บรรยายไทย',
|
|
'Thai Sub T': 'Soundtrack บรรยายไทย (แปล)',
|
|
'No Sub': 'Soundtrack ไม่มีบรรยาย',
|
|
'ENG Sub': 'Soundtrack Sub ENG'
|
|
}
|
|
# await self.login()
|
|
# await self.get_tracker()
|
|
|
|
# name = f"[SeFree] {name}"
|
|
# description=f"[size=6]{name}[/size] \n\n [img]{poster_url}[/img] \n\n [size=6]อัพโหลดอัตโนมัติผ่านสคริป[/size] \n\n [size=4]ชื่อไฟล์ | ซีซัน อัตโนมัติจาก Sonarr/Radarr โดยอิงจาก TVDB[/size] \n\n [size=6]มีปัญหาอะไรสามารถ DM | Comment แจ้งได้[/size]"
|
|
payload = {
|
|
'passk': self.TRACKER_BEARBIT,
|
|
'name': name.encode('iso8859_11', errors='ignore'),
|
|
'sdescr': sdescr.encode('iso8859_11', errors='ignore'),
|
|
'ss': poster_url,
|
|
|
|
'type': CATEGORY_MAP[category],
|
|
|
|
'imdb': imdburl,
|
|
|
|
# 'sc1031': SOURCE_TYPE_MAP[source_type],
|
|
# 'sc1032': CODEC_MAP[codec],
|
|
# 'sc1033': STANDARD_MAP[standard],
|
|
|
|
'mtype': SOURCE_MAP[source].encode('iso8859_11', errors='ignore') if source else None,
|
|
'mcoun': COUNTRY_MAP[country].encode('iso8859_11', errors='ignore') if country else None,
|
|
'mtv': ORIGINAL_PLATFORM_MAP[original_platform] if original_platform else None,
|
|
'msub': TRACKTYPE_MAP[tracktype].encode('iso8859_11', errors='ignore') if tracktype else None,
|
|
'color': 0,
|
|
'font': 0,
|
|
'size': 0,
|
|
'descr': description.encode('iso8859_11', errors='ignore')
|
|
}
|
|
|
|
if CATEGORY_MAP[category] in [52, 5, 51, 54, 53, 57]:
|
|
# payload.update({"scs31": SOURCE_TYPE_MAP[source_type]}) if source_type else None
|
|
# payload.update({"scs32": CODEC_MAP[codec]}) if codec else None
|
|
# payload.update({"scs33": STANDARD_MAP[standard]}) if standard else None
|
|
payload['scs31']= SOURCE_TYPE_MAP[source_type] if source_type else None
|
|
payload['scs32']= CODEC_MAP[codec] if codec else None
|
|
payload['scs33']= STANDARD_MAP.get(standard,29) if standard else None
|
|
elif CATEGORY_MAP[category] in [10]:
|
|
# payload.update({"sc1031": SOURCE_TYPE_MAP[source_type]}) if source_type else None
|
|
# payload.update({"sc1032": CODEC_MAP[codec]}) if codec else None
|
|
# payload.update({"sc1033": STANDARD_MAP[standard]}) if standard else None
|
|
|
|
payload['sc1031']= SOURCE_TYPE_MAP[source_type] if source_type else None
|
|
payload['sc1032']= CODEC_MAP[codec] if codec else None
|
|
payload['sc1033']= STANDARD_MAP.get(standard,29) if standard else None
|
|
elif CATEGORY_MAP[category] in [92]:
|
|
# For 4K and Hi-Def Movie, we can add more specific fields if needed
|
|
payload['sc9131'] = SOURCE_TYPE_MAP[source_type] if source_type else None
|
|
payload['sc9132'] = CODEC_MAP[codec] if codec else None
|
|
payload['sc9133'] = STANDARD_MAP.get(standard,29) if standard else None
|
|
elif CATEGORY_MAP[category] in [17]:
|
|
# For 4K, we can add more specific fields if needed
|
|
payload['sc1731'] = SOURCE_TYPE_MAP[source_type] if source_type else None
|
|
payload['sc1732'] = CODEC_MAP[codec] if codec else None
|
|
payload['sc1733'] = STANDARD_MAP.get(standard,29) if standard else None
|
|
|
|
if not await self.check_login():
|
|
await self.login()
|
|
|
|
self.console.log(f"Uploading torrent with payload: {payload}")
|
|
|
|
form = aiohttp.FormData()
|
|
for key, value in payload.items():
|
|
if isinstance(value, bytes):
|
|
value = value.decode('iso8859_11', errors='ignore')
|
|
form.add_field(name=key, value=value,content_type='application/x-www-form-urlencoded; charset=iso-8859-11')
|
|
elif not isinstance(value, str):
|
|
value = str(value) # 🔧 Convert int, float, etc. to str
|
|
form.add_field(name=key, value=value)
|
|
else:
|
|
form.add_field(name=key, value=value)
|
|
|
|
form.add_field('file', open(torrent_file_path, 'rb'), filename=os.path.basename(torrent_file_path), content_type='application/octet-stream')
|
|
self.console.log(f"Uploading torrent with form data: {form._gen_form_data()}")
|
|
|
|
url = f"{self.baseurl}/taketoserv.php"
|
|
headers = {
|
|
'User-Agent': self.user_agent,
|
|
'Referer': f'{self.baseurl}/toserv.php'
|
|
}
|
|
try:
|
|
|
|
async with self.session.post(url, headers=headers, data=form, allow_redirects=False, verify_ssl=False) as response:
|
|
raw = await response.read()
|
|
text = raw.decode('iso8859_11', errors='ignore')
|
|
|
|
self.console.log(f"Response status code: {response.status}")
|
|
# response.encoding = "iso8859_11"
|
|
|
|
if response.status == 302 and 'Location' in response.headers:
|
|
redirect_url:str = "https://bearbit.org/"+response.headers['Location']
|
|
|
|
redirect_id=parse_qs(urlparse(redirect_url).query)["id"][0]
|
|
|
|
redirect_url= "https://bearbit.org/details.php?id={id}".format(id=redirect_id)
|
|
self.console.log(f"Redirected to: {redirect_url}")
|
|
return {"success": True, "url": redirect_url, "message": "Torrent uploaded successfully"}
|
|
else:
|
|
self.console.error(f"Upload failed with status code: {response.status}")
|
|
return {"success": False, "url": None, "message": text}
|
|
|
|
except Exception as e:
|
|
return {"success": False, "url": None, "message": e}
|
|
|
|
|
|
async def retry(self,func, *args, max_attempts=3, delay_seconds=5, **kwargs):
|
|
attempts = 0
|
|
while attempts < max_attempts:
|
|
try:
|
|
return await func(*args, **kwargs)
|
|
except Exception as e:
|
|
self.console.warn(f"Error: {e}. Retrying...")
|
|
attempts += 1
|
|
await asyncio.sleep(delay_seconds) # If using asyncio, replace with await asyncio.sleep(delay_seconds)
|
|
raise Exception("Max retry attempts reached.")
|
|
|
|
class TorrentDD:
|
|
"""Class to interact with TorrentDD API"""
|
|
|
|
category_dict = {
|
|
# "พระมหากษัตริย์": 1,
|
|
"Anime": 2,
|
|
# "Game/PSP/ND/Mobile": 3,
|
|
# "Game/Pc/Ps/Xbox": 4,
|
|
# "สื่อเรียนรู้/แม่และเด็ก": 5,
|
|
# "MV/karaoke": 6,
|
|
# "เพลง": 7,
|
|
# "OS Windows/Office": 8,
|
|
# "AntiVirus/Antispyware": 9,
|
|
# "ซอฟแวร์ มือถือ/ธีม": 10,
|
|
# "ซอฟแวร์ แม็ค/ลินุกซ์": 11,
|
|
# "คอนเสิร์ต": 13,
|
|
# "ทอล์คโชว์/ตลก/วิทยุ": 14,
|
|
# "ทั่วไป": 15,
|
|
# "ธรรมะ": 16,
|
|
# "font/icon/source": 17,
|
|
"4K": 18,
|
|
"Hi-Def Movie": 19,
|
|
# "ภาพยนตร์ DVD": 20,
|
|
# "ภาพยนตร์ VCD": 21,
|
|
# "XviD/DivX/AVI/RM": 22,
|
|
# "รายการทีวี/วาไรตี้": 23,
|
|
# "กีฬา/ฟุตบอล": 24,
|
|
# "รูปภาพ/วอลเปเปอร์": 25,
|
|
"สารคดี": 26,
|
|
# "สื่อเรียนรู้/หนังสือ/Ebook": 27,
|
|
"Korean Series": 28,
|
|
"Japanese Series": 29,
|
|
"Chinese Series": 30,
|
|
"Western Series": 31,
|
|
"Thai Series": 32,
|
|
"Other Series": 40,
|
|
# "คลิปทั่วไป": 33,
|
|
# "XXX (uncensor)": 34,
|
|
# "XXX (censor)": 35,
|
|
# "XXX Clip": 36,
|
|
# "XXX cartoon/book/pic": 37,
|
|
# "XXX Gay/Lesbian": 38
|
|
}
|
|
def __init__(self,console:logger=None):
|
|
load_dotenv(".env")
|
|
self.id = os.getenv('dd_id')
|
|
self.password = os.getenv('dd_pass')
|
|
self.baseurl = "https://www.torrentdd.com"
|
|
self.user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0"
|
|
# self.session= requests.Session()
|
|
self.session = aiohttp.ClientSession()
|
|
self.console = console or logger(app_name="TorrentDD",log_dir="./log")
|
|
|
|
|
|
async def login(self):
|
|
"""Login to TorrentDD"""
|
|
url = urljoin(self.baseurl, '/takelogin.php')
|
|
|
|
payload = {
|
|
'username': self.id,
|
|
'password': self.password
|
|
}
|
|
|
|
headers = {
|
|
'User-Agent': self.user_agent,
|
|
}
|
|
|
|
# response = await self.session.post(url, headers=headers, data=payload, allow_redirects=False)
|
|
async with self.session.post(url, headers=headers, data=payload, allow_redirects=False) as response:
|
|
await response.text()
|
|
|
|
if response.status == 200:
|
|
return False # Login Failed
|
|
else:
|
|
return True
|
|
|
|
async def check_login(self):
|
|
"""Check login status"""
|
|
url = "https://www.torrentdd.com/home.php"
|
|
|
|
headers = {
|
|
'User-Agent': self.user_agent
|
|
}
|
|
|
|
# response = await self.retry(self.session.get, url, headers=headers, allow_redirects=False)
|
|
async with self.session.get(url, headers=headers, allow_redirects=False) as response:
|
|
await response.text()
|
|
if 300 <= response.status < 400:
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
async def retry(self, func, *args, max_attempts=3, delay_seconds=5, **kwargs) -> any:
|
|
attempts = 0
|
|
while attempts < max_attempts:
|
|
try:
|
|
return await func(*args, **kwargs)
|
|
except Exception as e:
|
|
self.console.warn(f"Error: {e}. Retrying...")
|
|
attempts += 1
|
|
# time.sleep(delay_seconds)
|
|
await asyncio.sleep(delay_seconds)
|
|
raise Exception("Max retry attempts reached.")
|
|
|
|
async def upload_torrent(self ,category ,torrent_file_path, name, poster_url, description):
|
|
"""Upload torrent to TorrentDD"""
|
|
if not os.path.exists(torrent_file_path):
|
|
raise FileNotFoundError(f"Torrent file not found: {torrent_file_path}")
|
|
|
|
if not await self.check_login():
|
|
await self.login()
|
|
|
|
url = urljoin(self.baseurl, '/takeupload.php')
|
|
|
|
# payload = {
|
|
# 'type': self.category_dict[category],
|
|
# 'name': name,
|
|
# 'poster': poster_url,
|
|
# 'descr': description,
|
|
# 'countdown': 12000 - len(description),
|
|
# 'cf1': 'on',
|
|
# 'cf2': 'on'
|
|
# }
|
|
# print(f"Uploading torrent with payload: {payload}")
|
|
|
|
# files = [
|
|
# ('file',(os.path.basename(torrent_file_path) ,open(torrent_file_path, 'rb'),'application/octet-stream'))
|
|
# ]
|
|
form = aiohttp.FormData()
|
|
form.add_field('type', str(self.category_dict[category]))
|
|
form.add_field('name', name)
|
|
form.add_field('poster', poster_url)
|
|
form.add_field('descr', description)
|
|
form.add_field('countdown', str(12000 - len(description)))
|
|
form.add_field('cf1', 'on')
|
|
form.add_field('cf2', 'on')
|
|
form.add_field('file', open(torrent_file_path, 'rb'), filename=os.path.basename(torrent_file_path), content_type='application/octet-stream')
|
|
|
|
self.console.log(f"Uploading torrent with form data: {form}")
|
|
headers = {
|
|
'User-Agent': self.user_agent,
|
|
'Referer': f'{urljoin(self.baseurl, '/upload.php')}',
|
|
}
|
|
response = None
|
|
try:
|
|
# response = await self.session.post(url, headers=headers, data=payload, files=files, allow_redirects=False)
|
|
async with self.session.post(url, headers=headers, data=form, allow_redirects=False) as response:
|
|
await response.text()
|
|
self.console.log(f"Response status code: {response.status}")
|
|
# print(response.status_code)
|
|
if response.status == 302 and 'Location' in response.headers:
|
|
return {
|
|
"success": True,
|
|
"url": f"{self.baseurl}/" + response.headers['Location'].lstrip("./").replace('&uploaded=1', ''),
|
|
"raw_url": response.headers['Location'].lstrip("./").replace('&uploaded=1', ''),
|
|
"message": "Success!"
|
|
}
|
|
else:
|
|
extracted_text = BeautifulSoup(await response.text(), 'html.parser').find('div', class_='alert alert-warning').get_text(strip=True)
|
|
return {"success": False, "url":None, "message":extracted_text}
|
|
|
|
# response = None
|
|
except Exception as e:
|
|
return {"success": False, "url":None, "message":e}
|
|
|
|
class Torrent_File:
|
|
def __init__(self, torrent_path:dict[str, str]=None, ss_url:list[str]=None, metadata:list[dict[str,str]]=None):
|
|
self.torrent_path:dict[str, str] = torrent_path
|
|
self.ss_url:list[str] = ss_url
|
|
self.metadata:list[dict[str,str]] = metadata
|
|
class TorrentCreator:
|
|
"""Class to create torrents for files and folders"""
|
|
CODEC_VIDEO_MAP = {
|
|
"hevc": "H.265",
|
|
"h264": "H.264",
|
|
"av1" : "AV1",
|
|
"vp9": "VP9",
|
|
|
|
}
|
|
CODEC_AUDIO_MAP = {
|
|
"aac": "AAC",
|
|
"eac3": "DDP"
|
|
}
|
|
|
|
|
|
# # torrent_files = {"torrent_file":{"bearbit": [], "torrentdd": [], "all": []},"ss_url":[],"metadata":[]}
|
|
|
|
|
|
def __init__(self,console:logger=None):
|
|
load_dotenv(".env")
|
|
self.total_created = 0
|
|
self.start_time = None
|
|
self.current_pbar = None
|
|
|
|
# self.bb_id = dotenv_values('.env')['bb_id']
|
|
# self.bb_pass = dotenv_values('.env')['bb_pass']
|
|
# self.bb_baseurl =f"https://bearbit.org"
|
|
# self.bb_user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0"
|
|
|
|
# self.bb_session = requests.Session()
|
|
|
|
self.TRACKER_BEARBIT = None
|
|
self.TRACKER_TORRENTDD = os.getenv('TRACKER_TORRENTDD')
|
|
# self.bb_login()
|
|
# self.bb_get_tracker()
|
|
# self.TRACKER_SETS = [
|
|
# ('bearbit', [self.TRACKER_BEARBIT]),
|
|
# ('torrentdd', [self.TRACKER_TORRENTDD]),
|
|
# ('both', [self.TRACKER_BEARBIT, self.TRACKER_TORRENTDD]),
|
|
# ]
|
|
self.TRACKER_SETS = []
|
|
|
|
self.comment="[SeFree] Auto-generated torrent"
|
|
|
|
self.console = console or logger(app_name="torrent_uploader",log_dir="./log")
|
|
|
|
# self.ss_url = []
|
|
|
|
async def create_single_file_torrent(self, file_path, trackers, output_path) -> str:
|
|
"""Create torrent for a single file"""
|
|
output_path=output_path.replace('#', '')
|
|
file_name = os.path.basename(file_path)
|
|
|
|
|
|
self.console.log(f"\n🔹 Creating torrent for: {file_name}")
|
|
|
|
torrent = torf.Torrent(
|
|
path=file_path,
|
|
trackers=trackers,
|
|
private=True,
|
|
created_by="[SeFree]",
|
|
# piece_size=1048576, # 1 MiB
|
|
# piece_size_max=16777216, # 16 MiB
|
|
piece_size_min=1048576, # 1 MiB
|
|
comment=self.comment,
|
|
)
|
|
|
|
|
|
file_size = os.path.getsize(file_path)
|
|
self.current_pbar = tqdm(total=file_size, unit='B', unit_scale=True, desc="Generating hashes")
|
|
|
|
def progress_callback(torrent, path, pieces_done, pieces_total):
|
|
if self.current_pbar:
|
|
position = (pieces_done / pieces_total) * file_size
|
|
self.current_pbar.update(position - self.current_pbar.n)
|
|
|
|
# torrent.generate(callback=progress_callback)
|
|
await asyncio.to_thread(torrent.generate, callback=progress_callback)
|
|
self.current_pbar.close()
|
|
self.current_pbar = None
|
|
|
|
torrent.write(output_path)
|
|
self.console.log(f"✅ Created: {os.path.basename(output_path)}")
|
|
self.total_created += 1
|
|
return output_path
|
|
|
|
async def create_multi_file_torrent(self, folder_path, trackers, output_path) -> str:
|
|
"""Create torrent for a folder (multiple files)"""
|
|
output_path=output_path.replace('#', '')
|
|
folder_name = os.path.basename(folder_path)
|
|
self.console.log(f"\n📁 Creating torrent for folder: {folder_name}")
|
|
|
|
torrent = torf.Torrent(
|
|
path=folder_path,
|
|
trackers=trackers,
|
|
private=True,
|
|
created_by="[SeFree]",
|
|
# piece_size=1048576, # 1 MiB
|
|
# piece_size_max=16777216, # 16 MiB
|
|
piece_size_min=1048576, # 1 MiB
|
|
comment=self.comment,
|
|
)
|
|
|
|
total_size = 0
|
|
|
|
for root, _, files in os.walk(folder_path):
|
|
for file in files:
|
|
|
|
# print(f"🔹 Adding file: {file}")
|
|
if not file.endswith('.torrent') :
|
|
|
|
|
|
total_size += os.path.getsize(os.path.join(root, file))
|
|
|
|
self.current_pbar = tqdm(total=total_size, unit='B', unit_scale=True, desc="Generating hashes")
|
|
|
|
def progress_callback(torrent, path, pieces_done, pieces_total):
|
|
if self.current_pbar:
|
|
position = (pieces_done / pieces_total) * total_size
|
|
self.current_pbar.update(position - self.current_pbar.n)
|
|
|
|
# torrent.generate(callback=progress_callback)
|
|
await asyncio.to_thread(torrent.generate, callback=progress_callback)
|
|
|
|
self.current_pbar.close()
|
|
self.current_pbar = None
|
|
|
|
torrent.write(output_path)
|
|
self.console.log(f"✅ Created: {os.path.basename(output_path)}")
|
|
self.total_created += 1
|
|
return output_path
|
|
|
|
async def create_torrents_for_directory(self, directory_path,pack=False,rename:str=None) -> list[Torrent_File]:
|
|
"""Create torrents for all files and folders in the given directory"""
|
|
self.screenshot = ScreenShot()
|
|
|
|
|
|
self.console.log(f"\n🚀 Starting torrent creation in: {directory_path}")
|
|
# print("📡 Trackers: BearBit, TorrentDD, All")
|
|
self.start_time = time.time()
|
|
|
|
items = []
|
|
if not pack:
|
|
for item in sorted(os.listdir(directory_path)):
|
|
item_path = os.path.join(directory_path, item)
|
|
if os.path.isfile(item_path) and not item.endswith('.torrent') and not item.endswith('.jpg') and not item.endswith('.png'):
|
|
items.append(('file', item_path))
|
|
elif os.path.isdir(item_path):
|
|
items.append(('folder', item_path))
|
|
else:
|
|
items.append(('folder', directory_path))
|
|
|
|
if not items:
|
|
self.console.error("❌ No files or folders found to create torrents")
|
|
return
|
|
|
|
# total_items = len(items) * len(self.TRACKER_SETS)
|
|
current_item = 0
|
|
|
|
# torrent_files = {"torrent_file":{"bearbit": [], "torrentdd": [], "all": []},"ss_url":[],"metadata":[]}
|
|
torrent_files=[]
|
|
await self.screenshot.login()
|
|
for item_type, item_path in items:
|
|
torrent_file=Torrent_File()
|
|
current_item += 1
|
|
base_name = os.path.splitext(os.path.basename(item_path))[0] if item_type == 'file' else os.path.basename(item_path) if not rename else rename
|
|
|
|
ss_output=None
|
|
# ss_url=None
|
|
# metadata=None
|
|
if item_type == 'file':
|
|
|
|
ss_output=await self.screenshot.run(item_path)
|
|
torrent_file.ss_url= [await self.screenshot.upload_to_imgbb(ss_output)]
|
|
# torrent_files['ss_url'].append(ss_url)
|
|
# ss_urls.append(ss_url)
|
|
|
|
duration, video_codec, audio_codec, audio_channels, resolution, size_mb, audio_lang, subtitle_lang, json_metadata = self.get_metadata(item_path)
|
|
torrent_file.metadata=[{
|
|
"path": item_path,
|
|
"sub_path": item_path,
|
|
"duration": duration,
|
|
"video_codec": video_codec,
|
|
"audio_codec": audio_codec,
|
|
"audio_channels": audio_channels,
|
|
"resolution": resolution,
|
|
"size_mb": size_mb,
|
|
"audio_lang": audio_lang,
|
|
"subtitle_lang": subtitle_lang,
|
|
"json_metadata": json_metadata
|
|
}]
|
|
# torrent_files['metadata'].append({
|
|
# "path": item_path,
|
|
# "duration": duration,
|
|
# "video_codec": video_codec,
|
|
# "audio_codec": audio_codec,
|
|
# "audio_channels": audio_channels,
|
|
# "resolution": resolution,
|
|
# "size_mb": size_mb,
|
|
# "audio_lang": audio_lang,
|
|
# "subtitle_lang": subtitle_lang,
|
|
# "json_metadata": json_metadata
|
|
# })
|
|
|
|
# self.ss_url.append({item_path.replace("#", ""):self.screenshot.upload_to_imgbb(ss_output)})
|
|
else:
|
|
# stop= False
|
|
torrent_file.ss_url=[]
|
|
torrent_file.metadata=[]
|
|
for root, _, files in sorted(os.walk(item_path)):
|
|
|
|
base_name = base_name + " " + os.path.splitext(os.path.basename(item_path))[0] if rename and not pack else base_name
|
|
# ss_url=None
|
|
|
|
# if stop and pack:
|
|
# break
|
|
|
|
for i in sorted(files):
|
|
# torrent_file=Torrent_File()
|
|
if i.endswith('.torrent') or i.endswith('.jpg') or i.endswith('.png'):
|
|
continue
|
|
ss_output=await self.screenshot.run(os.path.join(root, i))
|
|
torrent_file.ss_url.append(await self.screenshot.upload_to_imgbb(ss_output))
|
|
# ss_urls.append(ss_url)
|
|
# self.ss_url.append({root.replace("#", ""):self.screenshot.upload_to_imgbb(ss_output)})
|
|
|
|
|
|
duration, video_codec, audio_codec, audio_channels, resolution, size_mb, audio_lang, subtitle_lang, json_metadata = self.get_metadata(os.path.join(root,i))
|
|
torrent_file.metadata.append({
|
|
"path": item_path,
|
|
"sub_path": os.path.join(root, i),
|
|
"duration": duration,
|
|
"video_codec": video_codec,
|
|
"audio_codec": audio_codec,
|
|
"audio_channels": audio_channels,
|
|
"resolution": resolution,
|
|
"size_mb": size_mb,
|
|
"audio_lang": audio_lang,
|
|
"subtitle_lang": subtitle_lang,
|
|
"json_metadata": json_metadata
|
|
})
|
|
# torrent_files['metadata'].append({
|
|
# "path": item_path,
|
|
# "duration": duration,
|
|
# "video_codec": video_codec,
|
|
# "audio_codec": audio_codec,
|
|
# "audio_channels": audio_channels,
|
|
# "resolution": resolution,
|
|
# "size_mb": size_mb,
|
|
# "audio_lang": audio_lang,
|
|
# "subtitle_lang": subtitle_lang,
|
|
# "json_metadata": json_metadata
|
|
# })
|
|
# stop = True
|
|
break # Only take the first file's metadata for the folders
|
|
# torrent_file.metadatas=metadatas
|
|
# torrent_file.ss_urls=ss_urls
|
|
# torrent_files['metadata'].append(metadatas)
|
|
# torrent_files['ss_url'].append(ss_urls)
|
|
torrent_file.torrent_path={}
|
|
for label, trackers in self.TRACKER_SETS:
|
|
output_name = f"{base_name}.{label}.torrent"
|
|
# os.makedirs(os.path.join(directory_path, "torrent"), exist_ok=True)
|
|
output_path = os.path.join(directory_path, output_name) # if item_type != 'file' else os.path.join(os.path.dirname(directory_path), output_name)
|
|
|
|
self.console.log(f"\n📌 Processing item {current_item}/{len(items)} ({label})")
|
|
|
|
try:
|
|
if not pack:
|
|
if item_type == 'file':
|
|
torrent_file.torrent_path[label]= await self.create_single_file_torrent(item_path, trackers, output_path)
|
|
# torrent_files["torrent_file"][label].extend(await self.create_single_file_torrent(item_path, trackers, output_path))
|
|
else:
|
|
torrent_file.torrent_path[label]= await self.create_multi_file_torrent(item_path, trackers, output_path)
|
|
|
|
# torrent_files["torrent_file"][label].extend(await self.create_multi_file_torrent(item_path, trackers, output_path))
|
|
else:
|
|
# torrent_files["torrent_file"][label].extend(await self.create_single_file_torrent(item_path, trackers,os.path.join(os.path.dirname(item_path),output_name)))
|
|
torrent_file.torrent_path[label]= await self.create_single_file_torrent(item_path, trackers,os.path.join(os.path.dirname(item_path),output_name))
|
|
|
|
except Exception as e:
|
|
self.console.error(f"❌ Error creating torrent for {item_path}: {str(e)}")
|
|
continue
|
|
|
|
torrent_files.append(torrent_file)
|
|
elapsed_time = time.time() - self.start_time
|
|
self.console.log(f"\n🎉 Torrent creation completed! Created {self.total_created} torrent(s) in {elapsed_time:.2f} seconds")
|
|
return torrent_files
|
|
|
|
def get_metadata(self, file_path):
|
|
|
|
def ffprobe_entry(stream, entry,stream_type="stream"):
|
|
cmd = [
|
|
"ffprobe", "-v", "error", "-select_streams", stream,
|
|
"-show_entries", f"{stream_type}={entry}",
|
|
"-of", "default=noprint_wrappers=1:nokey=1", file_path
|
|
]
|
|
result = subprocess.run(cmd, stdout=subprocess.PIPE)
|
|
return result.stdout.decode().strip()
|
|
|
|
def get_json_metadata():
|
|
"""Get json metadata for the file using ffprobe"""
|
|
cmd = [
|
|
"ffprobe", "-v", "error", "-print_format", "json","-show_format", "-show_streams",
|
|
"-show_chapters", file_path
|
|
]
|
|
result = subprocess.run(cmd, stdout=subprocess.PIPE)
|
|
if result.returncode != 0:
|
|
raise Exception(f"ffprobe error: {result.stderr.decode().strip()}")
|
|
|
|
return json.loads(result.stdout.decode())
|
|
|
|
def ffprobe_tag_languages(stream_type):
|
|
cmd = [
|
|
"ffprobe", "-v", "error", "-select_streams", stream_type,
|
|
"-show_entries", "stream_tags=language",
|
|
"-of", "csv=p=0", file_path
|
|
]
|
|
# print(" ".join(cmd))
|
|
result = subprocess.run(cmd, stdout=subprocess.PIPE)
|
|
# Get unique non-empty language tags
|
|
langs = list(set([lang.strip() for lang in result.stdout.decode().splitlines() if lang.strip()]))
|
|
return ",".join(langs) if langs else "und"
|
|
|
|
duration = float(self.get_duration(file_path))
|
|
video_codec = ffprobe_entry("v:0", "codec_name").splitlines()[0] + " " + ffprobe_entry("v:0", "profile").splitlines()[0]
|
|
|
|
audio_codec = ffprobe_entry("a:0", "codec_name").splitlines()[0] + (" DDP" if "Dolby Digital Plus" in ffprobe_entry("a:0", "profile").splitlines()[0] else " DD" if "Dolby Digital" in ffprobe_entry("a:0", "profile").splitlines()[0] else "") + (" Atmos" if "Atmos" in ffprobe_entry("a:0", "profile").splitlines()[0] else "")
|
|
audio_channels = ffprobe_entry("a:0", "channel_layout").splitlines()[0]
|
|
|
|
|
|
width = ffprobe_entry("v:0", "width").splitlines()[0]
|
|
height = ffprobe_entry("v:0", "height").splitlines()[0]
|
|
resolution = f"{width}x{height}"
|
|
size_mb = os.path.getsize(file_path) / (1024 * 1024)
|
|
|
|
audio_lang = ffprobe_tag_languages("a").upper()
|
|
subtitle_lang = ffprobe_tag_languages("s").upper()
|
|
|
|
json_metadata = dict(get_json_metadata())
|
|
|
|
return duration, video_codec.upper().strip(), audio_codec.upper().strip(), audio_channels, resolution, size_mb, audio_lang, subtitle_lang, json_metadata
|
|
|
|
@staticmethod
|
|
def get_duration(filename):
|
|
result = subprocess.run(
|
|
["ffprobe", "-v", "error", "-show_entries", "format=duration",
|
|
"-of", "default=noprint_wrappers=1:nokey=1", filename],
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT
|
|
)
|
|
return float(result.stdout)
|
|
|
|
class TorrentUpload(TorrentCreator):
|
|
def __init__(self, qbit=None, bb=None, dd=None,console:logger=None):
|
|
super().__init__(console=console)
|
|
self.qbit = qbit or qBittorrent(console=console)
|
|
self.bb = bb or BearBit(console=console)
|
|
self.dd = dd or TorrentDD(console=console)
|
|
# self.console = console or logger(app_name="torrent_uploader",log_dir="./log")
|
|
|
|
async def upload_torrent(self,entry:dict,qbit_category="SeFree"):
|
|
|
|
file_path = entry['file_path']
|
|
imdb_id = entry['imdb_id']
|
|
tmdb_id = entry.get('tmdb_id', None) # Optional TMDB ID
|
|
category = entry['category']
|
|
source_type = entry['source_type']
|
|
source = entry['source']
|
|
country = entry['country']
|
|
original_platform = entry['original_platform']
|
|
is_subdir = entry['is_subdir']
|
|
bearbit = entry['bearbit']
|
|
torrentdd = entry['torrentdd']
|
|
is_movie = entry['is_movie']
|
|
pack = entry['pack']
|
|
|
|
await self.bb.login()
|
|
await self.bb.get_tracker()
|
|
|
|
|
|
if tmdb_id:
|
|
tmdb_response = await tmdb_info(tmdb_id, type='movie' if is_movie else 'tv')
|
|
tmdb_response_eng = await tmdb_info(tmdb_id, type='movie' if is_movie else 'tv', language='en-US')
|
|
|
|
trailers=await tmdb_trailer(tmdb_response,type='movie' if is_movie else 'tv')
|
|
|
|
|
|
self.TRACKER_SETS = []
|
|
imdburl= f'https://www.imdb.com/title/{imdb_id}/'
|
|
if bearbit:
|
|
self.TRACKER_SETS.append(('bearbit', [self.bb.TRACKER_BEARBIT]))
|
|
if torrentdd:
|
|
self.TRACKER_SETS.append(('torrentdd', [self.TRACKER_TORRENTDD]))
|
|
all_list=[]
|
|
for i in self.TRACKER_SETS:
|
|
# print(f"Using tracker: {i[0]} - {i[1]}")
|
|
all_list.extend(i[1])
|
|
|
|
self.TRACKER_SETS.append(('all', all_list))
|
|
|
|
torrent_files = []
|
|
|
|
# name_short = await rename(m, torrent, i, is_movie,pack=pack,tmdb_response=tmdb_response_eng,short=True)
|
|
# print(torrent)
|
|
# print(name_short)
|
|
# exit()
|
|
|
|
if os.path.isdir(file_path):
|
|
# if dir:
|
|
if not pack:
|
|
if is_subdir:
|
|
for root, directories, _ in os.walk(file_path):
|
|
for directory in sorted(directories):
|
|
torrent_files+= await self.create_torrents_for_directory(os.path.join(root, directory))
|
|
# torrent_files = {k: torrents[k] + torrent_files[k] for k in torrents}
|
|
|
|
else:
|
|
name_short = await rename({"path":file_path}, None, is_movie,pack=pack,tmdb_response=tmdb_response_eng,short=True)
|
|
torrent_files = await self.create_torrents_for_directory(file_path,rename=name_short)
|
|
else:
|
|
name_short = await rename({"path":file_path}, None, is_movie,pack=pack,tmdb_response=tmdb_response_eng,short=True)
|
|
torrent_files = await self.create_torrents_for_directory(file_path,rename=name_short, pack=True)
|
|
|
|
elif os.path.isfile(file_path):
|
|
torrent_file=Torrent_File()
|
|
duration, video_codec, audio_codec, audio_channels, resolution, size_mb, audio_lang, subtitle_lang, json_metadata = self.get_metadata(file_path)
|
|
torrent_file.metadata=[{
|
|
"path": file_path,
|
|
"sub_path": file_path,
|
|
"duration": duration,
|
|
"video_codec": video_codec,
|
|
"audio_codec": audio_codec,
|
|
"audio_channels": audio_channels,
|
|
"resolution": resolution,
|
|
"size_mb": size_mb,
|
|
"audio_lang": audio_lang,
|
|
"subtitle_lang": subtitle_lang,
|
|
"json_metadata": json_metadata
|
|
}]
|
|
torrent_file.torrent_path={}
|
|
for label, trackers in self.TRACKER_SETS:
|
|
output_name = f"{os.path.splitext(os.path.basename(file_path))[0]}.{label}.torrent"
|
|
|
|
output_path = os.path.join(os.path.dirname(os.path.dirname(file_path)), output_name) if not is_movie else os.path.join(os.path.dirname(file_path), output_name)
|
|
# os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
|
torrent_file.torrent_path[label] = await self.create_single_file_torrent(file_path, trackers, output_path)
|
|
|
|
screenshot = ScreenShot(console=self.console)
|
|
await screenshot.login()
|
|
ss_output = await screenshot.run(file_path,is_movie)
|
|
ss_url= await screenshot.upload_to_imgbb(ss_output)
|
|
# creator.ss_url.append({os.path.join(os.path.dirname(file_path), os.path.splitext(os.path.basename(file_path))[0]).replace("#", ""):creator.screenshot.upload_to_imgbb(ss_output)})
|
|
torrent_file.ss_url=[ss_url]
|
|
torrent_files.append(torrent_file)
|
|
|
|
# print("\nGenerated torrent files:")
|
|
# with open("torrent_files.json", "w") as f:
|
|
# json.dump([item.__dict__ for item in torrent_files], f, indent=4)
|
|
# exit()
|
|
# status={"bearbit": False, "torrentdd": False}
|
|
self.console.debug("torrent status : ",[item.__dict__ for item in torrent_files])
|
|
status={"bearbit": [], "torrentdd": []}
|
|
# for label, files in torrent_files.items():
|
|
# print(f"\n{label.capitalize()} Torrents:")
|
|
# for torrent_file in files:
|
|
# print(f" - {torrent_file}")
|
|
|
|
embed = discord.Embed(
|
|
title="Torrent Creation Completed",
|
|
description="The torrent files have been created successfully.",
|
|
color=discord.Color.green()
|
|
)
|
|
embed.add_field(name="Torrent Files", value="\n".join([i for item in torrent_files for i in item.torrent_path["all"]]), inline=False)
|
|
embed.set_footer(text="Now uploading to BearBit and TorrentDD...")
|
|
# await channel.send(embed=embed)
|
|
for torrent in torrent_files:
|
|
|
|
# meta= torrent_files['metadata'][i] if i < len(torrent_files['metadata']) else None
|
|
|
|
# for m in meta:
|
|
# for torrent_all in torrent.torrent_path["all"]:
|
|
name = await rename(torrent.metadata[0], torrent.torrent_path["all"], is_movie,pack=pack,tmdb_response=tmdb_response_eng)
|
|
await self.qbit.add_torrent(torrent.torrent_path["all"], save_path=os.path.dirname(torrent.metadata[0]['path']),category=qbit_category, rename=name)
|
|
await asyncio.sleep(1)
|
|
await self.qbit.set_super_seed(name)
|
|
await asyncio.sleep(1) # Sleep to avoid overwhelming the qBittorrent API
|
|
# for i,torrent in enumerate(sorted(torrent_files['all'])):
|
|
# print(f"Adding torrent for: {torrent_files['metadata'][i]['path']}")
|
|
# qbit.add_torrent(torrent, save_path=os.path.dirname(torrent_files['metadata'][i]['path']))
|
|
# time.sleep(1) # Sleep to avoid overwhelming the qBittorrent API
|
|
# return
|
|
|
|
for torrent in torrent_files:
|
|
|
|
poster_url = torrent.ss_url
|
|
meta= torrent.metadata
|
|
|
|
# # name = "[SeFree] " + name
|
|
# name = rename(torrent_files, torrent, i, is_movie,pack=pack)
|
|
# print(f"Uploading {name}...")
|
|
name = None
|
|
# continue
|
|
|
|
# await qbit.add_torrent(torrent, save_path=os.path.dirname(torrent_files['metadata'][i]['path']), rename=name)
|
|
|
|
# continue
|
|
|
|
# if "THA" in torrent_files['metadata'][i]['audio_lang'] and "THA" in torrent_files['metadata'][i]['subtitle_lang']:
|
|
# tracktype = 'Thai Audio Thai Sub'
|
|
# elif "THA" not in torrent_files['metadata'][i]['audio_lang'] and "THA" in torrent_files['metadata'][i]['subtitle_lang']:
|
|
# tracktype = "Thai Sub"
|
|
# elif "THA" in torrent_files['metadata'][i]['audio_lang'] and "THA" not in torrent_files['metadata'][i]['subtitle_lang']:
|
|
# tracktype = 'Thai Audio'
|
|
tracktype = None
|
|
|
|
# for url in poster_url:
|
|
# if url:
|
|
# description += f"[img]{url}[/img]\n\n"
|
|
# description += f"[img]{poster_url}[/img]\n\n"
|
|
for m in meta:
|
|
if name is None:
|
|
name = await rename(m, torrent.torrent_path["all"], is_movie,pack=pack,tmdb_response=tmdb_response_eng)
|
|
sdescr = tmdb_response.name + " | " + tmdb_response_eng.name
|
|
# description = f"[color=Red][b][size=6]{name}[/size][/b][/color]\n\n"
|
|
description = "[size=5]พูดคุย ติชม แจ้งปัญหา ขอไฟล์ได้ที่ [color=Red]Discord[/color][/size]\n"
|
|
description += "[size=5]https://discord.gg/cdN69PMbnV[/size]\n\n"
|
|
|
|
description += f"[color=Red][b][size=6]{tmdb_response_eng.name} ({tmdb_response_eng.year})[/size][/b][/color]\n"
|
|
description += f"[color=Red][b][size=6]{tmdb_response.name}[/size][/b][/color]\n\n"
|
|
description += f"[img]{tmdb_response.poster_url(SizeType.w500)}[/img]\n\n"
|
|
|
|
for trailer in trailers:
|
|
description += f"[youtube]{trailer["key"]}[/youtube]\n"
|
|
break
|
|
# description += f"[youtube]{trailers[0]["key"]}[/youtube]\n"
|
|
|
|
description += f"[quote][color=red][b][size=4] Overview:[/size][/b][/color]\n[size=3]{tmdb_response.overview}\n\n{tmdb_response_eng.overview}[/size][/quote]\n\n"
|
|
|
|
if poster_url:
|
|
for url in poster_url:
|
|
description += f"[img]{url}[/img]\n\n"
|
|
# description += f"[size=4][b]{os.path.basename(m['sub_path'])}[/b][/size]\n"
|
|
description += ffprobe_streams_to_bbcode(m['json_metadata'], os.path.basename(m['sub_path']))
|
|
|
|
if "TH" in m['audio_lang'] and "TH" in m['subtitle_lang']:
|
|
tracktype = 'Thai Audio Thai Sub'
|
|
elif "TH" not in m['audio_lang'] and "TH" in m['subtitle_lang']:
|
|
tracktype = "Thai Sub"
|
|
elif "TH" in m['audio_lang'] and "TH" not in m['subtitle_lang']:
|
|
tracktype = 'Thai Audio'
|
|
|
|
|
|
# description += f"[color=Red][size=3]ปล่อยผ่าน IPv6 ตรง | IPv4 Port forward[/size][/color]\n"
|
|
# description += f"[color=Red][size=3]อยากโหลดเร็ว เกาะไว แนะนำเปิด IPv6 ครับ[/size][/color]\n\n"
|
|
description += "[color=Red][size=3]อัพโหลดอัตโนมัติผ่านสคริป[/size][/color]\n\n"
|
|
description += "[size=2]ทุกไฟล์โหลดตรงจาก Source ไม่ได้ผ่านการ Transcode codec[/size]\n"
|
|
description += "[size=2]หากแต่บางทีจะมีการ Remux File เช่น BLBL ไม่มีพากย์ไทย ก็จะไปเอาของ TID มาใส่[/size]\n\n"
|
|
# description += f"[size=4]ชื่อไฟล์ | ซีซัน อัตโนมัติจาก Sonarr/Radarr โดยอิงจาก TVDB[/size]\n\n"
|
|
# description += f"[size=5]มีปัญหาอะไรสามารถ DM | Comment แจ้งได้[/size]"
|
|
description += "[size=5]พูดคุย ติชม แจ้งปัญหา ขอไฟล์ได้ที่ [color=Red]Discord[/color][/size]\n"
|
|
description += "[size=5]https://discord.gg/cdN69PMbnV[/size]\n\n"
|
|
|
|
self.console.debug("Name :",name)
|
|
self.console.debug("description :",description)
|
|
# print(f"Uploading torrent: {name} with description:\n{description}")
|
|
# print(f"Uploading torrent: {name}")
|
|
# print(f"JSON Metadata: {torrent_files['metadata'][i]['json_metadata']}")
|
|
# print(ffprobe_streams_to_bbcode(torrent_files['metadata'][i]['json_metadata']))3
|
|
# print(sorted(torrent_files['bearbit']))
|
|
# print(i)
|
|
# print(sorted(torrent_files['bearbit'])[i])
|
|
# continue
|
|
if bearbit:
|
|
result_bb = await self.bb.upload_torrent(
|
|
name=name,
|
|
sdescr=sdescr,
|
|
# poster_url=poster_url[0],
|
|
poster_url=tmdb_response.poster_url(SizeType.w500),
|
|
imdburl=imdburl,
|
|
category=category,
|
|
source_type=source_type,
|
|
codec=meta[0]['video_codec'].split(" ")[0] if meta[0]['video_codec'].split(" ")[0] in ['H264','HEVC'] else "Other",
|
|
standard=meta[0]['resolution'].split('x')[1] + "p",
|
|
country=country,
|
|
source=source,
|
|
original_platform=original_platform,
|
|
tracktype=tracktype,
|
|
torrent_file_path=torrent.torrent_path["bearbit"],
|
|
description=description
|
|
)
|
|
|
|
if result_bb is not None:
|
|
if result_bb.get('success', False):
|
|
self.console.log(f"Upload result BearBit: {result_bb['message']}")
|
|
status['bearbit'].append(({"status":True,
|
|
"name":name,
|
|
"path":torrent.torrent_path['bearbit'],
|
|
"url":result_bb.get('url', "")}))
|
|
else:
|
|
self.console.error(f"❌ Failed to upload torrent: {torrent} - {result_bb['message']}")
|
|
status['bearbit'].append(({"status":False,
|
|
"name":name,
|
|
"path":torrent.torrent_path['bearbit'],
|
|
"url":result_bb.get('url', "")}))
|
|
else:
|
|
self.console.error(f"❌ Failed to upload torrent. No response: {torrent}")
|
|
status['bearbit'].append(({"status":False,
|
|
"name":name,
|
|
"path":torrent.torrent_path['bearbit'],
|
|
"url":result_bb.get('url', "")}))
|
|
# await asyncio.sleep(5) # Sleep to avoid overwhelming the API
|
|
|
|
if torrentdd:
|
|
result_dd = await self.dd.upload_torrent(
|
|
category=category,
|
|
torrent_file_path=torrent.torrent_path['torrentdd'],
|
|
name=name,
|
|
poster_url=poster_url[0],
|
|
description=description,
|
|
)
|
|
if result_dd is not None:
|
|
if result_dd.get('success', False):
|
|
self.console.log(f"Upload result TorrentDD: {result_dd['message']}")
|
|
status['torrentdd'].append(({"status":True,
|
|
"name":name,
|
|
"path":torrent.torrent_path['torrentdd'],
|
|
"url":result_dd.get('url', "")}))
|
|
else:
|
|
self.console.error(f"❌ Failed to upload torrent: {torrent} - {result_dd['message']}")
|
|
status['torrentdd'].append(({"status":False,
|
|
"name":name,
|
|
"path":torrent.torrent_path['torrentdd'],
|
|
"url":result_dd.get('url', "")}))
|
|
# continue
|
|
else:
|
|
self.console.error(f"❌ Failed to upload torrent. No response: {torrent}")
|
|
status['torrentdd'].append(({"status":False,
|
|
"name":name,
|
|
"path":torrent.torrent_path['torrentdd'],
|
|
"url":result_dd.get('url', "")}))
|
|
|
|
await asyncio.sleep(5) # Sleep to avoid overwhelming the API
|
|
if not (any(item['status'] is False for item in status['bearbit']) or any(item['status'] is False for item in status['torrentdd'])):
|
|
|
|
# embed = discord.Embed(
|
|
# title="Torrent Upload Completed",
|
|
# description="All torrents have been created and uploaded successfully.",
|
|
# color=discord.Color.green()
|
|
# )
|
|
# bb_text=""
|
|
# for staBB in status['bearbit']:
|
|
# bb_text+=f"{"Uploaded" if staBB["status"] else "Fail Upload"} : {staBB["name"]}"
|
|
# bb_text+="\n"
|
|
# embed.add_field(name="BearBit", value=bb_text, inline=False)
|
|
|
|
# dd_text=""
|
|
# for staDD in status['torrentdd']:
|
|
# dd_text+=f"{"Uploaded" if staDD["status"] else "Fail Upload"} : {staDD["name"]}"
|
|
# dd_text+="\n"
|
|
# embed.add_field(name="TorrentDD", value=dd_text, inline=False)
|
|
|
|
# # if status['bearbit'] or status['torrentdd']:
|
|
# # for i,torrent in enumerate(torrent_files['all']):
|
|
# # embed.add_field(name="Title", value=os.path.basename(torrent).replace(".all.torrent",""), inline=False)
|
|
# # embed.add_field(name="BearBit", value="[Click here to visit BearBit](https://bearbit.org/)", inline=False)
|
|
# # embed.add_field(name="TorrentDD", value="[Click here to visit TorrentDD](https://torrentdd.com/)", inline=False)
|
|
# embed.set_footer(text="Thank you for visiting!")
|
|
# console.debug(status)
|
|
self.console.log('Torrent Upload Completed')
|
|
|
|
# await channel.send(embed=embed)
|
|
return status
|
|
else:
|
|
# elif not status['bearbit'] or not status['torrentdd']:
|
|
# embed = discord.Embed(
|
|
# title="Torrent Upload Failed",
|
|
# description="Some or all torrents were fail to uploaded. Please check the logs for details.",
|
|
# color=discord.Color.red() if (any(item['status'] is False for item in status['bearbit']) and any(item['status'] is False for item in status['torrentdd'])) else discord.Color.orange()
|
|
# )
|
|
# bb_text=""
|
|
# torrent_name=None
|
|
# for staBB in status['bearbit']:
|
|
# bb_text+=f"{"Uploaded" if staBB["status"] else "Fail Upload"} : {staBB["name"]}"
|
|
# bb_text+="\n"
|
|
# embed.add_field(name="BearBit", value=bb_text, inline=False)
|
|
|
|
# dd_text=""
|
|
# for staDD in status['torrentdd']:
|
|
# dd_text+=f"{"Uploaded" if staDD["status"] else "Fail Upload"} : {staDD["name"]}"
|
|
# dd_text+="\n"
|
|
# embed.add_field(name="TorrentDD", value=dd_text, inline=False)
|
|
|
|
|
|
# embed.set_footer(text="Please check the logs for more details.")
|
|
self.console.debug(status)
|
|
self.console.error('Torrent Upload Failed :',status)
|
|
return status
|
|
|
|
|
|
async def tmdb_info(tmdb_id,type='movie',language='th-TH'):
|
|
load_dotenv(".env")
|
|
|
|
tmdb = TMDb(key=os.getenv('tmdb_api'), language=language, region="TH")
|
|
if type == 'movie':
|
|
response = await tmdb.movie(movie_id=tmdb_id).details(append_to_response="videos")
|
|
response.name = response.__str__()
|
|
|
|
elif type == 'tv':
|
|
response = await tmdb.tv(tv_id=tmdb_id).details(append_to_response="videos")
|
|
else:
|
|
raise ValueError("Invalid type specified. Use 'movie' or 'tv'.")
|
|
|
|
return response
|
|
|
|
async def tmdb_trailer(response: Movie | TV, type='movie'):
|
|
tmdb_trailer = await tmdb_info(response.id, type=type, language=response.original_language)
|
|
|
|
trailers=[]
|
|
for video in tmdb_trailer.videos.results:
|
|
if not video.official or video.site !="YouTube" or video.type!="Trailer":
|
|
continue
|
|
# print(video.official)
|
|
# print(video.site)
|
|
# print(video.name)
|
|
# print("https://www.youtube.com/watch?v="+video.key)
|
|
trailers.append({
|
|
"name":video.name,
|
|
"site":video.site,
|
|
"key": video.key
|
|
})
|
|
return trailers
|
|
|
|
|
|
|
|
async def rename(metadata,torrent,is_movie=False,pack=False,short=False,tmdb_response:Movie | TV=None):
|
|
import re
|
|
|
|
# i=Number
|
|
if not pack:
|
|
if os.path.isdir(metadata['path']) and torrent :
|
|
name = tmdb_response.name + " " + os.path.basename(metadata['path'])
|
|
name += f" ({tmdb_response.year})" if hasattr(tmdb_response, 'year') else ''
|
|
|
|
else:
|
|
name = tmdb_response.name #+" "+ os.path.dirname(torrent_files['path']).split('/')[-1]
|
|
|
|
if is_movie:
|
|
name += f" ({tmdb_response.year})" if hasattr(tmdb_response, 'year') else ''
|
|
else:
|
|
|
|
name = tmdb_response.name
|
|
match = re.search(r"S\d{2}", os.path.basename(metadata['path']))
|
|
if match:
|
|
name += " " +match.group(0)
|
|
name += f" ({tmdb_response.year})" if hasattr(tmdb_response, 'year') else ''
|
|
|
|
|
|
|
|
if short:
|
|
return name
|
|
|
|
match = re.search(r"S\d{2}E\d{2}", os.path.basename(torrent))
|
|
if match:
|
|
name += ' | ' + match.group(0)
|
|
|
|
name += " | " + metadata['video_codec']
|
|
name += " | " + metadata['audio_codec'] + " " + metadata['audio_channels']
|
|
name += " | " + metadata['resolution'].split('x')[1] + "p"
|
|
name += " | " + "Audio : " + metadata['audio_lang'] + " | " + "Sub : " + metadata['subtitle_lang'] + " | "
|
|
|
|
return "[SeFree] " + name
|
|
|
|
def ffprobe_streams_to_bbcode(ffprobe_data,file_name):
|
|
output = []
|
|
output.append("[size=3][b]File Name: {}[/b][/size]".format(file_name))
|
|
for stream in ffprobe_data.get('streams', []):
|
|
if stream.get('codec_type') not in ["video","audio","subtitle"]:
|
|
continue
|
|
output.append("[b]Stream Index:[/b] {}".format(stream.get('index')))
|
|
type_=stream.get('codec_type')
|
|
for key, value in stream.items():
|
|
if type_=="video" and key not in ["codec_name","profile","codec_type","width","height","display_aspect_ratio","pix_fmt","color_range",
|
|
"color_space","r_frame_rate","bits_per_raw_sample","tags"]:
|
|
continue
|
|
if type_=="audio" and key not in ["codec_name","profile","codec_type","sample_rate","channels","channel_layout","tags"]:
|
|
continue
|
|
if type_=="subtitle" and key not in ["codec_name","codec_type","extradata_size","tags"]:
|
|
continue
|
|
|
|
if isinstance(value, dict):
|
|
|
|
for subkey, subval in value.items():
|
|
if type_=="video" and subkey not in ["title","BPS","DURATION","NUMBER_OF_FRAMES","NUMBER_OF_BYTES"]:
|
|
continue
|
|
if type_=="audio" and subkey not in ["title","language","BPS","DURATION","NUMBER_OF_FRAMES","NUMBER_OF_BYTES"]:
|
|
continue
|
|
if type_=="subtitle" and subkey not in ["title","language","BPS","DURATION","NUMBER_OF_FRAMES","NUMBER_OF_BYTES"]:
|
|
continue
|
|
output.append(" [b]{}:[/b] {}".format(subkey, subval))
|
|
# try:
|
|
# if int(subval) ==0:
|
|
# continue
|
|
|
|
# output.append(" [b]{}:[/b] {}".format(subkey, subval))
|
|
# except (ValueError, TypeError):
|
|
# output.append(" [b]{}:[/b] {}".format(subkey, subval))
|
|
else:
|
|
try:
|
|
if int(value) ==0:
|
|
continue
|
|
output.append(" [b]{}:[/b] {}".format(key, value))
|
|
except (ValueError, TypeError):
|
|
output.append(" [b]{}:[/b] {}".format(key, value))
|
|
output.append("") # blank line between streams
|
|
for i,chapter in enumerate(ffprobe_data.get('chapters', [])):
|
|
output.append("[b]Chapter : {}[/b]".format(chapter.get("tags",{}).get("title",{})))
|
|
for key, value in chapter.items():
|
|
|
|
if key not in ["start_time","end_time"]:
|
|
continue
|
|
try:
|
|
if int(value) ==0:
|
|
continue
|
|
output.append(" [b]{}:[/b] {}".format(key, value))
|
|
except (ValueError, TypeError):
|
|
output.append(" [b]{}:[/b] {}".format(key, value))
|
|
# output.append("")
|
|
return f"[quote]{"\n".join(output)}[/quote]\n\n"
|
|
# return f"[detail][quote]{"\n".join(output)}[/quote][/detail]\n\n"
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
async def main():
|
|
# entry={'title': 'SHIBOYUGI: Playing Death Games to Put Food on the Table (Thai Dub)', 'season': 1, 'episode': 5, 'sonarr_id': 1086, 'air_time': '2235', 'day_of_week': 'Wednesday', 'offset': 0, 'start_timestamp': 1773761700}
|
|
# title_config={'ID': 1086, 'Service': 'CR', 'Title': 'SHIBOYUGI: Playing Death Games to Put Food on the Table (Thai Dub)', 'url': 'GT00365787', 'audio_lang': 'ja,th', 'sub_lang': 'th,en', 'quality': 1080, 'codec': '264', 'range': 'SDR', 'audio_channel': None, 'title_lang': None, 'season': 1, 'if_dub': 0, 'org_lang': None, 'url_org': None, 'absolute_season': None, 'absolute': None, 'worst': 0, 'video_bitrate': None, 'proxy': None}
|
|
|
|
qbit=qBittorrent()
|
|
await qbit.login()
|
|
|
|
# await qbit.stop_previous_episode_torrent("263330",entry=entry,title_config=title_config)
|
|
asyncio.run(main())
|
|
|
|
# cli()
|
|
|
|
|