Compare commits

...

59 Commits

Author SHA1 Message Date
Karl Hudgell
f55d917cc9 quick .5 fix 2024-03-02 11:23:02 +00:00
Karl Hudgell
475466617d update message 2023-08-08 15:44:12 +01:00
Karl Hudgell
02fa241ec6 fix for single episodes 2023-08-08 15:42:53 +01:00
3b599893bc
Merge pull request #16 from karl0ss/latestUpdates
Latest updates
2023-08-06 16:51:42 +01:00
664f5f4974
Merge branch 'master' into latestUpdates 2023-08-06 16:51:36 +01:00
Karl Hudgell
62712a3897 latest commit 2023-08-06 16:48:07 +01:00
Karl Hudgell
d865913154 latest update 2023-08-06 16:48:00 +01:00
2fc41cfa56
Merge pull request #12 from Arctic4161/master
Fix Backend.py
2023-05-23 17:20:59 +01:00
Arctic4161
5211c127e4
Merge pull request #3 from karl0ss/master
Update Fork
2022-11-21 22:16:45 -06:00
Arctic4161
fd1046f8a7
Update backend.py 2022-11-21 22:11:05 -06:00
78a394ffd5
Merge pull request #9 from karl0ss/CreateCLITool
Create cli tool
2022-06-28 08:50:02 +01:00
karl.hudgell
2d1eda3b92 upversion 2022-06-28 08:48:37 +01:00
karl.hudgell
8210e0b07b working CLI tool 2022-06-28 08:48:05 +01:00
karl.hudgell
fc19a690bd loadDownloadHistory 2022-06-21 12:32:46 +01:00
karl.hudgell
b002368705 ignore downloadHistory.json 2022-06-21 12:32:23 +01:00
karl.hudgell
ee82a26e76 handle special characters in show name 2022-06-21 12:22:59 +01:00
karl.hudgell
7c4a569678 update config.json 2022-06-21 12:09:19 +01:00
karl.hudgell
9ea3c6a6ce create new CLI to process users bookmarks 2022-06-21 12:09:10 +01:00
karl.hudgell
64acd0abb2 get_show_from_bookmark 2022-06-21 12:08:52 +01:00
karl.hudgell
a09ed8c802 ignore bookmarkList 2022-06-21 12:08:36 +01:00
karl.hudgell
a8d54be2d3 ignore app.log 2022-06-20 12:50:22 +01:00
karl.hudgell
0dffecc6ff ignore the launch.json 2022-06-20 12:50:07 +01:00
karl.hudgell
46001b041c initial commit of clovisNyu cli 2022-06-20 12:49:36 +01:00
Arctic4161
5a2d682cae
Merge pull request #1 from karl0ss/master
update my fork
2022-06-06 22:19:45 -05:00
1b2738d4b2
update to new domain 2022-03-13 17:35:54 +00:00
72cef9b292
Merge pull request #7 from digitalw00t/domainfix
remove fixed domain from test
2022-03-12 21:10:13 +00:00
Andrew Falgout
6ba9477a89
remove fixed domain from test 2022-03-12 14:50:49 -06:00
093437f464
Merge pull request #6 from RobinLaevaert/AddBetterLastEpisodeChecker
Add better last episode checker
2022-02-28 09:20:17 +00:00
Robin Laevaert
7a382de2f6 Add better last episode checker 2022-02-27 15:41:44 +01:00
karl.hudgell
02f275e13a 3.1.1 2022-02-23 08:47:45 +00:00
karl.hudgell
bc957b0607 replace litterals and cross platform cls 2022-02-23 08:47:31 +00:00
b3b02e040c
Merge pull request #5 from karl0ss/addLoggedInCheck
quick check for logged in user and some logs
2022-02-22 16:35:44 +00:00
karl.hudgell
bbec182b38 quick check for logged in user and some logs 2022-02-22 16:35:18 +00:00
0b48cca908
Merge pull request #4 from karl0ss/retry_url
Retry url
2022-02-17 14:00:29 +00:00
karl.hudgell
7bfc18383d long winded working retry logic 2022-02-17 13:59:44 +00:00
karl.hudgell
81b4c63892 rework download logic 2022-02-15 10:42:40 +00:00
18092af206
Update README.md 2022-01-20 15:14:13 +00:00
79a5b7ee5f
Update README.md 2022-01-20 15:13:23 +00:00
259973f780
Merge pull request #2 from karl0ss/move_to_userpass
Move to userpass - v2.0.0
2022-01-20 14:49:14 +00:00
karl.hudgell
1852b8fe2e move to user/pass not token 2022-01-20 14:47:21 +00:00
karl.hudgell
79f0c1e248 rename main to GoGoDownloader 2022-01-20 14:47:07 +00:00
karl.hudgell
7adc913e3f ignore build and dist 2022-01-19 10:32:26 +00:00
karl.hudgell
f5dc0915f4 restrict downloads max to 6 2022-01-19 10:29:02 +00:00
karl.hudgell
01dab52b42 Overwrite via config 2022-01-19 10:23:17 +00:00
karl.hudgell
088c7b3dca add OverwriteDownloads 2022-01-19 10:16:45 +00:00
karl.hudgell
2df3c5ce1a move everything to download class 2022-01-19 10:16:34 +00:00
90f041d1f6 resize 2022-01-19 09:36:40 +00:00
a6d0b6036f readme 2022-01-19 09:35:40 +00:00
a82d14cf7b screenshot 2022-01-19 09:19:19 +00:00
9679fc4cb0 gogo logo 2022-01-19 08:47:56 +00:00
99f808d37c
Merge pull request #1 from karl0ss/GoGoDownloader
GoGoDownloader
2022-01-18 20:56:06 +00:00
bf777d1a29 gogo initial commit 2022-01-18 20:55:11 +00:00
3262b2c69e clean repo 2022-01-18 20:53:34 +00:00
1fa2e28b70 formatting and allowing the domain to be changed 2022-01-08 13:47:12 +00:00
Arctic4161
6fd7e6ee9e
Update bitanime.py 2021-11-08 09:09:12 -06:00
Arctic4161
5f46810de6
Update backend.py 2021-11-08 09:08:38 -06:00
Arctic4161
ecebb4b7c0
Update README.md 2021-11-04 10:56:09 -05:00
Arctic4161
4606c148a6
Update README.md 2021-11-04 10:54:10 -05:00
Arctic4161
df43704459
Update backend.py
gogoanime changed their response headers again. Fixed 403 error again
2021-11-04 10:45:55 -05:00
18 changed files with 582 additions and 435 deletions

