redfetch 1.2.0__tar.gz → 1.4.0__tar.gz

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.
Files changed (65) hide show
  1. {redfetch-1.2.0 → redfetch-1.4.0}/.github/workflows/pipeline.yml +65 -16
  2. {redfetch-1.2.0 → redfetch-1.4.0}/.gitignore +4 -0
  3. redfetch-1.4.0/AGENTS.md +21 -0
  4. {redfetch-1.2.0 → redfetch-1.4.0}/CHANGELOG.md +17 -0
  5. {redfetch-1.2.0 → redfetch-1.4.0}/PKG-INFO +38 -16
  6. {redfetch-1.2.0 → redfetch-1.4.0}/README.md +23 -2
  7. {redfetch-1.2.0 → redfetch-1.4.0}/pyproject.toml +13 -12
  8. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/__about__.py +2 -2
  9. redfetch-1.4.0/src/redfetch/api.py +50 -0
  10. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/auth.py +1 -1
  11. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/config.py +213 -103
  12. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/config_firstrun.py +24 -9
  13. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/desktop_shortcut.py +0 -7
  14. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/download.py +199 -36
  15. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/listener.py +16 -4
  16. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/main.py +147 -150
  17. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/meta.py +4 -0
  18. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/navmesh.py +32 -98
  19. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/net.py +1 -10
  20. redfetch-1.4.0/src/redfetch/post_update.py +141 -0
  21. redfetch-1.4.0/src/redfetch/processes.py +290 -0
  22. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/push.py +2 -0
  23. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/runtime_errors.py +1 -3
  24. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/settings.toml +15 -5
  25. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/special.py +2 -0
  26. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/store.py +11 -15
  27. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/sync.py +98 -32
  28. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/sync_discovery.py +19 -42
  29. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/sync_executor.py +2 -0
  30. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/sync_planner.py +3 -3
  31. redfetch-1.4.0/src/redfetch/sync_remote.py +135 -0
  32. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/sync_types.py +53 -13
  33. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/terminal_ui.py +496 -395
  34. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/terminal_ui.tcss +31 -79
  35. redfetch-1.4.0/src/redfetch/update_status.py +62 -0
  36. redfetch-1.4.0/src/redfetch/utils.py +310 -0
  37. redfetch-1.4.0/tests/test_check.py +144 -0
  38. redfetch-1.4.0/tests/test_close_gating.py +787 -0
  39. redfetch-1.4.0/tests/test_env_selection.py +164 -0
  40. redfetch-1.4.0/tests/test_fetch_sync_info.py +67 -0
  41. {redfetch-1.2.0 → redfetch-1.4.0}/tests/test_first_run_setup.py +20 -39
  42. redfetch-1.4.0/tests/test_licensed_resources_filtering.py +83 -0
  43. redfetch-1.4.0/tests/test_process_lock.py +48 -0
  44. redfetch-1.4.0/tests/test_remote_snapshot.py +97 -0
  45. redfetch-1.4.0/tests/test_settings_local_deltas.py +56 -0
  46. redfetch-1.4.0/tests/test_staged_extraction.py +340 -0
  47. {redfetch-1.2.0 → redfetch-1.4.0}/tests/test_sync_pipeline_regressions.py +65 -0
  48. {redfetch-1.2.0 → redfetch-1.4.0}/tests/test_sync_queue_rules.py +29 -8
  49. redfetch-1.2.0/src/redfetch/ResourceInstallBtn.js +0 -237
  50. redfetch-1.2.0/src/redfetch/api.py +0 -135
  51. redfetch-1.2.0/src/redfetch/processes.py +0 -106
  52. redfetch-1.2.0/src/redfetch/sync_remote.py +0 -179
  53. redfetch-1.2.0/src/redfetch/unloadmq.py +0 -148
  54. redfetch-1.2.0/src/redfetch/utils.py +0 -149
  55. redfetch-1.2.0/tests/test_cancellation_e2e.py +0 -123
  56. redfetch-1.2.0/tests/test_licensed_resources_filtering.py +0 -238
  57. redfetch-1.2.0/tests/test_publish_message_file_fallback.py +0 -158
  58. redfetch-1.2.0/tests/test_special_resources_e2e.py +0 -506
  59. redfetch-1.2.0/tests/test_uninstall_e2e.py +0 -233
  60. {redfetch-1.2.0 → redfetch-1.4.0}/LICENSE +0 -0
  61. {redfetch-1.2.0 → redfetch-1.4.0}/redfetch.ico +0 -0
  62. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/__init__.py +0 -0
  63. {redfetch-1.2.0 → redfetch-1.4.0}/src/redfetch/detecteq.py +0 -0
  64. {redfetch-1.2.0 → redfetch-1.4.0}/tests/test_is_special_or_dependency.py +0 -0
  65. {redfetch-1.2.0 → redfetch-1.4.0}/tests/test_path_reset.py +0 -0
