From a5bbcd15e67b963ca6122de14c0fd475aef5cdfd Mon Sep 17 00:00:00 2001 From: Elebertus Date: Tue, 4 Feb 2020 22:32:57 -0800 Subject: [PATCH 01/38] adding in.txt for old default --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index da335e4..ca1226e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Addon input files addons.txt +in.txt installed.ini addons-classic.txt addons-retail.txt From 5b301040df535e554f8d9402b13fc2f326945249 Mon Sep 17 00:00:00 2001 From: Elebertus Date: Tue, 4 Feb 2020 23:07:15 -0800 Subject: [PATCH 02/38] test --- config.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config.ini b/config.ini index e344fd5..c952877 100644 --- a/config.ini +++ b/config.ini @@ -1,6 +1,6 @@ [WOW ADDON UPDATER] -WoW Addon Location = C:\Program Files (x86)\World of Warcraft\_retail_\Interface\AddOns -Addon List File = addons.txt +WoW Addon Location = ./addons +Addon List File = in.txt Installed Versions File = installed.ini # retail or classic -Game Version = retail \ No newline at end of file +Game Version = retail From f63f6a1c3203a6b5a1551fbd21a1a3c2d66b0a84 Mon Sep 17 00:00:00 2001 From: Elebertus Date: Thu, 13 Aug 2020 17:06:43 -0700 Subject: [PATCH 03/38] adding pipenv-setup and short note for it --- Pipfile | 1 + README.md | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/Pipfile b/Pipfile index 30d10e7..0ddb44a 100644 --- a/Pipfile +++ b/Pipfile @@ -5,6 +5,7 @@ verify_ssl = true [dev-packages] coverage = "*" +pipenv-setup = "*" [packages] requests = "*" diff --git a/README.md b/README.md index 0a5c12b..88de069 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,11 @@ pipenv run coverage run --source=updater -m unittest -v pipenv run coverage report ``` +Updating deps in setup.py: +```bash +pipenv-setup sync -d +``` + 1. Submit Issues, PR's, or make general comments 1. ???? 1. Profit From 507e0de9a5fa2ff629ba0e622deed0cc972b3172 Mon Sep 17 00:00:00 2001 From: Elebertus Date: Thu, 13 Aug 2020 17:07:01 -0700 Subject: [PATCH 04/38] adding setup.py so this can be an installable module --- setup.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 setup.py diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..4c2fea7 --- /dev/null +++ b/setup.py @@ -0,0 +1,63 @@ +import os +from setuptools import setup + +setup( + name="wow_addon_updater", + version="v1.7.1", + description=( + "This utility provides an alternative to the Twitch/Curse " + "client for management and updating of addons for World of Warcraft. " + ), + author="Garrett Edwards", + url="https://github.com/grrttedwards/wow-addon-manager", + packages=["updater", "updater.manager", "updater.site"], + entry_points={"console_scripts": ["wow-addon-updater = updater.__main__:main"],}, + extras_require={ + "dev": [ + "appdirs==1.4.4", + "attrs==19.3.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "black==19.10b0; python_version >= '3.6'", + "cached-property==1.5.1", + "cerberus==1.3.2", + "certifi==2020.6.20", + "chardet==3.0.4", + "click==7.1.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "colorama==0.4.3; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "coverage==5.2.1", + "distlib==0.3.1", + "idna==2.10; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "orderedmultidict==1.0.1", + "packaging==20.4; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "pathspec==0.8.0", + "pep517==0.8.2", + "pip-shims==0.5.3; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "pipenv-setup==3.1.1", + "pipfile==0.0.2", + "plette[validation]==0.2.3; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "pyparsing==2.4.7; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "python-dateutil==2.8.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "regex==2020.7.14", + "requests==2.24.0", + "requirementslib==1.5.12; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "six==1.15.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "toml==0.10.1", + "tomlkit==0.7.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "typed-ast==1.4.1", + "urllib3==1.25.10; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "vistir==0.5.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "wheel==0.34.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + ] + }, + install_requires=[ + "beautifulsoup4==4.9.1", + "certifi==2020.6.20", + "chardet==3.0.4", + "cloudscraper==1.2.46", + "idna==2.10; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "pyparsing==2.4.7; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "requests==2.24.0", + "requests-toolbelt==0.9.1", + "soupsieve==2.0.1; python_version >= '3.5'", + "urllib3==1.25.10; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + ], +) From 4a86e049a2600e5c3283d0347c2610f201d5906a Mon Sep 17 00:00:00 2001 From: Elebertus Date: Thu, 13 Aug 2020 17:09:04 -0700 Subject: [PATCH 05/38] making imports easier --- updater/__init__.py | 2 ++ updater/manager/__init__.py | 1 + 2 files changed, 3 insertions(+) create mode 100644 updater/__init__.py diff --git a/updater/__init__.py b/updater/__init__.py new file mode 100644 index 0000000..7376389 --- /dev/null +++ b/updater/__init__.py @@ -0,0 +1,2 @@ +import updater.manager +import updater.site \ No newline at end of file diff --git a/updater/manager/__init__.py b/updater/manager/__init__.py index e69de29..34185d2 100644 --- a/updater/manager/__init__.py +++ b/updater/manager/__init__.py @@ -0,0 +1 @@ +from .addon_manager import AddonManager From da39e619a7e1e870c5854408da3207ea14b25f27 Mon Sep 17 00:00:00 2001 From: Elebertus Date: Thu, 13 Aug 2020 17:30:35 -0700 Subject: [PATCH 06/38] adding some more notes about installed the module and using the entry_points --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 88de069..e3f877a 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,24 @@ To run directly from the command line, use `pipenv run`: pipenv run python -m updater [-c FILE] ``` +Alternatively you can install the python module and have a command named `wow-addon-updater` available: +```bash +pipenv install +pipenv shell +# Make sure you're in a virtual environment! pipenv shell should place you in the venv +# created by `pipenv install` but to be sure you can check with `which python` and +# verify that it's $VIRTUAL_ENV/bin/python + +# Using the distutils method +`python setup.py install` +# Using pip +`pip install .` +# Make sure the command is where we expect +`which wow-addon-updater` +# This should output $VIRTUAL_ENV/bin/wow-addon-updater + +``` + More advanced usage includes optionally specifying a configuration file, which is detailed in the next section. ## Issues Downloading Addons? From 9a632de7a1ca751d19df6d8aaebc2a7568a778d6 Mon Sep 17 00:00:00 2001 From: Elebertus Date: Thu, 13 Aug 2020 17:31:30 -0700 Subject: [PATCH 07/38] removing graves in code block --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e3f877a..eb21994 100644 --- a/README.md +++ b/README.md @@ -60,11 +60,11 @@ pipenv shell # verify that it's $VIRTUAL_ENV/bin/python # Using the distutils method -`python setup.py install` +python setup.py install # Using pip -`pip install .` +pip install . # Make sure the command is where we expect -`which wow-addon-updater` +which wow-addon-updater # This should output $VIRTUAL_ENV/bin/wow-addon-updater ``` From 8b386a74443f3dd7fd76935f326d3592c29245e4 Mon Sep 17 00:00:00 2001 From: Elebertus Date: Thu, 13 Aug 2020 17:42:23 -0700 Subject: [PATCH 08/38] accidental commit of config.ini --- config.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.ini b/config.ini index c952877..05e83fe 100644 --- a/config.ini +++ b/config.ini @@ -1,6 +1,6 @@ [WOW ADDON UPDATER] -WoW Addon Location = ./addons -Addon List File = in.txt +WoW Addon Location = C:\Program Files (x86)\World of Warcraft\_retail_\Interface\AddOns +Addon List File = addons.txt Installed Versions File = installed.ini # retail or classic Game Version = retail From 9366fd278765c3832e5b9494c11c7b2f319e8586 Mon Sep 17 00:00:00 2001 From: amas Date: Tue, 1 Sep 2020 16:09:22 -0400 Subject: [PATCH 09/38] Add curse version parsing logic In order to facilitate the ability to follow alpha/beta releases of addons, we need to source the latest releases from the /files endpoint of a given addon on curseforge. The `versions()` generator and `CurseAddonVersion` object provide abstractions for iterating through the list of all releases alpha/beta/release for any given GameVersion. Note, the `game_version_filter` string is used in the URL params to effectively select from the dropdown the appropriate game version when searching for the release list. Currently `get_latest_version` is broken due to searching for the wrong version string. --- updater/site/curse.py | 79 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 67 insertions(+), 12 deletions(-) diff --git a/updater/site/curse.py b/updater/site/curse.py index 15444c8..0cc0eae 100644 --- a/updater/site/curse.py +++ b/updater/site/curse.py @@ -1,6 +1,10 @@ import logging import re +from dataclasses import dataclass +from datetime import datetime +from typing import Generator +import bs4 import cloudscraper from updater.site import CURSE_UA @@ -10,6 +14,48 @@ logger = logging.getLogger(__name__) +@dataclass +class CurseAddonVersion: + type: str + name: str + size: str + uploaded: str + game_version: str + downloads: int + download_link: str + + @classmethod + def from_tr(cls, tr: bs4.element.Tag): + cells = tr.find_all('td') + name = cells[1].text.strip() + size = cells[2].text.strip() + uploaded = datetime.fromtimestamp(int(cells[3].find('abbr').attrs.get('data-epoch'))).isoformat() + game_version = cells[4].text.strip() + downloads = int(cells[5].text.replace(',', '').strip()) + + return cls(type=cls.get_type(cells[0]), name=name, size=size, + uploaded=uploaded, game_version=game_version, downloads=downloads, + download_link=cls.get_link(cells[6])) + + @staticmethod + def get_type(td: bs4.element.Tag) -> str: + class_fields = td.find('div').attrs.get('class') + bg_field = next(field for field in class_fields if field.startswith('bg-')) + if 'blue' in bg_field: + return 'beta' + elif 'green' in bg_field: + return 'release' + elif 'offset' in bg_field: + return 'alpha' + else: + raise ValueError + + @staticmethod + def get_link(td: bs4.element.Tag) -> str: + relative_link = td.find('a').attrs.get('href') + return f'https://curseforge.com{relative_link}/file' + + class Curse(AbstractSite): _OLD_URL = 'https://mods.curse.com/addons/wow/' _OLD_PROJECT_URL = 'https://wow.curseforge.com/projects/' @@ -41,22 +87,31 @@ def find_zip_url(self): except Exception as e: raise self.download_error() from e - def get_latest_version(self): + def versions(self, *, page=1) -> Generator[CurseAddonVersion, None, None]: + if self.game_version == GameVersion.classic: + game_version_filter = '1738749986:67408' + elif self.game_version == GameVersion.classic: + game_version_filter = '1738749986:517' + else: + game_version_filter = '' + request_params = {'filter-game-version': game_version_filter, 'page': page} try: - page = Curse.session.get(self.url) - if page.status_code in [403, 503]: - logger.error("Curse is blocking requests because it thinks you are a bot... please try later.") - page.raise_for_status() # Raise an exception for HTTP errors - content_string = str(page.content) - # the first one encountered will be the WoW retail version - main_version, *classic_version = re.findall( - r"cf-recentfiles.+?data-id=.+?data-name=\"(?P.+?)\"", - content_string) - # if classic, choose the explicit "classic version" listed, or fall back to the only version available - return classic_version[-1] if self.game_version is GameVersion.classic and classic_version else main_version + p = Curse.session.get(f'{self.url}/files/all', params=request_params) + soup = bs4.BeautifulSoup(p.text, 'html.parser') + versions_table = soup.find('table', {'class': 'listing listing-project-file project-file-listing b-table b-table-a'}) + _, *version_rows = versions_table.find_all('tr') + yield from (CurseAddonVersion.from_tr(row) for row in version_rows) + pages_exist = soup.find('div', {'class': 'pagination pagination-top flex items-center'}) + inactive_next_page = soup.find('div', {'class': 'pagination-next h-6 w-6 flex items-center justify-center pagination-next--inactive'}) + if pages_exist and not inactive_next_page: + yield from self.versions(page=page+1) except Exception as e: raise self.version_error() from e + def get_latest_version(self): + latest_release = next(version for version in self.versions() if version.type == 'release') + return latest_release.game_version + @classmethod def _convert_old_curse_urls(cls, url: str) -> str: if any(old_url in url for old_url in [Curse._OLD_URL, Curse._OLD_PROJECT_URL]): From 294e4ac5ded11704461d517f23064a6bc2f67866 Mon Sep 17 00:00:00 2001 From: amas Date: Tue, 1 Sep 2020 23:31:18 -0400 Subject: [PATCH 10/38] Fix `get_latest_version` The previous logic for `get_latest_version` simply pulled the most recent release from the main page of the addon. The string used to mark the latest_release was the name associated with the addon release. The `CurseAddonVersion` object simply returns `version.name` to comply with this. --- updater/site/curse.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/updater/site/curse.py b/updater/site/curse.py index 0cc0eae..8d46b6b 100644 --- a/updater/site/curse.py +++ b/updater/site/curse.py @@ -109,8 +109,8 @@ def versions(self, *, page=1) -> Generator[CurseAddonVersion, None, None]: raise self.version_error() from e def get_latest_version(self): - latest_release = next(version for version in self.versions() if version.type == 'release') - return latest_release.game_version + latest_release = next(version.name for version in self.versions() if version.type == 'release') + return latest_release @classmethod def _convert_old_curse_urls(cls, url: str) -> str: From 56c1783c34ccae87121726d81d480d3899437592 Mon Sep 17 00:00:00 2001 From: amas Date: Wed, 2 Sep 2020 19:34:01 -0400 Subject: [PATCH 11/38] Fix bug in class vs retail version filtering --- updater/site/curse.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/updater/site/curse.py b/updater/site/curse.py index 8d46b6b..fbb883b 100644 --- a/updater/site/curse.py +++ b/updater/site/curse.py @@ -90,15 +90,16 @@ def find_zip_url(self): def versions(self, *, page=1) -> Generator[CurseAddonVersion, None, None]: if self.game_version == GameVersion.classic: game_version_filter = '1738749986:67408' - elif self.game_version == GameVersion.classic: + elif self.game_version == GameVersion.retail: game_version_filter = '1738749986:517' - else: + else: # Agnostic version game_version_filter = '' request_params = {'filter-game-version': game_version_filter, 'page': page} try: p = Curse.session.get(f'{self.url}/files/all', params=request_params) soup = bs4.BeautifulSoup(p.text, 'html.parser') versions_table = soup.find('table', {'class': 'listing listing-project-file project-file-listing b-table b-table-a'}) + # Header row consumed by _ _, *version_rows = versions_table.find_all('tr') yield from (CurseAddonVersion.from_tr(row) for row in version_rows) pages_exist = soup.find('div', {'class': 'pagination pagination-top flex items-center'}) From b9fe4cc0e147f852ab9d59fc293df6a030909dd4 Mon Sep 17 00:00:00 2001 From: Eric Black <302387+elebertus@users.noreply.github.com> Date: Wed, 2 Sep 2020 23:13:49 -0700 Subject: [PATCH 12/38] Update setup.py ^^ ty @grrttedwards Co-authored-by: Garrett Edwards --- setup.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/setup.py b/setup.py index 4c2fea7..48ba5ba 100644 --- a/setup.py +++ b/setup.py @@ -10,6 +10,12 @@ ), author="Garrett Edwards", url="https://github.com/grrttedwards/wow-addon-manager", + license="GPLv3", + classifiers=[ + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Programming Language :: Python :: 3 :: Only", + "Operating System :: OS Independent", + ], packages=["updater", "updater.manager", "updater.site"], entry_points={"console_scripts": ["wow-addon-updater = updater.__main__:main"],}, extras_require={ From 186cbee59a7f934a6b010f77817da153206043b4 Mon Sep 17 00:00:00 2001 From: Eric Black <302387+elebertus@users.noreply.github.com> Date: Wed, 2 Sep 2020 23:15:46 -0700 Subject: [PATCH 13/38] Update README.md pulling in the readme changes from @grrttedwards Co-authored-by: Garrett Edwards --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index eb21994..8270d36 100644 --- a/README.md +++ b/README.md @@ -59,9 +59,9 @@ pipenv shell # created by `pipenv install` but to be sure you can check with `which python` and # verify that it's $VIRTUAL_ENV/bin/python -# Using the distutils method +# Either using the distutils method python setup.py install -# Using pip +# Or using pip pip install . # Make sure the command is where we expect which wow-addon-updater From a0247c92315dcdb4481ecc0ff50b540a45b32d38 Mon Sep 17 00:00:00 2001 From: Elebertus Date: Wed, 2 Sep 2020 23:23:39 -0700 Subject: [PATCH 14/38] adding additional ignore paths, adding python version constraint to setup.py --- .gitignore | 5 +++++ setup.py | 1 + 2 files changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index c4e6139..580ec1f 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,8 @@ desktop.ini \.coverage *.log + +# Python setuptools cache +build/ +dist/ +wow_addon_updater.egg-info/ \ No newline at end of file diff --git a/setup.py b/setup.py index 4c2fea7..50f9c91 100644 --- a/setup.py +++ b/setup.py @@ -60,4 +60,5 @@ "soupsieve==2.0.1; python_version >= '3.5'", "urllib3==1.25.10; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", ], + python_requires=">=3.8.0", ) From 21854095dfd5b3770928d3610e47659764279300 Mon Sep 17 00:00:00 2001 From: amas Date: Thu, 3 Sep 2020 13:12:23 -0400 Subject: [PATCH 15/38] Add logic to track alpha/beta/release versions Includes the `AddonVersion` enum to provide an ordering of release precedent and the `Curse` object takes an element of this enum when being instantiated. Defaults to tracking "release" versions of the addon. The important logic falls in the `get_latest_version` function where it selects the addon the meets or exceeds the desired track. Tracking `AddonVersion.alpha` will always grab the latest version, whereas `AddonVersion.beta` will ignore alpha releases and so on. --- updater/site/curse.py | 33 ++++++++++++++++++++++++--------- updater/site/enum.py | 8 +++++++- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/updater/site/curse.py b/updater/site/curse.py index fbb883b..1b56fa3 100644 --- a/updater/site/curse.py +++ b/updater/site/curse.py @@ -9,14 +9,14 @@ from updater.site import CURSE_UA from updater.site.abstract_site import AbstractSite, SiteError -from updater.site.enum import GameVersion +from updater.site.enum import AddonVersion, GameVersion logger = logging.getLogger(__name__) @dataclass class CurseAddonVersion: - type: str + type: AddonVersion name: str size: str uploaded: str @@ -38,15 +38,15 @@ def from_tr(cls, tr: bs4.element.Tag): download_link=cls.get_link(cells[6])) @staticmethod - def get_type(td: bs4.element.Tag) -> str: + def get_type(td: bs4.element.Tag) -> AddonVersion: class_fields = td.find('div').attrs.get('class') bg_field = next(field for field in class_fields if field.startswith('bg-')) if 'blue' in bg_field: - return 'beta' + return AddonVersion.beta elif 'green' in bg_field: - return 'release' + return AddonVersion.release elif 'offset' in bg_field: - return 'alpha' + return AddonVersion.alpha else: raise ValueError @@ -69,9 +69,10 @@ class Curse(AbstractSite): session = cloudscraper.create_scraper(browser=CURSE_UA) - def __init__(self, url: str, game_version: GameVersion): + def __init__(self, url: str, game_version: GameVersion, addon_version: AddonVersion = AddonVersion.release): url = Curse._convert_old_curse_urls(url) super().__init__(url, game_version) + self.addon_version = addon_version def find_zip_url(self): try: @@ -88,6 +89,11 @@ def find_zip_url(self): raise self.download_error() from e def versions(self, *, page=1) -> Generator[CurseAddonVersion, None, None]: + """Yields a sequence of CurseAddonVersions corresponding to addon releases + + Ordered descending in time, so the first version yielded is the most recent. + Will page through until exhausted. + """ if self.game_version == GameVersion.classic: game_version_filter = '1738749986:67408' elif self.game_version == GameVersion.retail: @@ -109,8 +115,17 @@ def versions(self, *, page=1) -> Generator[CurseAddonVersion, None, None]: except Exception as e: raise self.version_error() from e - def get_latest_version(self): - latest_release = next(version.name for version in self.versions() if version.type == 'release') + def get_latest_version(self) -> str: + """Returns the latest version released for retail/classic + + The `version.type >= self.addon_version` logic chooses the most recent + addon according to the ordering that release > beta > alpha. So if you + are following the beta track and a new alpha version is release, you won't + get it, but a new release version you will. + + Returns the name of the most recent release. + """ + latest_release = next(version.name for version in self.versions() if version.type >= self.addon_version) return latest_release @classmethod diff --git a/updater/site/enum.py b/updater/site/enum.py index 942e59a..714e463 100644 --- a/updater/site/enum.py +++ b/updater/site/enum.py @@ -1,7 +1,13 @@ -from enum import auto, Enum +from enum import auto, Enum, IntEnum class GameVersion(Enum): agnostic = auto() retail = auto() classic = auto() + + +class AddonVersion(IntEnum): + release = 3 + beta = 2 + alpha = 1 From 1403bd4ad0d3a3dff597526f73dfadca668fa8b0 Mon Sep 17 00:00:00 2001 From: amas Date: Thu, 3 Sep 2020 13:24:57 -0400 Subject: [PATCH 16/38] Refactor `find_zip_url` to be compatible with `versions()` --- updater/site/curse.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/updater/site/curse.py b/updater/site/curse.py index 1b56fa3..2e7d832 100644 --- a/updater/site/curse.py +++ b/updater/site/curse.py @@ -53,7 +53,7 @@ def get_type(td: bs4.element.Tag) -> AddonVersion: @staticmethod def get_link(td: bs4.element.Tag) -> str: relative_link = td.find('a').attrs.get('href') - return f'https://curseforge.com{relative_link}/file' + return f'https://www.curseforge.com{relative_link}/file' class Curse(AbstractSite): @@ -76,15 +76,8 @@ def __init__(self, url: str, game_version: GameVersion, addon_version: AddonVers def find_zip_url(self): try: - page = Curse.session.get(self.url) - page.raise_for_status() # Raise an exception for HTTP errors - content_string = str(page.content) - main_zip_url, *classic_zip_url = re.findall( - r"cf-recentfiles-credits-wrapper ml-auto my-auto.+?href=\"(?P.+?)\"", - content_string) - # if classic, choose the explicit "classic download" listed, or fall back to the only download available - zip_url = classic_zip_url[-1] if self.game_version is GameVersion.classic and classic_zip_url else main_zip_url - return f'https://www.curseforge.com{zip_url}/file' + latest_release = next(version for version in self.versions() if version.type >= self.addon_version) + return latest_release.download_link except Exception as e: raise self.download_error() from e @@ -125,8 +118,8 @@ def get_latest_version(self) -> str: Returns the name of the most recent release. """ - latest_release = next(version.name for version in self.versions() if version.type >= self.addon_version) - return latest_release + latest_release = next(version for version in self.versions() if version.type >= self.addon_version) + return latest_release.name @classmethod def _convert_old_curse_urls(cls, url: str) -> str: From 58869df6827a2323f720dfb5480ca7cf762759fc Mon Sep 17 00:00:00 2001 From: amas Date: Thu, 3 Sep 2020 13:31:55 -0400 Subject: [PATCH 17/38] Add clarification comment --- updater/site/curse.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/updater/site/curse.py b/updater/site/curse.py index 2e7d832..8e2bb75 100644 --- a/updater/site/curse.py +++ b/updater/site/curse.py @@ -101,6 +101,8 @@ def versions(self, *, page=1) -> Generator[CurseAddonVersion, None, None]: # Header row consumed by _ _, *version_rows = versions_table.find_all('tr') yield from (CurseAddonVersion.from_tr(row) for row in version_rows) + + # determine if there are more pages of versions, recurse if so pages_exist = soup.find('div', {'class': 'pagination pagination-top flex items-center'}) inactive_next_page = soup.find('div', {'class': 'pagination-next h-6 w-6 flex items-center justify-center pagination-next--inactive'}) if pages_exist and not inactive_next_page: From 0c596d4de019ea15ae987a7638cbcfbcec654e30 Mon Sep 17 00:00:00 2001 From: amas Date: Thu, 3 Sep 2020 13:34:31 -0400 Subject: [PATCH 18/38] Remove unused re import --- updater/site/curse.py | 1 - 1 file changed, 1 deletion(-) diff --git a/updater/site/curse.py b/updater/site/curse.py index 8e2bb75..f1d3fd7 100644 --- a/updater/site/curse.py +++ b/updater/site/curse.py @@ -1,5 +1,4 @@ import logging -import re from dataclasses import dataclass from datetime import datetime from typing import Generator From 4a75a1734abe11e946e2010d96287ba761858574 Mon Sep 17 00:00:00 2001 From: amas Date: Fri, 4 Sep 2020 14:44:00 -0400 Subject: [PATCH 19/38] Refactor test_curse for explicit version testing Previous testing would test all GameVersions for each of the test addons. Classiccodex was one such addon that only supports classic, but would pass in unit testing for retail under the previous master branch. Changes to the underlying Curse scraping in order to support alpha/beta released broke this test and discovered the bug. This change in testing will only test Classiccodex for `GameVersion.classic` and `GameVersion.agnostic`, which it should support just fine. --- test/site/test_curse.py | 42 +++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/test/site/test_curse.py b/test/site/test_curse.py index 41419de..bc00349 100644 --- a/test/site/test_curse.py +++ b/test/site/test_curse.py @@ -1,14 +1,36 @@ import unittest +from dataclasses import dataclass +from typing import Collection from updater.site import curse from updater.site.enum import GameVersion + +@dataclass +class VersionTestData: + url: str + version_regex: str + supported_game_versions: Collection[GameVersion] + + +ALL_VERSIONS = (GameVersion.classic, GameVersion.retail, GameVersion.agnostic) + version_test_data = [ - ['https://www.curseforge.com/wow/addons/classiccodex', r'[0-9]+\.[0-9]+\.[0-9]+'], - ['https://www.curseforge.com/wow/addons/bartender4', r"[0-9]+\.[0-9]+\.[0-9]+"], - ['https://www.curseforge.com/wow/addons/big-wigs', r"v[0-9]+"], - ['https://www.curseforge.com/wow/addons/deadly-boss-mods', r"[0-9]+\.[0-9]+\.[0-9]+"], - ['https://www.curseforge.com/wow/addons/weakauras-2', r"[0-9]+\.[0-9]+\.[0-9]+"] + VersionTestData(url='https://www.curseforge.com/wow/addons/classiccodex', + version_regex=r'[0-9]+\.[0-9]+\.[0-9]+', + supported_game_versions=(GameVersion.classic, GameVersion.agnostic)), + VersionTestData(url='https://www.curseforge.com/wow/addons/bartender4', + version_regex=r"[0-9]+\.[0-9]+\.[0-9]+", + supported_game_versions=ALL_VERSIONS), + VersionTestData(url='https://www.curseforge.com/wow/addons/big-wigs', + version_regex=r"v[0-9]+", + supported_game_versions=ALL_VERSIONS), + VersionTestData(url='https://www.curseforge.com/wow/addons/deadly-boss-mods', + version_regex=r"[0-9]+\.[0-9]+\.[0-9]+", + supported_game_versions=ALL_VERSIONS), + VersionTestData(url='https://www.curseforge.com/wow/addons/weakauras-2', + version_regex=r"[0-9]+\.[0-9]+\.[0-9]+", + supported_game_versions=ALL_VERSIONS) ] @@ -30,13 +52,13 @@ def test_integration_curse_get_addon_name(self): self.assertEqual(addon_name, 'bartender4') def test_integration_curse_get_latest_version(self): - for url, version_regex in version_test_data: - for game_version in GameVersion.__members__.values(): - with self.subTest((game_version, url, version_regex)): - c = curse.Curse(url, game_version) + for vtd in version_test_data: + for game_version in vtd.supported_game_versions: + with self.subTest((game_version, vtd.url, vtd.version_regex)): + c = curse.Curse(vtd.url, game_version) latest_version = c.get_latest_version() # something like 4.5.6, or v163 - self.assertRegex(latest_version, version_regex) + self.assertRegex(latest_version, vtd.version_regex) def test_curse_get_supported_urls(self): supported_urls = self.curse.get_supported_urls() From 9c5b212ab09019e1aa353c4c773033bdafaffd63 Mon Sep 17 00:00:00 2001 From: amas Date: Fri, 4 Sep 2020 14:50:00 -0400 Subject: [PATCH 20/38] Add test for unsupported GameVersion exception --- test/site/test_curse.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/site/test_curse.py b/test/site/test_curse.py index bc00349..1c76772 100644 --- a/test/site/test_curse.py +++ b/test/site/test_curse.py @@ -65,6 +65,13 @@ def test_curse_get_supported_urls(self): self.assertIsNotNone(supported_urls) self.assertTrue(len(supported_urls) != 0) + def test_curse_unsupported_game_version(self): + # classiccodex should throw an version error when searching for an + # unsupported retail game version + classiccodex = version_test_data[0] + c = curse.Curse(classiccodex.url, GameVersion.retail) + self.assertRaises(curse.SiteError, c.get_latest_version) + if __name__ == '__main__': unittest.main() From f87f2298bd2702f0b23ba77ee6ec171ae988280c Mon Sep 17 00:00:00 2001 From: amas Date: Fri, 4 Sep 2020 16:02:03 -0400 Subject: [PATCH 21/38] Update CurseAddonVersion name parsing Some addons on curse have multiple files associated with a release and need additional parsing of the file name field to get the appropriate version. See the simulationcraft addon page for and example. --- updater/site/curse.py | 1 + 1 file changed, 1 insertion(+) diff --git a/updater/site/curse.py b/updater/site/curse.py index f1d3fd7..c6415e2 100644 --- a/updater/site/curse.py +++ b/updater/site/curse.py @@ -27,6 +27,7 @@ class CurseAddonVersion: def from_tr(cls, tr: bs4.element.Tag): cells = tr.find_all('td') name = cells[1].text.strip() + name = name if '\n' not in name else name.split('\n')[0] size = cells[2].text.strip() uploaded = datetime.fromtimestamp(int(cells[3].find('abbr').attrs.get('data-epoch'))).isoformat() game_version = cells[4].text.strip() From 88f12bdcb008aaef3efb959fd88dcfa444d29796 Mon Sep 17 00:00:00 2001 From: amas Date: Fri, 4 Sep 2020 16:30:11 -0400 Subject: [PATCH 22/38] Add testing of Curse alpha/beta/release tracks Unit test in `test_curse` uses a modified html page stored in `resources` to check the behavior of setting different possible AddonVersions when creating the Curse object. --- .../mock-curse-version-addons-simc.html | 1833 +++++++++++++++++ test/site/test_curse.py | 24 +- 2 files changed, 1856 insertions(+), 1 deletion(-) create mode 100644 test/resources/mock-curse-version-addons-simc.html diff --git a/test/resources/mock-curse-version-addons-simc.html b/test/resources/mock-curse-version-addons-simc.html new file mode 100644 index 0000000..17099f8 --- /dev/null +++ b/test/resources/mock-curse-version-addons-simc.html @@ -0,0 +1,1833 @@ + + + + + + + + + + + + + + Files - Simulationcraft - Addons - World of Warcraft - CurseForge + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+ + +
+
+
+ +
+
+
+ + +
+
+
+