147
.gitignore vendored
View File

@ -1,138 +1,11 @@
# 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__/
build/
# Spyder project settings dist/
.spyderproject main.spec
.spyproject .vscode/launch.json
app.log
# Rope project settings bookmarkList.json
.ropeproject downloadHistory.json
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/

3
CLIOutput/eng.bat Normal file
View File

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

View File

@ -1,50 +1,56 @@
import requests as req import requests
import ctypes import ctypes
import os import os
import concurrent.futures from backend import gogoanime, CustomMessage, config_check
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 logging
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}] "
try: try:
ctypes.windll.kernel32.SetConsoleTitleW("BitAnime") ctypes.windll.kernel32.SetConsoleTitleW("GoGo Downloader")
except AttributeError: except AttributeError:
pass pass
def bitanime(): def gogodownloader(config):
os.system("cls") CURRENT_URL = config["CurrentGoGoAnimeURL"]
os.system("cls" if os.name == "nt" else "clear")
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:
name = input(f"{IN}Enter anime name > ").lower() name = input(f"{IN}Enter anime name > ").lower()
logging.info("episode searched for " + name)
if "-" in name: if "-" in name:
title = name.replace("-", " ").title().strip() title = name.replace("-", " ").title().strip()
else: else:
title = name.title().strip() title = name.title().strip()
source = f"https://gogoanime.pe/category/{name}" source = f"https://{CURRENT_URL}/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"})
all_episodes = int(all_episodes.get_text().split("-")[-1].strip()) all_episodes = int(list(filter(None, "-".join(all_episodes.get_text().splitlines()).split("-")))[-1].strip())
break break
else: else:
print(f"{ERR}Error 404: Anime not found. Please try again.") print(f"{ERR}Error 404: Anime not found. Please try again.")
@ -53,19 +59,20 @@ 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.")
logging.info("quality selected " + episode_quality)
print(f"{OK}Title: {Fore.LIGHTCYAN_EX}{title}") print(f"{OK}Title: {Fore.LIGHTCYAN_EX}{title}")
print(f"{OK}Episode/s: {Fore.LIGHTCYAN_EX}{all_episodes}") print(f"{OK}Episode/s: {Fore.LIGHTCYAN_EX}{all_episodes}")
print(f"{OK}Quality: {Fore.LIGHTCYAN_EX}{episode_quality}") print(f"{OK}Quality: {Fore.LIGHTCYAN_EX}{episode_quality}")
@ -103,9 +110,9 @@ def bitanime():
CustomMessage( CustomMessage(
f"{ERR}episode_start or episode_end cannot be more than {all_episodes}" f"{ERR}episode_start or episode_end cannot be more than {all_episodes}"
).print_error() ).print_error()
elif episode_end <= episode_start: elif episode_end < episode_start:
CustomMessage( CustomMessage(
f"{ERR}episode_end cannot be less than or equal to episode_start" f"{ERR}episode_end cannot be less than episode_start"
).print_error() ).print_error()
else: else:
break break
@ -121,44 +128,49 @@ def bitanime():
else: else:
episode_end = all_episodes episode_end = all_episodes
download = Download( gogo = gogoanime(
name, episode_quality, folder, all_episodes, episode_start, episode_end config,
name,
episode_quality,
folder,
all_episodes,
episode_start,
episode_end,
title,
) )
gogo.user_logged_in_check()
source = f"https://gogoanime.pe/{name}" source = f"https://{CURRENT_URL}/{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
episode_links = download.get_links(source) dl_links = []
with concurrent.futures.ThreadPoolExecutor() as executor: episode_links = gogo.get_links(source)
download_links = list(executor.map(get_download_links, episode_links)) print(f"{OK}Scraping Links")
download_urls = list(executor.map(download.get_download_urls, download_links)) for link in episode_links:
print( dl_links.append(gogo.get_download_link(link))
f"{OK}Downloading {Fore.LIGHTCYAN_EX}{len(download_urls)}{Fore.RESET} episode/s"
) result = gogo.file_downloader(dl_links)
thread_map( if len(result.errors) > 0:
download.download_episodes, while len(result.errors) > 0:
download_urls, print(f"{ERR}{len(result.errors)} links failed retrying.")
ncols=75, episode_links = gogo.get_links(source)
total=len(download_urls) 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)
try:
os.startfile(folder)
except AttributeError:
opener = "open" if sys.platform == "darwin" else "xdg-open"
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" if os.name == "nt" else "clear")
else: else:
break break
if __name__ == "__main__": if __name__ == "__main__":
bitanime() config = config_check()
gogodownloader(config)