@@ -60,6 +60,13 @@ jobs:
60
60
  run: |
61
61
  hatch build
62
62
 
63
+ - name: Upload Python distribution
64
+ if: github.ref == 'refs/heads/develop'
65
+ uses: actions/upload-artifact@v7
66
+ with:
67
+ name: python-dist
68
+ path: dist/
69
+
63
70
  - name: Get Version
64
71
  id: get_version
65
72
  run: |
@@ -89,22 +96,26 @@ jobs:
89
96
  with:
90
97
  fetch-depth: 0 # Ensure full Git history is available for versioning
91
98
 
99
+ - name: Download Python distribution
100
+ if: github.ref == 'refs/heads/develop'
101
+ uses: actions/download-artifact@v8
102
+ with:
103
+ name: python-dist
104
+ path: dist
105
+
92
106
  - name: Set Environment Variables
93
107
  id: set_env_vars
94
108
  shell: pwsh
95
109
  run: |
96
- # Set static environment variables
97
- "PYAPP_PROJECT_NAME=redfetch" >> $env:GITHUB_ENV
98
110
  "PYAPP_EXEC_MODULE=redfetch.main" >> $env:GITHUB_ENV
99
111
  "PYAPP_PASS_LOCATION=1" >> $env:GITHUB_ENV
100
112
 
101
- # Conditionally set PYAPP_PIP_EXTRA_ARGS based on the branch or tag
102
113
  if ($env:GITHUB_REF -eq 'refs/heads/develop') {
103
- $pipArgs = "--index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/"
114
+ "PYAPP_EMBED_WHEEL=1" >> $env:GITHUB_ENV
104
115
  } else {
105
- $pipArgs = ""
116
+ "PYAPP_PROJECT_NAME=redfetch" >> $env:GITHUB_ENV
106
117
  }
107
- "PYAPP_PIP_EXTRA_ARGS=$pipArgs" >> $env:GITHUB_ENV
118
+ "PYAPP_PIP_EXTRA_ARGS=" >> $env:GITHUB_ENV
108
119
 
109
120
  # Output the values for verification
110
121
  Get-Content $env:GITHUB_ENV
@@ -145,18 +156,23 @@ jobs:
145
156
 
146
157
  - name: Build PyApp Executable
147
158
  run: |
148
- $env:PYAPP_PROJECT_NAME = "${{ env.PYAPP_PROJECT_NAME }}"
159
+ if ($env:PYAPP_EMBED_WHEEL -eq '1') {
160
+ # Develop embeds the built wheel so its deps resolve from PyPI rather than TestPyPI
161
+ $wheel = Get-ChildItem -Path "$env:GITHUB_WORKSPACE\dist\*.whl" | Select-Object -First 1
162
+ if (-not $wheel) { throw "No redfetch wheel found in dist/" }
163
+ $env:PYAPP_PROJECT_PATH = $wheel.FullName
164
+ Remove-Item Env:PYAPP_PROJECT_NAME -ErrorAction SilentlyContinue
165
+ Remove-Item Env:PYAPP_PROJECT_VERSION -ErrorAction SilentlyContinue
166
+ }
167
+
149
168
  $env:PYAPP_EXEC_MODULE = "${{ env.PYAPP_EXEC_MODULE }}"
