Compare commits

..

No commits in common. "master" and "v2.0.0" have entirely different histories.

19 changed files with 402 additions and 691 deletions

143
.gitignore vendored
View File

@ -1,11 +1,138 @@
venv/ # Byte-compiled / optimized / DLL files
*.mp4
config.json
__pycache__/ __pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/ build/
develop-eggs/
dist/ dist/
main.spec downloads/
.vscode/launch.json eggs/
app.log .eggs/
bookmarkList.json lib/
downloadHistory.json 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/

View File

@ -1,3 +0,0 @@
for /R %%a in ("*.mp4") do MP4Box -lang eng "%%a"
exit

View File

@ -1,176 +0,0 @@
import requests
import ctypes
import os
from backend import gogoanime, CustomMessage, config_check
from bs4 import BeautifulSoup
from colorama import Fore
import logging
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}] "
try:
ctypes.windll.kernel32.SetConsoleTitleW("GoGo Downloader")
except AttributeError:
pass
def gogodownloader(config):
CURRENT_URL = config["CurrentGoGoAnimeURL"]
os.system("cls" if os.name == "nt" else "clear")
while True:
print(
f""" {Fore.LIGHTBLUE_EX}
______ ______
/ ____/___ / ____/___
/ / __/ __ \/ / __/ __ \
/ /_/ / /_/ / /_/ / /_/ /
\__________/\____/\____/ __ __
/ __ \____ _ ______ / /___ ____ _____/ /__ _____
/ / / / __ \ | /| / / __ \/ / __ \/ __ `/ __ / _ \/ ___/
/ /_/ / /_/ / |/ |/ / / / / / /_/ / /_/ / /_/ / __/ /
/_____/\____/|__/|__/_/ /_/_/\____/\__,_/\__,_/\___/_/
{Fore.RED}
By: Karl0ss
Forked From: sh1nobuu
Github: https://github.com/karl0ss/GoGoDownloader
"""
)
while True:
name = input(f"{IN}Enter anime name > ").lower()
logging.info("episode searched for " + name)
if "-" in name:
title = name.replace("-", " ").title().strip()
else:
title = name.title().strip()
source = f"https://{CURRENT_URL}/category/{name}"
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"})
all_episodes = int(list(filter(None, "-".join(all_episodes.get_text().splitlines()).split("-")))[-1].strip())
break
else:
print(f"{ERR}Error 404: Anime not found. Please try again.")
while True:
quality = input(
f"{IN}Enter episode quality (1.SD/360P|2.SD/480P|3.HD/720P|4.FULLHD/1080P) > "
)
if quality == "1" or quality == "":
episode_quality = "360"
break
elif quality == "2":
episode_quality = "480"
break
elif quality == "3":
episode_quality = "720"
break
elif quality == "4":
episode_quality = "1080"
break
else:
print(f"{ERR}Invalid input. Please try again.")
logging.info("quality selected " + episode_quality)
print(f"{OK}Title: {Fore.LIGHTCYAN_EX}{title}")
print(f"{OK}Episode/s: {Fore.LIGHTCYAN_EX}{all_episodes}")
print(f"{OK}Quality: {Fore.LIGHTCYAN_EX}{episode_quality}")
print(f"{OK}Link: {Fore.LIGHTCYAN_EX}{source}")
folder = os.path.join(os.getcwd(), title)
if not os.path.exists(folder):
os.mkdir(folder)
choice = "y"
if all_episodes != 1:
while True:
choice = input(
f"{IN}Do you want to download all episode? (y/n) > "
).lower()
if choice in ["y", "n"]:
break
else:
print(f"{ERR}Invalid input. Please try again.")
episode_start = None
episode_end = None
if choice == "n":
while True:
try:
episode_start = int(input(f"{IN}Episode start > "))
episode_end = int(input(f"{IN}Episode end > "))
if episode_start <= 0 or episode_end <= 0:
CustomMessage(
f"{ERR}episode_start or episode_end cannot be less than or equal to 0"
).print_error()
elif episode_start >= all_episodes or episode_end > all_episodes:
CustomMessage(
f"{ERR}episode_start or episode_end cannot be more than {all_episodes}"
).print_error()
elif episode_end < episode_start:
CustomMessage(
f"{ERR}episode_end cannot be less than episode_start"
).print_error()
else:
break
except ValueError:
print(f"{ERR}Invalid input. Please try again.")
if episode_start is not None:
pass
else:
episode_start = 1
if episode_end is not None:
pass
else:
episode_end = all_episodes
gogo = gogoanime(
config,
name,
episode_quality,
folder,
all_episodes,
episode_start,
episode_end,
title,
)
gogo.user_logged_in_check()
source = f"https://{CURRENT_URL}/{name}"
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 = gogo.get_links(source)
print(f"{OK}Scraping Links")
for link in episode_links:
dl_links.append(gogo.get_download_link(link))
result = gogo.file_downloader(dl_links)
if len(result.errors) > 0:
while len(result.errors) > 0:
print(f"{ERR}{len(result.errors)} links failed retrying.")
episode_links = gogo.get_links(source)
print(f"{OK}Re-Scraping Links")
dl_links.clear()
for link in episode_links:
dl_links.append(gogo.get_download_link(link))
result = gogo.file_downloader(dl_links, overwrite_downloads=0)
use_again = input(f"{IN}Do you want to use the app again? (y|n) > ").lower()
if use_again == "y":
os.system("cls" if os.name == "nt" else "clear")
else:
break
if __name__ == "__main__":
config = config_check()
gogodownloader(config)

