mirror of
https://github.com/karl0ss/GoGoDownloader.git
synced 2025-05-03 14:03:42 +01:00
Compare commits
107 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f55d917cc9 | ||
![]() |
475466617d | ||
![]() |
02fa241ec6 | ||
3b599893bc | |||
664f5f4974 | |||
![]() |
62712a3897 | ||
![]() |
d865913154 | ||
2fc41cfa56 | |||
![]() |
5211c127e4 | ||
![]() |
fd1046f8a7 | ||
78a394ffd5 | |||
![]() |
2d1eda3b92 | ||
![]() |
8210e0b07b | ||
![]() |
fc19a690bd | ||
![]() |
b002368705 | ||
![]() |
ee82a26e76 | ||
![]() |
7c4a569678 | ||
![]() |
9ea3c6a6ce | ||
![]() |
64acd0abb2 | ||
![]() |
a09ed8c802 | ||
![]() |
a8d54be2d3 | ||
![]() |
0dffecc6ff | ||
![]() |
46001b041c | ||
![]() |
5a2d682cae | ||
1b2738d4b2 | |||
72cef9b292 | |||
![]() |
6ba9477a89 | ||
093437f464 | |||
![]() |
7a382de2f6 | ||
![]() |
02f275e13a | ||
![]() |
bc957b0607 | ||
b3b02e040c | |||
![]() |
bbec182b38 | ||
0b48cca908 | |||
![]() |
7bfc18383d | ||
![]() |
81b4c63892 | ||
18092af206 | |||
79a5b7ee5f | |||
259973f780 | |||
![]() |
1852b8fe2e | ||
![]() |
79f0c1e248 | ||
![]() |
7adc913e3f | ||
![]() |
f5dc0915f4 | ||
![]() |
01dab52b42 | ||
![]() |
088c7b3dca | ||
![]() |
2df3c5ce1a | ||
90f041d1f6 | |||
a6d0b6036f | |||
a82d14cf7b | |||
9679fc4cb0 | |||
99f808d37c | |||
bf777d1a29 | |||
3262b2c69e | |||
1fa2e28b70 | |||
![]() |
6fd7e6ee9e | ||
![]() |
5f46810de6 | ||
![]() |
ecebb4b7c0 | ||
![]() |
4606c148a6 | ||
![]() |
df43704459 | ||
![]() |
326f934c8a | ||
![]() |
bded493598 | ||
![]() |
196c192479 | ||
![]() |
c24be4c4e9 | ||
![]() |
bde87f1294 | ||
![]() |
06fec65d2e | ||
![]() |
c6bad6cc13 | ||
![]() |
2f9ba1c78b | ||
![]() |
e9e89e3159 | ||
![]() |
d1348cbd95 | ||
![]() |
bda3ead826 | ||
![]() |
9d4dc53961 | ||
![]() |
1ecec7c05d | ||
![]() |
23b99e6362 | ||
![]() |
f81a3699a3 | ||
![]() |
01562f595b | ||
![]() |
a3a5ca7ecd | ||
![]() |
4d456ba9b9 | ||
![]() |
fbf352579c | ||
![]() |
187983b348 | ||
![]() |
d1c222ff91 | ||
![]() |
b3766d5a6e | ||
![]() |
218eb40c40 | ||
![]() |
6b59251753 | ||
![]() |
7b23113f8b | ||
![]() |
a307c706cd | ||
![]() |
1acd36e45e | ||
![]() |
a5619c8c4d | ||
![]() |
46038e93a3 | ||
![]() |
13c49e5fe4 | ||
![]() |
ff72f7f2a4 | ||
![]() |
8397eeb7bf | ||
![]() |
2fe9bb38a6 | ||
![]() |
70cc84eede | ||
![]() |
c4a265d168 | ||
![]() |
228323c491 | ||
![]() |
fc3db694fc | ||
![]() |
fe3832b127 | ||
![]() |
0d321c8107 | ||
![]() |
89301e6f19 | ||
![]() |
1626601b56 | ||
![]() |
8657080d34 | ||
![]() |
01c7a60133 | ||
![]() |
ca2eae558b | ||
![]() |
f7997fb25a | ||
![]() |
0e1ccd1f73 | ||
![]() |
abc745dc1c | ||
![]() |
7b3b22cf1f |
150
.gitignore
vendored
150
.gitignore
vendored
@ -1,141 +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/
|
||||
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/
|
||||
|
||||
# Executable files
|
||||
exe/
|
||||
*.mp4
|
||||
config.json
|
||||
__pycache__/
|
||||
build/
|
||||
dist/
|
||||
main.spec
|
||||
.vscode/launch.json
|
||||
app.log
|
||||
bookmarkList.json
|
||||
downloadHistory.json
|
||||
|
3
CLIOutput/eng.bat
Normal file
3
CLIOutput/eng.bat
Normal file
@ -0,0 +1,3 @@
|
||||
for /R %%a in ("*.mp4") do MP4Box -lang eng "%%a"
|
||||
|
||||
exit
|
176
GoGoDownloader.py
Normal file
176
GoGoDownloader.py
Normal file
@ -0,0 +1,176 @@
|
||||
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)
|
128
GoGoDownloaderCLI.py
Normal file
128
GoGoDownloaderCLI.py
Normal 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
40
GoGoDownloaderCLI.spec
Normal 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 )
|
66
README.md
66
README.md
@ -1,54 +1,76 @@
|
||||
|
||||
<div align="center">
|
||||
<img
|
||||
style="width: 165px; height: 165px"
|
||||
src="https://i.postimg.cc/VkSMVQrg/ba-logo.png"
|
||||
title="BitAnime"
|
||||
alt="BitAnime"
|
||||
style="width: 300px; height: 300px"
|
||||
src="https://github.com/karl0ss/GoGoDownloader/raw/master/img/gogo_logo.png"
|
||||
title="GoGoDownloader"
|
||||
alt="GoGoDownloader"
|
||||
/>
|
||||
<h3>BitAnime</h3>
|
||||
<h3>GoGo Downloader</h3>
|
||||
<h4>Forked from <a href="https://github.com/sh1nobuu/BitAnime">BitAnime</a></h4>
|
||||
<p>
|
||||
A Python script that allows you to download all of an anime's episodes at once.
|
||||
</p>
|
||||
<a href="https://github.com/sh1nobuu/BitAnime/releases"> <strong>· Download executable version ·</strong></a>
|
||||
|
||||
</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/). This application can only download **1**-**99** episodes at the time. At the moment, the quality of the episodes that will be downloaded is different for every anime. For **older anime**, the quality will be **360p** to **480p**, for **newer anime** the quality will be **720p** to **1080p**.
|
||||
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
|
||||
You have 2 options here, you can download the exe on the releases page and run on Windows
|
||||
|
||||
```console
|
||||
git clone https://github.com/sh1nobuu/BitAnime.git
|
||||
```
|
||||
- Download the zip
|
||||
- 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
|
||||
|
||||
<div align="center">
|
||||
<img src="https://i.postimg.cc/q76DzZ5y/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">
|
||||
</div>
|
||||
|
||||
## Dependencies
|
||||
|
||||
**BitAnime** is highly reliant on the python modules `requests`, `colorama`, `tqdm`, and `BeautifulSoup`.
|
||||
|
||||
```console
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
**GoGo Downloader** is highly reliant on the python modules `requests`, `colorama`, `parfive`, and `BeautifulSoup`.
|
||||
|
||||
## 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
|
||||
|
||||
##### One word title
|
||||
|
||||
- https://gogoanime.pe/category/bakemonogatari >> bakemonogatari
|
||||
- https://gogoanime.pe/category/steinsgate >> steinsgate
|
||||
- https://gogoanime3.gg/category/bakemonogatari >> bakemonogatari
|
||||
- https://gogoanime3.gg/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-
|
||||
- https://gogoanime3.gg/category/shadows-house >> shadows-house
|
||||
- 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
280
backend.py
Normal 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
10
config.json.default
Normal 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: 21 KiB |
BIN
img/gogo_icon.ico
Normal file
BIN
img/gogo_icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
BIN
img/gogo_logo.png
Normal file
BIN
img/gogo_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
BIN
img/screenshot.png
Normal file
BIN
img/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
BIN
requirements.txt
BIN
requirements.txt
Binary file not shown.
@ -1,68 +0,0 @@
|
||||
import requests
|
||||
import os
|
||||
import shutil
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
folder_path = ""
|
||||
|
||||
|
||||
def get_path(folder):
|
||||
global folder_path
|
||||
folder_path = folder
|
||||
|
||||
|
||||
def get_links(name, episode_number, source=None):
|
||||
if source is not None:
|
||||
source_ep = f"https://gogoanime.pe/{name}-episode-"
|
||||
episode_links = [f"{source_ep}{i}" for i in range(1, int(episode_number) + 1)]
|
||||
episode_links.insert(0, source)
|
||||
else:
|
||||
source_ep = f"https://gogoanime.pe/{name}-episode-"
|
||||
episode_links = [f"{source_ep}{i}" for i in range(1, int(episode_number) + 1)]
|
||||
return episode_links
|
||||
|
||||
|
||||
def get_download_links(episode_links):
|
||||
download_links = []
|
||||
for episode_link in episode_links:
|
||||
episode_link_resp = requests.get(episode_link)
|
||||
soup = BeautifulSoup(episode_link_resp.content, "html.parser")
|
||||
links = soup.find("li", {"class": "dowloads"})
|
||||
for link in links:
|
||||
link = link.get("href")
|
||||
download_links.append(link)
|
||||
return download_links
|
||||
|
||||
|
||||
def get_download_urls(download_links, bool):
|
||||
download_urls = []
|
||||
for link in download_links:
|
||||
link = requests.get(link)
|
||||
soup = BeautifulSoup(link.content, "html.parser")
|
||||
download_link = soup.find_all("div", {"class": "dowload"})
|
||||
download_urls.append(download_link[0].a.get("href"))
|
||||
if bool:
|
||||
conv_download_urls = {
|
||||
episode_title: url for episode_title, url in enumerate(download_urls)
|
||||
}
|
||||
else:
|
||||
conv_download_urls = {
|
||||
episode_title + 1: url
|
||||
for episode_title, url in enumerate(download_urls)
|
||||
}
|
||||
conv_download_urls = sorted(set(conv_download_urls.items()))
|
||||
return conv_download_urls
|
||||
|
||||
|
||||
def download_episodes(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",
|
||||
}
|
||||
url_resp = requests.get(url[1], headers=header, stream=True)
|
||||
file_name = os.path.join(folder_path, f"{url[0]}.mp4")
|
||||
with open(file_name, "wb") as file:
|
||||
shutil.copyfileobj(url_resp.raw, file)
|
114
src/bitanime.py
114
src/bitanime.py
@ -1,114 +0,0 @@
|
||||
# Dependencies
|
||||
|
||||
import requests
|
||||
import ctypes
|
||||
import os
|
||||
import backend as bd
|
||||
import colorama
|
||||
from tqdm.contrib.concurrent import thread_map
|
||||
from bs4 import BeautifulSoup
|
||||
from colorama import Fore
|
||||
|
||||
colorama.init(autoreset=True)
|
||||
ctypes.windll.kernel32.SetConsoleTitleW("BitAnime")
|
||||
|
||||
|
||||
def bitanime():
|
||||
again = True
|
||||
while again:
|
||||
print(
|
||||
f""" {Fore.LIGHTBLUE_EX}
|
||||
____ _ _ _ _
|
||||
| __ )(_) |_ / \ _ __ (_)_ __ ___ ___
|
||||
| _ \| | __| / _ \ | '_ \| | '_ ` _ \ / _ \\
|
||||
| |_) | | |_ / ___ \| | | | | | | | | | __/
|
||||
|____/|_|\__/_/ \_\_| |_|_|_| |_| |_|\___|
|
||||
{Fore.LIGHTYELLOW_EX}
|
||||
By: sh1nobu
|
||||
Github: https://github.com/sh1nobuu/BitAnime
|
||||
"""
|
||||
)
|
||||
"""
|
||||
Ask user for input and then check if the anime provided exists or if not, loop
|
||||
"""
|
||||
check = True
|
||||
while check:
|
||||
name = input(f"Enter anime name >> ").lower()
|
||||
if "-" in name:
|
||||
title = name.replace("-", " ").title().strip()
|
||||
else:
|
||||
title = name.title().strip()
|
||||
source = f"https://gogoanime.pe/category/{name}"
|
||||
resp = requests.get(source)
|
||||
if resp.status_code == 200:
|
||||
print(f"{Fore.LIGHTGREEN_EX}====================================")
|
||||
check = False
|
||||
else:
|
||||
print(
|
||||
f"{Fore.LIGHTRED_EX}Error 404: Anime not found. Please try again."
|
||||
)
|
||||
check = True
|
||||
"""
|
||||
Get how many episode/s the anime has
|
||||
"""
|
||||
soup = BeautifulSoup(resp.content, "html.parser")
|
||||
episode_number = soup.find("ul", {"id": "episode_page"})
|
||||
episode_number = episode_number.get_text().split("-")[1].strip()
|
||||
"""
|
||||
Print the anime name, episode, and the link of the anime
|
||||
"""
|
||||
print(f"Title: {Fore.LIGHTCYAN_EX}{title}")
|
||||
print(f"Episode/s: {Fore.LIGHTCYAN_EX}{episode_number}")
|
||||
print(f"Link: {Fore.LIGHTCYAN_EX}{source}")
|
||||
print(f"{Fore.LIGHTGREEN_EX}====================================")
|
||||
"""
|
||||
Create a download folder for the anime
|
||||
"""
|
||||
folder = os.path.join(os.getcwd(), title)
|
||||
if not os.path.exists(folder):
|
||||
os.mkdir(folder)
|
||||
"""
|
||||
Check if the anime has episode 0 or not
|
||||
"""
|
||||
source = f"https://gogoanime.pe/{name}"
|
||||
resp = requests.get(source)
|
||||
soup = BeautifulSoup(resp.content, "html.parser")
|
||||
episode_zero = soup.find("h1", {"class": "entry-title"})
|
||||
if episode_zero is None:
|
||||
# Episode 0 does exist
|
||||
episode_links = bd.get_links(name, episode_number, source)
|
||||
download_links = bd.get_download_links(episode_links)
|
||||
download_urls = bd.get_download_urls(download_links, True)
|
||||
print(f"Downloading {Fore.LIGHTCYAN_EX}{len(download_urls)} episode/s")
|
||||
print(f"{Fore.LIGHTGREEN_EX}====================================")
|
||||
print(download_urls)
|
||||
bd.get_path(folder)
|
||||
thread_map(
|
||||
bd.download_episodes, download_urls, ncols=75, total=len(download_urls)
|
||||
)
|
||||
os.startfile(folder)
|
||||
|
||||
else:
|
||||
# Episode 0 does not exist
|
||||
episode_links = bd.get_links(name, episode_number)
|
||||
download_links = bd.get_download_links(episode_links)
|
||||
download_urls = bd.get_download_urls(download_links, False)
|
||||
print(
|
||||
f"Downloading {Fore.LIGHTCYAN_EX}{len(download_urls)}{Fore.RESET} episode/s"
|
||||
)
|
||||
print(f"{Fore.LIGHTGREEN_EX}====================================")
|
||||
bd.get_path(folder)
|
||||
thread_map(
|
||||
bd.download_episodes, download_urls, ncols=75, total=len(download_urls)
|
||||
)
|
||||
os.startfile(folder)
|
||||
use_again = input("Do you want to download other anime? (y|n) >> ").lower()
|
||||
if use_again == "y":
|
||||
again = True
|
||||
os.system("cls")
|
||||
else:
|
||||
again = False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
bitanime()
|
1
version.txt
Normal file
1
version.txt
Normal file
@ -0,0 +1 @@
|
||||
3.2.0
|
1
winRun.bat
Normal file
1
winRun.bat
Normal file
@ -0,0 +1 @@
|
||||
cd C:\Users\Karl.Hudgell\Documents\GoGoDownloader && venv\Scripts\activate && python GoGoDownloaderCLI.py
|
Loading…
x
Reference in New Issue
Block a user