150
169
  $env:PYAPP_PASS_LOCATION = "${{ env.PYAPP_PASS_LOCATION }}"
151
170
  $env:PYAPP_PIP_EXTRA_ARGS = "${{ env.PYAPP_PIP_EXTRA_ARGS }}"
152
- $env:PYAPP_PROJECT_VERSION = "${{ env.PYAPP_PROJECT_VERSION }}"
153
-
154
- Write-Host "PYAPP_PROJECT_NAME: $env:PYAPP_PROJECT_NAME"
171
+
155
172
  Write-Host "PYAPP_EXEC_MODULE: $env:PYAPP_EXEC_MODULE"
156
173
  Write-Host "PYAPP_PASS_LOCATION: $env:PYAPP_PASS_LOCATION"
157
174
  Write-Host "PYAPP_PIP_EXTRA_ARGS: $env:PYAPP_PIP_EXTRA_ARGS"
158
- Write-Host "PYAPP_PROJECT_VERSION: $env:PYAPP_PROJECT_VERSION"
159
-
175
+
160
176
  cargo build --release
161
177
  working-directory: .\pyapp-latest
162
178
  shell: pwsh
@@ -184,11 +200,36 @@ jobs:
184
200
  --set-version-string "InternalName" "redfetch" `
185
201
  --set-icon "redfetch.ico"
186
202
 
203
+ - name: Install Google Cloud KMS CNG Provider
204
+ shell: pwsh
205
+ run: |
206
+ if (Test-Path "$env:SystemRoot\System32\kmscng.dll") {
207
+ Write-Host "kmscng already installed."
208
+ return
209
+ }
210
+ $url = "https://github.com/GoogleCloudPlatform/kms-integrations/releases/download/cng-v1.3/kmscng-1.3-windows-amd64.zip"
211
+ $zip = Join-Path $env:RUNNER_TEMP "kmscng.zip"
212
+ $dest = Join-Path $env:RUNNER_TEMP "kmscng"
213
+ Invoke-WebRequest $url -OutFile $zip
214
+ Expand-Archive -Path $zip -DestinationPath $dest -Force
215
+ $msi = Get-ChildItem -Path $dest -Recurse -Filter *.msi | Select-Object -First 1
216
+ if (-not $msi) { throw "kmscng MSI not found in archive" }
217
+ Start-Process msiexec.exe -ArgumentList "/i `"$($msi.FullName)`" /qn /norestart" -Wait -NoNewWindow
218
+ if (-not (Test-Path "$env:SystemRoot\System32\kmscng.dll")) { throw "kmscng installation failed" }
219
+ Write-Host "kmscng installed."
220
+
187
221
  - name: Sign Executable
188
222
  shell: pwsh
223
+ env:
224
+ KMS_KEY: projects/code-signing-rg/locations/global/keyRings/redkeys/cryptoKeys/rgkey/cryptoKeyVersions/1
225
+ SIGNING_CERT: ${{ secrets.SIGNING_CERT }}
226
+ GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
189
227
  run: |
190
- $certPath = Join-Path $env:RUNNER_TEMP "cert.pfx"
191
- [IO.File]::WriteAllBytes($certPath, [Convert]::FromBase64String("${{ secrets.CERTIFICATE_PFX }}"))
228
+ $CertFile = Join-Path $env:RUNNER_TEMP "user.crt"
229
+ $KeyFile = Join-Path $env:RUNNER_TEMP "gcp-sa.json"
230
+ [IO.File]::WriteAllBytes($CertFile, [Convert]::FromBase64String($env:SIGNING_CERT))
231
+ [IO.File]::WriteAllText($KeyFile, $env:GCP_SA_KEY)
232
+ $env:GOOGLE_APPLICATION_CREDENTIALS = $KeyFile
192
233
 
