diff --git a/.gitignore b/.gitignore index a81c8ee..3a51d3f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,138 +1,4 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ +*.mp4 +config.json +__pycache__/ diff --git a/README.md b/README.md deleted file mode 100644 index cf618b4..0000000 --- a/README.md +++ /dev/null @@ -1,55 +0,0 @@ -
- BitAnime -

BitAnime

-

- A Python script that allows you to download all of an anime's episodes at once. -

- · Download executable version · -
- -## About BitAnime - -**BitAnime** is a python script that allows you to download anime in large batches. It can also be used to download anime films. **BitAnime** gets its content from [gogoanime](https://gogoanime.pe/). If you get a **404** error, please look up the correct anime name on [gogoanime](https://gogoanime.pe/). The application will let you download all the episodes, or you can choose how many episodes you want to download. - -## Installation - -```console -git clone https://github.com/Arctic4161/BitAnime.git -``` - -## Screenshot - -
- BitAnime Screenshot - Downloaded anime with BitAnime -
- -## Dependencies - -**BitAnime** is highly reliant on the python modules `requests`, `colorama`, `tqdm`, and `BeautifulSoup`. - -```console -pip install -r requirements.txt -``` - -## Usage - -The anime name is separated by "-". You can either type it manually, or go to [gogoanime.pe](https://gogoanime.pe/) and search for the anime you want to download and copy the name from the URL. - -### Examples - -##### One word title - -- https://gogoanime.pe/category/bakemonogatari >> bakemonogatari -- https://gogoanime.pe/category/steinsgate >> steinsgate - -##### Multiple word title - -- https://gogoanime.pe/category/shadows-house >> shadows-house -- https://gogoanime.pe/category/kono-subarashii-sekai-ni-shukufuku-wo- >> kono-subarashii-sekai-ni-shukufuku-wo- diff --git a/backend.py b/backend.py new file mode 100644 index 0000000..c4d1935 --- /dev/null +++ b/backend.py @@ -0,0 +1,130 @@ +import requests +import json +import os +from bs4 import BeautifulSoup +from dataclasses import dataclass +from colorama import Fore +from parfive import Downloader +from threading import Semaphore + + +OK = f"{Fore.RESET}[{Fore.GREEN}+{Fore.RESET}] " +ERR = f"{Fore.RESET}[{Fore.RED}-{Fore.RESET}] " +IN = f"{Fore.RESET}[{Fore.LIGHTBLUE_EX}>{Fore.RESET}] " + +global CONFIG + +screenlock = Semaphore(value=1) + + +def config_check(): + if os.path.exists("./config.json"): + with open("./config.json", "r") as f: + CONFIG = json.load(f) + if not "GoGoAnimeAuthKey" in CONFIG or len(CONFIG["GoGoAnimeAuthKey"]) == 0: + print("GoGoAnimeAuthKey not set in config.json") + exit(0) + else: + return CONFIG + else: + print("config.json file not found") + exit(0) + + +CURRENT_DOMAIN = "film" + + +@dataclass(init=True) +class Download: + name: str + episode_quality: str + folder: str + all_episodes: int + episode_start: int + episode_end: int + config: object + printed: bool = False + + def get_links(self, source=None): + if source is not None: + source_ep = f"https://gogoanime.{self.config['CurrentGoGoAnimeDomain']}/{self.name}-episode-" + episode_links = [ + f"{source_ep}{i}" + for i in range(self.episode_start, self.episode_end + 1) + ] + episode_links.insert(0, source) + else: + source_ep = f"https://gogoanime.{self.config['CurrentGoGoAnimeDomain']}/{self.name}-episode-" + episode_links = [ + f"{source_ep}{i}" + for i in range(self.episode_start, self.episode_end + 1) + ] + return episode_links + + +def get_download_link(config, url, episode_quality): + + page = requests.get( + url, + cookies=dict(auth=config["GoGoAnimeAuthKey"]), + ) + + soup = BeautifulSoup(page.content, "html.parser") + + for link in soup.find_all("a", href=True): + if episode_quality in link.text: + return link["href"] + + +def file_downloader(file_list: dict, title: str, config: object): + dl = Downloader( + max_conn=config["MaxConcurrentDownloads"], + overwrite=False, + headers=dict( + [ + ( + "User-Agent", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36", + ), + ("authority", "gogo-cdn.com"), + ("referer", f"https://gogoanime.{config['CurrentGoGoAnimeDomain']}/"), + ] + ), + ) + + for link in file_list: + dl.enqueue_file( + link, + path=f"./{title}", + ) + + files = dl.download() + return files + + +@dataclass(init=True) +class CustomMessage(Exception): + """Custom message that will accept message as a parameter and it will print it on the console.""" + + message: str = None + episode_quality: str = None + workingepisode: str = None + + def print_error(self): + screenlock.acquire() + print(ERR, self.message, end=" ") + screenlock.release() + + def qual_not_found(self): + screenlock.acquire() + print( + f"{ERR}Episode {self.workingepisode} {Fore.LIGHTCYAN_EX}{self.episode_quality}{Fore.RESET} quality not found." + ) + screenlock.release() + + def use_default_qual(self): + screenlock.acquire() + print( + f"{OK}Trying {Fore.LIGHTCYAN_EX}{self.episode_quality}{Fore.RESET} quality for Episode {self.workingepisode}." + ) + screenlock.release() diff --git a/config.json.default b/config.json.default new file mode 100644 index 0000000..9438ecf --- /dev/null +++ b/config.json.default @@ -0,0 +1,5 @@ +{ + "GoGoAnimeAuthKey": "", + "MaxConcurrentDownloads": 4, + "CurrentGoGoAnimeDomain": "film" +} \ No newline at end of file diff --git a/images/ba-logo.png b/images/ba-logo.png deleted file mode 100644 index cb8d0e6..0000000 Binary files a/images/ba-logo.png and /dev/null differ diff --git a/images/ba-screenshot.PNG b/images/ba-screenshot.PNG deleted file mode 100644 index 1aca9c7..0000000 Binary files a/images/ba-screenshot.PNG and /dev/null differ diff --git a/images/downloaded.PNG b/images/downloaded.PNG deleted file mode 100644 index cc56f1c..0000000 Binary files a/images/downloaded.PNG and /dev/null differ diff --git a/src/bitanime.py b/main.py similarity index 69% rename from src/bitanime.py rename to main.py index 57a0a83..6747a80 100644 --- a/src/bitanime.py +++ b/main.py @@ -1,37 +1,40 @@ -import requests as req +import requests import ctypes import os -import concurrent.futures -from backend import Download, CustomMessage, get_download_links -from tqdm.contrib.concurrent import thread_map +from backend import * from bs4 import BeautifulSoup from colorama import Fore -import sys -import subprocess OK = f"{Fore.RESET}[{Fore.GREEN}+{Fore.RESET}] " ERR = f"{Fore.RESET}[{Fore.RED}-{Fore.RESET}] " IN = f"{Fore.RESET}[{Fore.LIGHTBLUE_EX}>{Fore.RESET}] " -CURRENT_DOMAIN = "film" try: - ctypes.windll.kernel32.SetConsoleTitleW("BitAnime") + ctypes.windll.kernel32.SetConsoleTitleW("GoGo Downloader") except AttributeError: pass -def bitanime(): +def gogodownloader(config): + CURRENT_DOMAIN = config["CurrentGoGoAnimeDomain"] os.system("cls") while True: print( f""" {Fore.LIGHTBLUE_EX} - ____ _ _ _ _ - | __ )(_) |_ / \ _ __ (_)_ __ ___ ___ - | _ \| | __| / _ \ | '_ \| | '_ ` _ \ / _ \\ - | |_) | | |_ / ___ \| | | | | | | | | | __/ - |____/|_|\__/_/ \_\_| |_|_|_| |_| |_|\___| - {Fore.LIGHTYELLOW_EX} - By: sh1nobu - Github: https://github.com/sh1nobuu/BitAnime + + ______ ______ + / ____/___ / ____/___ + / / __/ __ \/ / __/ __ \ + / /_/ / /_/ / /_/ / /_/ / + \__________/\____/\____/ __ __ + / __ \____ _ ______ / /___ ____ _____/ /__ _____ + / / / / __ \ | /| / / __ \/ / __ \/ __ `/ __ / _ \/ ___/ + / /_/ / /_/ / |/ |/ / / / / / /_/ / /_/ / /_/ / __/ / + /_____/\____/|__/|__/_/ /_/_/\____/\__,_/\__,_/\___/_/ + + {Fore.RED} + By: Karl0ss + Forked From: sh1nobuu + Github: https://github.com/karl0ss/GoGoDownloader """ ) while True: @@ -41,7 +44,7 @@ def bitanime(): else: title = name.title().strip() source = f"https://gogoanime.{CURRENT_DOMAIN}/category/{name}" - with req.get(source) as res: + with requests.get(source) as res: if res.status_code == 200: soup = BeautifulSoup(res.content, "html.parser") all_episodes = soup.find("ul", {"id": "episode_page"}) @@ -54,16 +57,16 @@ def bitanime(): f"{IN}Enter episode quality (1.SD/360P|2.SD/480P|3.HD/720P|4.FULLHD/1080P) > " ) if quality == "1" or quality == "": - episode_quality = "SDP" + episode_quality = "360" break elif quality == "2": - episode_quality = "SHD" + episode_quality = "480" break elif quality == "3": - episode_quality = "HDP" + episode_quality = "720" break elif quality == "4": - episode_quality = "FullHDP" + episode_quality = "1080" break else: print(f"{ERR}Invalid input. Please try again.") @@ -123,39 +126,31 @@ def bitanime(): episode_end = all_episodes download = Download( - name, episode_quality, folder, all_episodes, episode_start, episode_end + name, + episode_quality, + folder, + all_episodes, + episode_start, + episode_end, + config, ) source = f"https://gogoanime.{CURRENT_DOMAIN}/{name}" - with req.get(source) as res: + with requests.get(source) as res: soup = BeautifulSoup(res.content, "html.parser") episode_zero = soup.find("h1", {"class": "entry-title"}) # value: 404 if choice == "n" or episode_zero is not None: source = None + dl_links = [] episode_links = download.get_links(source) - with concurrent.futures.ThreadPoolExecutor() as executor: - download_links = list(executor.map(get_download_links, episode_links)) - download_urls = list( - executor.map(download.get_download_urls, download_links) - ) - print( - f"{OK}Downloading {Fore.LIGHTCYAN_EX}{len(download_urls)}{Fore.RESET} episode/s" - ) - thread_map( - download.download_episodes, - download_urls, - ncols=75, - total=len(download_urls), - ) - try: - os.startfile(folder) - except AttributeError: - opener = "open" if sys.platform == "darwin" else "xdg-open" - subprocess.call([opener, folder]) - print("\n") + for link in episode_links: + dl_links.append(get_download_link(config, link, episode_quality)) + + file_downloader(dl_links, title, config) + use_again = input(f"{IN}Do you want to use the app again? (y|n) > ").lower() if use_again == "y": os.system("cls") @@ -164,4 +159,5 @@ def bitanime(): if __name__ == "__main__": - bitanime() + config = config_check() + gogodownloader(config) diff --git a/requirements.txt b/requirements.txt index 079fb68..bafbca4 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/src/backend.py b/src/backend.py deleted file mode 100644 index 1df1655..0000000 --- a/src/backend.py +++ /dev/null @@ -1,223 +0,0 @@ -# Dependencies -import requests as req -import shutil -import re -import os -from bs4 import BeautifulSoup -from dataclasses import dataclass -from colorama import Fore -from random import choice -from requests.exceptions import Timeout -import time -from threading import Semaphore - -OK = f"{Fore.RESET}[{Fore.GREEN}+{Fore.RESET}] " -ERR = f"{Fore.RESET}[{Fore.RED}-{Fore.RESET}] " -IN = f"{Fore.RESET}[{Fore.LIGHTBLUE_EX}>{Fore.RESET}] " - -screenlock = Semaphore(value=1) - - -def random_headers(): - desktop_agents = [ - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36 Edg/94.0.992.47", - 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36', - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36', - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36', - 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36', - 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36', - 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36', - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36'] - return { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", - "Accept-Language": "en-US,en;q=0.5", - "Accept-Encoding": "gzip, deflate, br", - "Connection": "keep-alive", - "Referer": "https://gogoplay1.com/", - 'User-Agent': choice(desktop_agents)} - - - -def get_download_links(episode_link): - with req.get(episode_link) as res: - soup = BeautifulSoup(res.content, "html.parser") - exist = soup.find("h1", {"class": "entry-title"}) - workinglinkis = episode_link.split('-') - if exist is None: - # Episode link == 200 - episode_link = soup.find("li", {"class": "dowloads"}) - return [workinglinkis[-1], episode_link.a.get("href")] - else: - # Episode link == 404 - episode_link = f"{episode_link}-" - with req.get(episode_link) as find: - soup = BeautifulSoup(find.content, "html.parser") - exist = soup.find("h1", {"class": "entry-title"}) - if exist is None: - episode_link = soup.find("li", {"class": "dowloads"}) - return [workinglinkis[-1], episode_link.a.get("href")] - else: - return None - - -@dataclass(init=True) -class Download: - name: str - episode_quality: str - folder: str - all_episodes: int - episode_start: int - episode_end: int - printed: bool = False - - def get_links(self, source=None): - if source is not None: - source_ep = f"https://gogoanime.wiki/{self.name}-episode-" - episode_links = [ - f"{source_ep}{i}" - for i in range(self.episode_start, self.episode_end + 1) - ] - episode_links.insert(0, source) - else: - source_ep = f"https://gogoanime.wiki/{self.name}-episode-" - episode_links = [ - f"{source_ep}{i}" - for i in range(self.episode_start, self.episode_end + 1) - ] - return episode_links - - def get_download_urls(self, download_link): - episode_quality = self.episode_quality - workingepisode = download_link[0] - if episode_quality == "FullHDP": - episode_quality = "1080P - mp4" - elif episode_quality == "HDP": - episode_quality = "720P - mp4" - elif episode_quality == "SHD": - episode_quality = "480P - mp4" - elif episode_quality == "SDP": - episode_quality = "360P - mp4" - else: - episode_quality = "1080P - mp4" - with req.get(download_link[1], headers=random_headers(), timeout=3) as res: - soup = BeautifulSoup(res.content, "html.parser") - link = soup.find("div", {"class": "dowload"}, text=re.compile(episode_quality)) - if link is None: - pass - else: - try: - with req.get(link.a.get("href"), headers=random_headers(), stream=True, - timeout=3) as workingit: - if workingit.status_code != 200: - link = None - elif workingit.headers['Content-Type'] != 'video/mp4': - link = None - except Timeout: - link = None - if link is None: - if episode_quality == "1080P - mp4": - episode_quality = "FullHDP" - time.sleep(1) - CustomMessage('None', episode_quality, workingepisode).qual_not_found() - episode_quality = "HDP" - time.sleep(1) - CustomMessage('None', episode_quality, workingepisode).use_default_qual() - episode_quality = "720P - mp4" - link = soup.find("div", {"class": "dowload"}, text=re.compile(episode_quality)) - if link is None: - pass - else: - try: - with req.get(link.a.get("href"), headers=random_headers(), stream=True, - timeout=3) as workingit: - if workingit.status_code != 200: - link = None - elif workingit.headers['Content-Type'] != 'video/mp4': - link = None - except Timeout: - link = None - if link is None: - if episode_quality == "720P - mp4": - episode_quality = "HDP" - time.sleep(1) - CustomMessage('None', episode_quality, workingepisode).qual_not_found() - episode_quality = "SHD" - time.sleep(1) - CustomMessage('None', episode_quality, workingepisode).use_default_qual() - episode_quality = "480P - mp4" - link = soup.find("div", {"class": "dowload"}, text=re.compile(episode_quality)) - if link is None: - pass - else: - try: - with req.get(link.a.get("href"), headers=random_headers(), stream=True, - timeout=3) as workingit: - if workingit.status_code != 200: - link = None - elif workingit.headers['Content-Type'] != 'video/mp4': - link = None - except Timeout: - link = None - if link is None: - if episode_quality == "480P - mp4": - episode_quality = "SHD" - time.sleep(1) - CustomMessage('None', episode_quality, workingepisode).qual_not_found() - episode_quality = "SDP" - time.sleep(1) - CustomMessage('None', episode_quality, workingepisode).use_default_qual() - episode_quality = "360P - mp4" - link = soup.find("div", {"class": "dowload"}, text=re.compile(episode_quality)) - else: - pass - return [download_link[1].split("+")[-1], link.a.get("href")] - - def download_episodes(self, url): - with req.get(url[1], headers=random_headers(), stream=True, timeout=10, - allow_redirects=True) as workingurl: - episode_name = "EP." + url[0] + ".mp4" - file_loc = os.path.join(self.folder, episode_name) - with open(file_loc, "w+b") as file: - shutil.copyfileobj(workingurl.raw, file, 8192) - - size = os.stat(file_loc).st_size - count = 0 - while int(size) < 5 and count < 5: - with req.get(url[1], headers=random_headers(), stream=True, timeout=10, - allow_redirects=True) as workingurl: - episode_name = "EP." + url[0] + ".mp4" - file_loc = os.path.join(self.folder, episode_name) - with open(file_loc, "w+b") as file: - shutil.copyfileobj(workingurl.raw, file, 8192) - count += 1 - size = os.stat(file_loc).st_size - size = os.stat(file_loc).st_size - if int(size) < 5: - print("\n") - CustomMessage('Could not download episode ' + url[0]).print_error() - os.remove(file_loc) - - -@dataclass(init=True) -class CustomMessage(Exception): - """Custom message that will accept message as a parameter and it will print it on the console.""" - - message: str = None - episode_quality: str = None - workingepisode: str = None - - def print_error(self): - screenlock.acquire() - print(ERR, self.message, end=' ') - screenlock.release() - - def qual_not_found(self): - screenlock.acquire() - print( - f"{ERR}Episode {self.workingepisode} {Fore.LIGHTCYAN_EX}{self.episode_quality}{Fore.RESET} quality not found.") - screenlock.release() - - def use_default_qual(self): - screenlock.acquire() - print( - f"{OK}Trying {Fore.LIGHTCYAN_EX}{self.episode_quality}{Fore.RESET} quality for Episode {self.workingepisode}.") - screenlock.release()