128
GoGoDownloaderCLI.py Normal file
View File

@ -0,0 +1,128 @@
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()

40
GoGoDownloaderCLI.spec Normal file
View File

@ -0,0 +1,40 @@
# -*- 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,55 +1,76 @@
<div align="center"> <div align="center">
<img <img
style="width: 165px; height: 165px" style="width: 300px; height: 300px"
src="https://i.postimg.cc/VkSMVQrg/ba-logo.png" src="https://github.com/karl0ss/GoGoDownloader/raw/master/img/gogo_logo.png"
title="BitAnime" title="GoGoDownloader"
alt="BitAnime" alt="GoGoDownloader"
/> />
<h3>BitAnime</h3> <h3>GoGo Downloader</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 BitAnime ## About 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 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**.
**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
```console - Download the zip
git clone https://github.com/sh1nobuu/BitAnime.git - Extract and set your GoGoAnime Username and Password in the config.json
``` - 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://i.postimg.cc/cLgf8994/ba-screenshot.png" <img style="height:386px; width:688px;" src="https://github.com/karl0ss/GoGoDownloader/raw/master/img/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
**BitAnime** is highly reliant on the python modules `requests`, `colorama`, `tqdm`, and `BeautifulSoup`. **GoGo Downloader** is highly reliant on the python modules `requests`, `colorama`, `parfive`, 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.pe](https://gogoanime.pe/) 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.gg](https://gogoanime3.gg/) 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://gogoanime.pe/category/bakemonogatari >> bakemonogatari - https://gogoanime3.gg/category/bakemonogatari >> bakemonogatari
- https://gogoanime.pe/category/steinsgate >> steinsgate - https://gogoanime3.gg/category/steinsgate >> steinsgate
##### Multiple word title ##### Multiple word title
- https://gogoanime.pe/category/shadows-house >> shadows-house - https://gogoanime3.gg/category/shadows-house >> shadows-house
- https://gogoanime.pe/category/kono-subarashii-sekai-ni-shukufuku-wo- >> kono-subarashii-sekai-ni-shukufuku-wo- - https://gogoanime3.gg/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.

280
backend.py Normal file
View File

@ -0,0 +1,280 @@
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()

10
config.json.default Normal file
View File

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

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

BIN
img/gogo_icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
img/gogo_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
img/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

View File

@ -1,222 +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 {'User-Agent': choice(desktop_agents),
"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, br",
"Referer": "https://goload.one/",
"Connection": "keep-alive"}
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.pe/{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.pe/{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]) 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()

1
version.txt Normal file
View File

@ -0,0 +1 @@
3.2.0

1
winRun.bat Normal file
View File

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