Skip to content

Commit

Permalink
Merge pull request #4 from greg-ynx/dev
Browse files Browse the repository at this point in the history
Release 2.0.0
  • Loading branch information
greg-ynx authored Nov 3, 2023
2 parents 7fce6e4 + ccc0165 commit a33b403
Show file tree
Hide file tree
Showing 18 changed files with 789 additions and 67 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ MANIFEST
# 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
Expand Down
34 changes: 15 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,24 @@
# NexusDownloadFlow 2022 : Auto clicker script using computer vision
# NexusDownloadFlow: Auto-downloader for Nexus Mods

NexusDownloaderFlow (NDF) 2022 is a script that take screenshots and classify if any template match with the current
screenshot taken. It was made in order to automate process with `Wabbajack modlist installation of Nexus' mods` in which
you have to manually click on `Slow download` button is your NexusMods account is not premium.
NexusDownloadFlow (NDF) is a program that automates the download process with `Wabbajack modlist installation of Nexus
Mods` in which you have to manually click on `Slow download` button if your Nexus Mods account is not premium.

## How to use NDF 2022 ?
## How to use NexusDownloadFlow?

Just execute `NexusDownloadFlow 2022.exe` and open your NexusMods' download page.
### Without Wabbajack

## Auto clicker is not clicking
Execute `NexusDownloadFlow.exe` and open your Nexus Mods download page.

Do not worry, you have to replace the templates files where you installed NDF with the one you will screenshot:
`NexusDownloadFlow 2022/assets/template{x}.png`
### With Wabbajack

+ `template1.png` is the raw `Slow download` button
+ `template2.png` is the `Slow download` button with mouse over
+ `template3.png` is the `Click here` link appearing five seconds after clicking on `Slow download` button
Execute `NexusDownloadFlow.exe` while the mod list is downloading.

## Credits
## Auto-clicker is not clicking