View File

@ -1,128 +0,0 @@
import json
import io
import os
import re
from backend import *
def renameFile(filename: str):
"""_summary_
Args:
filename (str): _description_
Returns:
_type_: _description_
"""
newFileName = "".join(re.split("\(|\)|\[|\]", filename)[::2])
try:
os.rename(filename, newFileName)
return True
except OSError as err:
return err
def loadDownloadHistory():
"""Loads the downloadHistory.json, creates it if it doesn't exist
Returns:
object: download history list
"""
if os.path.isfile("./downloadHistory.json") and os.access(
"./downloadHistory.json", os.R_OK
):
return json.load(open("./downloadHistory.json"))
else:
with io.open(os.path.join("./", "downloadHistory.json"), "w") as db_file:
db_file.write(json.dumps([]))
return json.load(open("./downloadHistory.json"))
def writeShowToDownloadHistory(showName: str, downloadHistory: list):
"""Writes the showName and latestEpisode to the downloadHistory.json file
Args:
showName (str): _description_
downloadHistory (list): _description_
Returns:
_type_: _description_
"""
downloadHistory.append(showName)
with io.open(os.path.join("./", "downloadHistory.json"), "w") as db_file:
db_file.write(json.dumps(downloadHistory))
return json.load(open("./downloadHistory.json"))
def readDownloadHistory(fileNameObject: object, downloadHistory: list):
"""Reads the downloadHistory.json and checks if the fileName is present
Args:
fileNameObject (str): _description_
downloadHistory (list): _description_
Returns:
_type_: _description_
"""
dhFileName = (
fileNameObject["showName"] + " - " + str(fileNameObject["latestEpisode"])
)
if dhFileName not in downloadHistory:
writeShowToDownloadHistory(dhFileName, downloadHistory)
return False
else:
return True
def main():
dh = loadDownloadHistory()
config = config_check()
downloader = gogoanime(
config,
1,
config["CLIQuality"],
"a",
1,
1,
1,
config["CLIDownloadLocation"],
)
list = downloader.get_show_from_bookmark()
dl_links = {}
for ep in list:
if readDownloadHistory(ep, dh):
showName = ep["showName"] + " - " + str(ep["latestEpisode"])
print(f"{IN}{showName} already downloaded")
else:
print(
f"{IN}Scraping DL for "
+ ep["showName"]
+ " Ep "
+ str(ep["latestEpisode"])
)
dl_links[downloader.get_download_link(ep["downloadURL"])] = (
ep["showName"],
ep["latestEpisode"],
)
result = downloader.file_downloader(dl_links)
if config["CleanUpFileName"]:
for file in result.data:
renameFile(file)
if len(result.errors) > 0:
while len(result.errors) > 0:
print(f"{ERR}{len(result.errors)} links failed retrying.")
print(f"{IN}Re-Scraping Links")
dl_links.clear()
for ep in list:
dl_links[downloader.get_download_link(ep["downloadURL"])] = (
ep["showName"],
ep["latestEpisode"],
)
result = downloader.file_downloader(dl_links, overwrite_downloads=0)
if config["CleanUpFileName"]:
for file in result.data:
renameFile(file)
if __name__ == "__main__":
main()

