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 @@
-
-
-## 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
-
-
-

-

-
-
-## 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()