Open an issue [here](https://github.com/greg-ynx/NexusDownloadFlow/issues/new), and if possible, give the scenario in which you had this issue, which version of NDF you are using
and provide a screenshot of your logs or the contents of your current `{date}_ndf.log` file.

Thanks to @parsiad for inspiring me with his repository named `parsiad/nexus-autodl`
(I could not download his auto clicker).
## Credits

Requirements used for this script are :
+ PyAutoGUI~=0.9.53
+ opencv-python==4.5.5.64
+ mss~=6.1.0
Thanks to [parsiad](https://github.com/parsiad) for inspiring me with his repository named
[`parsiad/nexus-autodl`](https://github.com/parsiad/nexus-autodl).
Binary file modified assets/template1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/template2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/template3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions build.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pyinstaller build.spec --clean --workpath ./target --distpath ./target/dist
41 changes: 41 additions & 0 deletions build.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# -*- mode: python ; coding: utf-8 -*-

extra_files = [
('assets/*', 'assets'),
('pyproject.toml', '.')
]

a = Analysis(
['main.py'],
pathex=[],
binaries=[],
datas=extra_files,
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
)
pyz = PYZ(a.pure)

exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='NexusDownloadFlow',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
29 changes: 29 additions & 0 deletions config/ascii_art.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Print NexusDownloadFlow ascii art."""
import sys
from typing import Any

from config.definitions import PYPROJECT_DATA

ASCII_COLOR: str = "\033[33m"
ASCII_TEXT: str = """
_ _
| \\ | |
| \\| | _____ ___ _ ___
| . ` |/ _ \\ \\/ / | | / __|
| |\\ | __/> <| |_| \\__ \\
\\_| \\_/\\___/_/\\_\\__,_|___/
______ _ _ ______ _
| _ \\ | | | | | ___| |
| | | |_____ ___ __ | | ___ __ _ __| | | |_ | | _____ __
| | | / _ \\ \\ /\\ / / '_ \\| |/ _ \\ / _` |/ _` | | _| | |/ _ \\ \\ /\\ / /
| |/ / (_) \\ V V /| | | | | (_) | (_| | (_| | | | | | (_) \\ V V /
|___/ \\___/ \\_/\\_/ |_| |_|_|\\___/ \\__,_|\\__,_| \\_| |_|\\___/ \\_/\\_/\
"""

PROJECT_DATA: Any = PYPROJECT_DATA.get("project")
PROJECT_VERSION: str = "v{0}".format(str(PROJECT_DATA.get("version")))


def print_ascii_art() -> None:
"""Print NexusDownloadFlow ascii art with the project version."""
sys.stdout.write(ASCII_COLOR + ASCII_TEXT + PROJECT_VERSION + "\033[0m\n")
28 changes: 25 additions & 3 deletions config/definitions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,29 @@
"""Define global constants used in the project."""
import os
import sys
import tomllib
from typing import Any

_TEMP_DIRECTORY: str
_EXE_DIRECTORY: str = os.path.realpath(os.path.join(sys.executable, ".."))
_DEV_DIRECTORY: str = os.path.realpath(os.path.join(os.path.dirname(__file__), ".."))
MAIN_PATH: str
ASSETS_DIRECTORY: str
LOGS_DIRECTORY: str
PYPROJECT_DIRECTORY: str

ROOT_DIR = os.path.realpath(os.path.join(os.path.dirname(__file__), '..'))
main_path = os.path.join(ROOT_DIR, 'main.py')
assets_dir = os.path.join(ROOT_DIR, 'assets')
if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
_TEMP_DIRECTORY = os.path.join(sys._MEIPASS)
MAIN_PATH = os.path.join(_TEMP_DIRECTORY, "main.py")
ASSETS_DIRECTORY = os.path.join(_TEMP_DIRECTORY, "assets")
LOGS_DIRECTORY = os.path.join(_EXE_DIRECTORY, "logs")
PYPROJECT_DIRECTORY = os.path.join(_TEMP_DIRECTORY, "pyproject.toml")
else:
MAIN_PATH = os.path.join(_DEV_DIRECTORY, "main.py")
ASSETS_DIRECTORY = os.path.join(_DEV_DIRECTORY, "assets")
LOGS_DIRECTORY = os.path.join(_DEV_DIRECTORY, "logs")
PYPROJECT_DIRECTORY = os.path.join(_DEV_DIRECTORY, "pyproject.toml")


with open(PYPROJECT_DIRECTORY, "rb") as pyproject:
PYPROJECT_DATA: dict[str, Any] = tomllib.load(pyproject)
78 changes: 78 additions & 0 deletions config/ndf_logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""Logging configuration."""

import logging
import os
import sys
import time
from logging import Handler
from typing import Iterable

from config.definitions import LOGS_DIRECTORY

_LOG_EXTENSION: str = ".log"
_NDF_STR: str = "ndf"
_LOGFILE_NAME: str = time.strftime("%Y_%m_%d_") + _NDF_STR + _LOG_EXTENSION


def _logs_directory_exists() -> bool:
"""
Check if the logs directory exists.
:return: Bool value indicating if the logs directory exists.
"""
return os.path.exists(LOGS_DIRECTORY)


def _setup_logfile_path() -> str:
"""
Set up log file.
:return: String representing the log file path.
"""
return os.path.join(LOGS_DIRECTORY, _LOGFILE_NAME)


def _stop_logging() -> None:
"""Shut down the logger."""
logging.shutdown()


def delete_logfile() -> None:
"""Delete the log file."""
logging.debug("Try to delete the current logfile...")
logfile_path: str = get_logfile_path()
_stop_logging()
if os.path.exists(logfile_path):
os.remove(path=logfile_path)
logging.debug("Logfile deleted.")


def get_logfile_path() -> str:
"""
Getter for the current log file path.
:return: Log file path.
"""
return _setup_logfile_path()


def logging_report() -> None:
"""Log report to open an issue on the project's repository."""
logging.critical(
"Please report this exception to our repository on GitHub: "
"https://github.com/greg-ynx/NexusDownloadFlow/issues?q=is%3Aissue+is%3Aopen"
)


def setup_logging() -> None:
"""Set up logging configuration."""
if not _logs_directory_exists():
os.makedirs(LOGS_DIRECTORY)
_handlers: Iterable[Handler] = [logging.FileHandler(_setup_logfile_path()), logging.StreamHandler(sys.stdout)]
logging.basicConfig(
level=logging.INFO,
handlers=_handlers,
format="%(asctime)s | %(levelname)s | %(message)s",
datefmt="%d/%m/%Y - %H:%M:%S",
)
logging.debug("Logger setup complete.")
56 changes: 15 additions & 41 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,18 @@
import os
import time
"""Main executable file of NexusDownloadFlow."""
import logging

import pyautogui
import cv2
from mss import mss
from config.ascii_art import print_ascii_art
from config.ndf_logging import setup_logging
from scripts.ndf_run import try_run

from config.definitions import assets_dir

if __name__ == '__main__':
print('NexusDownloadFlow 2022 starting...')
print('Do not forget to replace the assets templates (1, 2 & 3) in order to match with the screenshots '
'taken from your monitor!')
try:
templates = [cv2.imread(os.path.join(assets_dir, 'template1.png')),
cv2.imread(os.path.join(assets_dir, 'template2.png')),
cv2.imread(os.path.join(assets_dir, 'template3.png'))]
with mss() as sct:
while True:
for i in range(1, 4):
template = templates[i - 1]
template_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
screenshot = cv2.imread(sct.shot())
screenshot_gray = cv2.cvtColor(screenshot, cv2.COLOR_BGR2GRAY)
res = cv2.matchTemplate(screenshot_gray, template_gray, cv2.TM_SQDIFF)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
threshold = 3000
if min_val < threshold:
print('Matching template!')
top_left = min_loc
target = (top_left[0] + template_gray.shape[1] / 2, top_left[1] + template_gray.shape[0] / 2)
pyautogui.leftClick(target)
break
time.sleep(6)
except SystemExit:
print('Exiting the program...')
raise
finally:
time.sleep(5)
if os.path.exists("monitor-1.png"):
os.remove("monitor-1.png")
else:
print("The file does not exist")
print('Program ended')
def main() -> None:
"""NexusDownloadFlow main function."""
setup_logging()
print_ascii_art()
logging.info("NexusDownloadFlow is starting...")
try_run()


if __name__ == "__main__":
main()
50 changes: 50 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
[project]
name = "NexusDownloadFlow"
version = "2.0.0"
authors = [
{name = "Gregory Ployart", email = "[email protected]", alias = "greg-ynx"},
]
description = "Auto-downloader program to automate Nexus modlist downloads for free."
readme = "README.md"
requires-python = ">=3.11"
release-date = 2023-10-01

[python]
version = "3.11.5"

[github]
owner = "greg-ynx"
repository = "https://github.com/greg-ynx/NexusDownloadFlow"
issues = "https://github.com/greg-ynx/NexusDownloadFlow/issues"

[tool.mypy]
python_version = "3.11"
disallow_untyped_defs = true
disallow_any_unimported = true
no_implicit_optional = true
check_untyped_defs = true
warn_return_any = true
show_error_codes = true
warn_unused_ignores = true

[tool.ruff]
extend-select = [
"W",
"I",
"N",
"D",
"S",
"T20",
"C4",
"SIM",
"TCH"
]
ignore = [
"D203",
"D212"
]
fix = true
show-fixes = true
show-source = false
line-length = 120
target-version = "py311"
6 changes: 3 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
PyAutoGUI~=0.9.53
opencv-python~=4.5.5.64
mss~=6.1.0
PyAutoGUI==0.9.54
opencv-python==4.8.0.76
mss==9.0.1
24 changes: 24 additions & 0 deletions scripts/ndf_params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""Parameters file."""
import logging


def ask_to_keep_logfile() -> bool:
"""
Ask if the user wants to keep the log file.
:return: Bool value representing whether to keep the log file or not.
True, if user's answer is "y" or "Y".
False, if user's answer is "n" or "N".
Will repeat if the input value is not valid.
"""
while True:
keep: str = str(input("Would you like to save the logfile? (y/n)\n"))
match keep:
case "y" | "Y":
logging.info("Logfile will be saved.")
return True
case "n" | "N":
logging.info("Logfile will be saved only if an exception/error occurred.")
return False
case _:
continue
Loading

0 comments on commit a33b403

Please sign in to comment.