diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 63feffc..244b224 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -10,8 +10,8 @@ jobs:
name: Windows
runs-on: windows-2019
steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-python@v4
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
@@ -24,7 +24,7 @@ jobs:
- name: Build binary
run: pyinstaller CurseBreaker.spec
- name: Upload binary
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: Windows
path: dist/CurseBreaker.exe
@@ -33,8 +33,8 @@ jobs:
name: Linux
runs-on: ubuntu-20.04
steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-python@v4
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
@@ -45,7 +45,7 @@ jobs:
- name: Build binary
run: pyinstaller CurseBreaker.spec
- name: Upload binary
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: Linux
path: dist/CurseBreaker
@@ -54,8 +54,8 @@ jobs:
name: macOS
runs-on: macos-11
steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-python@v4
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
@@ -66,7 +66,7 @@ jobs:
- name: Build binary
run: pyinstaller CurseBreaker.spec
- name: Upload binary
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: macOS
path: dist/CurseBreaker
diff --git a/.sourcery.yaml b/.sourcery.yaml
new file mode 100644
index 0000000..24c239b
--- /dev/null
+++ b/.sourcery.yaml
@@ -0,0 +1,18 @@
+version: '1'
+
+ignore:
+- CB\SLPP.py
+
+rule_settings:
+ enable:
+ - default
+ disable:
+ - list-comprehension
+ - dict-comprehension
+ - for-append-to-extend
+ - use-next
+ rule_types:
+ - refactoring
+ - suggestion
+ - comment
+ python_version: '3.12'
\ No newline at end of file
diff --git a/CB/Compat.py b/CB/Compat.py
index 8c58937..2407dfb 100644
--- a/CB/Compat.py
+++ b/CB/Compat.py
@@ -26,13 +26,13 @@ def set_normal_term(self):
if system != 'Windows':
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old_term)
- def getch(self):
+ def getch(self): # sourcery skip: assign-if-exp
if system == 'Windows':
return msvcrt.getch()
else:
return sys.stdin.read(1)
- def kbhit(self):
+ def kbhit(self): # sourcery skip: remove-unnecessary-else
if system == 'Windows':
return msvcrt.kbhit()
else:
diff --git a/CB/Core.py b/CB/Core.py
index 394de11..fdd8784 100644
--- a/CB/Core.py
+++ b/CB/Core.py
@@ -55,8 +55,8 @@ def init_config(self):
with open(self.configPath, 'r') as f:
try:
self.config = json.load(f)
- except (StopIteration, UnicodeDecodeError, json.JSONDecodeError):
- raise RuntimeError
+ except (StopIteration, UnicodeDecodeError, json.JSONDecodeError) as e:
+ raise RuntimeError from e
else:
self.config = {'Addons': [],
'WAStash': [],
@@ -83,69 +83,70 @@ def save_config(self):
json.dump(self.config, outfile, sort_keys=True, indent=4, separators=(',', ': '))
def update_config(self):
- if 'Version' not in self.config.keys() or self.config['Version'] != __version__:
- urlupdate = {'elvui-classic': 'elvui', 'elvui-classic:dev': 'elvui:dev', 'tukui-classic': 'tukui',
- 'sle:dev': 'shadow&light:dev', 'elvui:beta': 'elvui:dev'}
- # 4.0.0
- if 'WACompanionVersion' in self.config and os.path.isdir(Path('Interface/AddOns/WeakAurasCompanion')):
- shutil.rmtree(Path('Interface/AddOns/WeakAurasCompanion'), ignore_errors=True)
- for addon in self.config['Addons']:
- # 1.1.0
- if 'Checksums' not in addon.keys():
- checksums = {}
- for directory in addon['Directories']:
- checksums[directory] = dirhash(self.path / directory)
- addon['Checksums'] = checksums
- # 1.1.1
- if addon['Version'] is None:
- addon['Version'] = '1'
- # 2.2.0, 3.9.4, 3.12.0
- if addon['URL'].lower() in urlupdate:
- addon['URL'] = urlupdate[addon['URL'].lower()]
- # 2.4.0
- if addon['Name'] == 'TukUI':
- addon['Name'] = 'Tukui'
- addon['URL'] = 'Tukui'
- # 2.7.3
- addon['Directories'] = list(filter(None, set(addon['Directories'])))
- # 3.0.2
- if addon['URL'].endswith('/'):
- addon['URL'] = addon['URL'][:-1]
- # 3.3.0
- if 'Development' in addon.keys() and isinstance(addon['Development'], bool):
- addon['Development'] = 1
- # 4.3.0
- if addon['URL'].startswith('https://www.tukui.org/classic-tbc-addons.php?id='):
- addon['URL'] = addon['URL'].replace('https://www.tukui.org/classic-tbc-addons.php?id=',
- 'https://www.tukui.org/classic-wotlk-addons.php?id=')
- for add in [['2.1.0', 'WAUsername', ''],
- ['2.2.0', 'WAAccountName', ''],
- ['2.2.0', 'WAAPIKey', ''],
- ['2.2.0', 'WACompanionVersion', 0],
- ['2.8.0', 'IgnoreClientVersion', {}],
- ['3.0.1', 'CFCacheTimestamp', 0],
- ['3.1.10', 'CFCacheCloudFlare', {}],
- ['3.7.0', 'CompactMode', False],
- ['3.10.0', 'AutoUpdate', True],
- ['3.12.0', 'ShowAuthors', True],
- ['3.16.0', 'IgnoreDependencies', {}],
- ['3.18.0', 'WAStash', []],
- ['3.20.0', 'GHAPIKey', ''],
- ['4.0.0', 'WAAAPIKey', ''],
- ['4.0.0', 'CBCompanionVersion', 0],
- ['4.2.0', 'ShowSources', False]]:
- if add[1] not in self.config.keys():
- self.config[add[1]] = add[2]
- for delete in [['1.3.0', 'URLCache'],
- ['3.0.1', 'CurseCache'],
- ['4.0.0', 'CFCacheCloudFlare'],
- ['4.0.0', 'CFCacheTimestamp'],
- ['4.0.0', 'IgnoreDependencies'],
- ['4.0.0', 'WACompanionVersion']]:
- if delete[1] in self.config.keys():
- self.config.pop(delete[1], None)
- self.config['Version'] = __version__
- self.save_config()
+ if 'Version' in self.config.keys() and self.config['Version'] == __version__:
+ return
+ urlupdate = {'elvui-classic': 'elvui', 'elvui-classic:dev': 'elvui:dev', 'tukui-classic': 'tukui',
+ 'sle:dev': 'shadow&light:dev', 'elvui:beta': 'elvui:dev'}
+ # 4.0.0
+ if 'WACompanionVersion' in self.config and os.path.isdir(Path('Interface/AddOns/WeakAurasCompanion')):
+ shutil.rmtree(Path('Interface/AddOns/WeakAurasCompanion'), ignore_errors=True)
+ for addon in self.config['Addons']:
+ # 1.1.0
+ if 'Checksums' not in addon.keys():
+ checksums = {}
+ for directory in addon['Directories']:
+ checksums[directory] = dirhash(self.path / directory)
+ addon['Checksums'] = checksums
+ # 1.1.1
+ if addon['Version'] is None:
+ addon['Version'] = '1'
+ # 2.2.0, 3.9.4, 3.12.0
+ if addon['URL'].lower() in urlupdate:
+ addon['URL'] = urlupdate[addon['URL'].lower()]
+ # 2.4.0
+ if addon['Name'] == 'TukUI':
+ addon['Name'] = 'Tukui'
+ addon['URL'] = 'Tukui'
+ # 2.7.3
+ addon['Directories'] = list(filter(None, set(addon['Directories'])))
+ # 3.0.2
+ if addon['URL'].endswith('/'):
+ addon['URL'] = addon['URL'][:-1]
+ # 3.3.0
+ if 'Development' in addon.keys() and isinstance(addon['Development'], bool):
+ addon['Development'] = 1
+ # 4.3.0
+ if addon['URL'].startswith('https://www.tukui.org/classic-tbc-addons.php?id='):
+ addon['URL'] = addon['URL'].replace('https://www.tukui.org/classic-tbc-addons.php?id=',
+ 'https://www.tukui.org/classic-wotlk-addons.php?id=')
+ for add in [['2.1.0', 'WAUsername', ''],
+ ['2.2.0', 'WAAccountName', ''],
+ ['2.2.0', 'WAAPIKey', ''],
+ ['2.2.0', 'WACompanionVersion', 0],
+ ['2.8.0', 'IgnoreClientVersion', {}],
+ ['3.0.1', 'CFCacheTimestamp', 0],
+ ['3.1.10', 'CFCacheCloudFlare', {}],
+ ['3.7.0', 'CompactMode', False],
+ ['3.10.0', 'AutoUpdate', True],
+ ['3.12.0', 'ShowAuthors', True],
+ ['3.16.0', 'IgnoreDependencies', {}],
+ ['3.18.0', 'WAStash', []],
+ ['3.20.0', 'GHAPIKey', ''],
+ ['4.0.0', 'WAAAPIKey', ''],
+ ['4.0.0', 'CBCompanionVersion', 0],
+ ['4.2.0', 'ShowSources', False]]:
+ if add[1] not in self.config.keys():
+ self.config[add[1]] = add[2]
+ for delete in [['1.3.0', 'URLCache'],
+ ['3.0.1', 'CurseCache'],
+ ['4.0.0', 'CFCacheCloudFlare'],
+ ['4.0.0', 'CFCacheTimestamp'],
+ ['4.0.0', 'IgnoreDependencies'],
+ ['4.0.0', 'WACompanionVersion']]:
+ if delete[1] in self.config.keys():
+ self.config.pop(delete[1], None)
+ self.config['Version'] = __version__
+ self.save_config()
def check_if_installed(self, url):
for addon in self.config['Addons']:
@@ -158,12 +159,8 @@ def check_if_installed_dirs(self, directories):
return addon
def check_if_dev(self, url):
- addon = self.check_if_installed(url)
- if addon:
- if 'Development' in addon.keys():
- return addon['Development']
- else:
- return 0
+ if addon := self.check_if_installed(url):
+ return addon['Development'] if 'Development' in addon.keys() else 0
else:
return 0
@@ -172,8 +169,7 @@ def check_if_overlap(self):
found = set()
for addon in self.config['Addons']:
directories = directories + addon['Directories']
- dupes = [x for x in directories if x in found or found.add(x)]
- if len(dupes) > 0:
+ if dupes := [x for x in directories if x in found or found.add(x)]:
addons = []
for addon in self.config['Addons']:
if set(addon['Directories']).intersection(dupes):
@@ -184,20 +180,13 @@ def check_if_overlap(self):
return False
def check_if_blocked(self, addon):
- if addon:
- if 'Block' in addon.keys():
- return True
- else:
- return False
- else:
- return False
+ return bool(addon and 'Block' in addon.keys())
def check_if_dev_global(self):
for addon in self.config['Addons']:
if addon['URL'].startswith('https://addons.wago.io/addons/') and 'Development' in addon.keys():
return addon['Development']
- else:
- return 0
+ return 0
def cleanup(self, directories):
if len(directories) > 0:
@@ -247,6 +236,26 @@ def parse_url_source(self, url):
else:
return '?', None
+ def parse_new_addon(self, ignore, url):
+ if ignore:
+ self.config['IgnoreClientVersion'][url] = True
+ new = self.parse_url(url)
+ new.get_addon()
+ if addon := self.check_if_installed_dirs(new.directories):
+ return False, addon['Name'], addon['Version']
+ self.cleanup(new.directories)
+ new.install(self.path)
+ checksums = {}
+ for directory in new.directories:
+ checksums[directory] = dirhash(self.path / directory)
+ self.config['Addons'].append({'Name': new.name,
+ 'URL': url,
+ 'Version': new.currentVersion,
+ 'Directories': new.directories,
+ 'Checksums': checksums})
+ self.save_config()
+ return True, new.name, new.currentVersion
+
def add_addon(self, url, ignore):
if url.endswith(':'):
raise NotImplementedError('Provided URL is not supported.')
@@ -260,33 +269,13 @@ def add_addon(self, url, ignore):
url = f'https://github.com/{url[3:]}'
if url.endswith('/'):
url = url[:-1]
- addon = self.check_if_installed(url)
- if not addon:
- if ignore:
- self.config['IgnoreClientVersion'][url] = True
- new = self.parse_url(url)
- new.get_addon()
- addon = self.check_if_installed_dirs(new.directories)
- if addon:
- return False, addon['Name'], addon['Version']
- self.cleanup(new.directories)
- new.install(self.path)
- checksums = {}
- for directory in new.directories:
- checksums[directory] = dirhash(self.path / directory)
- self.config['Addons'].append({'Name': new.name,
- 'URL': url,
- 'Version': new.currentVersion,
- 'Directories': new.directories,
- 'Checksums': checksums
- })
- self.save_config()
- return True, new.name, new.currentVersion
- return False, addon['Name'], addon['Version']
+ if addon := self.check_if_installed(url):
+ return False, addon['Name'], addon['Version']
+ else:
+ return self.parse_new_addon(ignore, url)
def del_addon(self, url, keep):
- old = self.check_if_installed(url)
- if old:
+ if old := self.check_if_installed(url):
if not keep:
self.cleanup(old['Directories'])
self.config['IgnoreClientVersion'].pop(old['URL'], None)
@@ -296,41 +285,40 @@ def del_addon(self, url, keep):
return old['Name'], old['Version']
return False, False
- def update_addon(self, url, update, force):
- old = self.check_if_installed(url)
- if old:
- dev = self.check_if_dev(old['URL'])
- blocked = self.check_if_blocked(old)
- oldversion = old['Version']
- if old['URL'] in self.checksumCache:
- modified = self.checksumCache[old['URL']]
- else:
- modified = self.check_checksum(old, False)
- if old['URL'].startswith(('https://www.townlong-yak.com/addons/',
- 'https://www.curseforge.com/wow/addons/',
- 'https://www.tukui.org/')):
- return old['Name'], [], oldversion, oldversion, None, modified, blocked, 'Unsupported', old['URL'], \
+ def update_addon(self, url, update, force): # sourcery skip: extract-method
+ if not (old := self.check_if_installed(url)):
+ return url, [], False, False, None, False, False, '?', None, None, None
+ dev = self.check_if_dev(old['URL'])
+ blocked = self.check_if_blocked(old)
+ oldversion = old['Version']
+ if old['URL'] in self.checksumCache:
+ modified = self.checksumCache[old['URL']]
+ else:
+ modified = self.check_checksum(old, False)
+ if old['URL'].startswith(('https://www.townlong-yak.com/addons/',
+ 'https://www.curseforge.com/wow/addons/',
+ 'https://www.tukui.org/')):
+ return old['Name'], [], oldversion, oldversion, None, modified, blocked, 'Unsupported', old['URL'], \
None, dev
- source, sourceurl = self.parse_url_source(old['URL'])
- new = self.parse_url(old['URL'])
- if force or (new.currentVersion != old['Version'] and update and not modified and not blocked):
- new.get_addon()
- self.cleanup(old['Directories'])
- new.install(self.path)
- checksums = {}
- for directory in new.directories:
- checksums[directory] = dirhash(self.path / directory)
- old['Name'] = new.name
- old['Version'] = new.currentVersion
- old['Directories'] = new.directories
- old['Checksums'] = checksums
- self.save_config()
- if force:
- modified = False
- blocked = False
- return new.name, new.author, new.currentVersion, oldversion, new.uiVersion, modified, blocked, source, \
- sourceurl, new.changelogUrl, dev
- return url, [], False, False, None, False, False, '?', None, None, None
+ source, sourceurl = self.parse_url_source(old['URL'])
+ new = self.parse_url(old['URL'])
+ if force or (new.currentVersion != old['Version'] and update and not modified and not blocked):
+ new.get_addon()
+ self.cleanup(old['Directories'])
+ new.install(self.path)
+ checksums = {}
+ for directory in new.directories:
+ checksums[directory] = dirhash(self.path / directory)
+ old['Name'] = new.name
+ old['Version'] = new.currentVersion
+ old['Directories'] = new.directories
+ old['Checksums'] = checksums
+ self.save_config()
+ if force:
+ modified = False
+ blocked = False
+ return new.name, new.author, new.currentVersion, oldversion, new.uiVersion, modified, blocked, source, \
+ sourceurl, new.changelogUrl, dev
def check_checksum(self, addon, bulk=True):
checksums = {}
@@ -370,8 +358,7 @@ def dev_toggle(self, url):
self.save_config()
return state
else:
- addon = self.check_if_installed(url)
- if addon:
+ if addon := self.check_if_installed(url):
if addon['URL'].startswith('https://addons.wago.io/addons/'):
state = self.check_if_dev(url)
if state == 0:
@@ -387,8 +374,7 @@ def dev_toggle(self, url):
return None
def block_toggle(self, url):
- addon = self.check_if_installed(url)
- if addon:
+ if addon := self.check_if_installed(url):
state = self.check_if_blocked(addon)
if state:
addon.pop('Block', None)
@@ -409,17 +395,15 @@ def generic_toggle(self, option, inside=None):
return self.config[option]
def backup_check(self):
- if self.config['Backup']['Enabled']:
- if not os.path.isfile(Path('WTF-Backup', f'{datetime.datetime.now().strftime("%d%m%y")}.zip')):
- listofbackups = [Path(x) for x in glob.glob('WTF-Backup/*.zip')]
- if len(listofbackups) == self.config['Backup']['Number']:
- oldest_file = min(listofbackups, key=os.path.getctime)
- os.remove(oldest_file)
- return True
- else:
- return False
- else:
+ if not self.config['Backup']['Enabled']:
return False
+ if os.path.isfile(Path('WTF-Backup', f'{datetime.datetime.now().strftime("%d%m%y")}.zip')):
+ return False
+ listofbackups = [Path(x) for x in glob.glob('WTF-Backup/*.zip')]
+ if len(listofbackups) == self.config['Backup']['Number']:
+ oldest_file = min(listofbackups, key=os.path.getctime)
+ os.remove(oldest_file)
+ return True
def backup_wtf(self, console):
archive = Path('WTF-Backup', f'{datetime.datetime.now().strftime("%d%m%y")}.zip')
@@ -433,7 +417,7 @@ def backup_wtf(self, console):
zipf = zipfile.ZipFile(archive, 'w', zipfile.ZIP_DEFLATED)
filecount = 0
for _, _, files in os.walk('WTF/', topdown=True, followlinks=True):
- files = [f for f in files if not f[0] == '.']
+ files = [f for f in files if f[0] != '.']
filecount += len(files)
if filecount > 0:
with Progress('{task.completed}/{task.total}', '|', BarColumn(bar_width=None), '|', auto_refresh=False,
@@ -441,7 +425,7 @@ def backup_wtf(self, console):
task = progress.add_task('', total=filecount)
while not progress.finished:
for root, _, files in os.walk('WTF/', topdown=True, followlinks=True):
- files = [f for f in files if not f[0] == '.']
+ files = [f for f in files if f[0] != '.']
for f in files:
zipf.write(Path(root, f))
progress.update(task, advance=1, refresh=True)
@@ -482,15 +466,12 @@ def search(self, query):
raise RuntimeError('This feature only searches the database of the Wago Addons. '
'So their API key is required.\n'
'It can be obtained here: https://addons.wago.io/patreon')
- results = []
payload = requests.get(f'https://addons.wago.io/api/external/addons/_search?query={quote_plus(query.strip())}'
f'&game_version={self.clientType}', headers=HEADERS,
auth=APIAuth('Bearer', self.config['WAAAPIKey']), timeout=5)
self.parse_wagoaddons_error(payload.status_code)
payload = payload.json()
- for result in payload['data']:
- results.append(result['website_url'])
- return results
+ return [result['website_url'] for result in payload['data']]
def create_reg(self):
with open('CurseBreaker.reg', 'w') as outfile:
@@ -527,7 +508,7 @@ def parse_wagoapp_payload(self, url):
return f'https://addons.wago.io/addons/{payload["slug"]}'
# TODO Improve the check when Wago Addons API will be will be expanded
- def bulk_check(self, addons):
+ def bulk_check(self, addons): # sourcery skip: extract-method
ids_wowi = []
ids_wago = []
for addon in addons:
@@ -536,13 +517,13 @@ def bulk_check(self, addons):
elif addon['URL'].startswith('https://addons.wago.io/addons/') and \
addon['URL'] not in self.config['IgnoreClientVersion'].keys():
ids_wago.append({'slug': addon['URL'].replace('https://addons.wago.io/addons/', ''), 'id': ''})
- if len(ids_wowi) > 0:
+ if ids_wowi:
payload = requests.get(f'https://api.mmoui.com/v3/game/WOW/filedetails/{",".join(ids_wowi)}.json',
headers=HEADERS, timeout=5).json()
if 'ERROR' not in payload:
for addon in payload:
self.wowiCache[str(addon['UID'])] = addon
- if len(ids_wago) > 0 and self.config['WAAAPIKey'] != '':
+ if ids_wago and self.config['WAAAPIKey'] != '':
if not self.wagoIdCache:
self.wagoIdCache = requests.get(f'https://addons.wago.io/api/data/slugs?game_version={self.clientType}',
headers=HEADERS, timeout=5)
@@ -569,16 +550,15 @@ def bulk_tukui_check(self):
self.tukuiCache = requests.get('https://api.tukui.org/v1/addons', headers=HEADERS, timeout=5).json()
def detect_accounts(self):
- if os.path.isdir(Path('WTF/Account')):
- accounts = os.listdir(Path('WTF/Account'))
- accounts_processed = []
- for account in accounts:
- if os.path.isfile(Path(f'WTF/Account/{account}/SavedVariables/WeakAuras.lua')) or \
- os.path.isfile(Path(f'WTF/Account/{account}/SavedVariables/Plater.lua')):
- accounts_processed.append(account)
- return accounts_processed
- else:
+ if not os.path.isdir(Path('WTF/Account')):
return []
+ accounts = os.listdir(Path('WTF/Account'))
+ accounts_processed = []
+ for account in accounts:
+ if os.path.isfile(Path(f'WTF/Account/{account}/SavedVariables/WeakAuras.lua')) or \
+ os.path.isfile(Path(f'WTF/Account/{account}/SavedVariables/Plater.lua')):
+ accounts_processed.append(account)
+ return accounts_processed
def detect_addons(self):
if self.config['WAAAPIKey'] == '':
@@ -649,7 +629,7 @@ def parse_wagoaddons_error(self, code):
raise RuntimeError('Provided Wago Addons API key is expired. Please acquire a new one.')
elif code == 423:
raise RuntimeError('Provided Wago Addons API key is blocked. Please acquire a new one.')
- elif code == 429 or code == 500:
+ elif code in [429, 500]:
raise RuntimeError('Temporary Wago Addons API issue. Please try later.')
diff --git a/CB/GitHub.py b/CB/GitHub.py
index 356f383..1e7cdca 100644
--- a/CB/GitHub.py
+++ b/CB/GitHub.py
@@ -16,8 +16,8 @@ def __init__(self, url, clienttype, apikey):
try:
self.payload = requests.get(f'https://api.github.com/repos/{project}/releases', headers=HEADERS,
auth=APIAuth('token', self.apiKey), timeout=10)
- except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
- raise RuntimeError(f'{project}\nGitHub API failed to respond.')
+ except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e:
+ raise RuntimeError(f'{project}\nGitHub API failed to respond.') from e
if self.payload.status_code == 401:
raise RuntimeError(f'{project}\nIncorrect or expired GitHub API personal access token.')
elif self.payload.status_code == 403:
@@ -33,7 +33,7 @@ def __init__(self, url, clienttype, apikey):
self.payloads.append(release)
if len(self.payloads) > 14:
break
- if len(self.payloads) == 0:
+ if not self.payloads:
raise RuntimeError(f'{url}\nThis integration supports only the projects that provide packaged'
f' releases.')
self.name = project.split('/')[1]
@@ -55,7 +55,10 @@ def parse(self):
self.currentVersion = self.payloads[self.releaseDepth]['tag_name'] or self.payloads[self.releaseDepth]['name']
self.changelogUrl = self.payloads[self.releaseDepth]['html_url']
self.parse_metadata()
- self.get_latest_package()
+ if self.metadata:
+ self.get_latest_package()
+ else:
+ self.get_latest_package_nometa()
def parse_metadata(self):
for release in self.payloads[self.releaseDepth]['assets']:
@@ -68,60 +71,60 @@ def parse_metadata(self):
self.metadata = None
def get_latest_package(self):
- if self.metadata:
- targetfile = None
- if self.clientType == 'classic':
- targetflavor = 'classic'
- elif self.clientType == 'wotlk':
- targetflavor = 'wrath'
- else:
- targetflavor = 'mainline'
- for release in self.metadata['releases']:
- if not release['nolib']:
- for flavor in release['metadata']:
- if flavor['flavor'] == targetflavor:
- targetfile = release['filename']
- if 'name' in release:
- self.name = release['name']
- self.currentVersion = release['version']
- break
- if targetfile:
+ targetfile = None
+ if self.clientType == 'classic':
+ targetflavor = 'classic'
+ elif self.clientType == 'wotlk':
+ targetflavor = 'wrath'
+ else:
+ targetflavor = 'mainline'
+ for release in self.metadata['releases']:
+ if not release['nolib']:
+ for flavor in release['metadata']:
+ if flavor['flavor'] == targetflavor:
+ targetfile = release['filename']
+ if 'name' in release:
+ self.name = release['name']
+ self.currentVersion = release['version']
break
- if not targetfile:
- self.releaseDepth += 1
- self.parse()
- for release in self.payloads[self.releaseDepth]['assets']:
- if release['name'] and release['name'] == targetfile:
- self.downloadUrl = release['url']
+ if targetfile:
break
- if not self.downloadUrl:
- self.releaseDepth += 1
- self.parse()
+ if not targetfile:
+ self.releaseDepth += 1
+ self.parse()
+ for release in self.payloads[self.releaseDepth]['assets']:
+ if release['name'] and release['name'] == targetfile:
+ self.downloadUrl = release['url']
+ break
+ if not self.downloadUrl:
+ self.releaseDepth += 1
+ self.parse()
+
+ def get_latest_package_nometa(self):
+ latest = None
+ latestclassic = None
+ latestwrath = None
+ for release in self.payloads[self.releaseDepth]['assets']:
+ if release['name'] and release['name'].endswith('.zip') and '-nolib' not in release['name'] \
+ and release['content_type'] in ['application/x-zip-compressed', 'application/zip', 'raw']:
+ if not latest and not release['name'].endswith(('-classic.zip', '-bc.zip', '-bcc.zip',
+ '-wrath.zip')):
+ latest = release['url']
+ elif not latestclassic and release['name'].endswith('-classic.zip'):
+ latestclassic = release['url']
+ elif not latestwrath and release['name'].endswith('-wrath.zip'):
+ latestwrath = release['url']
+ if (self.clientType == 'retail' and latest) \
+ or (self.clientType == 'classic' and latest and not latestclassic) \
+ or (self.clientType == 'wotlk' and latest and not latestwrath):
+ self.downloadUrl = latest
+ elif self.clientType == 'classic' and latestclassic:
+ self.downloadUrl = latestclassic
+ elif self.clientType == 'wotlk' and latestwrath:
+ self.downloadUrl = latestwrath
else:
- latest = None
- latestclassic = None
- latestwrath = None
- for release in self.payloads[self.releaseDepth]['assets']:
- if release['name'] and release['name'].endswith('.zip') and '-nolib' not in release['name'] \
- and release['content_type'] in ['application/x-zip-compressed', 'application/zip', 'raw']:
- if not latest and not release['name'].endswith(('-classic.zip', '-bc.zip', '-bcc.zip',
- '-wrath.zip')):
- latest = release['url']
- elif not latestclassic and release['name'].endswith('-classic.zip'):
- latestclassic = release['url']
- elif not latestwrath and release['name'].endswith('-wrath.zip'):
- latestwrath = release['url']
- if (self.clientType == 'retail' and latest) \
- or (self.clientType == 'classic' and latest and not latestclassic) \
- or (self.clientType == 'wotlk' and latest and not latestwrath):
- self.downloadUrl = latest
- elif self.clientType == 'classic' and latestclassic:
- self.downloadUrl = latestclassic
- elif self.clientType == 'wotlk' and latestwrath:
- self.downloadUrl = latestwrath
- else:
- self.releaseDepth += 1
- self.parse()
+ self.releaseDepth += 1
+ self.parse()
@retry()
def get_addon(self):
@@ -134,7 +137,7 @@ def get_addon(self):
if '/' not in os.path.dirname(file):
self.directories.append(os.path.dirname(file))
self.directories = list(filter(None, set(self.directories)))
- if len(self.directories) == 0:
+ if not self.directories:
raise RuntimeError(f'{self.name}.\nProject package is corrupted or incorrectly packaged.')
def install(self, path):
@@ -151,8 +154,8 @@ def __init__(self, addon, apikey):
try:
self.payload = requests.get(f'https://api.github.com/repos/{repository}/branches/{self.branch}',
headers=HEADERS, auth=APIAuth('token', self.apiKey), timeout=10)
- except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
- raise RuntimeError(f'{self.name}\nGitHub API failed to respond.')
+ except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e:
+ raise RuntimeError(f'{self.name}\nGitHub API failed to respond.') from e
if self.payload.status_code == 401:
raise RuntimeError(f'{self.name}\nIncorrect or expired GitHub API personal access token.')
elif self.payload.status_code == 403:
diff --git a/CB/Tukui.py b/CB/Tukui.py
index a261869..4f55726 100644
--- a/CB/Tukui.py
+++ b/CB/Tukui.py
@@ -30,7 +30,7 @@ def get_addon(self):
if '/' not in os.path.dirname(file):
self.directories.append(os.path.dirname(file))
self.directories = list(filter(None, set(self.directories)))
- if len(self.directories) == 0:
+ if not self.directories:
raise RuntimeError(f'{self.name}.\nProject package is corrupted or incorrectly packaged.')
def install(self, path):
diff --git a/CB/Wago.py b/CB/Wago.py
index 9fc6ddb..597f423 100644
--- a/CB/Wago.py
+++ b/CB/Wago.py
@@ -47,11 +47,11 @@ def parse_storage(self):
for wa in wadata['displays']:
if 'url' in wadata['displays'][wa]:
search = self.urlParser.search(wadata['displays'][wa]['url'])
- if search is not None and search.group(1) and search.group(2):
- if 'parent' not in wadata['displays'][wa] and 'ignoreWagoUpdate' not in wadata['displays'][wa]:
- if 'skipWagoUpdate' in wadata['displays'][wa]:
- self.ignored[search.group(1)] = int(wadata['displays'][wa]['skipWagoUpdate'])
- self.list[search.group(1)] = int(search.group(2))
+ if (search is not None and search.group(1) and search.group(2) and
+ 'parent' not in wadata['displays'][wa] and 'ignoreWagoUpdate' not in wadata['displays'][wa]):
+ if 'skipWagoUpdate' in wadata['displays'][wa]:
+ self.ignored[search.group(1)] = int(wadata['displays'][wa]['skipWagoUpdate'])
+ self.list[search.group(1)] = int(search.group(2))
class PlaterParser(BaseParser):
@@ -65,11 +65,11 @@ def parse_storage_internal(self, data):
for script in data:
if data[script]['url']:
search = self.urlParser.search(data[script]['url'])
- if search is not None and search.group(1) and search.group(2):
- if 'ignoreWagoUpdate' not in data[script]:
- if 'skipWagoUpdate' in data[script]:
- self.ignored[search.group(1)] = int(data[script]['skipWagoUpdate'])
- self.list[search.group(1)] = int(search.group(2))
+ if (search is not None and search.group(1) and search.group(2) and
+ 'ignoreWagoUpdate' not in data[script]):
+ if 'skipWagoUpdate' in data[script]:
+ self.ignored[search.group(1)] = int(data[script]['skipWagoUpdate'])
+ self.list[search.group(1)] = int(search.group(2))
def parse_storage(self):
with open(Path(f'WTF/Account/{self.accountName}/SavedVariables/Plater.lua'), 'r', encoding='utf-8',
@@ -77,20 +77,17 @@ def parse_storage(self):
data = file.read().replace('PlaterDB = {', '{', 1).rsplit('PlaterLanguage = {', 1)[0]
platerdata = loads(data)
for profile in platerdata['profiles']:
- data = platerdata['profiles'][profile]['script_data']
- if data:
+ if data := platerdata['profiles'][profile]['script_data']:
self.parse_storage_internal(data)
- data = platerdata['profiles'][profile]['hook_data']
- if data:
+ if data := platerdata['profiles'][profile]['hook_data']:
self.parse_storage_internal(data)
- data = platerdata['profiles'][profile]['url']
- if data:
+ if data := platerdata['profiles'][profile]['url']:
search = self.urlParser.search(data)
- if search is not None and search.group(1) and search.group(2):
- if 'ignoreWagoUpdate' not in platerdata['profiles'][profile]:
- if 'skipWagoUpdate' in platerdata['profiles'][profile]:
- self.ignored[search.group(1)] = int(platerdata['profiles'][profile]['skipWagoUpdate'])
- self.list[search.group(1)] = int(search.group(2))
+ if (search is not None and search.group(1) and search.group(2) and
+ 'ignoreWagoUpdate' not in platerdata['profiles'][profile]):
+ if 'skipWagoUpdate' in platerdata['profiles'][profile]:
+ self.ignored[search.group(1)] = int(platerdata['profiles'][profile]['skipWagoUpdate'])
+ self.list[search.group(1)] = int(search.group(2))
class WagoUpdater:
@@ -122,10 +119,10 @@ def check_updates(self, addon):
raise RuntimeError
for entry in payload:
if 'username' in entry and (not self.username or entry['username'] != self.username):
- if not entry['slug'] in addon.list:
+ if entry['slug'] not in addon.list:
entry['slug'] = entry['_id']
- if entry['version'] > addon.list[entry['slug']] and (not entry['slug'] in addon.ignored or
- (entry['slug'] in addon.ignored and entry['version'] != addon.ignored[entry['slug']])):
+ if (entry['version'] > addon.list[entry['slug']] and
+ (entry['slug'] not in addon.ignored or entry['version'] != addon.ignored[entry['slug']])):
output[0].append([entry['name'], entry['url']])
self.update_entry(entry, addon)
elif 'name' in entry:
@@ -138,7 +135,7 @@ def check_updates(self, addon):
def check_stash(self, wa, plater):
output = []
if len(self.stash) > 0:
- payload = requests.post(f'https://data.wago.io/api/check/',
+ payload = requests.post('https://data.wago.io/api/check/',
json={'ids': self.stash}, headers=self.headers, timeout=15).json()
for entry in payload:
output.append(entry['name'])
@@ -158,13 +155,12 @@ def check_stash(self, wa, plater):
return output
def parse_changelog(self, entry):
- if 'changelog' in entry and 'text' in entry['changelog']:
- if entry['changelog']['format'] == 'bbcode':
- return self.bbParser.strip(entry['changelog']['text'])
- elif entry['changelog']['format'] == 'markdown':
- return self.mdParser.convert(entry['changelog']['text'])
- else:
+ if 'changelog' not in entry or 'text' not in entry['changelog']:
return ''
+ if entry['changelog']['format'] == 'bbcode':
+ return self.bbParser.strip(entry['changelog']['text'])
+ elif entry['changelog']['format'] == 'markdown':
+ return self.mdParser.convert(entry['changelog']['text'])
@retry('Failed to parse Wago data. Wago might be down or provided API key is incorrect.')
def update_entry(self, entry, addon):
diff --git a/CB/WagoAddons.py b/CB/WagoAddons.py
index 2c7345e..e97ba4f 100644
--- a/CB/WagoAddons.py
+++ b/CB/WagoAddons.py
@@ -27,23 +27,23 @@ def __init__(self, url, checkcache, clienttype, allowdev, apikey):
self.payload = requests.get(f'https://addons.wago.io/api/external/addons/{project}?game_version='
f'{self.clientType}', headers=HEADERS, auth=APIAuth('Bearer', self.apiKey),
timeout=5)
- except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
- raise RuntimeError(f'{url}\nWago Addons API failed to respond.')
+ except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e:
+ raise RuntimeError(f'{url}\nWago Addons API failed to respond.') from e
if self.payload.status_code == 401:
raise RuntimeError(f'{url}\nWago Addons API key is missing or incorrect.')
elif self.payload.status_code == 403:
raise RuntimeError(f'{url}\nProvided Wago Addons API key is expired. Please acquire a new one.')
elif self.payload.status_code == 423:
raise RuntimeError(f'{url}\nProvided Wago Addons API key is blocked. Please acquire a new one.')
- elif self.payload.status_code == 404 or self.payload.status_code == 429 or self.payload.status_code == 500:
+ elif self.payload.status_code in {404, 429, 500}:
raise RuntimeError(f'{url}\nThis might be a temporary issue with Wago Addons API or the project was '
f'removed/renamed. In this case, uninstall it (and reinstall if it still exists) '
f'to fix this issue.')
else:
try:
self.payload = self.payload.json()
- except (StopIteration, JSONDecodeError):
- raise RuntimeError(f'{url}\nThis might be a temporary issue with Wago Addons API.')
+ except (StopIteration, JSONDecodeError) as e:
+ raise RuntimeError(f'{url}\nThis might be a temporary issue with Wago Addons API.') from e
self.name = self.payload['display_name'].strip().strip('\u200b')
self.allowDev = allowdev
self.downloadUrl = None
@@ -81,7 +81,7 @@ def get_current_version(self):
else:
release.pop('alpha', None)
release.pop('beta', None)
- if len(release) == 0:
+ if not release:
raise RuntimeError(f'{self.name}.\nFailed to find release for your client version.')
release = self.payload['recent_release'][max(release, key=release.get)]
@@ -98,7 +98,7 @@ def get_addon(self):
if '/' not in os.path.dirname(file):
self.directories.append(os.path.dirname(file))
self.directories = list(filter(None, set(self.directories)))
- if len(self.directories) == 0:
+ if not self.directories:
raise RuntimeError(f'{self.name}.\nProject package is corrupted or incorrectly packaged.')
def install(self, path):
diff --git a/CB/WoWInterface.py b/CB/WoWInterface.py
index ddad293..0e94017 100644
--- a/CB/WoWInterface.py
+++ b/CB/WoWInterface.py
@@ -16,8 +16,8 @@ def __init__(self, url, checkcache):
try:
self.payload = requests.get(f'https://api.mmoui.com/v3/game/WOW/filedetails/{project}.json',
headers=HEADERS, timeout=5).json()
- except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
- raise RuntimeError(f'{url}\nWoWInterface API failed to respond.')
+ except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e:
+ raise RuntimeError(f'{url}\nWoWInterface API failed to respond.') from e
if 'ERROR' in self.payload:
raise RuntimeError(f'{url}\nThis might be a temporary error or this project is not supported '
f'by WoWInterface API.')
@@ -39,7 +39,7 @@ def get_addon(self):
if '/' not in os.path.dirname(file):
self.directories.append(os.path.dirname(file))
self.directories = list(filter(None, set(self.directories)))
- if len(self.directories) == 0:
+ if not self.directories:
raise RuntimeError(f'{self.name}.\nProject package is corrupted or incorrectly packaged.')
def install(self, path):
diff --git a/CB/__init__.py b/CB/__init__.py
index 574a6c7..ec7c1cf 100644
--- a/CB/__init__.py
+++ b/CB/__init__.py
@@ -21,15 +21,13 @@ def inner(*args, **kwargs):
continue
else:
return result
+ if custom_error:
+ raise RuntimeError(custom_error) from None
+ elif description:
+ raise RuntimeError(f'Failed to parse addon data: {description}') from None
else:
- if custom_error:
- raise RuntimeError(custom_error) from None
- else:
- if description:
- raise RuntimeError(f'Failed to parse addon data: {description}') from None
- else:
- raise RuntimeError('Unknown error during parsing addon data. '
- 'There may be some issue with the website.') from None
+ raise RuntimeError('Unknown error during parsing addon data. '
+ 'There may be some issue with the website.') from None
return inner
return wraps
diff --git a/CurseBreaker.py b/CurseBreaker.py
index 1fe5f11..5cd12ed 100644
--- a/CurseBreaker.py
+++ b/CurseBreaker.py
@@ -17,7 +17,7 @@
from shlex import split
from pathlib import Path
from datetime import datetime
-from contextlib import nullcontext
+from contextlib import nullcontext, suppress
from rich import box
from rich.text import Text
from rich.rule import Rule
@@ -63,11 +63,9 @@ def start(self):
# Check if executable is in good location
if not glob.glob('World*.app') and not glob.glob('Wow*.exe') or \
not os.path.isdir(Path('Interface/AddOns')) or not os.path.isdir('WTF'):
- self.console.print('[bold red]This executable should be placed in the same directory where Wow.exe, '
- 'WowClassic.exe or World of Warcraft.app is located. Additionally, make sure that '
- 'this WoW installation was started at least once.[/bold red]\n')
- pause(self.headless)
- sys.exit(1)
+ self.handle_shutdown('[bold red]This executable should be placed in the same directory where Wow.exe, WowCl'
+ 'assic.exe or World of Warcraft.app is located. Additionally, make sure that this WoW '
+ 'installation was started at least once.[/bold red]\n')
# Detect client flavor
if 'CURSEBREAKER_FLAVOR' in os.environ:
flavor = os.environ.get('CURSEBREAKER_FLAVOR')
@@ -82,27 +80,21 @@ def start(self):
self.core.clientType = 'classic'
set_terminal_title(f'CurseBreaker v{__version__} - Classic')
else:
- self.console.print('[bold red]This client release is currently unsupported by CurseBreaker.[/bold red]\n')
- pause(self.headless)
- sys.exit(1)
+ self.handle_shutdown('[bold red]This client release is currently unsupported by CurseBreaker.[/bold red]\n')
# Check if client have write access
try:
with open('PermissionTest', 'w') as _:
pass
os.remove('PermissionTest')
except IOError:
- self.console.print('[bold red]CurseBreaker doesn\'t have write rights for the current directory.\n'
- 'Try starting it with administrative privileges.[/bold red]\n')
- pause(self.headless)
- sys.exit(1)
+ self.handle_shutdown('[bold red]CurseBreaker doesn\'t have write rights for the current directory.\nTry sta'
+ 'rting it with administrative privileges.[/bold red]\n')
self.auto_update()
try:
self.core.init_config()
except RuntimeError:
- self.console.print('[bold red]The config file is corrupted. Restore the earlier version from backup.'
- '[/bold red]\n')
- pause(self.headless)
- sys.exit(1)
+ self.handle_shutdown('[bold red]The config file is corrupted. Restore the earlier version from backup.[/bol'
+ 'd red]\n')
self.setup_table()
# Wago Addons URI Support
if len(sys.argv) == 2 and sys.argv[1].startswith('wago-app://addons/'):
@@ -169,9 +161,7 @@ def start(self):
self.console.print('Press [bold]I[/bold] to enter interactive mode or any other button to close'
' the application.')
keypress = self.handle_keypress(0)
- if keypress and keypress.lower() in [b'i', 'i']:
- pass
- else:
+ if not keypress or keypress.lower() not in [b'i', 'i']:
sys.exit(0)
if self.headless:
sys.exit(1)
@@ -188,7 +178,7 @@ def start(self):
'dons.')
self.motd_parser()
if self.core.backup_check():
- self.console.print(f'[green]Backing up WTF directory:[/green]')
+ self.console.print('[green]Backing up WTF directory:[/green]')
self.core.backup_wtf(self.console)
self.console.print('')
# Prompt session
@@ -211,56 +201,55 @@ def start(self):
else:
self.console.print('Command not found.')
- def auto_update(self):
- if getattr(sys, 'frozen', False) and 'CURSEBREAKER_VARDEXMODE' not in os.environ:
+ def auto_update(self): # sourcery skip: extract-method
+ if not getattr(sys, 'frozen', False) or 'CURSEBREAKER_VARDEXMODE' in os.environ:
+ return
+ try:
+ if os.path.isfile(f'{sys.executable}.old'):
+ with suppress(PermissionError):
+ os.remove(f'{sys.executable}.old')
try:
- if os.path.isfile(sys.executable + '.old'):
- try:
- os.remove(sys.executable + '.old')
- except PermissionError:
- pass
- try:
- payload = requests.get('https://api.github.com/repos/AcidWeb/CurseBreaker/releases/latest',
- headers=HEADERS, timeout=10).json()
- except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
- return
- if 'name' in payload and 'body' in payload and 'assets' in payload:
- remoteversion = payload['name']
- changelog = payload['body']
- url = None
- for binary in payload['assets']:
- if (self.os == 'Windows' and '.exe' in binary['name'])\
+ payload = requests.get('https://api.github.com/repos/AcidWeb/CurseBreaker/releases/latest',
+ headers=HEADERS, timeout=10).json()
+ except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
+ return
+ if 'name' in payload and 'body' in payload and 'assets' in payload:
+ remoteversion = payload['name']
+ changelog = payload['body']
+ url = None
+ for binary in payload['assets']:
+ if (self.os == 'Windows' and '.exe' in binary['name'])\
or (self.os == 'Darwin' and '.zip' in binary['name'])\
or (self.os == 'Linux' and '.gz' in binary['name']):
- url = binary['browser_download_url']
- break
- if url and Version(remoteversion[1:]) > Version(__version__):
- self.console.print('[green]Updating CurseBreaker...[/green]')
- shutil.move(sys.executable, sys.executable + '.old')
- payload = requests.get(url, headers=HEADERS, timeout=10)
- if self.os == 'Darwin':
- zipfile.ZipFile(io.BytesIO(payload.content)).extractall(path=os.path.dirname(
- os.path.abspath(sys.executable)))
- else:
- with open(sys.executable, 'wb') as f:
- if self.os == 'Windows':
- f.write(payload.content)
- elif self.os == 'Linux':
- f.write(gzip.decompress(payload.content))
- os.chmod(sys.executable, 0o775)
- self.console.print(f'[bold green]Update complete! The application will be restarted now.'
- f'[/bold green]\n\n[green]Changelog:[/green]\n{changelog}\n')
- self.print_log()
- pause(self.headless)
- subprocess.call([sys.executable] + sys.argv[1:])
- sys.exit(0)
- except Exception as e:
- if os.path.isfile(sys.executable + '.old'):
- shutil.move(sys.executable + '.old', sys.executable)
- self.console.print(f'[bold red]Update failed!\n\nReason: {str(e)}[/bold red]\n')
- self.print_log()
- pause(self.headless)
- sys.exit(1)
+ url = binary['browser_download_url']
+ break
+ if url and Version(remoteversion[1:]) > Version(__version__):
+ self.console.print('[green]Updating CurseBreaker...[/green]')
+ shutil.move(sys.executable, f'{sys.executable}.old')
+ payload = requests.get(url, headers=HEADERS, timeout=10)
+ if self.os == 'Darwin':
+ zipfile.ZipFile(io.BytesIO(payload.content)).extractall(path=os.path.dirname(
+ os.path.abspath(sys.executable)))
+ else:
+ with open(sys.executable, 'wb') as f:
+ if self.os == 'Windows':
+ f.write(payload.content)
+ elif self.os == 'Linux':
+ f.write(gzip.decompress(payload.content))
+ os.chmod(sys.executable, 0o775)
+ self.console.print(f'[bold green]Update complete! The application will be restarted now.'
+ f'[/bold green]\n\n[green]Changelog:[/green]\n{changelog}\n')
+ self.print_log()
+ pause(self.headless)
+ subprocess.call([sys.executable] + sys.argv[1:])
+ sys.exit(0)
+ except Exception as e:
+ if os.path.isfile(f'{sys.executable}.old'):
+ shutil.move(f'{sys.executable}.old', sys.executable)
+ self.console.print(f'[bold red]Update failed!\n\nReason: {str(e)}[/bold red]\n')
+ self.print_log()
+ pause(self.headless)
+ sys.exit(1)
def motd_parser(self):
payload = requests.get('https://storage.googleapis.com/cursebreaker/motd', headers=HEADERS, timeout=5)
@@ -285,13 +274,13 @@ def handle_exception(self, e, table=True):
self.console.print(Traceback.from_exception(exc_type=e.__class__, exc_value=e,
traceback=e.__traceback__, width=width))
+ # noinspection PyUnboundLocalVariable
def handle_keypress(self, wait):
if not self.headless:
kb = KBHit()
starttime = time.time()
keypress = None
while True:
- # noinspection PyUnboundLocalVariable
if self.headless:
break
elif kb.kbhit():
@@ -304,6 +293,11 @@ def handle_keypress(self, wait):
kb.set_normal_term()
return keypress
+ def handle_shutdown(self, message):
+ self.console.print(message)
+ pause(self.headless)
+ sys.exit(1)
+
def print_header(self):
if self.headless:
self.console.print(f'[bold green]CurseBreaker[/bold green] [bold red]v{__version__}[/bold red] | '
@@ -321,26 +315,26 @@ def print_log(self):
log.write(html)
def print_author_reminder(self):
- if random.randint(1, 10) == 1:
- addon = random.choice(self.core.config['Addons'])
- project_url = addon['URL']
- if not project_url.startswith('https://'):
- project_url = project_url.lower()
- if not project_url.endswith(':dev'):
- project_url = f'{project_url}:dev'
- project_url = self.core.masterConfig['CustomRepository'][project_url]['SupportURL']
- self.console.print(Panel(f'Hey! You use [bold white]{addon["Name"]}[/bold white] quite often. Maybe it\'s t'
- f'ime to check out the project page how you can support the author(s)? [link='
- f'{project_url}]{project_url}[/link]', title=':sparkles: Support :sparkles:',
- border_style='yellow'))
- self.console.print('')
+ if random.randint(1, 10) != 1:
+ return
+ addon = random.choice(self.core.config['Addons'])
+ project_url = addon['URL']
+ if not project_url.startswith('https://'):
+ project_url = project_url.lower()
+ if not project_url.endswith(':dev'):
+ project_url = f'{project_url}:dev'
+ project_url = self.core.masterConfig['CustomRepository'][project_url]['SupportURL']
+ self.console.print(Panel(f'Hey! You use [bold white]{addon["Name"]}[/bold white] quite often. Maybe it\'s t'
+ f'ime to check out the project page how you can support the author(s)? [link='
+ f'{project_url}]{project_url}[/link]', title=':sparkles: Support :sparkles:',
+ border_style='yellow'))
+ self.console.print('')
def setup_console(self):
if self.headless:
self.console = Console(record=True)
if self.os == 'Windows':
- window = windll.kernel32.GetConsoleWindow()
- if window:
+ if window := windll.kernel32.GetConsoleWindow():
windll.user32.ShowWindow(window, 0)
elif detect_legacy_windows():
set_terminal_size(100, 50)
@@ -371,9 +365,7 @@ def setup_completer(self):
slugs.append(f'gh:{item}')
for item in self.slugs['custom']:
slugs.append(f'{item}')
- accounts = []
- for account in self.core.detect_accounts():
- accounts.append(account)
+ accounts = list(self.core.detect_accounts())
self.completer = NestedCompleter.from_nested_dict({
'install': WordCompleter(slugs, ignore_case=True, match_middle=True, WORD=True),
'uninstall': WordCompleter(addons, ignore_case=True),
@@ -456,10 +448,10 @@ def c_install(self, args):
if '-i' in pargs:
optignore = True
args = args.replace('-i', '', 1)
- args = re.sub(r'([a-zA-Z0-9_:])([ ]+)([a-zA-Z0-9_:])', r'\1,\3', args)
+ args = re.sub(r'([a-zA-Z0-9_:])( +)([a-zA-Z0-9_:])', r'\1,\3', args)
addons = [re.sub(r'[\[\]]', '', addon).strip() for addon in list(reader([args], skipinitialspace=True))[0]]
exceptions = []
- if len(addons) > 0:
+ if addons:
if self.core.clientType != 'retail':
for addon in addons:
if addon.startswith('https://www.wowinterface.com/downloads/') or addon.startswith('wowi:'):
@@ -484,7 +476,7 @@ def c_install(self, args):
exceptions.append(e)
progress.update(task, advance=1, refresh=True)
self.console.print(self.table)
- if len(exceptions) > 0:
+ if exceptions:
self.handle_exception(exceptions, False)
else:
self.console.print('[green]Usage:[/green]\n\tThis command accepts a space-separated list of links as an arg'
@@ -512,10 +504,10 @@ def c_uninstall(self, args):
for addon in addons:
name, version = self.core.del_addon(addon, optkeep)
if name:
- self.table.add_row(f'[bold red]Uninstalled[/bold red]',
+ self.table.add_row('[bold red]Uninstalled[/bold red]',
Text(name, no_wrap=True), Text(version, no_wrap=True))
else:
- self.table.add_row(f'[bold black]Not installed[/bold black]',
+ self.table.add_row('[bold black]Not installed[/bold black]',
Text(addon, no_wrap=True), Text('', no_wrap=True))
progress.update(task, advance=1, refresh=True)
self.console.print(self.table)
@@ -540,10 +532,8 @@ def c_update(self, args, addline=False, update=True, force=False, reverseprovide
auto_refresh=False, console=None if self.headless else self.console) as progress:
task = progress.add_task('', total=len(addons))
if not args:
- try:
+ with suppress(RuntimeError):
self.core.bulk_check(addons)
- except RuntimeError:
- pass
self.core.bulk_check_checksum(addons, progress)
while not progress.finished:
for addon in addons:
@@ -562,25 +552,22 @@ def c_update(self, args, addline=False, update=True, force=False, reverseprovide
payload = [f'[bold red]Modified[/bold red]{additionalstatus}',
self.parse_link(name, sourceurl, authors=authors),
self.parse_link(versionold, changelog, dstate, uiversion=uiversion)]
+ elif compact and compacted > -1 and source != 'Unsupported':
+ compacted += 1
else:
- if compact and compacted > -1 and source != 'Unsupported':
- compacted += 1
- else:
- payload = [f'[green]Up-to-date[/green]{additionalstatus}',
- self.parse_link(name, sourceurl, authors=authors),
- self.parse_link(versionold, changelog, dstate,
- uiversion=uiversion)]
- else:
- if modified or blocked:
- payload = [f'[bold red]Update suppressed[/bold red]{additionalstatus}',
+ payload = [f'[green]Up-to-date[/green]{additionalstatus}',
self.parse_link(name, sourceurl, authors=authors),
self.parse_link(versionold, changelog, dstate, uiversion=uiversion)]
- else:
- version = self.parse_link(versionnew, changelog, dstate, uiversion=uiversion)
- version.stylize('yellow')
- payload = [f'[yellow]{"Updated" if update else "Update available"}[/yellow]'
- f'{additionalstatus}', self.parse_link(name, sourceurl,
- authors=authors), version]
+ elif modified or blocked:
+ payload = [f'[bold red]Update suppressed[/bold red]{additionalstatus}',
+ self.parse_link(name, sourceurl, authors=authors),
+ self.parse_link(versionold, changelog, dstate, uiversion=uiversion)]
+ else:
+ version = self.parse_link(versionnew, changelog, dstate, uiversion=uiversion)
+ version.stylize('yellow')
+ payload = [f'[yellow]{"Updated" if update else "Update available"}[/yellow]'
+ f'{additionalstatus}', self.parse_link(name, sourceurl,
+ authors=authors), version]
else:
payload = [f'[bold black]Not installed[/bold black]{additionalstatus}',
Text(addon, no_wrap=True), Text('', no_wrap=True)]
@@ -599,26 +586,23 @@ def c_update(self, args, addline=False, update=True, force=False, reverseprovide
self.console.print(self.table)
if compacted > 0:
self.console.print(f'Additionally [green]{compacted}[/green] addons are up-to-date.')
- overlap = self.core.check_if_overlap()
- if overlap:
+ if overlap := self.core.check_if_overlap():
self.console.print(f'\n[bold red]Detected addon directory overlap. This will cause issues. Affected add'
f'ons:[/bold red]\n{overlap}')
else:
self.console.print('Apparently there are no addons installed by CurseBreaker (or you provided incorrect add'
'on name).\nCommand [green]import[/green] might be used to detect already installed addo'
'ns.', highlight=False)
- if len(exceptions) > 0:
+ if exceptions:
self.handle_exception(exceptions, False)
+ # noinspection PyTypeChecker
def c_force_update(self, args):
if args:
self.c_update(args, False, True, True)
- else:
- # noinspection PyTypeChecker
- answer = confirm(HTML('Execute a forced update of all addons and overwrite ALL local changes'
- '?'))
- if answer:
- self.c_update(False, False, True, True)
+ elif confirm(HTML('Execute a forced update of all addons and overwrite ALL local changes?')):
+ self.c_update(False, False, True, True)
def c_status(self, args):
optsource = False
@@ -660,8 +644,7 @@ def c_toggle(self, args):
if args:
args = args.strip()
if args.startswith('channel'):
- args = args[8:]
- if args:
+ if args := args[8:]:
status = self.core.dev_toggle(args)
if status is None:
self.console.print('[bold red]This addon doesn\'t exist or it is not installed yet.[/bold red]')
@@ -684,8 +667,7 @@ def c_toggle(self, args):
self.console.print('[green]Usage:[/green]\n\tThis command accepts an addon name (or "global") as an'
' argument.', highlight=False)
elif args.startswith('pinning'):
- args = args[8:]
- if args:
+ if args := args[8:]:
status = self.core.block_toggle(args)
if status is None:
self.console.print('[bold red]This addon does not exist or it is not installed yet.[/bold red]')
@@ -696,8 +678,7 @@ def c_toggle(self, args):
else:
self.console.print('[green]Usage:[/green]\n\tThis command accepts an addon name as an argument.')
elif args.startswith('wago'):
- args = args[5:]
- if args:
+ if args := args[5:]:
if args == self.core.config['WAUsername']:
self.console.print(f'Wago version check is now: [green]ENABLED[/green]\nEntries created by '
f'[bold white]{self.core.config["WAUsername"]}[/bold white] are now '
@@ -708,14 +689,13 @@ def c_toggle(self, args):
self.console.print(f'Wago version check is now: [green]ENABLED[/green]\nEntries created by '
f'[bold white]{self.core.config["WAUsername"]}[/bold white] are now '
f'ignored.')
+ elif self.core.config['WAUsername'] == 'DISABLED':
+ self.core.config['WAUsername'] = ''
+ self.console.print('Wago version check is now: [green]ENABLED[/green]')
else:
- if self.core.config['WAUsername'] == 'DISABLED':
- self.core.config['WAUsername'] = ''
- self.console.print('Wago version check is now: [green]ENABLED[/green]')
- else:
- self.core.config['WAUsername'] = 'DISABLED'
- shutil.rmtree(Path('Interface/AddOns/WeakAurasCompanion'), ignore_errors=True)
- self.console.print('Wago version check is now: [red]DISABLED[/red]')
+ self.core.config['WAUsername'] = 'DISABLED'
+ shutil.rmtree(Path('Interface/AddOns/WeakAurasCompanion'), ignore_errors=True)
+ self.console.print('Wago version check is now: [red]DISABLED[/red]')
self.core.save_config()
elif args.startswith('authors'):
status = self.core.generic_toggle('ShowAuthors')
@@ -753,48 +733,37 @@ def c_toggle(self, args):
'green]\n\t\tEnables/disables automatic Wago updates.\n\t\tIf a username is provided che'
'ck will start to ignore the specified author.', highlight=False)
+ def _c_set_parse(self, msg, key, value):
+ self.console.print(msg)
+ self.core.config[key] = value.strip()
+ self.core.save_config()
+
def c_set(self, args):
if args:
args = args.strip()
if args.startswith('wago_addons_api'):
- args = args[16:]
- if args:
- self.console.print('Wago Addons API key is now set.')
- self.core.config['WAAAPIKey'] = args.strip()
- self.core.save_config()
+ if args := args[16:]:
+ self._c_set_parse('Wago Addons API key is now set.', 'WAAAPIKey', args)
elif self.core.config['WAAAPIKey'] != '':
- self.console.print('Wago Addons API key is now removed.')
- self.core.config['WAAAPIKey'] = ''
- self.core.save_config()
+ self._c_set_parse('Wago Addons API key is now removed.', 'WAAAPIKey', '')
else:
self.console.print('[green]Usage:[/green]\n\tThis command accepts API key as an argument.')
elif args.startswith('wago_api'):
- args = args[9:]
- if args:
- self.console.print('Wago API key is now set.')
- self.core.config['WAAPIKey'] = args.strip()
- self.core.save_config()
+ if args := args[9:]:
+ self._c_set_parse('Wago API key is now set.', 'WAAPIKey', args)
elif self.core.config['WAAPIKey'] != '':
- self.console.print('Wago API key is now removed.')
- self.core.config['WAAPIKey'] = ''
- self.core.save_config()
+ self._c_set_parse('Wago API key is now removed.', 'WAAPIKey', '')
else:
self.console.print('[green]Usage:[/green]\n\tThis command accepts API key as an argument.')
elif args.startswith('gh_api'):
- args = args[7:]
- if args:
- self.console.print('GitHub API key is now set.')
- self.core.config['GHAPIKey'] = args.strip()
- self.core.save_config()
+ if args := args[7:]:
+ self._c_set_parse('GitHub API key is now set.', 'GHAPIKey', args)
elif self.core.config['GHAPIKey'] != '':
- self.console.print('GitHub API key is now removed.')
- self.core.config['GHAPIKey'] = ''
- self.core.save_config()
+ self._c_set_parse('GitHub API key is now removed.', 'GHAPIKey', '')
else:
self.console.print('[green]Usage:[/green]\n\tThis command accepts API key as an argument.')
elif args.startswith('wago_wow_account'):
- args = args[17:]
- if args:
+ if args := args[17:]:
args = args.strip()
if os.path.isfile(Path(f'WTF/Account/{args}/SavedVariables/WeakAuras.lua')) or \
os.path.isfile(Path(f'WTF/Account/{args}/SavedVariables/Plater.lua')):
@@ -819,7 +788,7 @@ def c_set(self, args):
'count.\n\t[green]set gh_api [API key][/green]\n\t\tSets GitHub API key. Might be needed'
' to get around API rate limits.', highlight=False)
- def c_wago_update(self, _, verbose=True, flush=True):
+ def c_wago_update(self, _, verbose=True, flush=True): # sourcery skip: extract-duplicate-method
if os.path.isdir(Path('Interface/AddOns/WeakAuras')) or os.path.isdir(Path('Interface/AddOns/Plater')):
accounts = self.core.detect_accounts()
if self.core.config['WAAccountName'] != '' and self.core.config['WAAccountName'] not in accounts:
@@ -904,7 +873,7 @@ def c_import(self, args):
if args == 'install' and len(slugs) > 0:
self.c_install(','.join(slugs))
else:
- self.console.print(f'[green]New addons found:[/green]')
+ self.console.print('[green]New addons found:[/green]')
for addon in names:
self.console.print(addon, highlight=False)
self.console.print(f'\n[yellow]Already installed addons:[/yellow]')
@@ -983,8 +952,7 @@ def c_exit(self, _):
if __name__ == '__main__':
freeze_support()
- clientpath = os.environ.get('CURSEBREAKER_PATH')
- if clientpath:
+ if clientpath := os.environ.get('CURSEBREAKER_PATH'):
os.chdir(clientpath)
elif getattr(sys, 'frozen', False):
os.chdir(os.path.dirname(os.path.abspath(sys.executable)))
diff --git a/CurseBreaker.spec b/CurseBreaker.spec
index 5615326..14a2d97 100644
--- a/CurseBreaker.spec
+++ b/CurseBreaker.spec
@@ -1,3 +1,6 @@
+import sys
+sys.path.append(SPECPATH)
+
import platform
from CB import __version__ as version
diff --git a/Pipfile b/Pipfile
index 2b27d10..a60846d 100644
--- a/Pipfile
+++ b/Pipfile
@@ -15,6 +15,7 @@ bbcode = "*"
rich = "*"
[dev-packages]
+sourcery = "*"
[requires]
python_version = "3.12"
diff --git a/Pipfile.lock b/Pipfile.lock
index f959bce..b77308a 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "a8ceab836cd8add089859509237fe87211e31d27dd244171bc3ad3bff8078926"
+ "sha256": "5575c6f9afc617cac84f49205b436b0bbf7f88ff4936441e430c0779a6b611a7"
},
"pipfile-spec": 6,
"requires": {
@@ -250,5 +250,15 @@
"version": "==0.2.13"
}
},
- "develop": {}
+ "develop": {
+ "sourcery": {
+ "hashes": [
+ "sha256:5c2d7723b7c1bbd3a904fa2d85ef074b9ef1091c917a4e8c57a88b70bcda0699",
+ "sha256:5d8ab8f61d0a55418b5debb999f76538d8e946a9a69a9e0be66593c1e17ff20f",
+ "sha256:ab3438770f4f1535a2a4d872f5a84d12773eeb00825b96a5f2ed321ec3912b5e"
+ ],
+ "index": "pypi",
+ "version": "==1.15.0"
+ }
+ }
}