Merge pull request #1 from karl0ss/GoGoDownloader

GoGoDownloader
This commit is contained in:
Karl Hudgell 2022-01-18 20:56:06 +00:00 committed by GitHub
commit 99f808d37c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 179 additions and 460 deletions

140
.gitignore vendored
View File

@ -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/ venv/
ENV/ *.mp4
env.bak/ config.json
venv.bak/ __pycache__/
# 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/

View File

@ -1,55 +0,0 @@
<div align="center">
<img
style="width: 165px; height: 165px"
src="https://i.postimg.cc/VkSMVQrg/ba-logo.png"
title="BitAnime"
alt="BitAnime"
/>
<h3>BitAnime</h3>
<p>
A Python script that allows you to download all of an anime's episodes at once.
</p>
<a href="https://github.com/Arctic4161/BitAnime/releases"> <strong>· Download executable version ·</strong></a>
</div>
## 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
<div align="center">
<img style="height:386px; width:688px;" src="https://i.postimg.cc/cLgf8994/ba-screenshot.png"
title="BitAnime in action" alt="BitAnime Screenshot">
<img style="height:386px; width:688px;" src="https://i.postimg.cc/G2qGDpfV/downloade.png" title="Katekyo Hitman Reborn" alt="Downloaded anime with BitAnime">
</div>
## 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-

130
backend.py Normal file
View File

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

5
config.json.default Normal file
View File

@ -0,0 +1,5 @@
{
"GoGoAnimeAuthKey": "",
"MaxConcurrentDownloads": 4,
"CurrentGoGoAnimeDomain": "film"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 652 KiB

View File

@ -1,37 +1,40 @@
import requests as req import requests
import ctypes import ctypes
import os import os
import concurrent.futures from backend import *
from backend import Download, CustomMessage, get_download_links
from tqdm.contrib.concurrent import thread_map
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from colorama import Fore from colorama import Fore
import sys
import subprocess
OK = f"{Fore.RESET}[{Fore.GREEN}+{Fore.RESET}] " OK = f"{Fore.RESET}[{Fore.GREEN}+{Fore.RESET}] "
ERR = f"{Fore.RESET}[{Fore.RED}-{Fore.RESET}] " ERR = f"{Fore.RESET}[{Fore.RED}-{Fore.RESET}] "
IN = f"{Fore.RESET}[{Fore.LIGHTBLUE_EX}>{Fore.RESET}] " IN = f"{Fore.RESET}[{Fore.LIGHTBLUE_EX}>{Fore.RESET}] "
CURRENT_DOMAIN = "film"
try: try:
ctypes.windll.kernel32.SetConsoleTitleW("BitAnime") ctypes.windll.kernel32.SetConsoleTitleW("GoGo Downloader")
except AttributeError: except AttributeError:
pass pass
def bitanime(): def gogodownloader(config):
CURRENT_DOMAIN = config["CurrentGoGoAnimeDomain"]
os.system("cls") os.system("cls")
while True: while True:
print( print(
f""" {Fore.LIGHTBLUE_EX} 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: while True:
@ -41,7 +44,7 @@ def bitanime():
else: else:
title = name.title().strip() title = name.title().strip()
source = f"https://gogoanime.{CURRENT_DOMAIN}/category/{name}" 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: if res.status_code == 200:
soup = BeautifulSoup(res.content, "html.parser") soup = BeautifulSoup(res.content, "html.parser")
all_episodes = soup.find("ul", {"id": "episode_page"}) 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) > " f"{IN}Enter episode quality (1.SD/360P|2.SD/480P|3.HD/720P|4.FULLHD/1080P) > "
) )
if quality == "1" or quality == "": if quality == "1" or quality == "":
episode_quality = "SDP" episode_quality = "360"
break break
elif quality == "2": elif quality == "2":
episode_quality = "SHD" episode_quality = "480"
break break
elif quality == "3": elif quality == "3":
episode_quality = "HDP" episode_quality = "720"
break break
elif quality == "4": elif quality == "4":
episode_quality = "FullHDP" episode_quality = "1080"
break break
else: else:
print(f"{ERR}Invalid input. Please try again.") print(f"{ERR}Invalid input. Please try again.")
@ -123,39 +126,31 @@ def bitanime():
episode_end = all_episodes episode_end = all_episodes
download = Download( 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}" 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") soup = BeautifulSoup(res.content, "html.parser")
episode_zero = soup.find("h1", {"class": "entry-title"}) # value: 404 episode_zero = soup.find("h1", {"class": "entry-title"}) # value: 404
if choice == "n" or episode_zero is not None: if choice == "n" or episode_zero is not None:
source = None source = None
dl_links = []
episode_links = download.get_links(source) 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: for link in episode_links:
os.startfile(folder) dl_links.append(get_download_link(config, link, episode_quality))
except AttributeError:
opener = "open" if sys.platform == "darwin" else "xdg-open" file_downloader(dl_links, title, config)
subprocess.call([opener, folder])
print("\n")
use_again = input(f"{IN}Do you want to use the app again? (y|n) > ").lower() use_again = input(f"{IN}Do you want to use the app again? (y|n) > ").lower()
if use_again == "y": if use_again == "y":
os.system("cls") os.system("cls")
@ -164,4 +159,5 @@ def bitanime():
if __name__ == "__main__": if __name__ == "__main__":
bitanime() config = config_check()
gogodownloader(config)

Binary file not shown.

View File

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