View File

@ -1,40 +0,0 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['GoGoDownloaderCLI.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='GoGoDownloaderCLI',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None )

View File

@ -1,76 +1,55 @@
<div align="center"> <div align="center">
<img <img
style="width: 300px; height: 300px" style="width: 165px; height: 165px"
src="https://github.com/karl0ss/GoGoDownloader/raw/master/img/gogo_logo.png" src="https://i.postimg.cc/VkSMVQrg/ba-logo.png"
title="GoGoDownloader" title="BitAnime"
alt="GoGoDownloader" alt="BitAnime"
/> />
<h3>GoGo Downloader</h3> <h3>BitAnime</h3>
<h4>Forked from <a href="https://github.com/sh1nobuu/BitAnime">BitAnime</a></h4>
<p> <p>
A Python script that allows you to download all of an anime's episodes at once. A Python script that allows you to download all of an anime's episodes at once.
</p> </p>
<a href="https://github.com/sh1nobuu/BitAnime/releases"> <strong>· Download executable version ·</strong></a>
</div> </div>
## About GoGo Downloader ## About BitAnime
GoGo Downloader is based on the now broken **BitAnime**. I have had to rework quite a bit of the code to get it working again, and have ideas for some other improvements, I don't want to mess with the original codebase too much, hence **GoGo Downloader**. **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.
**GoGo Downloader** gets its content from [gogoanime](http://gogoanime3.net). If you get a **404** error, please look up the correct anime name on [gogoanime](http://gogoanime3.net). The application will let you download all the episodes, or you can choose how many episodes you want to download.
GoGo Anime has changed the way they show download links, and this no longer works via BS4, as the recaptcha blocks the links, but if you are logged in, there are other routes to get to download links, I have taken one of these routes to restore the application.
## Features
- Download all qualities options from GoGoAnime
- Update the current GoGoAnime domain via config file (as they keep changing it)
- Specify the number of concurrent downloads via config file (Max is 6)
- Set file overwrite via config file (0 = Skip / 1 = Overwrite)
## Installation ## Installation
You have 2 options here, you can download the exe on the releases page and run on Windows
- Download the zip ```console
- Extract and set your GoGoAnime Username and Password in the config.json git clone https://github.com/sh1nobuu/BitAnime.git
- Run the exe ```
If you want to run from source, or are using Linux/Mac you can run directly from source doing the following -
- `git clone https://github.com/karl0ss/GoGoDownloader.git`
- `pip install -r requirements.txt`
- Create config.json from config.json.default
- Add your GoGoAnime Username and Password to config.json (Can't be a Google account)
- Run the app with `python GoGoDownloader.py`
## Screenshot ## Screenshot
<div align="center"> <div align="center">
<img style="height:386px; width:688px;" src="https://github.com/karl0ss/GoGoDownloader/raw/master/img/screenshot.png" <img style="height:386px; width:688px;" src="https://i.postimg.cc/cLgf8994/ba-screenshot.png"
title="BitAnime in action" alt="BitAnime Screenshot"> 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> </div>
## Dependencies ## Dependencies
**GoGo Downloader** is highly reliant on the python modules `requests`, `colorama`, `parfive`, and `BeautifulSoup`. **BitAnime** is highly reliant on the python modules `requests`, `colorama`, `tqdm`, and `BeautifulSoup`.
```console
pip install -r requirements.txt
```
## Usage ## Usage
The anime name is separated by "-". You can either type it manually, or go to [gogoanime.gg](https://gogoanime3.gg/) and search for the anime you want to download and copy the name from the URL. 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 ### Examples
##### One word title ##### One word title
- https://gogoanime3.gg/category/bakemonogatari >> bakemonogatari - https://gogoanime.pe/category/bakemonogatari >> bakemonogatari
- https://gogoanime3.gg/category/steinsgate >> steinsgate - https://gogoanime.pe/category/steinsgate >> steinsgate
##### Multiple word title ##### Multiple word title
- https://gogoanime3.gg/category/shadows-house >> shadows-house - https://gogoanime.pe/category/shadows-house >> shadows-house
- https://gogoanime3.gg/category/kono-subarashii-sekai-ni-shukufuku-wo- >> kono-subarashii-sekai-ni-shukufuku-wo- - https://gogoanime.pe/category/kono-subarashii-sekai-ni-shukufuku-wo- >> kono-subarashii-sekai-ni-shukufuku-wo-
# GoGoDownloader CLI
I have now also created the GoGoDownloader CLI, this tool can be used to run on a scheduled basis, it will login and get the latest episodes from your GoGoAnime bookmarks, and download the latest episode if it has not been downloaded yet.

View File

@ -1,280 +0,0 @@
import re
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
import logging
logging.basicConfig(
level=logging.INFO,
filename="app.log",
filemode="w",
format="%(name)s - %(levelname)s - %(message)s",
)
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():
"""Check for config.json and check required keys are set
Returns:
[object]: Config object
"""
if os.path.exists("./config.json"):
logging.info("Config.json loaded")
with open("./config.json", "r") as f:
CONFIG = json.load(f)
if not "GoGoAnime_Username" in CONFIG or len(CONFIG["GoGoAnime_Username"]) == 0:
logging.error("GoGoAnime_Username not set in config.json")
print("GoGoAnime_Username not set in config.json")
exit(0)
else:
if (
not "GoGoAnime_Password" in CONFIG
or len(CONFIG["GoGoAnime_Password"]) == 0
):
logging.error("GoGoAnime_Password not set in config.json")
print("GoGoAnime_Password not set in config.json")
exit(0)
else:
logging.info(
"Config loaded and "
+ CONFIG["GoGoAnime_Username"]
+ " username found"
)
return CONFIG
else:
logging.error("config.json not found")
print("config.json file not found")
exit(0)
def max_concurrent_downloads(max_conn: int):
"""Check max_concurrent_downloads value and restrict to below 6
Args:
max_conn (int): Max concurrent downloads to allow
Returns:
[int]: Max concurrent downloads allowed
"""
if max_conn > 6:
return 6
else:
return max_conn
CURRENT_DOMAIN = "film"
@dataclass(init=True)
class gogoanime:
config: object
name: str
episode_quality: str
folder: str
all_episodes: int
episode_start: int
episode_end: int
title: str
printed: bool = False
def get_gogoanime_auth_cookie(self):
session = requests.session()
page = session.get(
f"https://{self.config['CurrentGoGoAnimeURL']}/login.html"
)
soup = BeautifulSoup(page.content, "html.parser")
meta_path = soup.select('meta[name="csrf-token"]')
csrf_token = meta_path[0].attrs["content"]
url = f"https://{self.config['CurrentGoGoAnimeURL']}/login.html"
payload = f"email={self.config['GoGoAnime_Username']}&password={self.config['GoGoAnime_Password']}&_csrf={csrf_token}"
headers = {
"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://{self.config['CurrentGoGoAnimeURL']}/",
"content-type": "application/x-www-form-urlencoded",
}
session.headers = headers
r = session.post(url, data=payload, headers=headers)
if r.status_code == 200:
return session.cookies.get_dict().get("auth")
else:
print("ldldl")
def user_logged_in_check(
self,
):
page = requests.get(
f"https://{self.config['CurrentGoGoAnimeURL']}/one-piece-episode-1",
cookies=dict(auth=gogoanime.get_gogoanime_auth_cookie(self)),
)
soup = BeautifulSoup(page.content, "html.parser")
loginCheck = soup(text=re.compile("Logout"))
if len(loginCheck) == 0:
raise Exception(
"User is not logged in, make sure account has been activated"
)
def get_links(self, source=None):
if source is not None:
source_ep = f"https://{self.config['CurrentGoGoAnimeURL']}/{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://{self.config['CurrentGoGoAnimeURL']}/{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(self, url):
page = requests.get(
url,
cookies=dict(auth=gogoanime.get_gogoanime_auth_cookie(self)),
)
quality_arr = ["1080", "720", "640", "480"]
soup = BeautifulSoup(page.content, "html.parser")
try:
for link in soup.find_all(
"a", href=True, string=re.compile(self.episode_quality)
):
return link["href"]
else:
ep_num = url.rsplit("-", 1)[1]
print(
f"{self.episode_quality} not found for ep{ep_num} checking for next best"
)
for q in quality_arr:
for link in soup.find_all("a", href=True, string=re.compile(q)):
print(f"{q} found.")
return link["href"]
except:
print("No matching download found")
def file_downloader(self, file_list: dict, overwrite_downloads: bool = None):
"""[summary]
Args:
file_list (dict): [description]
overwrite_downloads (bool, optional): [description]. Defaults to None.
Returns:
[type]: [description]
"""
if overwrite_downloads is None:
overwrite = self.config["OverwriteDownloads"]
else:
overwrite = overwrite_downloads
dl = Downloader(
max_conn=max_concurrent_downloads(self.config["MaxConcurrentDownloads"]),
overwrite=overwrite,
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://{self.config['CurrentGoGoAnimeURL']}/",
),
]
),
)
for link in file_list:
if link is not None:
try:
dl.enqueue_file(
link,
path=f"./{self.title}",
)
except:
pass
files = dl.download()
return files
def get_show_from_bookmark(self):
print(f"{IN}Loading shows from bookmarks")
bookmarkList = []
a = dict(auth=gogoanime.get_gogoanime_auth_cookie(self))
resp = requests.get(
f"https://{self.config['CurrentGoGoAnimeURL']}/user/bookmark",
cookies=a,
)
soup = BeautifulSoup(resp.text, "html.parser")
table = soup.find("div", attrs={"class": "article_bookmark"})
splitTableLines = table.text.split("Remove")
for rows in splitTableLines:
fullRow = " ".join(rows.split())
if "Anime name" in fullRow:
fullRow = fullRow.replace("Anime name Latest", "")
splitRow = fullRow.split("Latest")
elif fullRow == "Status":
break
else:
fullRow = fullRow.replace("Status ", "")
splitRow = fullRow.split("Latest")
animeName = splitRow[0].strip().encode("ascii", "ignore").decode()
animeName = re.sub("[^A-Za-z0-9 ]+", "", animeName)
animeDownloadName = animeName.replace(" ", "-").lower()
episodeNum = splitRow[-1].split()[-1]
bookmarkList.append(
{
"showName": animeName,
"latestEpisode": float(episodeNum),
"downloadURL": f"https://{self.config['CurrentGoGoAnimeURL']}/{animeDownloadName}-episode-{str(episodeNum)}",
}
)
with open("bookmarkList.json", "w") as f:
json.dump(bookmarkList, f)
return bookmarkList
@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()

View File

@ -1,10 +0,0 @@
{
"GoGoAnime_Username":"",
"GoGoAnime_Password":"",
"MaxConcurrentDownloads": 4,
"CurrentGoGoAnimeURL": "gogoanime3.net",
"OverwriteDownloads": 0,
"CLIQuality":"720",
"CLIDownloadLocation": "CLIOutput",
"CleanUpFileName": false
}

BIN
images/ba-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
images/ba-screenshot.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
images/downloaded.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

83
src/backend.py Normal file
View File

@ -0,0 +1,83 @@
# Dependencies
import requests as req
import shutil
import re
import os
from bs4 import BeautifulSoup
from dataclasses import dataclass
@dataclass(init=True)
class Download:
name: str
episode_quality: str
episode_number: int
folder: str
def get_links(self, source=None):
if source != None:
source_ep = f"https://gogoanime.pe/{self.name}-episode-"
episode_links = [
f"{source_ep}{i}" for i in range(1, self.episode_number + 1)
]
episode_links.insert(0, source)
else:
source_ep = f"https://gogoanime.pe/{self.name}-episode-"
episode_links = [
f"{source_ep}{i}" for i in range(1, self.episode_number + 1)
]
return episode_links
def get_download_links(self, episode_link):
with req.get(episode_link) as res:
soup = BeautifulSoup(res.content, "html.parser")
exist = soup.find("h1", {"class": "entry-title"})
if exist is None:
# Episode link == 200
episode_link = soup.find("li", {"class": "dowloads"})
return episode_link.a.get("href")
else:
# Episode link == 404
episode_link = f"{episode_link}-"
with req.get(episode_link) as res:
soup = BeautifulSoup(res.content, "html.parser")
exist = soup.find("h1", {"class": "entry-title"})
if exist is None:
episode_link = soup.find("li", {"class": "dowloads"})
return episode_link.a.get("href")
else:
return None
def get_download_urls(self, download_link):
with req.get(download_link) as res:
soup = BeautifulSoup(res.content, "html.parser")
link = soup.find("div", {"class": "mirror_link"}).find(
"div",
text=re.compile(fr"\b{self.episode_quality}\b"),
attrs={"class": "dowload"},
)
if link == None:
link = soup.find("div", {"class": "mirror_link"}).find(
"div", {"class": "dowload"}
)
return [download_link.split("+")[-1], link.a.get("href")]
def download_episodes(self, url):
header = {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate",
"Connection": "close",
}
with req.get(url[1], headers=header, stream=True) as res:
episode_name = f"EP.{url[0]}.mp4"
file_loc = os.path.join(self.folder, episode_name)
with open(file_loc, "wb") as file:
shutil.copyfileobj(res.raw, file, 8192)
class InvalidInputValue(Exception):
"""Raise when custom_episode_number is equal to 0 or custom_episode_number is greater than episode_number"""
pass

161
src/bitanime.py Normal file
View File

@ -0,0 +1,161 @@
import requests as req
import ctypes
import os
import backend as bd
import colorama
import concurrent.futures
from tqdm.contrib.concurrent import thread_map
from bs4 import BeautifulSoup
from colorama import Fore
colorama.init(autoreset=True)
try:
ctypes.windll.kernel32.SetConsoleTitleW("BitAnime")
except (AttributeError):
pass
def bitanime():
while True:
print(
f""" {Fore.LIGHTBLUE_EX}
____ _ _ _ _
| __ )(_) |_ / \ _ __ (_)_ __ ___ ___
| _ \| | __| / _ \ | '_ \| | '_ ` _ \ / _ \\
| |_) | | |_ / ___ \| | | | | | | | | | __/
|____/|_|\__/_/ \_\_| |_|_|_| |_| |_|\___|
{Fore.LIGHTYELLOW_EX}
By: sh1nobu
Github: https://github.com/sh1nobuu/BitAnime
"""
)
while True:
name = input(f"[{Fore.GREEN}+{Fore.RESET}] Enter anime name > ").lower()
if "-" in name:
title = name.replace("-", " ").title().strip()
else:
title = name.title().strip()
source = f"https://gogoanime.pe/category/{name}"
with req.get(source) as res:
if res.status_code == 200:
soup = BeautifulSoup(res.content, "html.parser")
episode_number = soup.find("ul", {"id": "episode_page"})
episode_number = episode_number.get_text().split("-")[-1].strip()
break
else:
print(
f"[{Fore.RED}-{Fore.RESET}] {Fore.LIGHTRED_EX}Error 404: Anime not found. Please try again."
)
while True:
quality = input(
f"[{Fore.GREEN}+{Fore.RESET}] Enter episode quality (1.SD/360P|2.HD/720P|3.FULLHD/1080P) > "
)
if quality == "1" or quality == "":
episode_quality = "SDP"
break
elif quality == "2":
episode_quality = "HDP"
break
elif quality == "3":
episode_quality = "FullHDP"
break
else:
print(
f"[{Fore.RED}-{Fore.RESET}] {Fore.LIGHTRED_EX}Invalid input. Please try again."
)
print(f"[{Fore.GREEN}+{Fore.RESET}] Title: {Fore.LIGHTCYAN_EX}{title}")
print(
f"[{Fore.GREEN}+{Fore.RESET}] Episode/s: {Fore.LIGHTCYAN_EX}{episode_number}"
)
print(
f"[{Fore.GREEN}+{Fore.RESET}] Quality: {Fore.LIGHTCYAN_EX}{episode_quality}"
)
print(f"[{Fore.GREEN}+{Fore.RESET}] Link: {Fore.LIGHTCYAN_EX}{source}")
folder = os.path.join(os.getcwd(), title)
if not os.path.exists(folder):
os.mkdir(folder)
while True:
choice = input(
f"[{Fore.GREEN}+{Fore.RESET}] Do you want to download all episode? (y/n) > "
)
if choice in ["y", "n"]:
break
else:
print(
f"[{Fore.RED}-{Fore.RESET}] {Fore.LIGHTRED_EX}Invalid input. Please try again."
)
if choice == "n":
while True:
try:
custom_episode_number = int(
input(
f"[{Fore.GREEN}+{Fore.RESET}] How many episode do you want to download? > "
)
)
if custom_episode_number == 0 or custom_episode_number > int(
episode_number
):
raise bd.InvalidInputValue
else:
episode_number = custom_episode_number
break
except ValueError:
print(
f"[{Fore.RED}-{Fore.RESET}] {Fore.LIGHTRED_EX}Invalid input. Please try again."
)
except bd.InvalidInputValue:
print(
f"[{Fore.RED}-{Fore.RESET}] {Fore.LIGHTRED_EX}Custom episode cannot be equal to 0 or it cannot be greater than {episode_number}"
)
download = bd.Download(name, episode_quality, int(episode_number), folder)
source = f"https://gogoanime.pe/{name}"
with req.get(source) as res:
soup = BeautifulSoup(res.content, "html.parser")
episode_zero = soup.find("h1", {"class": "entry-title"})
if episode_zero is None:
# Episode 0 == 200
with concurrent.futures.ThreadPoolExecutor() as exec:
episode_links = download.get_links(source)
download_links = list(
exec.map(download.get_download_links, episode_links)
)
download_urls = list(
exec.map(download.get_download_urls, download_links)
)
else:
# Episode 0 == 404
with concurrent.futures.ThreadPoolExecutor() as exec:
episode_links = download.get_links()
download_links = list(
exec.map(download.get_download_links, episode_links)
)
download_urls = list(
exec.map(download.get_download_urls, download_links)
)
print(
f"[{Fore.GREEN}+{Fore.RESET}] 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):
import sys, subprocess
opener = "open" if sys.platform == "darwin" else "xdg-open"
subprocess.call([opener, folder])
use_again = input(
f"[{Fore.GREEN}+{Fore.RESET}] Do you want to use the app again? (y|n) > "
).lower()
if use_again == "y":
os.system("cls")
else:
break
if __name__ == "__main__":
bitanime()

View File

@ -1 +0,0 @@
3.2.0

View File

@ -1 +0,0 @@
cd C:\Users\Karl.Hudgell\Documents\GoGoDownloader && venv\Scripts\activate && python GoGoDownloaderCLI.py