redfetch 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- redfetch/__about__.py +24 -0
- redfetch/__init__.py +0 -0
- redfetch/api.py +134 -0
- redfetch/auth.py +573 -0
- redfetch/config.py +483 -0
- redfetch/config_firstrun.py +455 -0
- redfetch/desktop_shortcut.py +81 -0
- redfetch/detecteq.py +116 -0
- redfetch/download.py +312 -0
- redfetch/listener.py +216 -0
- redfetch/main.py +744 -0
- redfetch/meta.py +561 -0
- redfetch/navmesh.py +371 -0
- redfetch/net.py +109 -0
- redfetch/processes.py +118 -0
- redfetch/push.py +246 -0
- redfetch/redfetch.ico +0 -0
- redfetch/runtime_errors.py +96 -0
- redfetch/settings.toml +303 -0
- redfetch/special.py +81 -0
- redfetch/store.py +505 -0
- redfetch/sync.py +261 -0
- redfetch/sync_discovery.py +352 -0
- redfetch/sync_executor.py +164 -0
- redfetch/sync_planner.py +196 -0
- redfetch/sync_remote.py +182 -0
- redfetch/sync_types.py +348 -0
- redfetch/terminal_ui.py +2318 -0
- redfetch/terminal_ui.tcss +569 -0
- redfetch/unloadmq.py +148 -0
- redfetch/update_status.py +62 -0
- redfetch/utils.py +256 -0
- redfetch-1.3.0.dist-info/METADATA +257 -0
- redfetch-1.3.0.dist-info/RECORD +37 -0
- redfetch-1.3.0.dist-info/WHEEL +4 -0
- redfetch-1.3.0.dist-info/entry_points.txt +2 -0
- redfetch-1.3.0.dist-info/licenses/LICENSE +674 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Writes update_status.json after `redfetch check` completes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from typing import Literal
|
|
8
|
+
|
|
9
|
+
from redfetch import config
|
|
10
|
+
from redfetch.sync_types import ExecutionPlan
|
|
11
|
+
|
|
12
|
+
SCHEMA_VERSION = 1
|
|
13
|
+
UPDATE_STATUS_FILENAME = "update_status.json"
|
|
14
|
+
|
|
15
|
+
AuthState = Literal["ok", "needs_login", "not_configured"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def update_status_path() -> str:
|
|
19
|
+
"""Location beside last_command.json so external apps need only one path."""
|
|
20
|
+
return os.path.join(config.DEFAULT_CONFIG_DIR, UPDATE_STATUS_FILENAME)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def build_items_from_plan(execution_plan: ExecutionPlan) -> list[dict]:
|
|
24
|
+
"""Only stuff we're actually going to download (opt-outs, blocks, and new installs aren't in the plan)."""
|
|
25
|
+
items: list[dict] = []
|
|
26
|
+
for action in execution_plan.actions.values():
|
|
27
|
+
if action.action != "download" or action.reason != "outdated":
|
|
28
|
+
continue
|
|
29
|
+
items.append(
|
|
30
|
+
{
|
|
31
|
+
"resource_id": action.resource_id,
|
|
32
|
+
"name": action.title or action.resource_id,
|
|
33
|
+
"available_version_id": action.remote_version,
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
return items
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def write_update_status(
|
|
40
|
+
*,
|
|
41
|
+
env: str,
|
|
42
|
+
auth_state: AuthState,
|
|
43
|
+
items: list[dict] | None = None,
|
|
44
|
+
managed_path: str | None = None,
|
|
45
|
+
) -> dict:
|
|
46
|
+
"""Write update_status.json and return the payload. Only auth_state "ok" carries items."""
|
|
47
|
+
items = items or []
|
|
48
|
+
if auth_state != "ok":
|
|
49
|
+
items = []
|
|
50
|
+
|
|
51
|
+
payload = {
|
|
52
|
+
"schema_version": SCHEMA_VERSION,
|
|
53
|
+
"checked_at": int(datetime.now(timezone.utc).timestamp()),
|
|
54
|
+
"env": env.upper(),
|
|
55
|
+
"auth_state": auth_state,
|
|
56
|
+
"managed_path": managed_path,
|
|
57
|
+
"updates": {
|
|
58
|
+
"items": items,
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
config.atomic_write_json(update_status_path(), payload)
|
|
62
|
+
return payload
|
redfetch/utils.py
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""Miscellaneous helpers, mostly path resolution and URL parsing."""
|
|
2
|
+
|
|
3
|
+
# Standard
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import shlex
|
|
7
|
+
import sys
|
|
8
|
+
from urllib.parse import urlparse
|
|
9
|
+
|
|
10
|
+
# Local
|
|
11
|
+
from redfetch import config
|
|
12
|
+
|
|
13
|
+
#
|
|
14
|
+
# path functions
|
|
15
|
+
#
|
|
16
|
+
|
|
17
|
+
def get_base_path() -> str:
|
|
18
|
+
"""Determine the base path based on the active version."""
|
|
19
|
+
return get_vvmq_path() or get_current_download_folder()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def resolve_special_destination(special_resource: dict | None, download_folder: str) -> str | None:
|
|
23
|
+
"""Resolve a special resource destination path without side effects."""
|
|
24
|
+
if not special_resource:
|
|
25
|
+
return None
|
|
26
|
+
custom_path = special_resource.get("custom_path")
|
|
27
|
+
if custom_path:
|
|
28
|
+
return os.path.normpath(os.path.realpath(custom_path))
|
|
29
|
+
default_path = special_resource.get("default_path")
|
|
30
|
+
if default_path:
|
|
31
|
+
return os.path.normpath(os.path.join(download_folder, default_path))
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _resolve_current_special_path(resource_id: str) -> str | None:
|
|
36
|
+
"""Resolve the path for a special resource in the current environment."""
|
|
37
|
+
settings = config.settings.from_env(config.settings.ENV)
|
|
38
|
+
return resolve_special_destination(
|
|
39
|
+
settings.SPECIAL_RESOURCES.get(resource_id), settings.DOWNLOAD_FOLDER
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def is_safe_path(base_directory: str, target_path: str) -> bool:
|
|
44
|
+
"""Check for directory traversal."""
|
|
45
|
+
abs_base = os.path.realpath(base_directory)
|
|
46
|
+
abs_target = os.path.realpath(target_path)
|
|
47
|
+
return os.path.commonpath([abs_base, abs_target]) == abs_base
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_current_vvmq_id(settings_env: str | None = None) -> str | None:
|
|
51
|
+
env = (settings_env or config.settings.ENV).upper()
|
|
52
|
+
for resource_id, env_name in config.VANILLA_MAP.items():
|
|
53
|
+
if env_name.upper() == env:
|
|
54
|
+
return str(resource_id)
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_vvmq_path() -> str | None:
|
|
59
|
+
vvmq_id = get_current_vvmq_id()
|
|
60
|
+
if not vvmq_id:
|
|
61
|
+
return None
|
|
62
|
+
return _resolve_current_special_path(vvmq_id)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_current_myseq_id() -> str | None:
|
|
66
|
+
current_env = config.settings.ENV.upper()
|
|
67
|
+
for resource_id, env_name in config.MYSEQ_MAP.items():
|
|
68
|
+
if env_name.upper() == current_env:
|
|
69
|
+
return str(resource_id)
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_myseq_path() -> str | None:
|
|
74
|
+
myseq_id = get_current_myseq_id()
|
|
75
|
+
if not myseq_id:
|
|
76
|
+
return None
|
|
77
|
+
return _resolve_current_special_path(myseq_id)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def get_current_download_folder() -> str:
|
|
81
|
+
return os.path.normpath(config.settings.from_env(config.settings.ENV).DOWNLOAD_FOLDER)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def get_eq_maps_status() -> str | None:
|
|
85
|
+
"""Get the status of EQ maps (Brewall's and Good's)."""
|
|
86
|
+
special_resources = config.settings.from_env(config.settings.ENV).SPECIAL_RESOURCES
|
|
87
|
+
brewall_opt_in = special_resources.get('153', {}).get('opt_in', False)
|
|
88
|
+
good_opt_in = special_resources.get('303', {}).get('opt_in', False)
|
|
89
|
+
|
|
90
|
+
if brewall_opt_in and good_opt_in:
|
|
91
|
+
return "all"
|
|
92
|
+
elif brewall_opt_in:
|
|
93
|
+
return "brewall"
|
|
94
|
+
elif good_opt_in:
|
|
95
|
+
return "good"
|
|
96
|
+
else:
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def parse_resource_id(input_string: str) -> str:
|
|
101
|
+
# Check if it's already a number
|
|
102
|
+
if input_string.isdigit():
|
|
103
|
+
return input_string
|
|
104
|
+
|
|
105
|
+
# Parse the URL
|
|
106
|
+
parsed_url = urlparse(input_string)
|
|
107
|
+
|
|
108
|
+
# Check if it's a redguides.com URL
|
|
109
|
+
if not parsed_url.netloc.endswith('redguides.com'):
|
|
110
|
+
print(f"Invalid URL: Neither a redguides.com URL nor a valid resource id")
|
|
111
|
+
raise ValueError("Invalid URL: Neither a redguides.com URL nor a valid resource id")
|
|
112
|
+
|
|
113
|
+
# Check if it's a thread URL
|
|
114
|
+
if 'threads' in parsed_url.path:
|
|
115
|
+
print(f"Invalid URL: This appears to be a discussion thread, not a resource")
|
|
116
|
+
raise ValueError("Invalid URL: This appears to be a discussion thread, not a resource")
|
|
117
|
+
|
|
118
|
+
# Extract the resource ID using regex
|
|
119
|
+
match = re.search(r'\.(\d+)(?:/|$)', parsed_url.path)
|
|
120
|
+
if match:
|
|
121
|
+
return str(match.group(1))
|
|
122
|
+
else:
|
|
123
|
+
print(f"Could not find a valid resource ID in the URL")
|
|
124
|
+
raise ValueError("Could not find a valid resource ID in the URL")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def validate_file_in_path(path: str | None, filename: str) -> bool:
|
|
128
|
+
"""Validate that the given path contains a specific file."""
|
|
129
|
+
if not path:
|
|
130
|
+
return False
|
|
131
|
+
try:
|
|
132
|
+
return os.path.isfile(os.path.join(path, filename))
|
|
133
|
+
except (TypeError, ValueError):
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
#
|
|
138
|
+
# post-update launch
|
|
139
|
+
#
|
|
140
|
+
|
|
141
|
+
# Presets offered as "Also start post-update" toggles.
|
|
142
|
+
POST_UPDATE_PRESETS = {
|
|
143
|
+
"eqbcs": (get_vvmq_path, "EQBCS.exe"),
|
|
144
|
+
"myseq": (get_myseq_path, "MySEQ.exe"),
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
POST_UPDATE_PRESET_LABELS = {
|
|
148
|
+
"eqbcs": "EQBCS",
|
|
149
|
+
"myseq": "MySEQ",
|
|
150
|
+
"custom": "Custom",
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def post_update_launch_choices() -> list[tuple[str, str]]:
|
|
155
|
+
"""Return ordered ``(value, label)`` toggle choices (presets + custom)."""
|
|
156
|
+
choices: list[tuple[str, str]] = [
|
|
157
|
+
(key, POST_UPDATE_PRESET_LABELS[key]) for key in POST_UPDATE_PRESETS
|
|
158
|
+
]
|
|
159
|
+
choices.append(("custom", POST_UPDATE_PRESET_LABELS["custom"]))
|
|
160
|
+
return choices
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def get_post_update_targets(env: str | None = None) -> list[str]:
|
|
164
|
+
"""Return enabled post-update targets for ``env`` (``targets``, or legacy ``target``)."""
|
|
165
|
+
env = env or config.settings.ENV
|
|
166
|
+
cfg = config.settings.from_env(env).get("POST_UPDATE_LAUNCH", {})
|
|
167
|
+
raw = cfg.get("targets")
|
|
168
|
+
if raw is None:
|
|
169
|
+
single = cfg.get("target")
|
|
170
|
+
raw = [single] if single else []
|
|
171
|
+
elif isinstance(raw, str):
|
|
172
|
+
raw = [raw]
|
|
173
|
+
|
|
174
|
+
result: list[str] = []
|
|
175
|
+
for item in raw:
|
|
176
|
+
value = str(item).strip().lower()
|
|
177
|
+
if value and value != "none" and value not in result:
|
|
178
|
+
result.append(value)
|
|
179
|
+
return result
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _command_program(command: list[str] | str) -> str:
|
|
183
|
+
"""Return the program (first token) of a command list or string."""
|
|
184
|
+
if isinstance(command, str):
|
|
185
|
+
s = command.strip()
|
|
186
|
+
if s.startswith('"'):
|
|
187
|
+
end = s.find('"', 1)
|
|
188
|
+
return s[1:end] if end != -1 else s[1:]
|
|
189
|
+
return s.split(None, 1)[0] if s else ""
|
|
190
|
+
return str(command[0]) if command else ""
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def resolve_post_update_launch(
|
|
194
|
+
env: str | None = None,
|
|
195
|
+
) -> list[tuple[list[str] | str, str | None]]:
|
|
196
|
+
"""Resolve enabled targets for ``env`` to ``(command, cwd)`` pairs, skipping unresolvable ones."""
|
|
197
|
+
env = env or config.settings.ENV
|
|
198
|
+
resolved: list[tuple[list[str] | str, str | None]] = []
|
|
199
|
+
for target in get_post_update_targets(env):
|
|
200
|
+
item = _resolve_launch_target(target, env)
|
|
201
|
+
if item:
|
|
202
|
+
resolved.append(item)
|
|
203
|
+
return resolved
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _resolve_launch_target(
|
|
207
|
+
target: str,
|
|
208
|
+
env: str | None = None,
|
|
209
|
+
) -> tuple[list[str] | str, str | None] | None:
|
|
210
|
+
"""Resolve a single post-update ``target`` to a ``(command, cwd)`` pair."""
|
|
211
|
+
env = env or config.settings.ENV
|
|
212
|
+
cfg = config.settings.from_env(env).get("POST_UPDATE_LAUNCH", {})
|
|
213
|
+
|
|
214
|
+
if target == "custom":
|
|
215
|
+
command = cfg.get("command")
|
|
216
|
+
if not command:
|
|
217
|
+
print("Post-update launch is set to Custom, but no command is configured; skipping.")
|
|
218
|
+
return None
|
|
219
|
+
|
|
220
|
+
is_ps1 = (
|
|
221
|
+
sys.platform == "win32"
|
|
222
|
+
and _command_program(command).lower().endswith(".ps1")
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
if isinstance(command, str):
|
|
226
|
+
command = command.strip()
|
|
227
|
+
if not command:
|
|
228
|
+
print("Post-update launch command is empty; skipping.")
|
|
229
|
+
return None
|
|
230
|
+
if sys.platform == "win32":
|
|
231
|
+
if is_ps1:
|
|
232
|
+
command = (
|
|
233
|
+
"powershell -NoProfile -ExecutionPolicy Bypass -File " + command
|
|
234
|
+
)
|
|
235
|
+
return (command, None)
|
|
236
|
+
return (shlex.split(command, posix=True), None)
|
|
237
|
+
|
|
238
|
+
if not isinstance(command, (list, tuple)):
|
|
239
|
+
raise TypeError("POST_UPDATE_LAUNCH command must be a string or list.")
|
|
240
|
+
argv = [str(part) for part in command]
|
|
241
|
+
if not argv:
|
|
242
|
+
print("Post-update launch command is empty; skipping.")
|
|
243
|
+
return None
|
|
244
|
+
if is_ps1:
|
|
245
|
+
argv = ["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", *argv]
|
|
246
|
+
return (argv, None)
|
|
247
|
+
|
|
248
|
+
preset = POST_UPDATE_PRESETS.get(target)
|
|
249
|
+
if not preset:
|
|
250
|
+
print(f"Unknown POST_UPDATE_LAUNCH target: {target}; skipping.")
|
|
251
|
+
return None
|
|
252
|
+
resolver, exe = preset
|
|
253
|
+
folder = resolver()
|
|
254
|
+
if folder and validate_file_in_path(folder, exe):
|
|
255
|
+
return ([os.path.join(folder, exe)], folder)
|
|
256
|
+
return None
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: redfetch
|
|
3
|
+
Version: 1.3.0
|
|
4
|
+
Summary: Download and publish EverQuest scripts and software using the RedGuides API
|
|
5
|
+
Project-URL: Homepage, https://www.redguides.com
|
|
6
|
+
Project-URL: Documentation, https://www.redguides.com/community/resources/redfetch.3177/
|
|
7
|
+
Project-URL: Repository, https://github.com/RedGuides/redfetch
|
|
8
|
+
Project-URL: Issues, https://github.com/RedGuides/redfetch/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/RedGuides/redfetch/blob/main/CHANGELOG.md
|
|
10
|
+
Project-URL: Source_archive, https://github.com/RedGuides/redfetch/archive/aaf2d42ab82d45abeadfafa943e2b61abba2cb60.zip
|
|
11
|
+
Author-email: Redbot <ask@redguides.com>
|
|
12
|
+
License-Expression: GPL-3.0-or-later
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
16
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Utilities
|
|
20
|
+
Requires-Python: >=3.12
|
|
21
|
+
Requires-Dist: aiofiles~=25.1
|
|
22
|
+
Requires-Dist: aiohttp~=3.14
|
|
23
|
+
Requires-Dist: aiosqlite~=0.22
|
|
24
|
+
Requires-Dist: cachetools~=7.1
|
|
25
|
+
Requires-Dist: diskcache~=5.6
|
|
26
|
+
Requires-Dist: dynaconf<3.3,~=3.2
|
|
27
|
+
Requires-Dist: httpx[http2]~=0.28
|
|
28
|
+
Requires-Dist: keepachangelog~=2.0
|
|
29
|
+
Requires-Dist: keyring~=25.7
|
|
30
|
+
Requires-Dist: md2bbcode
|
|
31
|
+
Requires-Dist: packaging~=26.2
|
|
32
|
+
Requires-Dist: platformdirs~=4.10
|
|
33
|
+
Requires-Dist: psutil~=7.2
|
|
34
|
+
Requires-Dist: pydantic~=2.13
|
|
35
|
+
Requires-Dist: pyperclip~=1.11
|
|
36
|
+
Requires-Dist: pywin32; sys_platform == 'win32'
|
|
37
|
+
Requires-Dist: rich~=15.0
|
|
38
|
+
Requires-Dist: tenacity~=9.1
|
|
39
|
+
Requires-Dist: textual-fspicker~=1.0
|
|
40
|
+
Requires-Dist: textual~=8.2
|
|
41
|
+
Requires-Dist: tomlkit~=0.15
|
|
42
|
+
Requires-Dist: typer~=0.26
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
|
|
45
|
+

|
|
46
|
+
|
|
47
|
+
redfetch is for updating software and scripts for EverQuest that RedGuides recommends, as well as those you "[watch](https://www.redguides.com/community/watched/resources)". It's also open source, how nice.
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
On Windows the easiest way to install redfetch is to [download](https://www.redguides.com/community/resources/redfetch.3177/download) and run [`redfetch.exe`](https://www.redguides.com/community/resources/redfetch.3177/download). (*optional: If you're still on Windows 10 and want a more modern appearance, follow [this guide](https://www.redguides.com/community/threads/redfetch.92998/post-634938) to set [Windows Terminal](https://www.redguides.com/community/threads/redfetch.92998/post-634938) as your default terminal.*)
|
|
52
|
+
|
|
53
|
+
<details>
|
|
54
|
+
<summary>Terminal / Python / Linux</summary>
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
Make sure you have a recent version of [Python](https://www.python.org/downloads/)
|
|
58
|
+
|
|
59
|
+
1) **Install pipx**
|
|
60
|
+
```bash
|
|
61
|
+
python -m pip install --user pipx
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
2) **Make it so you can run packages without having to type "python -m"**
|
|
65
|
+
```bash
|
|
66
|
+
python -m pipx ensurepath
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
3) **Install redfetch**
|
|
70
|
+
```bash
|
|
71
|
+
pipx install redfetch
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
When you open a new terminal window you'll be able to run redfetch by typing `redfetch` from the command line.
|
|
75
|
+
|
|
76
|
+
</details>
|
|
77
|
+
|
|
78
|
+
## Usage
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
### 1) Double-click [`redfetch.exe`](https://www.redguides.com/community/resources/redfetch.3177/download) to run the script.
|
|
82
|
+
Take a moment to consider your configuration and the settings tab.
|
|
83
|
+
|
|
84
|
+
### 2) Click the big blue "Easy Update" button, and then "Yes" or "Always" on the popup.
|
|
85
|
+

|
|
86
|
+
(It's updating *Very Vanilla MQ* and any of its scripts or plugins you have [watched on RedGuides](https://www.redguides.com/community/watched/resources), your licensed resources, and scripts recommended by staff. You can customize this if you like.)
|
|
87
|
+
|
|
88
|
+
Now you're ready to play EQ with the big boys.
|
|
89
|
+
|
|
90
|
+
## Add more MQ Scripts
|
|
91
|
+
To add more MacroQuest scripts, "watch" them on [www.redguides.com/community/resources](https://www.redguides.com/community/resources), and then click the *Easy Update* button again.
|
|
92
|
+
|
|
93
|
+

|
|
94
|
+
|
|
95
|
+
If there are non-MQ resources you'd like to keep in sync with redfetch, you can add them as a "special resource" in the local settings file, as shown in settings section.
|
|
96
|
+
|
|
97
|
+
## Command Line
|
|
98
|
+
To run redfetch from the command line:
|
|
99
|
+
|
|
100
|
+
| .exe file | python |
|
|
101
|
+
|---------|-----------|
|
|
102
|
+
| `.\redfetch.exe update` | `redfetch update` |
|
|
103
|
+
|
|
104
|
+

|
|
105
|
+
|
|
106
|
+
## Command Line Reference
|
|
107
|
+
|
|
108
|
+
> Run `redfetch --help` or `.\redfetch.exe --help` to see something like this in your terminal:
|
|
109
|
+
>
|
|
110
|
+
> ### 📦 Resource Management
|
|
111
|
+
> - `update` - Update all watched and special resources
|
|
112
|
+
> - `--force` / `-f` - Force re-download of all watched resources
|
|
113
|
+
> - `--server` / `-s` - Switch to this server before updating (`LIVE`, `TEST`, `EMU`)
|
|
114
|
+
> - `download <ID_OR_URL>` - Download a specific resource by ID or URL
|
|
115
|
+
> - `--force` / `-f` - Force re-download by resetting this resource's download date
|
|
116
|
+
> - `--server` / `-s` - Switch to this server before downloading (`LIVE`, `TEST`, `EMU`)
|
|
117
|
+
> - `list` - List resources and dependencies in the cache database
|
|
118
|
+
> - `reset` - Reset download dates for watched resources
|
|
119
|
+
>
|
|
120
|
+
> ### 🍔 Configuration
|
|
121
|
+
> - `server <SERVER>` - Switch the current server/environment to `LIVE`, `TEST`, or `EMU`
|
|
122
|
+
> - `config <SETTING_PATH> <VALUE>` - Update a setting by path and value
|
|
123
|
+
> - `SETTING_PATH` - Dot-separated setting path (e.g., `SPECIAL_RESOURCES.1974.opt_in`)
|
|
124
|
+
> - `VALUE` - New value for the setting
|
|
125
|
+
> - `--server` / `-s` - Server to apply the change in (`LIVE`, `TEST`, `EMU`)
|
|
126
|
+
> - `status` - Show the configuration for the current or specified server
|
|
127
|
+
> - `--server` / `-s` - Server to show (defaults to current)
|
|
128
|
+
>
|
|
129
|
+
> ### 🔧 System & Utilities
|
|
130
|
+
> - `ui` - Launch the Terminal User Interface
|
|
131
|
+
> - `web` - Launch the RedGuides.com web interface
|
|
132
|
+
> - `version` - Show version and exit
|
|
133
|
+
> - `logout` - Disconnect your account from redfetch
|
|
134
|
+
> - `uninstall` - Uninstall redfetch and clean up data
|
|
135
|
+
>
|
|
136
|
+
> ### 📤 Publishing
|
|
137
|
+
> - `publish <resource_id>` - Publish an update to you or your team's resource. [There's also a github action for this.](https://github.com/marketplace/actions/redguides-publish)
|
|
138
|
+
> - `resource_id` - Existing RedGuides resource ID
|
|
139
|
+
> - `--description <README.md>` / `-d` - Path to a file (e.g. `README.md`) that will become the resource's overview description
|
|
140
|
+
> - `--version <version_number>` / `-v` - New version string (e.g., `v1.0.1`)
|
|
141
|
+
> - `--message <CHANGELOG.md | MESSAGE>` / `-m` - Version update message, a message file (e.g. `message.md` / `message.txt`), or a `CHANGELOG.md` (keep a changelog) file.
|
|
142
|
+
> - `--file <FILE.zip>` / `-f` - Path to your zipped release file
|
|
143
|
+
> - `--domain <URL>` - Domain to prepend to relative URLs in README.md or CHANGELOG.md files. (mostly for images. e.g., `https://raw.githubusercontent.com/yourusername/yourrepo/main/`)
|
|
144
|
+
|
|
145
|
+
## Settings
|
|
146
|
+
|
|
147
|
+
`settings.local.toml` is found in your configuration directory, which by default is `c:\Users\Public\redfetch\settings.local.toml`. Any keys you add will override their default values in [`settings.toml`](./src/redfetch/settings.toml).
|
|
148
|
+
|
|
149
|
+
All settings are prefixed with the environment,
|
|
150
|
+
|
|
151
|
+
- `[DEFAULT]` - encompasses all environments that are not explicitly defined.
|
|
152
|
+
- `[LIVE]` - EverQuest Live
|
|
153
|
+
- `[TEST]` - EverQuest Test
|
|
154
|
+
- `[EMU]` - EverQuest Emulator
|
|
155
|
+
|
|
156
|
+
### Adding a special resource
|
|
157
|
+
To add a "special resource" (a non-MQ resource that you want to keep updated), open `settings.local.toml` and add an entry. You'll need the [resource ID (numbers at the end of the url)](https://www.redguides.com/community/resources/brewalls-everquest-maps.153/) and a target directory. Example:
|
|
158
|
+
|
|
159
|
+
```toml
|
|
160
|
+
[LIVE.SPECIAL_RESOURCES.153]
|
|
161
|
+
custom_path = 'C:\Users\Public\Daybreak Game Company\Installed Games\EverQuest\maps\Brewall_Maps'
|
|
162
|
+
opt_in = true
|
|
163
|
+
```
|
|
164
|
+
* Note the use of single quotes around the path, which are required for windows paths.
|
|
165
|
+
|
|
166
|
+
The above will install Brewall's maps to the EQ maps directory the next time `--download-watched` is run for `LIVE` servers.
|
|
167
|
+
|
|
168
|
+
### Overwrite protection
|
|
169
|
+
|
|
170
|
+
If there are local files you don't want overwritten by a resource, you can add them to the `PROTECTED_FILES_BY_RESOURCE` setting. Include the resource ID and files you want to protect. e.g.,
|
|
171
|
+
|
|
172
|
+
```toml
|
|
173
|
+
[LIVE.PROTECTED_FILES_BY_RESOURCE]
|
|
174
|
+
1974 = ["CharSelect.cfg", "Zoned.cfg", "MQ2Map.ini", "MQ2MoveUtils.ini"]
|
|
175
|
+
153 = ["citymist.txt", "innothule.txt", "oasis.txt"]
|
|
176
|
+
navmesh = ["befallen.navmesh", "innothuleb.navmesh"]
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Custom category directories
|
|
180
|
+
|
|
181
|
+
If you share `lua`, `macros`, or `plugins` directories across multiple MQ environments, you can override where an entire category is installed. Add a `CATEGORY_PATHS` section to your `settings.local.toml`:
|
|
182
|
+
|
|
183
|
+
```toml
|
|
184
|
+
[DEFAULT.CATEGORY_PATHS]
|
|
185
|
+
lua = 'D:\\shared\\lua'
|
|
186
|
+
macros = 'D:\\shared\\macros'
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Absolute paths are used as-is. Relative paths are joined to `DOWNLOAD_FOLDER`. You can set this globally in `[DEFAULT]` or per-environment (`[LIVE.CATEGORY_PATHS]`, `[TEST.CATEGORY_PATHS]`, etc.).
|
|
190
|
+
|
|
191
|
+
## Tinkerers
|
|
192
|
+
|
|
193
|
+
If you self-compile MacroQuest or use a discord friend's copy, you can still keep your scripts and plugins in sync with redfetch by opting out of Very Vanilla:
|
|
194
|
+
|
|
195
|
+
```powershell
|
|
196
|
+
redfetch.exe config SPECIAL_RESOURCES.1974.opt_in false --server LIVE
|
|
197
|
+
redfetch.exe config SPECIAL_RESOURCES.60.opt_in false --server EMU
|
|
198
|
+
redfetch.exe config SPECIAL_RESOURCES.2218.opt_in false --server TEST
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Then assign the *Very Vanilla MQ* path to your self-compiled MacroQuest.
|
|
202
|
+
|
|
203
|
+
### Custom post-update launch
|
|
204
|
+
|
|
205
|
+
redfetch can launch extra programs after an update completes. Aside from the normal UI toggles, you can add `custom` to `POST_UPDATE_LAUNCH.targets` in `settings.local.toml`, then set `command` to whatever redfetch should run:
|
|
206
|
+
|
|
207
|
+
```toml
|
|
208
|
+
[LIVE.POST_UPDATE_LAUNCH]
|
|
209
|
+
targets = ["custom"]
|
|
210
|
+
command = ['C:\Tools\AfterRedfetch\after-update.exe', '--server', 'LIVE']
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
You can also combine it with the built-in post-update launches. For example, to start EQBCS and MySEQ and run your own Python script:
|
|
214
|
+
|
|
215
|
+
```toml
|
|
216
|
+
[LIVE.POST_UPDATE_LAUNCH]
|
|
217
|
+
targets = ["myseq", "custom", "eqbcs"]
|
|
218
|
+
command = ["C:\\Users\\Public\\Python\\python.exe", "C:\\Users\\Public\\redfetch\\after_update.py"]
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
You can set these per-server, e.g. `[TEST.POST_UPDATE_LAUNCH]`, or global `[DEFAULT.POST_UPDATE_LAUNCH]`.
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
## Trailmap
|
|
225
|
+
- Add custom buttons for "fetch" tab.
|
|
226
|
+
- Option: Close after update
|
|
227
|
+
- Indicate when updated VV is available
|
|
228
|
+
- Run from MQ
|
|
229
|
+
- Deeper integration with the forums
|
|
230
|
+
|
|
231
|
+

|
|
232
|
+
|
|
233
|
+
## Contributing
|
|
234
|
+
|
|
235
|
+
I'd love help, conceptually and technically. I'm not a developer and this is my first big python script.
|
|
236
|
+
|
|
237
|
+
> [!NOTE]
|
|
238
|
+
> This project is built with LLM assistance.
|
|
239
|
+
|
|
240
|
+
To set up a [development environment](https://hatch.pypa.io/latest/environment/),
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
git clone https://github.com/RedGuides/redfetch
|
|
244
|
+
cd redfetch
|
|
245
|
+
pip install hatch
|
|
246
|
+
hatch env create dev
|
|
247
|
+
hatch shell dev
|
|
248
|
+
```
|
|
249
|
+
You can then run your dev version with,
|
|
250
|
+
|
|
251
|
+
`redfetch`
|
|
252
|
+
|
|
253
|
+
Or if the issue is ui-specific, run the [terminal UI in debug mode](https://textual.textualize.io/guide/devtools/#live-editing),
|
|
254
|
+
|
|
255
|
+
`textual run --dev .\src\redfetch\main.py`
|
|
256
|
+
|
|
257
|
+
When you're done, type `exit` to leave the shell.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
redfetch/__about__.py,sha256=kjMXs_RelAfH3nsfXcggy4eqwR8yDdd160wJ9EHB63g,520
|
|
2
|
+
redfetch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
redfetch/api.py,sha256=bsjEQ34qV2cEPG4PyCbIs-UFg5RTif7BgW9gLLmCWVY,4795
|
|
4
|
+
redfetch/auth.py,sha256=vmwCnRQlmnUgDwYseu_EHZ4KKKfC-aG4VeIHE9ZNDyI,20005
|
|
5
|
+
redfetch/config.py,sha256=4rJJk8Rs9t4UyFkw_rP6qs83UDwA9hjNmxya9veXjoU,16265
|
|
6
|
+
redfetch/config_firstrun.py,sha256=MgXNPGE3EbLFH5wWdxOxVOThi3YQB2btB6UQeibWPNg,21196
|
|
7
|
+
redfetch/desktop_shortcut.py,sha256=yE2I6fHLGYjUQyHFzZA6Xr2LLSGvFJTgdB4PqWIH3Q4,2345
|
|
8
|
+
redfetch/detecteq.py,sha256=99r7jowZOT59BFmFJDQ1OsiXqnaeqLSpivuI14kHf9E,3777
|
|
9
|
+
redfetch/download.py,sha256=fQRTtkN-PXz9qVH8tZrkuz0ehg3B5g3Ux5eLv1S9aIs,11344
|
|
10
|
+
redfetch/listener.py,sha256=JC1ZOy1lGrJAKgOs7JVeMOulXu-c0q6tsODjN6bUWug,7756
|
|
11
|
+
redfetch/main.py,sha256=0ft3_BJvU6qA2cjt5e3wp_fkL7kzo_hnESVizXZxTEI,27118
|
|
12
|
+
redfetch/meta.py,sha256=8-AtUmHhktNWKO91g109G2RBdeJGw-WTr3Y98cxrmRA,20309
|
|
13
|
+
redfetch/navmesh.py,sha256=Yg4SdQFwarTxuyECH-5DCG8m3VZeRqE5v9fh95HUhW8,13193
|
|
14
|
+
redfetch/net.py,sha256=9FJegQ4FjgvlgQi2M6Cgcv4iRoFqjHdONENzfRTDzdk,3801
|
|
15
|
+
redfetch/processes.py,sha256=Cv0myebAy2KwcnJTdBNmz2vEO_eP9jJ6wAl82brjsSw,3984
|
|
16
|
+
redfetch/push.py,sha256=Je_I_XcuBDl6KzHuo97AI1_FQB-CUf3EoOVzbZPrABk,9645
|
|
17
|
+
redfetch/runtime_errors.py,sha256=U5AhI3Jwl5t7D0sLoKCbVWCx4BIBKSd1ZbzdxAYMddY,2848
|
|
18
|
+
redfetch/settings.toml,sha256=JGm8ZU30maPc-HVYxbAd154xUnwJinQu5Bj4NrywDtA,5910
|
|
19
|
+
redfetch/special.py,sha256=iaFJ7FcTp58-HIfxmmdZjLdnvpu2diEMtkxJiY_8E6g,3087
|
|
20
|
+
redfetch/store.py,sha256=HmkmV2u557vtlLZuOLO89d0uco6lT5SLuYBH1Iy2Xk4,16054
|
|
21
|
+
redfetch/sync.py,sha256=vnd2V8XP6OoOxfxj-eMf8fca53lqadF4Ro_vmbs0sMo,8371
|
|
22
|
+
redfetch/sync_discovery.py,sha256=XvwVQpG8nWMhS_-d42EU5NDPP8pGwchWGIPuV7Rppks,13396
|
|
23
|
+
redfetch/sync_executor.py,sha256=gcxA-UD6Spb7TVo1KORlPbW5eS9-x9r8hKDzSanlcGs,5382
|
|
24
|
+
redfetch/sync_planner.py,sha256=xNZdufziwTk_N-u0VMZ1lA40GdZfvtcOcKX1EkyHvf4,7216
|
|
25
|
+
redfetch/sync_remote.py,sha256=pD0sQl5aiE10gwSBH7o2rkibtiUK5OOZ5V3f0yYR6cw,6875
|
|
26
|
+
redfetch/sync_types.py,sha256=fPCVr6ZPbgChVpgHJa-4L4s7-_DcyUvZlJN4Ze33ur4,12058
|
|
27
|
+
redfetch/terminal_ui.py,sha256=yh6sF_ropqYfDZs2Jhnvl04rPwUx20pXO874tIyr2HY,95146
|
|
28
|
+
redfetch/terminal_ui.tcss,sha256=Wc72DNYgLq5zO4QP72vIPaB7J3XRWf-qDlFDLRwj9-A,10974
|
|
29
|
+
redfetch/unloadmq.py,sha256=SG-Zgd7_rKbx5Scyqr4qcdcRWe_4oodJiLb0aVPn-uM,5356
|
|
30
|
+
redfetch/update_status.py,sha256=uqfut6U5k_ZVn9j0NV1fdG5h8q39bVgnTEwOMBB3NNw,1874
|
|
31
|
+
redfetch/utils.py,sha256=TFbazOiXYwRk6NW82BI72w2_z-nB9EH_tCHtMPnggrA,8457
|
|
32
|
+
redfetch/redfetch.ico,sha256=iMUv7RKxph5H9L9w3nll8aTA3DOSgu_OU_irW2ixtgI,32038
|
|
33
|
+
redfetch-1.3.0.dist-info/METADATA,sha256=ix16apnjvkRaz9KTm9IfMIsOWIh_g7VAqSgf-X97-zQ,11444
|
|
34
|
+
redfetch-1.3.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
35
|
+
redfetch-1.3.0.dist-info/entry_points.txt,sha256=c0H5w7j7HZcWLAzdmJzAFVY7XGqT7kEvQNRLdbFGeJ0,48
|
|
36
|
+
redfetch-1.3.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
37
|
+
redfetch-1.3.0.dist-info/RECORD,,
|