193
234
  $signtool = Get-ChildItem -Path "${env:ProgramFiles(x86)}\Windows Kits" -Recurse -Filter "signtool.exe" |
194
235
  Where-Object { $_.FullName -match "\\x64\\" } |
@@ -198,10 +239,18 @@ jobs:
198
239
  if (-not $signtool) { throw "signtool.exe not found" }
199
240
  Write-Host "Using signtool: $signtool"
200
241
 
201
- & $signtool sign /f $certPath /p "${{ secrets.CERTIFICATE_PFX_PASSWORD }}" /fd sha256 /tr "http://timestamp.digicert.com" /td sha256 /v redfetch.exe
242
+ $tsaServers = @('http://timestamp.digicert.com', 'http://timestamp.sectigo.com')
243
+ $maxAttempts = 5
244
+ for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) {
245
+ $tsa = $tsaServers[($attempt - 1) % $tsaServers.Count]
246
+ & $signtool sign /fd SHA256 /f $CertFile /csp "Google Cloud KMS Provider" /kc $env:KMS_KEY /tr $tsa /td sha256 /v redfetch.exe
247
+ if ($LASTEXITCODE -eq 0) { break }
248
+ if ($attempt -eq $maxAttempts) { throw "Signing failed after $maxAttempts attempts" }
249
+ Start-Sleep -Seconds 2
250
+ }
202
251
  & $signtool verify /pa /v redfetch.exe
203
252
 
204
- Remove-Item $certPath
253
+ Remove-Item -Force $CertFile, $KeyFile
205
254
 
206
255
  - name: Upload Executable Artifact
207
256
  uses: actions/upload-artifact@v7
@@ -7,6 +7,10 @@ __pycache__/
7
7
  #virtual environments
8
8
  env/
9
9
  venv/
10
+ .venv/
11
+
12
+ # uv (project is hatch-managed)
13
+ uv.lock
10
14
 
11
15
  # Downloaded files
12
16
  Downloads/