Simulationcraft

+
+ + Addons + +
+ 11,795,320 Downloads + Last Updated: Aug 26, 2020 + Game Version: 8.3.0 +
+
+
+
+
+ + + + + + + + +
+
+
+
+ +
+
+
+
+
+ + + +
+
+

All Files

+
+
+
+
+
+ + + + + +
+ +
+ +
+
+ +
+ + + +
+ +
+
+ + +
+ +
+ +
+ +
+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Type + + Name + + Size + + Uploaded + + Game Version + + Downloads + +Actions
+
+ A +
+
+ + v9.0.1-alpha-8 + ++1 More + 82.19 KB + + Aug 26, 2020 + +
+
+9.0.1
+
+
+ 597 + + +
+
+ B +
+
+ + v1.12.0-beta-2 + + +1 More + 81.36 KB + + May 12, 2019 + +
+
+ 8.1.5
+
+
+ 2,319 + + +
+
+ R +
+
+ + v1.12.5 + ++1 More + 81.48 KB + + Jan 24, 2020 + +
+
+8.3.0
+
+
+ 1,023,344 + + +
+
+ R +
+
+ + v1.12.4 + ++1 More + 81.31 KB + + Jan 5, 2020 + +
+
+8.3.0
+
+
+ 356,050 + + +
+
+ R +
+
+ + v1.12.3 + ++1 More + 81.30 KB + + Sep 28, 2019 + +
+
+8.2.5
+
+
+ 546,565 + + +
+
+ R +
+
+ + v1.12.2 + ++1 More + 81.25 KB + + Jul 16, 2019 + +
+
+8.2.0
+
+
+ 583,243 + + +
+
+ A +
+
+ + v1.12.2-alpha-1 + ++1 More + 81.35 KB + + Jul 16, 2019 + +
+
+8.2.0
+
+
+ 61 + + +
+
+ R +
+
+ + v1.12.1 + ++1 More + 81.52 KB + + Jul 1, 2019 + +
+
+8.2.0
+
+
+ 346,136 + + +
+
+ A +
+
+ + v1.12.1-alpha-1 + ++1 More + 81.45 KB + + Jul 1, 2019 + +
+
+8.2.0
+
+
+ 19 + + +
+
+ R +
+
+ + v1.12.0 + ++1 More + 81.30 KB + + May 28, 2019 + +
+
+8.1.5
+
+
+ 379,272 + + +
+
+ B +
+
+ + v1.12.0-beta-1 + ++1 More + 81.47 KB + + May 10, 2019 + +
+
+8.1.5
+
+
+ 1,263 + + +
+
+ A +
+
+ + v1.12.0-alpha-2 + ++1 More + 81.50 KB + + May 8, 2019 + +
+
+8.1.5
+
+
+ 738 + + +
+
+ A +
+
+ + v1.12.0-alpha-1 + ++1 More + 81.32 KB + + Apr 17, 2019 + +
+
+8.1.5
+
+
+ 1,427 + + +
+
+ R +
+
+ + v1.11.1 + ++1 More + 80.66 KB + + Apr 3, 2019 + +
+
+8.1.5
+
+
+ 432,861 + + +
+
+ R +
+
+ + v1.11.0 + ++1 More + 80.65 KB + + Mar 18, 2019 + +
+
+8.1.5
+
+
+ 324,703 + + +
+
+ A +
+
+ + v1.11.0-alpha-1 + ++1 More + 81.08 KB + + Mar 13, 2019 + +
+
+8.1.5
+
+
+ 1,341 + + +
+
+ R +
+
+ + v1.10.11 + ++1 More + 80.64 KB + + Feb 25, 2019 + +
+
+8.1.0
+
+
+ 378,095 + + +
+
+ R +
+
+ + v1.10.10 + ++1 More + 79.15 KB + + Jan 4, 2019 + +
+
+8.1.0
+
+
+ 480,918 + + +
+
+
+
+ + + + + + + +
+
+
+ +
+ +
+
+ +
+
+
+ +
+ + + + + + + + +
+
+ + + +
+ + + + + + + + + + + + + + +
+
+ + + + + + diff --git a/test/site/test_curse.py b/test/site/test_curse.py index 1c76772..c0d2e32 100644 --- a/test/site/test_curse.py +++ b/test/site/test_curse.py @@ -1,9 +1,11 @@ import unittest from dataclasses import dataclass from typing import Collection +from unittest.mock import MagicMock, patch from updater.site import curse -from updater.site.enum import GameVersion +from updater.site.enum import AddonVersion, GameVersion +from test.testutils import get_file @dataclass @@ -33,6 +35,10 @@ class VersionTestData: supported_game_versions=ALL_VERSIONS) ] +MOCK_VERSION_PAGE = MagicMock() +with open(get_file('mock-curse-version-addons-simc.html'), 'r') as f: + MOCK_VERSION_PAGE.text = f.read() + class TestCurse(unittest.TestCase): def setUp(self): @@ -72,6 +78,22 @@ def test_curse_unsupported_game_version(self): c = curse.Curse(classiccodex.url, GameVersion.retail) self.assertRaises(curse.SiteError, c.get_latest_version) + @patch.object(curse.Curse.session, 'get', MagicMock(return_value=MOCK_VERSION_PAGE)) + def test_curse_versions_parsing(self): + # The curse object is a throwaway here + _ = version_test_data[1] + d = curse.Curse(_.url, GameVersion.retail, AddonVersion.release) + # This mock pulls a static example of a curse addon files page for testing + expected_results = [ + (AddonVersion.release, 'v1.12.5'), + (AddonVersion.beta, 'v1.12.0-beta-2'), + (AddonVersion.alpha, 'v9.0.1-alpha-8') + ] + for addon_version, expected_value in expected_results: + with self.subTest((addon_version, expected_value)): + d.addon_version = addon_version + self.assertEqual(d.get_latest_version(), expected_value) + if __name__ == '__main__': unittest.main() From b9ceda6e2acb16a162936e0252f0b78b146e2ece Mon Sep 17 00:00:00 2001 From: amas Date: Fri, 4 Sep 2020 17:01:20 -0400 Subject: [PATCH 23/38] Add AddonManager support for alpha/beta releases --- updater/manager/addon_manager.py | 23 ++++++++++++++++++++--- updater/site/site_handler.py | 6 +++--- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/updater/manager/addon_manager.py b/updater/manager/addon_manager.py index 4057cea..5e4ddd8 100644 --- a/updater/manager/addon_manager.py +++ b/updater/manager/addon_manager.py @@ -8,13 +8,14 @@ from io import BytesIO from multiprocessing.pool import ThreadPool from os.path import isfile, isdir, join +from typing import List import requests from requests import HTTPError from updater.site import site_handler, github, tukui from updater.site.abstract_site import SiteError, AbstractSite -from updater.site.enum import GameVersion +from updater.site.enum import AddonVersion, GameVersion logger = logging.getLogger(__name__) @@ -78,10 +79,13 @@ def update_all(self): self.explain_curse_error() def update_addon(self, addon_entry): - # Expected format: "mydomain.com/myaddon" or "mydomain.com/myaddon|subfolder" + # Expected format: "mydomain.com/myaddon" or "mydomain.com/myaddon|subfolder [version_track]" + addon_entry, *addon_version_track = addon_entry.split(' ') addon_url, *subfolder = addon_entry.split('|') - site = site_handler.get_handler(addon_url, self.game_version) + addon_version_track = self.validate_addon_version_track(addon_version_track) + + site = site_handler.get_handler(addon_url, self.game_version, addon_version_track) try: addon_name = site.get_addon_name() @@ -199,3 +203,16 @@ def explain_curse_error(self): ]) logger.info('\n\n' + message) return + + @staticmethod + def validate_addon_version_track(version_track: List[str]) -> AddonVersion: + if not version_track: # Fallback to release version if omitted + return AddonVersion.release + + cleaned = version_track[0].strip().lower() + if cleaned == 'alpha': + return AddonVersion.alpha + elif cleaned == 'beta': + return AddonVersion.beta + else: + return AddonVersion.release diff --git a/updater/site/site_handler.py b/updater/site/site_handler.py index f1a4fd8..403116b 100644 --- a/updater/site/site_handler.py +++ b/updater/site/site_handler.py @@ -1,6 +1,6 @@ from updater.site.abstract_site import AbstractSite from updater.site.curse import Curse -from updater.site.enum import GameVersion +from updater.site.enum import AddonVersion, GameVersion from updater.site.github import GitHub from updater.site.tukui import Tukui from updater.site.wowace import WoWAce @@ -11,9 +11,9 @@ class UnknownSiteError(RuntimeError): pass -def get_handler(url: str, game_version: GameVersion) -> AbstractSite: +def get_handler(url: str, game_version: GameVersion, addon_version: AddonVersion) -> AbstractSite: if Curse.handles(url): - return Curse(url, game_version) + return Curse(url, game_version, addon_version) elif WoWAce.handles(url): return WoWAce(url, game_version) elif Tukui.handles(url): From 3ce1025b89a4e623e4ebdac89c27de69956e4bd0 Mon Sep 17 00:00:00 2001 From: Garrett Edwards Date: Fri, 4 Sep 2020 22:16:31 -0400 Subject: [PATCH 24/38] Update Pipfile.lock after adding pipenv-setup --- Pipfile.lock | 379 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 324 insertions(+), 55 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index b2e2983..7036f67 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "c22970e73afa8f578987c2f6f32828473e7deca80cc94fafd445bb18cb34446f" + "sha256": "6c5fa1e761787cfdfa02dc9cd6388af7aaa8c81073f1b005e5caf32c50f35ca8" }, "pipfile-spec": 6, "requires": {}, @@ -16,19 +16,19 @@ "default": { "beautifulsoup4": { "hashes": [ - "sha256:594ca51a10d2b3443cbac41214e12dbb2a1cd57e1a7344659849e2e20ba6a8d8", - "sha256:a4bbe77fd30670455c5296242967a123ec28c37e9702a8a81bd2f20a4baf0368", - "sha256:d4e96ac9b0c3a6d3f0caae2e4124e6055c5dcafde8e2f831ff194c104f0775a0" + "sha256:73cc4d115b96f79c7d77c1c7f7a0a8d4c57860d1041df407dd1aae7f07a77fd7", + "sha256:a6237df3c32ccfaee4fd201c8f5f9d9df619b93121d01353a64a73ce8c6ef9a8", + "sha256:e718f2342e2e099b640a34ab782407b7b676f47ee272d6739e60b8ea23829f2c" ], "index": "pypi", - "version": "==4.9.0" + "version": "==4.9.1" }, "certifi": { "hashes": [ - "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304", - "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519" + "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3", + "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41" ], - "version": "==2020.4.5.1" + "version": "==2020.6.20" }, "chardet": { "hashes": [ @@ -39,26 +39,33 @@ }, "cloudscraper": { "hashes": [ - "sha256:478d3cf2044d6b70a8e4f436afff4a8f74cb730cc34841cc9411c80d41d5e9e3", - "sha256:50a131a9bfc1969b01d48eecb4ecc1acd92ee992be31c0bee4f1f31ac3cbae8f" + "sha256:793095bbc37aae84a5c38d9f66b56f3c83a00f0b1ff2d334f75a2b8f88b924af", + "sha256:a7ae28b5dc4e4ef0789ef608c42a4382bf6d3e3dd6b8117c2e3633103131ae43" ], "index": "pypi", - "version": "==1.2.34" + "version": "==1.2.46" }, "idna": { "hashes": [ - "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", - "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" + "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", + "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" ], - "version": "==2.9" + "version": "==2.10" + }, + "pyparsing": { + "hashes": [ + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + ], + "version": "==2.4.7" }, "requests": { "hashes": [ - "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", - "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" + "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b", + "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898" ], "index": "pypi", - "version": "==2.23.0" + "version": "==2.24.0" }, "requests-toolbelt": { "hashes": [ @@ -69,56 +76,318 @@ }, "soupsieve": { "hashes": [ - "sha256:e914534802d7ffd233242b785229d5ba0766a7f487385e3f714446a07bf540ae", - "sha256:fcd71e08c0aee99aca1b73f45478549ee7e7fc006d51b37bec9e9def7dc22b69" + "sha256:1634eea42ab371d3d346309b93df7870a88610f0725d47528be902a0d95ecc55", + "sha256:a59dc181727e95d25f781f0eb4fd1825ff45590ec8ff49eadfd7f1a537cc0232" ], - "version": "==2.0" + "version": "==2.0.1" }, "urllib3": { "hashes": [ - "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527", - "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115" + "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a", + "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461" ], - "version": "==1.25.9" + "version": "==1.25.10" } }, "develop": { + "appdirs": { + "hashes": [ + "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", + "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" + ], + "version": "==1.4.4" + }, + "attrs": { + "hashes": [ + "sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a", + "sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff" + ], + "version": "==20.1.0" + }, + "black": { + "hashes": [ + "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b", + "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539" + ], + "markers": "python_version >= '3.6'", + "version": "==19.10b0" + }, + "cached-property": { + "hashes": [ + "sha256:3a026f1a54135677e7da5ce819b0c690f156f37976f3e30c5430740725203d7f", + "sha256:9217a59f14a5682da7c4b8829deadbfc194ac22e9908ccf7c8820234e80a1504" + ], + "version": "==1.5.1" + }, + "cerberus": { + "hashes": [ + "sha256:302e6694f206dd85cb63f13fd5025b31ab6d38c99c50c6d769f8fa0b0f299589" + ], + "version": "==1.3.2" + }, + "certifi": { + "hashes": [ + "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3", + "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41" + ], + "version": "==2020.6.20" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "click": { + "hashes": [ + "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", + "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" + ], + "version": "==7.1.2" + }, + "colorama": { + "hashes": [ + "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", + "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1" + ], + "version": "==0.4.3" + }, "coverage": { "hashes": [ - "sha256:00f1d23f4336efc3b311ed0d807feb45098fc86dee1ca13b3d6768cdab187c8a", - "sha256:01333e1bd22c59713ba8a79f088b3955946e293114479bbfc2e37d522be03355", - "sha256:0cb4be7e784dcdc050fc58ef05b71aa8e89b7e6636b99967fadbdba694cf2b65", - "sha256:0e61d9803d5851849c24f78227939c701ced6704f337cad0a91e0972c51c1ee7", - "sha256:1601e480b9b99697a570cea7ef749e88123c04b92d84cedaa01e117436b4a0a9", - "sha256:2742c7515b9eb368718cd091bad1a1b44135cc72468c731302b3d641895b83d1", - "sha256:2d27a3f742c98e5c6b461ee6ef7287400a1956c11421eb574d843d9ec1f772f0", - "sha256:402e1744733df483b93abbf209283898e9f0d67470707e3c7516d84f48524f55", - "sha256:5c542d1e62eece33c306d66fe0a5c4f7f7b3c08fecc46ead86d7916684b36d6c", - "sha256:5f2294dbf7875b991c381e3d5af2bcc3494d836affa52b809c91697449d0eda6", - "sha256:6402bd2fdedabbdb63a316308142597534ea8e1895f4e7d8bf7476c5e8751fef", - "sha256:66460ab1599d3cf894bb6baee8c684788819b71a5dc1e8fa2ecc152e5d752019", - "sha256:782caea581a6e9ff75eccda79287daefd1d2631cc09d642b6ee2d6da21fc0a4e", - "sha256:79a3cfd6346ce6c13145731d39db47b7a7b859c0272f02cdb89a3bdcbae233a0", - "sha256:7a5bdad4edec57b5fb8dae7d3ee58622d626fd3a0be0dfceda162a7035885ecf", - "sha256:8fa0cbc7ecad630e5b0f4f35b0f6ad419246b02bc750de7ac66db92667996d24", - "sha256:a027ef0492ede1e03a8054e3c37b8def89a1e3c471482e9f046906ba4f2aafd2", - "sha256:a3f3654d5734a3ece152636aad89f58afc9213c6520062db3978239db122f03c", - "sha256:a82b92b04a23d3c8a581fc049228bafde988abacba397d57ce95fe95e0338ab4", - "sha256:acf3763ed01af8410fc36afea23707d4ea58ba7e86a8ee915dfb9ceff9ef69d0", - "sha256:adeb4c5b608574a3d647011af36f7586811a2c1197c861aedb548dd2453b41cd", - "sha256:b83835506dfc185a319031cf853fa4bb1b3974b1f913f5bb1a0f3d98bdcded04", - "sha256:bb28a7245de68bf29f6fb199545d072d1036a1917dca17a1e75bbb919e14ee8e", - "sha256:bf9cb9a9fd8891e7efd2d44deb24b86d647394b9705b744ff6f8261e6f29a730", - "sha256:c317eaf5ff46a34305b202e73404f55f7389ef834b8dbf4da09b9b9b37f76dd2", - "sha256:dbe8c6ae7534b5b024296464f387d57c13caa942f6d8e6e0346f27e509f0f768", - "sha256:de807ae933cfb7f0c7d9d981a053772452217df2bf38e7e6267c9cbf9545a796", - "sha256:dead2ddede4c7ba6cb3a721870f5141c97dc7d85a079edb4bd8d88c3ad5b20c7", - "sha256:dec5202bfe6f672d4511086e125db035a52b00f1648d6407cc8e526912c0353a", - "sha256:e1ea316102ea1e1770724db01998d1603ed921c54a86a2efcb03428d5417e489", - "sha256:f90bfc4ad18450c80b024036eaf91e4a246ae287701aaa88eaebebf150868052" + "sha256:098a703d913be6fbd146a8c50cc76513d726b022d170e5e98dc56d958fd592fb", + "sha256:16042dc7f8e632e0dcd5206a5095ebd18cb1d005f4c89694f7f8aafd96dd43a3", + "sha256:1adb6be0dcef0cf9434619d3b892772fdb48e793300f9d762e480e043bd8e716", + "sha256:27ca5a2bc04d68f0776f2cdcb8bbd508bbe430a7bf9c02315cd05fb1d86d0034", + "sha256:28f42dc5172ebdc32622a2c3f7ead1b836cdbf253569ae5673f499e35db0bac3", + "sha256:2fcc8b58953d74d199a1a4d633df8146f0ac36c4e720b4a1997e9b6327af43a8", + "sha256:304fbe451698373dc6653772c72c5d5e883a4aadaf20343592a7abb2e643dae0", + "sha256:30bc103587e0d3df9e52cd9da1dd915265a22fad0b72afe54daf840c984b564f", + "sha256:40f70f81be4d34f8d491e55936904db5c527b0711b2a46513641a5729783c2e4", + "sha256:4186fc95c9febeab5681bc3248553d5ec8c2999b8424d4fc3a39c9cba5796962", + "sha256:46794c815e56f1431c66d81943fa90721bb858375fb36e5903697d5eef88627d", + "sha256:4869ab1c1ed33953bb2433ce7b894a28d724b7aa76c19b11e2878034a4e4680b", + "sha256:4f6428b55d2916a69f8d6453e48a505c07b2245653b0aa9f0dee38785939f5e4", + "sha256:52f185ffd3291196dc1aae506b42e178a592b0b60a8610b108e6ad892cfc1bb3", + "sha256:538f2fd5eb64366f37c97fdb3077d665fa946d2b6d95447622292f38407f9258", + "sha256:64c4f340338c68c463f1b56e3f2f0423f7b17ba6c3febae80b81f0e093077f59", + "sha256:675192fca634f0df69af3493a48224f211f8db4e84452b08d5fcebb9167adb01", + "sha256:700997b77cfab016533b3e7dbc03b71d33ee4df1d79f2463a318ca0263fc29dd", + "sha256:8505e614c983834239f865da2dd336dcf9d72776b951d5dfa5ac36b987726e1b", + "sha256:962c44070c281d86398aeb8f64e1bf37816a4dfc6f4c0f114756b14fc575621d", + "sha256:9e536783a5acee79a9b308be97d3952b662748c4037b6a24cbb339dc7ed8eb89", + "sha256:9ea749fd447ce7fb1ac71f7616371f04054d969d412d37611716721931e36efd", + "sha256:a34cb28e0747ea15e82d13e14de606747e9e484fb28d63c999483f5d5188e89b", + "sha256:a3ee9c793ffefe2944d3a2bd928a0e436cd0ac2d9e3723152d6fd5398838ce7d", + "sha256:aab75d99f3f2874733946a7648ce87a50019eb90baef931698f96b76b6769a46", + "sha256:b1ed2bdb27b4c9fc87058a1cb751c4df8752002143ed393899edb82b131e0546", + "sha256:b360d8fd88d2bad01cb953d81fd2edd4be539df7bfec41e8753fe9f4456a5082", + "sha256:b8f58c7db64d8f27078cbf2a4391af6aa4e4767cc08b37555c4ae064b8558d9b", + "sha256:c1bbb628ed5192124889b51204de27c575b3ffc05a5a91307e7640eff1d48da4", + "sha256:c2ff24df02a125b7b346c4c9078c8936da06964cc2d276292c357d64378158f8", + "sha256:c890728a93fffd0407d7d37c1e6083ff3f9f211c83b4316fae3778417eab9811", + "sha256:c96472b8ca5dc135fb0aa62f79b033f02aa434fb03a8b190600a5ae4102df1fd", + "sha256:ce7866f29d3025b5b34c2e944e66ebef0d92e4a4f2463f7266daa03a1332a651", + "sha256:e26c993bd4b220429d4ec8c1468eca445a4064a61c74ca08da7429af9bc53bb0" ], "index": "pypi", - "version": "==5.1" + "version": "==5.2.1" + }, + "distlib": { + "hashes": [ + "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb", + "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1" + ], + "version": "==0.3.1" + }, + "idna": { + "hashes": [ + "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", + "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" + ], + "version": "==2.10" + }, + "orderedmultidict": { + "hashes": [ + "sha256:04070bbb5e87291cc9bfa51df413677faf2141c73c61d2a5f7b26bea3cd882ad", + "sha256:43c839a17ee3cdd62234c47deca1a8508a3f2ca1d0678a3bf791c87cf84adbf3" + ], + "version": "==1.0.1" + }, + "packaging": { + "hashes": [ + "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", + "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" + ], + "version": "==20.4" + }, + "pathspec": { + "hashes": [ + "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0", + "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061" + ], + "version": "==0.8.0" + }, + "pep517": { + "hashes": [ + "sha256:576c480be81f3e1a70a16182c762311eb80d1f8a7b0d11971e5234967d7a342c", + "sha256:8e6199cf1288d48a0c44057f112acf18aa5ebabbf73faa242f598fbe145ba29e" + ], + "version": "==0.8.2" + }, + "pip-shims": { + "hashes": [ + "sha256:05b00ade9d1e686a98bb656dd9b0608a933897283dc21913fad6ea5409ff7e91", + "sha256:16ca9f87485667b16b978b68a1aae4f9cc082c0fa018aed28567f9f34a590569" + ], + "version": "==0.5.3" + }, + "pipenv-setup": { + "hashes": [ + "sha256:8a439aff7b16e18d7e07702c9186fc5fe86156679eace90e10c2578a43bd7af1", + "sha256:e1bfd55c1152024e762f1c17f6189fcb073166509e7c0228870f7ea160355648" + ], + "index": "pypi", + "version": "==3.1.1" + }, + "pipfile": { + "hashes": [ + "sha256:f7d9f15de8b660986557eb3cc5391aa1a16207ac41bc378d03f414762d36c984" + ], + "version": "==0.0.2" + }, + "plette": { + "extras": [ + "validation" + ], + "hashes": [ + "sha256:46402c03e36d6eadddad2a5125990e322dd74f98160c8f2dcd832b2291858a26", + "sha256:d6c9b96981b347bddd333910b753b6091a2c1eb2ef85bb373b4a67c9d91dca16" + ], + "version": "==0.2.3" + }, + "pyparsing": { + "hashes": [ + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + ], + "version": "==2.4.7" + }, + "python-dateutil": { + "hashes": [ + "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", + "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" + ], + "version": "==2.8.1" + }, + "regex": { + "hashes": [ + "sha256:0dc64ee3f33cd7899f79a8d788abfbec168410be356ed9bd30bbd3f0a23a7204", + "sha256:1269fef3167bb52631ad4fa7dd27bf635d5a0790b8e6222065d42e91bede4162", + "sha256:14a53646369157baa0499513f96091eb70382eb50b2c82393d17d7ec81b7b85f", + "sha256:3a3af27a8d23143c49a3420efe5b3f8cf1a48c6fc8bc6856b03f638abc1833bb", + "sha256:46bac5ca10fb748d6c55843a931855e2727a7a22584f302dd9bb1506e69f83f6", + "sha256:4c037fd14c5f4e308b8370b447b469ca10e69427966527edcab07f52d88388f7", + "sha256:51178c738d559a2d1071ce0b0f56e57eb315bcf8f7d4cf127674b533e3101f88", + "sha256:5ea81ea3dbd6767873c611687141ec7b06ed8bab43f68fad5b7be184a920dc99", + "sha256:6961548bba529cac7c07af2fd4d527c5b91bb8fe18995fed6044ac22b3d14644", + "sha256:75aaa27aa521a182824d89e5ab0a1d16ca207318a6b65042b046053cfc8ed07a", + "sha256:7a2dd66d2d4df34fa82c9dc85657c5e019b87932019947faece7983f2089a840", + "sha256:8a51f2c6d1f884e98846a0a9021ff6861bdb98457879f412fdc2b42d14494067", + "sha256:9c568495e35599625f7b999774e29e8d6b01a6fb684d77dee1f56d41b11b40cd", + "sha256:9eddaafb3c48e0900690c1727fba226c4804b8e6127ea409689c3bb492d06de4", + "sha256:bbb332d45b32df41200380fff14712cb6093b61bd142272a10b16778c418e98e", + "sha256:bc3d98f621898b4a9bc7fecc00513eec8f40b5b83913d74ccb445f037d58cd89", + "sha256:c11d6033115dc4887c456565303f540c44197f4fc1a2bfb192224a301534888e", + "sha256:c50a724d136ec10d920661f1442e4a8b010a4fe5aebd65e0c2241ea41dbe93dc", + "sha256:d0a5095d52b90ff38592bbdc2644f17c6d495762edf47d876049cfd2968fbccf", + "sha256:d6cff2276e502b86a25fd10c2a96973fdb45c7a977dca2138d661417f3728341", + "sha256:e46d13f38cfcbb79bfdb2964b0fe12561fe633caf964a77a5f8d4e45fe5d2ef7" + ], + "version": "==2020.7.14" + }, + "requests": { + "hashes": [ + "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b", + "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898" + ], + "index": "pypi", + "version": "==2.24.0" + }, + "requirementslib": { + "hashes": [ + "sha256:cdf8aa652ac52216d156cee2b89c3c9ee53373dded0035184d0b9af569a0f10c", + "sha256:fd98ea873effaede6b3394725a232bcbd3fe3985987e226109a841c85a69e2e3" + ], + "version": "==1.5.13" + }, + "six": { + "hashes": [ + "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", + "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" + ], + "version": "==1.15.0" + }, + "toml": { + "hashes": [ + "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f", + "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88" + ], + "version": "==0.10.1" + }, + "tomlkit": { + "hashes": [ + "sha256:6babbd33b17d5c9691896b0e68159215a9387ebfa938aa3ac42f4a4beeb2b831", + "sha256:ac57f29693fab3e309ea789252fcce3061e19110085aa31af5446ca749325618" + ], + "version": "==0.7.0" + }, + "typed-ast": { + "hashes": [ + "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", + "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", + "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", + "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", + "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", + "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", + "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", + "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", + "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", + "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", + "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", + "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", + "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", + "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", + "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", + "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", + "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", + "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", + "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", + "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", + "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" + ], + "version": "==1.4.1" + }, + "urllib3": { + "hashes": [ + "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a", + "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461" + ], + "version": "==1.25.10" + }, + "vistir": { + "hashes": [ + "sha256:a37079cdbd85d31a41cdd18457fe521e15ec08b255811e81aa061fd5f48a20fb", + "sha256:eff1d19ef50c703a329ed294e5ec0b0fbb35b96c1b3ee6dcdb266dddbe1e935a" + ], + "version": "==0.5.2" + }, + "wheel": { + "hashes": [ + "sha256:497add53525d16c173c2c1c733b8f655510e909ea78cc0e29d374243544b77a2", + "sha256:99a22d87add3f634ff917310a3d87e499f19e663413a52eb9232c447aa646c9f" + ], + "version": "==0.35.1" } } } From 568f44bf1b9728b494bc77caeb5f70dd09d48d49 Mon Sep 17 00:00:00 2001 From: Garrett Edwards Date: Fri, 4 Sep 2020 22:33:41 -0400 Subject: [PATCH 25/38] Add test-addon-directory/ to .gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 580ec1f..deb5fd8 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,6 @@ desktop.ini # Python setuptools cache build/ dist/ -wow_addon_updater.egg-info/ \ No newline at end of file +wow_addon_updater.egg-info/ + +test-addon-directory/ From e310f435eec22540148cca33bad61b80154de634 Mon Sep 17 00:00:00 2001 From: Garrett Edwards Date: Fri, 4 Sep 2020 22:34:29 -0400 Subject: [PATCH 26/38] Rename changelog.txt to CHANGELOG.md --- changelog.txt => CHANGELOG.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.txt => CHANGELOG.md (100%) diff --git a/changelog.txt b/CHANGELOG.md similarity index 100% rename from changelog.txt rename to CHANGELOG.md From 06c59daa24f49187d73b9d11696f5e23f919b156 Mon Sep 17 00:00:00 2001 From: Garrett Edwards Date: Fri, 4 Sep 2020 22:43:38 -0400 Subject: [PATCH 27/38] Overhaul format of the changelog --- CHANGELOG.md | 118 +++++++++++++++++++++++---------------------------- 1 file changed, 54 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6879846..535dc8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,88 +1,78 @@ -Changelog -* 04/29/2020 - v1.7.1 -Fix a regression and actually enable all of the Tukui/ElvUI family addons to work, including both retail and classic. +# Changelog -* 04/24/2020 - v1.7.0 -Fix some bugs, choose a fake useragent that Curse is happy with, support more Tukui addons. +## Unreleased +- Add support for installing as a module and have a command named `wow-addon-updater` available. See README.md for install details. -* 03/21/2020 - v1.6.3 -Update cloudscraper version. +## v1.7.1 - 04/29/2020 +- Fix a regression and actually enable all of the Tukui/ElvUI family addons to work, including both retail and classic. -* 03/11/2020 - v1.6.2 -Upgrade cloudscraper version and improve internal logging format. +## v1.7.0 - 04/24/2020 +- Fix some bugs, choose a fake useragent that Curse is happy with, support more Tukui addons. -* 03/04/2020 - v1.6.1 -Add current version logging and a broad exception clause in the main method. +## v1.6.3 - 03/21/2020 +- Update cloudscraper version. -* 03/04/2020 - v1.6.0 -Finally start logging exceptions properly. Output should look familiar in the console, but exceptions and detailed debug information is available in a log file. -Also fixes an issue with curse issuing reCaptcha challenges because cloudscraper was out of date. +## v1.6.2 - 03/11/2020 +- Upgrade cloudscraper version and improve internal logging format. -* 01/19/2020 - v1.5.2 -Improve the version check to actually check for latest releases and compare a new VERSION file. Should alert users only when a new release has been published, not just any change to master. +## v1.6.1 - 03/04/2020 +- Add current version logging and a broad exception clause in the main method. -* 01/19/2020 - v1.5.1 -Forgot to update changelog for v1.5.0, oops. -Bring in new versions of dependencies and unblock users of Curse, who has made more breaking anti-scraper changes. +## v1.6.0 - 03/04/2020 +- Finally start logging exceptions properly. Output should look familiar in the console, but exceptions and detailed debug information is available in a log file. +- Also fixes an issue with curse issuing reCaptcha challenges because cloudscraper was out of date. -* 10/11/2019 - v1.4.2 -Use a new internal method of fetching GitHub addons which appears to be more reliable, and fails less often. +## v1.5.2 - 01/19/2020 +- Improve the version check to actually check for latest releases and compare a new VERSION file. Should alert users only when a new release has been published, not just any change to master. -* 10/11/2019 - v1.4.1 -Fix for an issue involving subfolders and archives that aren't from git (GitHub, ElvUI). For example, you can now correctly extract single subfolders from Curse, etc. +## v1.5.1 - 01/19/2020 +- Forgot to update changelog for v1.5.0, oops. +- Bring in new versions of dependencies and unblock users of Curse, who has made more breaking anti-scraper changes. -* 10/6/2019 - v1.4.0 -Add support for WowAce classic addons. +## v1.4.2 - 10/11/2019 +- Use a new internal method of fetching GitHub addons which appears to be more reliable, and fails less often. -* 10/5/2019 - v1.3.0 -Add the ability to use a Windows-style addon directory path when using Windows, but calling the code from within Windows Subsystem for Linux. +## v1.4.1 - 10/11/2019 +- Fix for an issue involving subfolders and archives that aren't from git (GitHub, ElvUI). For example, you can now correctly extract single subfolders from Curse, etc. -* 10/5/2019 - v1.2.2 -Fix for a problem where an existing installed addon can be deleted, and the new extract fails, which essentially deletes the addon from the system. +## v1.4.0 - 10/06/2019 +- Add support for WowAce classic addons. -* 9/29/2019 - v1.2.1 -Relaxed the pattern match for most URLs which may not be prefixed with "www.". +## v1.3.0 - 10/05/2019 +- Add the ability to use a Windows-style addon directory path when using Windows, but calling the code from within Windows Subsystem for Linux. -* 9/28/2019 - v1.2.0 -Integrated cfscrape as an anti-measure for Cloudflare bot-detection, for Curse and WoWAce. Node.js is now a requirement for Curse-based sites. +## v1.2.2 - 10/05/2019 +- Fix for a problem where an existing installed addon can be deleted, and the new extract fails, which essentially deletes the addon from the system. -* 9/24/2019 - v1.1.2 -Fix WoWInterface site to accept any characters for the addon name instead of a more rigid regex. +## v1.2.1 - 09/29/2019 +- Relaxed the pattern match for most URLs which may not be prefixed with "www.". -* 9/23/2019 - v1.1.1 -Add message for when Curse is being a jerk and blocking your requests. +## v1.2.0 - 09/28/2019 +- Integrated cfscrape as an anti-measure for Cloudflare bot-detection, for Curse and WoWAce. Node.js is now a requirement for Curse-based sites. -* 9/3/2019 - v1.1.0 -Add support for WoWInterface classic addons. +## v1.1.2 - 09/24/2019 +- Fix WoWInterface site to accept any characters for the addon name instead of a more rigid regex. -* 8/31/2019 - v1.0 -Add command-line argument for specifying a configuration file. Now multiple independent configurations can be used i.e. one for retail, and one for classic. +## v1.1.1 - 09/23/2019 +- Add message for when Curse is being a jerk and blocking your requests. -* 8/31/2019 -Enhance support for Curse classic-only addons. Now those addons can download if the page only supports one release, and that release is the classic game version. +## v1.1.0 - 09/03/2019 +- Add support for WoWInterface classic addons. -* 8/28/2019 -Made the version number fetching for ElvUI more robust and human-readable. (v12.34 instead of 8f3ade9), as well as add support for ElvUI classic repo as well. - -* 8/25/2019 -Fixed an issue extracting addons that have multiple top-level folders. - -* 8/24/2019 -Fixed a folder naming issue with GitHub repository addons. - -* 8/18/2019 -Added basic support for Classic addons from Curse. - -* 8/16/2019 -Squashed bugs! -Fixed a major problem with subfolder extraction for archives i.e. you can now select folders again from the ElvUI repo! -Fixed an installation record being added for an addon which really didn't get installed due to a download or unzip failure. -Lastly, added support for any generic GitHub-hosted addon (thanks @Hjaltesorgenfrei)! - -* 8/11/2019 - Large internal project refactor. As a result, new commands to install and execute the module. Details in the README.md. - -* 8/7/2019 - Took over development of this great dead project. Improved run time by a huge factor (it should run in just a few seconds total, vs. a few seconds PER addon update). Added clickable executables, so users don't have to run python in their terminal. Cleaned up lots of style and refactored some code. +## v1.0 - 08/31/2019 +- Add command-line argument for specifying a configuration file. Now multiple independent configurations can be used i.e. one for retail, and one for classic. +- Enhance support for Curse classic-only addons. Now those addons can download if the page only supports one release, and that release is the classic game version. +- Made the version number fetching for ElvUI more robust and human-readable. (v12.34 instead of 8f3ade9), as well as add support for ElvUI classic repo as well. +- Fixed an issue extracting addons that have multiple top-level folders. +- Fixed a folder naming issue with GitHub repository addons. +- Added basic support for Classic addons from Curse. +- Fixed a major problem with subfolder extraction for archives i.e. you can now select folders again from the ElvUI repo! +- Fixed an installation record being added for an addon which really didn't get installed due to a download or unzip failure. +- Added support for any generic GitHub-hosted addon (thanks @Hjaltesorgenfrei)! +- Large internal project refactor. As a result, new commands to install and execute the module. Details in the README.md. +- Took over development of this great dead project. Improved run time by a huge factor (it should run in just a few seconds total, vs. a few seconds PER addon update). Added clickable executables, so users don't have to run python in their terminal. Cleaned up lots of style and refactored some code. +## Legacy Changes * 8/24/2018 - Added update message to notify if a new version of WoWAddonUpdater is available * 8/24/2018 - Added subfolder information to installed.txt - Should now allow multiple lines with different subfolders from the same addon From f88bc7c1b2c050a51025363fe855b1871199af0a Mon Sep 17 00:00:00 2001 From: amas Date: Fri, 4 Sep 2020 17:07:23 -0400 Subject: [PATCH 28/38] Update README to reflect Curse alpha/beta addons --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 0a5c12b..1b071cf 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,23 @@ If you want to extract a subfolder from the default downloaded folder, add a pip https://www.github.com/some-user/some-addon-repo|AddOn ``` +### Tracking alpha/beta addon releases from Curse +If you are running a beta or PTR version of the game or are simply interested in testing out the latest features of an addon, you may want to update as +they release alpha or beta versions are released. You can specify which version releases you would like to follow by including `alpha` +or `beta` after a space following the addon URL, see below: + +``` +https://www.curseforge.com/wow/addons/deadly-boss-mods beta +https://www.curseforge.com/wow/addons/auctionator alpha +``` + +The updater will follow a hierarchy of release versions, meaning that tracking the "alpha" releases will pull more recent beta or full releases. +Likewise, following beta will pull newer full release versions while ignoring alpha releases. + +Omitting the release option or incorrectly specifying it will automatically fall back on tracking the offical +release track. + + ## Contributing Bring up the dev `pipenv` with: ```bash From c2ab8a1509b2ab867cb10cf091f70c9e33587c32 Mon Sep 17 00:00:00 2001 From: amas Date: Sat, 5 Sep 2020 00:10:51 -0400 Subject: [PATCH 29/38] Add unit test for prerelease track parsing --- test/manager/test_addon_manager.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/manager/test_addon_manager.py b/test/manager/test_addon_manager.py index adfccb6..abd2e98 100644 --- a/test/manager/test_addon_manager.py +++ b/test/manager/test_addon_manager.py @@ -9,7 +9,7 @@ from updater.manager.addon_manager import AddonManager from updater.site import curse, github from updater.site.abstract_site import AbstractSite, SiteError -from updater.site.enum import GameVersion +from updater.site.enum import AddonVersion, GameVersion class MockSite(AbstractSite): @@ -132,6 +132,16 @@ def extract_should_fail(): self.assertRaises(KeyError, extract_should_fail) self.assertTrue(os.path.isdir(existing_addon_dir)) + def test_curse_prerelease_track(self): + tracks = ('alpha', 'beta', 'release', 'aalpha') + expected_versions = (AddonVersion.alpha, AddonVersion.beta, AddonVersion.release, AddonVersion.release) + addon_entry_strings = [f'https://www.curseforge.com/wow/addons/rarescanner {track}' for track in tracks] + for addon_entry, version in zip(addon_entry_strings, expected_versions): + with self.subTest((addon_entry, version)): + _, *addon_version_track = addon_entry.split(' ') + out = self.manager.validate_addon_version_track(addon_version_track) + self.assertEqual(out, version) + if __name__ == '__main__': unittest.main() From 45c3fa45b9b9e0d5a654b7ab710ba5cda917473a Mon Sep 17 00:00:00 2001 From: amas Date: Sat, 5 Sep 2020 00:12:20 -0400 Subject: [PATCH 30/38] Add default AddonVersion to site_handler for compatibility --- updater/site/site_handler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/updater/site/site_handler.py b/updater/site/site_handler.py index 403116b..8bb5bca 100644 --- a/updater/site/site_handler.py +++ b/updater/site/site_handler.py @@ -11,7 +11,8 @@ class UnknownSiteError(RuntimeError): pass -def get_handler(url: str, game_version: GameVersion, addon_version: AddonVersion) -> AbstractSite: +def get_handler(url: str, game_version: GameVersion, + addon_version: AddonVersion = AddonVersion.release) -> AbstractSite: if Curse.handles(url): return Curse(url, game_version, addon_version) elif WoWAce.handles(url): From ad1192adb1f072c69931dea26de456b441817943 Mon Sep 17 00:00:00 2001 From: amas Date: Sat, 5 Sep 2020 00:20:37 -0400 Subject: [PATCH 31/38] Add unit test to site_handler for Curse prerelease versions --- test/site/test_site_handler.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/site/test_site_handler.py b/test/site/test_site_handler.py index 887fe27..742e255 100644 --- a/test/site/test_site_handler.py +++ b/test/site/test_site_handler.py @@ -3,7 +3,7 @@ from updater.site import site_handler from updater.site.curse import Curse -from updater.site.enum import GameVersion +from updater.site.enum import AddonVersion, GameVersion from updater.site.tukui import Tukui from updater.site.wowace import WoWAce from updater.site.wowinterface import WoWInterface @@ -16,6 +16,14 @@ def test_handles_curse(self): handler = site_handler.get_handler(url, GameVersion.retail) self.assertIsInstance(handler, Curse) + def test_handles_curse_prerelease(self): + addon_versions = AddonVersion.__members__.values() + for url in Curse.get_supported_urls(): + Curse._convert_old_curse_urls = MagicMock(return_value=url) + for version in addon_versions: + handler = site_handler.get_handler(url, GameVersion.retail, version) + self.assertEqual(handler.addon_version, version) + def test_handles_wowace(self): for url in WoWAce.get_supported_urls(): handler = site_handler.get_handler(url, GameVersion.retail) From 8780bea09619e4e3789045555a320e5967a613bd Mon Sep 17 00:00:00 2001 From: Garrett Edwards Date: Sun, 6 Sep 2020 21:35:22 -0400 Subject: [PATCH 32/38] Improve some README.md wording --- README.md | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 8270d36..4cd7393 100644 --- a/README.md +++ b/README.md @@ -6,31 +6,28 @@ _Supporting both retail and classic addons!_ [![Build Status](https://github.com/grrttedwards/wow-addon-updater/workflows/Build%20and%20test%20wow-addon-updater/badge.svg?branch=master)](https://github.com/grrttedwards/wow-addon-updater/actions?query=workflow%3A%22Build+and+test+wow-addon-updater%22+branch%3Amaster) -## Downloading +# Downloading The best way to get the latest stable code is to head to the [Latest Releases page](https://github.com/grrttedwards/wow-addon-updater/releases/latest), or checking out the `master` branch. If you're feeling adventurous, you can also download the latest (possibly unstable) `develop` branch. -## First-time setup +# First-time setup -### System dependencies -- You must have a version of [Python](https://www.python.org/) 3.7.4+. +## Dependencies +- [Python](https://www.python.org/) 3.7.4+. Basically, any new version of Python. -### Python module dependencies +## Python module dependencies +- [requests](https://pypi.org/project/requests/), for making HTTP requests +- [BeautifulSoup4](https://pypi.org/project/beautifulsoup4/), for HTML document parsing +- [cloudscraper](https://pypi.org/project/cloudscraper/), for bypassing Curse's bot-detection measures You should already have `pip` included with your Python installation. This is the default package manager for Python. You can check by running `pip --version` on the command line. If it's not there, download the latest version of Python for your platform, and check the box during installation to include `pip`. -This utility has three Python module dependencies: - -- The [requests](https://pypi.org/project/requests/) module, for making HTTP requests -- The [BeautifulSoup4](https://pypi.org/project/beautifulsoup4/) module, for HTML document parsing -- The [cloudscraper](https://pypi.org/project/cloudscraper/) module, for bypassing Curse's bot-detection measures - -It's recommended you manage this with [`pipenv`](https://github.com/pypa/pipenv). All you need to do is run the following on the command line to install `pipenv` and the dependencies: +It's recommended you manage the dependencies with [`pipenv`](https://github.com/pypa/pipenv). All you need to do is run the following on the command line to install `pipenv` and the dependencies: ```bash cd wow-addon-updater/ @@ -40,7 +37,11 @@ pipenv install The packages will be automagically installed by `pipenv` to a local virtual environment. -## Running the utility +## Taking updates + +If the utility reports that there are updates available from this repo, simply extract the new version into the directory where you store the previous version. Be careful not to stomp on any input or configuration files that you would like to save (e.g. `config.ini`, `addons.txt`, or `installed.ini`) + +# Running the utility After performing the setup steps, you can run the executable scripts by clicking either: - `run_Windows.bat` for Windows or @@ -84,7 +85,7 @@ pipenv update After updating, re-run the utility to attempt updating the affected addons. -## Configuring the utility +# Configuring the utility The `config.ini` file is used by default to find where to install the addons to, and where to get the list of addons from. @@ -108,7 +109,7 @@ It requires that some properties be set, if you do not want to use the defaults - The game version (either `retail` or `classic`) that you would like to target for addons - (default `= retail`) -### Multiple configurations +## Multiple configurations The module supports a command-line configuration for maintaining multiple set of addons. For example, a set of addons for retail, and a different set of addons for classic. To use a different configuration file, specify it with the `--config` flag (or `-c`) e.g. @@ -116,7 +117,7 @@ To use a different configuration file, specify it with the `--config` flag (or ` pipenv run python -m updater -c my-custom-config.ini ``` -## Supported addon hosts +# Supported addon hosts The following hosts are supported as download targets. The URL specified should be to the main page of the addon, or in the case of GitHub, to the root of the repository. | | Retail | Classic | @@ -127,7 +128,7 @@ The following hosts are supported as download targets. The URL specified should | GitHub | ✅ | ✅ | | Tukui | ✅ | ✅ | -## Input file format +# Input file format Whatever file you use for your list of addons needs to be formatted in a particular way. Each line corresponds to an addon, and the line just needs to contain the link to the page for the addon. For example: @@ -145,14 +146,14 @@ Each link needs to be the main page for the addon, as shown above. >**_NOTE_**: Tukui addon URLs should point to the standard download page, and not the git repo. >i.e. https://www.tukui.org/classic-addons.php?id=2 and not https://git.tukui.org/elvui/elvui|ElvUI -### Addons archives containing subfolders +## Addons archives containing subfolders If you want to extract a subfolder from the default downloaded folder, add a pipe character (`|`) and the name of the subfolder at the end of the line. For example, the ElvUI addon can be added as follows: ``` https://www.github.com/some-user/some-addon-repo|AddOn ``` -## Contributing +# Contributing Bring up the dev `pipenv` with: ```bash pipenv install --dev @@ -178,5 +179,5 @@ pipenv-setup sync -d 1. ???? 1. Profit -## Thanks +# Thanks Shout out to GitHub user [`kuhnertdm`](https://github.com/kuhnertdm) for establishing the original base of this utility, and giving people an alternative to the wasteland of mainstream clients. From eb2955622ff2b27f89d107fe71f242c27b65af59 Mon Sep 17 00:00:00 2001 From: Garrett Edwards Date: Mon, 7 Sep 2020 15:41:20 -0400 Subject: [PATCH 33/38] Put installed-retail.ini under .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index deb5fd8..9fec9fa 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ config-classic.ini config-retail.ini config-test.ini installed-classic.ini +installed-retail.ini installed-test.ini # Settings for IntelliJ From c468ca805c535d7abfa95339f77fe2705c96ac7a Mon Sep 17 00:00:00 2001 From: amas0 Date: Mon, 7 Sep 2020 18:31:58 -0400 Subject: [PATCH 34/38] Update README.md Co-authored-by: Garrett Edwards --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1b071cf..cf0879f 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ https://www.github.com/some-user/some-addon-repo|AddOn ### Tracking alpha/beta addon releases from Curse If you are running a beta or PTR version of the game or are simply interested in testing out the latest features of an addon, you may want to update as -they release alpha or beta versions are released. You can specify which version releases you would like to follow by including `alpha` +alpha or beta versions are released. You can specify which version releases you would like to follow by including `alpha` or `beta` after a space following the addon URL, see below: ``` From 50e32a8e8586b7baba9fa2ea3e7a83e7e5962d3a Mon Sep 17 00:00:00 2001 From: amas Date: Mon, 7 Sep 2020 22:46:01 -0400 Subject: [PATCH 35/38] Refactor Curse latest release into method --- updater/site/curse.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/updater/site/curse.py b/updater/site/curse.py index c6415e2..f6159a8 100644 --- a/updater/site/curse.py +++ b/updater/site/curse.py @@ -76,8 +76,7 @@ def __init__(self, url: str, game_version: GameVersion, addon_version: AddonVers def find_zip_url(self): try: - latest_release = next(version for version in self.versions() if version.type >= self.addon_version) - return latest_release.download_link + return self.latest_release().download_link except Exception as e: raise self.download_error() from e @@ -110,6 +109,10 @@ def versions(self, *, page=1) -> Generator[CurseAddonVersion, None, None]: except Exception as e: raise self.version_error() from e + def latest_release(self) -> CurseAddonVersion: + latest = next(version for version in self.versions() if version.type >= self.addon_version) + return latest + def get_latest_version(self) -> str: """Returns the latest version released for retail/classic @@ -120,8 +123,7 @@ def get_latest_version(self) -> str: Returns the name of the most recent release. """ - latest_release = next(version for version in self.versions() if version.type >= self.addon_version) - return latest_release.name + return self.latest_release().name @classmethod def _convert_old_curse_urls(cls, url: str) -> str: From c884e434e079d38dcd268d9285abcf3d4466be40 Mon Sep 17 00:00:00 2001 From: amas Date: Tue, 8 Sep 2020 22:12:37 -0400 Subject: [PATCH 36/38] Add Curse URL normalization to all requests This addresses situations where renamed addons need to be supported through URL redirect. By accessing the URL directly it will resolve to the new, valid addon page. Tests were also updated to reflect this and one was added to test the `method-dungeon-tools` -> `mythic-dungeon-tools` situation that prompted this investigation. --- test/manager/test_addon_manager.py | 6 +++++- test/site/test_curse.py | 12 ++++++++++++ test/site/test_site_handler.py | 6 +++--- updater/site/curse.py | 23 ++++++++++------------- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/test/manager/test_addon_manager.py b/test/manager/test_addon_manager.py index abd2e98..ad146d3 100644 --- a/test/manager/test_addon_manager.py +++ b/test/manager/test_addon_manager.py @@ -2,7 +2,7 @@ import tempfile import unittest import zipfile -from unittest.mock import patch, Mock +from unittest.mock import patch, Mock, MagicMock from test import testutils from updater.manager import addon_manager @@ -86,6 +86,7 @@ def test_subfolder_extraction_fail_doesnt_install_addon(self): self.manager.update_addon(TEST_URL) self.assertFailedInstall() + @patch.object(curse.Curse, '_normalize_curse_urls', MagicMock(return_value='')) def test_extract_archive_subfolder(self): with tempfile.TemporaryDirectory() as temp_dir: self.extractAddon('some-fake-addon-with-many-folders.zip', temp_dir, curse.Curse("", GameVersion.retail), @@ -98,6 +99,7 @@ def test_extract_archive_subfolder_git(self): subfolder='sub-folder') self.assertExtractionSuccess(temp_dir, 'sub-folder', 'file1.txt') + @patch.object(curse.Curse, '_normalize_curse_urls', MagicMock(return_value='')) def test_extract_entire_archive(self): with tempfile.TemporaryDirectory() as temp_dir: self.extractAddon('some-fake-addon.zip', temp_dir, curse.Curse("", GameVersion.retail)) @@ -108,11 +110,13 @@ def test_extract_entire_archive_github_master_zipball(self): self.extractAddon('some-fake-addon-master.zip', temp_dir, github.GitHub("")) self.assertExtractionSuccess(temp_dir, 'some-fake-addon', 'sub-folder', 'file1.txt') + @patch.object(curse.Curse, '_normalize_curse_urls', MagicMock(return_value='')) def test_extract_entire_archive_curse(self): with tempfile.TemporaryDirectory() as temp_dir: self.extractAddon('some-fake-addon-from-curse.zip', temp_dir, curse.Curse("", GameVersion.retail)) self.assertExtractionSuccess(temp_dir, 'AddonName', 'sub-folder', 'file1.txt') + @patch.object(curse.Curse, '_normalize_curse_urls', MagicMock(return_value='')) def test_extract_archive_with_multiple_folders(self): with tempfile.TemporaryDirectory() as temp_dir: self.extractAddon('some-fake-addon-with-many-folders.zip', temp_dir, curse.Curse("", GameVersion.retail)) diff --git a/test/site/test_curse.py b/test/site/test_curse.py index c0d2e32..d385869 100644 --- a/test/site/test_curse.py +++ b/test/site/test_curse.py @@ -35,6 +35,10 @@ class VersionTestData: supported_game_versions=ALL_VERSIONS) ] +url_redirect_test_data = VersionTestData(url='https://www.curseforge.com/wow/addons/method-dungeon-tools', + version_regex=r'v[0-9]+\.[0-9]+\.[0-9]+', + supported_game_versions=(GameVersion.retail, GameVersion.agnostic)) + MOCK_VERSION_PAGE = MagicMock() with open(get_file('mock-curse-version-addons-simc.html'), 'r') as f: MOCK_VERSION_PAGE.text = f.read() @@ -94,6 +98,14 @@ def test_curse_versions_parsing(self): d.addon_version = addon_version self.assertEqual(d.get_latest_version(), expected_value) + def test_curse_url_redirect(self): + test_data = url_redirect_test_data + for game_version in test_data.supported_game_versions: + with self.subTest((game_version, test_data.url, test_data.version_regex)): + c = curse.Curse(test_data.url, game_version) + latest_version = c.get_latest_version() + self.assertRegex(latest_version, test_data.version_regex) + if __name__ == '__main__': unittest.main() diff --git a/test/site/test_site_handler.py b/test/site/test_site_handler.py index 742e255..ddd0f98 100644 --- a/test/site/test_site_handler.py +++ b/test/site/test_site_handler.py @@ -1,5 +1,5 @@ import unittest -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch from updater.site import site_handler from updater.site.curse import Curse @@ -10,16 +10,16 @@ class TestSiteHandler(unittest.TestCase): + @patch.object(Curse, '_normalize_curse_urls', MagicMock(return_value='')) def test_handles_curse(self): for url in Curse.get_supported_urls(): - Curse._convert_old_curse_urls = MagicMock(return_value=url) handler = site_handler.get_handler(url, GameVersion.retail) self.assertIsInstance(handler, Curse) + @patch.object(Curse, '_normalize_curse_urls', MagicMock(return_value='')) def test_handles_curse_prerelease(self): addon_versions = AddonVersion.__members__.values() for url in Curse.get_supported_urls(): - Curse._convert_old_curse_urls = MagicMock(return_value=url) for version in addon_versions: handler = site_handler.get_handler(url, GameVersion.retail, version) self.assertEqual(handler.addon_version, version) diff --git a/updater/site/curse.py b/updater/site/curse.py index f6159a8..965cc6c 100644 --- a/updater/site/curse.py +++ b/updater/site/curse.py @@ -70,7 +70,7 @@ class Curse(AbstractSite): session = cloudscraper.create_scraper(browser=CURSE_UA) def __init__(self, url: str, game_version: GameVersion, addon_version: AddonVersion = AddonVersion.release): - url = Curse._convert_old_curse_urls(url) + url = Curse._normalize_curse_urls(url) super().__init__(url, game_version) self.addon_version = addon_version @@ -126,15 +126,12 @@ def get_latest_version(self) -> str: return self.latest_release().name @classmethod - def _convert_old_curse_urls(cls, url: str) -> str: - if any(old_url in url for old_url in [Curse._OLD_URL, Curse._OLD_PROJECT_URL]): - try: - # Some old URL's may point to nonexistent pages. Rather than guess at what the new - # name and URL is, just try to load the old URL and see where Curse redirects us to. - page = Curse.session.get(url) - page.raise_for_status() - return page.url - except Exception as e: - raise SiteError(f"Failed to find the current page for old URL: {url}") from e - else: - return url + def _normalize_curse_urls(cls, url: str) -> str: + try: + # Some old URLs may point to nonexistent pages. Rather than guess at what the new + # name and URL is, just try to load the old URL and see where Curse redirects us to. + page = Curse.session.get(url) + page.raise_for_status() + return page.url + except Exception as e: + raise SiteError(f"Failed to find the current page for old URL: {url}") from e From a66e4a1dbf43d74219c0a9b932379a314b79a7b2 Mon Sep 17 00:00:00 2001 From: Garrett Edwards Date: Wed, 9 Sep 2020 20:45:20 -0400 Subject: [PATCH 37/38] Add note for Curse prerelease addons support --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 535dc8c..3054695 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # Changelog ## Unreleased -- Add support for installing as a module and have a command named `wow-addon-updater` available. See README.md for install details. +- Add support for installing as a module and have a command named `wow-addon-updater` available. +- Add ability to follow `alpha`, `beta`, or `release` tracks for addons hosted on Curse. ## v1.7.1 - 04/29/2020 - Fix a regression and actually enable all of the Tukui/ElvUI family addons to work, including both retail and classic. From 7f52f839939b7ec3681ce4541b18c441d9420e53 Mon Sep 17 00:00:00 2001 From: Garrett Edwards Date: Wed, 9 Sep 2020 20:55:29 -0400 Subject: [PATCH 38/38] Update VERSION and CHANGELOG.md for v1.8.0 --- CHANGELOG.md | 7 +++++-- VERSION | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3054695..464e8f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ # Changelog ## Unreleased -- Add support for installing as a module and have a command named `wow-addon-updater` available. -- Add ability to follow `alpha`, `beta`, or `release` tracks for addons hosted on Curse. +- + +## v1.8.0 - 09/09/2020 +- Add support for installing as a module and have a command named `wow-addon-updater` available. (Thanks @elebertus) +- Add ability to follow `alpha`, `beta`, or `release` tracks for addons hosted on Curse. (Thanks @amas0) ## v1.7.1 - 04/29/2020 - Fix a regression and actually enable all of the Tukui/ElvUI family addons to work, including both retail and classic. diff --git a/VERSION b/VERSION index 6c4fc20..4e2cea3 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v1.7.1 \ No newline at end of file +v1.8.0 \ No newline at end of file