@@ -0,0 +1,21 @@
1
+ # AGENTS.md
2
+
3
+ **redfetch** — a CLI/TUI for downloading and publishing MacroQuest and EverQuest scripts and software via the RedGuides API.
4
+
5
+ ## Running tests
6
+
7
+ Use `hatch test` — not `pytest`, `uv`, or an ad-hoc venv
8
+
9
+ ```sh
10
+ hatch test # full suite
11
+ hatch test tests/test_check.py # single file
12
+ ```
13
+ ## Libraries
14
+
15
+ - [Textual](https://textual.textualize.io/) - TUI
16
+ - [Hatch](https://hatch.pypa.io/latest/) - Build system
17
+ - [PYPA](https://www.pypa.io/en/latest/specifications/) - Package index
18
+
19
+ ## Conventions
20
+
21
+ - Use the `dev` environment (`hatch shell dev`) for development
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.3.0] - 2026-06-26
9
+
10
+ ### Added
11
+ - `--server` on `update` and `download`: use a different server for one run.
12
+ - TUI: "Also start post-update" — launch EQBCS, MySEQ, or a custom program after a successful update. ([#18](https://github.com/RedGuides/redfetch/issues/18))
13
+ - Staff picks: Ninjadvloot.inc is now on LIVE/TEST/EMU.
14
+
15
+ ### Changed
16
+ - Config paths that contain `eqgame.exe` are allowed with a warning instead of being blocked outright. ([#22](https://github.com/RedGuides/redfetch/issues/22))
17
+ - TUI: "Close MQ pre-update" and "Start MQ post-update" are now No / Ask / Yes toggles, so you can pick the prompt-each-time ("Ask") behavior from the UI instead of only on/off.
18
+ - Only settings you've actually changed are written to `settings.local.toml`, keeping the file tidy.
19
+
20
+ ### Fixed
21
+ - Licensed resources will now download even if you have duplicates. ([#24](https://github.com/RedGuides/redfetch/issues/24))
22
+ - Update checks notice when a resource's subfolder changes.
23
+ - Fixed "RecursionError" on startup by rolling back a dependency.
24
+
8
25
  ## [1.2.0] - 2026-04-05
9
26
 
10
27
  ### Added
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: redfetch
3
- Version: 1.2.0
3
+ Version: 1.4.0
4
4
  Summary: Download and publish EverQuest scripts and software using the RedGuides API
5
5
  Project-URL: Homepage, https://www.redguides.com
6
6
  Project-URL: Documentation, https://www.redguides.com/community/resources/redfetch.3177/
7
7
  Project-URL: Repository, https://github.com/RedGuides/redfetch
8
8
  Project-URL: Issues, https://github.com/RedGuides/redfetch/issues
9
9
  Project-URL: Changelog, https://github.com/RedGuides/redfetch/blob/main/CHANGELOG.md
10
- Project-URL: Source_archive, https://github.com/RedGuides/redfetch/archive/35e27c4778b6d07b5be189be22877ad5bb541d27.zip
10
+ Project-URL: Source_archive, https://github.com/RedGuides/redfetch/archive/ccf5cf558b6e6f261fab4079e837d5cb47e7975d.zip
11
11
  Author-email: Redbot <ask@redguides.com>
12
12
  License-Expression: GPL-3.0-or-later
13
13
  License-File: LICENSE
@@ -17,28 +17,29 @@ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (G
17
17
  Classifier: Operating System :: OS Independent
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Topic :: Utilities
20
+ Requires-Python: >=3.12
20
21
  Requires-Dist: aiofiles~=25.1
21
- Requires-Dist: aiohttp~=3.13
22
+ Requires-Dist: aiohttp~=3.14
22
23
  Requires-Dist: aiosqlite~=0.22
23
- Requires-Dist: cachetools~=7.0
24
24
  Requires-Dist: diskcache~=5.6
25
- Requires-Dist: dynaconf~=3.2
25
+ Requires-Dist: dynaconf>=3.3.2,~=3.3
26
+ Requires-Dist: filelock~=3.29
27
+ Requires-Dist: hishel[async,httpx]~=1.3
26
28
  Requires-Dist: httpx[http2]~=0.28
27
29
  Requires-Dist: keepachangelog~=2.0
28
- Requires-Dist: keyring~=25.6
30
+ Requires-Dist: keyring~=25.7
29
31
  Requires-Dist: md2bbcode
30
- Requires-Dist: packaging~=26.0
31
- Requires-Dist: platformdirs~=4.5
32
+ Requires-Dist: packaging~=26.2
33
+ Requires-Dist: platformdirs~=4.10
32
34
  Requires-Dist: psutil~=7.2
33
- Requires-Dist: pydantic~=2.12
34
- Requires-Dist: pyperclip~=1.11
35
+ Requires-Dist: pydantic~=2.13
35
36
  Requires-Dist: pywin32; sys_platform == 'win32'
36
- Requires-Dist: rich~=14.3
37
+ Requires-Dist: rich~=15.0
37
38
  Requires-Dist: tenacity~=9.1
38
39
  Requires-Dist: textual-fspicker~=1.0
39
- Requires-Dist: textual~=8.1
40
- Requires-Dist: tomlkit~=0.14
41
- Requires-Dist: typer~=0.21
40
+ Requires-Dist: textual~=8.2
41
+ Requires-Dist: tomlkit~=0.15
42
+ Requires-Dist: typer~=0.26
42
43
  Description-Content-Type: text/markdown
43
44
 
44
45
  ![six wizards levitating a package, the word redfetch underneath](https://www.redguides.com/images/redfetchlogo.png)
@@ -109,8 +110,10 @@ To run redfetch from the command line:
109
110
  > ### 📦 Resource Management
110
111
  > - `update` - Update all watched and special resources
111
112
  > - `--force` / `-f` - Force re-download of all watched resources
113
+ > - `--server` / `-s` - Switch to this server before updating (`LIVE`, `TEST`, `EMU`)
112
114
  > - `download <ID_OR_URL>` - Download a specific resource by ID or URL
113
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`)
114
117
  > - `list` - List resources and dependencies in the cache database
115
118
  > - `reset` - Reset download dates for watched resources
116
119
  >
@@ -197,12 +200,31 @@ redfetch.exe config SPECIAL_RESOURCES.2218.opt_in false --server TEST
197
200
 
198
201
  Then assign the *Very Vanilla MQ* path to your self-compiled MacroQuest.
199
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
+
200
224
  ## Trailmap
201
225
  - Add custom buttons for "fetch" tab.
202
226
  - Option: Close after update
203
- - Launch programs with cli options
204
227
  - Indicate when updated VV is available
205
- - Launch more than just mq (eqbcs, etc) upon update.
206
228
  - Run from MQ
207
229
  - Deeper integration with the forums
208
230
 
@@ -66,8 +66,10 @@ To run redfetch from the command line:
66
66
  > ### 📦 Resource Management
67
67
  > - `update` - Update all watched and special resources
68
68
  > - `--force` / `-f` - Force re-download of all watched resources
69
+ > - `--server` / `-s` - Switch to this server before updating (`LIVE`, `TEST`, `EMU`)
69
70
  > - `download <ID_OR_URL>` - Download a specific resource by ID or URL
70
71
  > - `--force` / `-f` - Force re-download by resetting this resource's download date
72
+ > - `--server` / `-s` - Switch to this server before downloading (`LIVE`, `TEST`, `EMU`)
71
73
  > - `list` - List resources and dependencies in the cache database
72
74
  > - `reset` - Reset download dates for watched resources
73
75
  >
@@ -154,12 +156,31 @@ redfetch.exe config SPECIAL_RESOURCES.2218.opt_in false --server TEST
154
156
 
155
157
  Then assign the *Very Vanilla MQ* path to your self-compiled MacroQuest.
156
158
 
159
+ ### Custom post-update launch
160
+
161
+ 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:
162
+
163
+ ```toml
164
+ [LIVE.POST_UPDATE_LAUNCH]
165
+ targets = ["custom"]
166
+ command = ['C:\Tools\AfterRedfetch\after-update.exe', '--server', 'LIVE']
167
+ ```
168
+
169
+ 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:
170
+
171
+ ```toml
172
+ [LIVE.POST_UPDATE_LAUNCH]
173
+ targets = ["myseq", "custom", "eqbcs"]
174
+ command = ["C:\\Users\\Public\\Python\\python.exe", "C:\\Users\\Public\\redfetch\\after_update.py"]
175
+ ```
176
+
177
+ You can set these per-server, e.g. `[TEST.POST_UPDATE_LAUNCH]`, or global `[DEFAULT.POST_UPDATE_LAUNCH]`.
178
+
179
+
157
180
  ## Trailmap
158
181
  - Add custom buttons for "fetch" tab.
159
182
  - Option: Close after update
160
- - Launch programs with cli options
161
183
  - Indicate when updated VV is available
162
- - Launch more than just mq (eqbcs, etc) upon update.
163
184
  - Run from MQ
164
185
  - Deeper integration with the forums
165
186
 
@@ -3,6 +3,7 @@ name = "redfetch"
3
3
  description = "Download and publish EverQuest scripts and software using the RedGuides API"
4
4
  readme = "README.md"
5
5
  license = "GPL-3.0-or-later"
6
+ requires-python = ">=3.12"
6
7
  dynamic = ["version", "urls"]
7
8
  authors = [
8
9
  { name = "Redbot", email = "ask@redguides.com" }
@@ -19,25 +20,25 @@ classifiers = [
19
20
  dependencies = [
20
21
  "aiofiles~=25.1",
21
22
  "aiosqlite~=0.22",
22
- "aiohttp~=3.13",
23
- "cachetools~=7.0",
23
+ "aiohttp~=3.14",
24
24
  "diskcache~=5.6",
25
- "dynaconf~=3.2",
25
+ "dynaconf~=3.3,>=3.3.2", # (pr 1409 fixed RecursionError)
26
+ "filelock~=3.29",
27
+ "hishel[httpx,async]~=1.3",
26
28
  "httpx[http2]~=0.28",
27
29
  "keepachangelog~=2.0",
28
- "keyring~=25.6",
30
+ "keyring~=25.7",
29
31
  "md2bbcode",
30
- "packaging~=26.0",
31
- "pydantic~=2.12",
32
- "platformdirs~=4.5",
32
+ "packaging~=26.2",
33
+ "pydantic~=2.13",
34
+ "platformdirs~=4.10",
33
35
  "psutil~=7.2",
34
- "pyperclip~=1.11",
35
- "rich~=14.3",
36
+ "rich~=15.0",
36
37
  "tenacity~=9.1",
37
- "textual~=8.1",
38
+ "textual~=8.2",
38
39
  "textual-fspicker~=1.0",
39
- "tomlkit~=0.14",
40
- "typer~=0.21",
40
+ "tomlkit~=0.15",
41
+ "typer~=0.26",
41
42
  "pywin32; sys_platform == 'win32'"
42
43
  ]
43
44
 
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '1.2.0'
22
- __version_tuple__ = version_tuple = (1, 2, 0)
21
+ __version__ = version = '1.4.0'
22
+ __version_tuple__ = version_tuple = (1, 4, 0)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -0,0 +1,50 @@
1
+ """Resource API client: fetch_*() takes a client, get_*() is self-contained."""
2
+
3
+ import httpx
4
+ from redfetch import net
5
+ from redfetch.sync_types import SyncInfo
6
+
7
+ BASE_URL = net.BASE_URL
8
+
9
+
10
+ async def fetch_sync_info(client: httpx.AsyncClient) -> SyncInfo:
11
+ """Per-user sync info"""
12
+ url = f'{BASE_URL}/api/rgsync'
13
+ is_level_2 = is_moderator = False
14
+ watched: set[str] = set()
15
+ licenses: list[dict] = []
16
+ page = 1
17
+ while True:
18
+ data = await net.get_json(client, url, params={'page': page})
19
+ is_level_2 = bool(data.get('is_level_2', False))
20
+ is_moderator = bool(data.get('is_moderator', False))
21
+ watched.update(str(rid) for rid in data.get('watched', []))
22
+ licenses.extend(data.get('licenses', []))
23
+ if not data.get('pagination', {}).get('has_more', False):
24
+ break
25
+ page += 1
26
+
27
+ licensed_ids = {
28
+ str(lic['resource_id']) for lic in licenses if lic.get('resource_id') is not None
29
+ }
30
+ return SyncInfo(
31
+ is_level_2=is_level_2,
32
+ is_moderator=is_moderator,
33
+ watched=watched,
34
+ licensed_ids=licensed_ids,
35
+ licenses=licenses,
36
+ )
37
+
38
+
39
+ async def get_sync_info(headers: dict) -> SyncInfo:
40
+ """fetch_sync_info for callers that hold headers but not a client."""
41
+ async with httpx.AsyncClient(headers=headers, http2=True, timeout=30.0) as client:
42
+ return await fetch_sync_info(client)
43
+
44
+
45
+ async def get_resource_details(resource_id: int, headers: dict) -> dict:
46
+ """Retrieve details of a specific resource from the API."""
47
+ url = f'{BASE_URL}/api/resources/{resource_id}'
48
+ async with httpx.AsyncClient(headers=headers, http2=True, timeout=30.0) as client:
49
+ data = await net.get_json(client, url)
50
+ return data['resource']
@@ -553,7 +553,7 @@ def initialize_keyring():
553
553
  raise RuntimeError(
554
554
  "No suitable keyring backend found, probably because you're not on Windows.\n\n"
555
555
  "Please install `keyrings.alt` by running:\n"
556
- " pip install keyrings.alt\n\n"
556
+ " pipx inject redfetch keyrings.alt\n\n"
557
557
  "Then restart the application."
558
558
  )
559
559
  except Exception as e: