Compare commits

...

187 commits
1.4.3 ... main

Author SHA1 Message Date
amd64fox
aa537c63d9
Remove scheduled cron job from ms-spotify.yml
Removed scheduled cron job for the Spotify MS workflow.
2026-03-30 10:39:54 +03:00
amd64fox
3f6cba6bdd Temporarily disabled automatic sync trigger 2026-03-19 06:39:16 +03:00
GitHub Action
65dcb761bd Added version 1.2.85.519.g549a528b 2026-03-12 05:46:36 +00:00
GitHub Action
df4be06104 Added version 1.2.85.513.g45f09625 2026-03-10 06:44:25 +00:00
GitHub Actions
a6c9716521 Added deb version 1.2.84.476.ga1ff6607 2026-03-09 16:51:25 +00:00
GitHub Action
86ea359ab0 Added version 1.2.85.507.g795fff93 2026-03-07 06:44:37 +00:00
GitHub Action
2f207d2667 Added version 1.2.85.504.gbcffb495 2026-03-06 21:44:38 +00:00
GitHub Action
d26ef77ead Added version 1.2.85.436.g80477277 2026-03-05 08:58:00 +00:00
GitHub Action
a174eda2e0 Added version 1.2.85.376.g54b36020 2026-03-04 08:54:32 +00:00
GitHub Action
cc256be6ac Added version 1.2.85.339.g3ff2369a 2026-03-03 08:56:28 +00:00
GitHub Action
2892713442 Added version 1.2.85.304.gfdc80894 2026-03-02 08:57:41 +00:00
GitHub Action
acab906723 Added version 1.2.85.301.g1a6ed5dd 2026-03-01 08:42:48 +00:00
amd64fox
7728100b3b fix: use full version string (with hash) in deb commit message 2026-02-28 11:55:22 +03:00
amd64fox
6ccbafbb47 refactor: optimize async messaging, progress tracking, and task lifecycle
- Replace `std::sync::mpsc` with unbounded `crossbeam-channel` for improved async task messaging.
- Switch from Mutex-based processed counters to `AtomicU64` for lock-free, accurate progress tracking.
- Refactor `search_installers` to use a new `SearchOptions` struct.
- Improve task lifecycle handling, including better pause and cancel mechanisms.
- Adjust URL and path generation to return owned `String`s.
- Add `OnceLock` to cache compiled regular expressions.
- Implement explicit logging for HTTP 429 (Too Many Requests) and network errors.
- Simplify Start/Pause button logic in the UI.
- Update `.gitignore` to exclude crate targets.
2026-02-28 11:50:50 +03:00
GitHub Action
162fa66840 Added version 1.2.85.293.gb38c1e46 2026-02-28 08:41:24 +00:00
dependabot[bot]
7d8b10ba7d
Bump actions/download-artifact from 7 to 8 (#18)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7 to 8.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v7...v8)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 16:33:27 +03:00
dependabot[bot]
fcc8e2b822
Bump actions/upload-artifact from 6 to 7 (#19)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 16:33:25 +03:00
GitHub Action
fe889d1026 Added version 1.2.85.240.g6c6690ee 2026-02-27 08:58:42 +00:00
GitHub Action
8728576c0c Added version 1.2.84.476.ga1ff6607 2026-02-26 11:44:35 +00:00
GitHub Action
3a6a75235c Added version 1.2.84.477.gcfdf84e8 2026-02-26 05:44:18 +00:00
GitHub Action
c197397e58 Added version 1.2.84.474.g9f006f58 2026-02-25 05:44:31 +00:00
GitHub Action
9c21bf44bf Added version 1.2.84.472.g6eba3a1b 2026-02-24 06:45:12 +00:00
GitHub Action
f8fc5f57a0 Added version 1.2.84.465.gf71dea39 2026-02-21 06:44:34 +00:00
GitHub Action
3b72261622 Added version 1.2.84.461.g21733ba7 2026-02-20 06:45:36 +00:00
amd64fox
61c8175e2a
Change buildType from 'Release' to 'Master' 2026-02-20 06:17:55 +03:00
GitHub Action
5002fadff1 Added version 1.2.84.354.gc8499b61 2026-02-18 08:59:58 +00:00
GitHub Action
45bc01e8bd Added version 1.2.84.283.g28ba6b94 2026-02-17 09:00:17 +00:00
GitHub Action
5618c88aaf Added version 1.2.84.236.g47fd8e6c 2026-02-16 09:00:53 +00:00
GitHub Action
72054c2fec Added version 1.2.84.232.g0b0c686f 2026-02-15 08:44:34 +00:00
GitHub Action
c73b6f8c90 Added version 1.2.84.227.ge1d1fbbf 2026-02-14 08:45:07 +00:00
GitHub Action
02c04bbbda Added version 1.2.84.194.g162bfeda 2026-02-13 09:00:15 +00:00
GitHub Action
a5b9f1ff60 Added version 1.2.83.461.g4d9c3dae 2026-02-12 05:44:34 +00:00
GitHub Action
d683626a49 Added version 1.2.83.460.gb5356552 2026-02-11 05:44:28 +00:00
GitHub Action
9b82042f98 Added version 1.2.83.456.g111afea6 2026-02-10 06:44:23 +00:00
GitHub Actions
0618b8d452 Added deb version 1.2.82.428 2026-02-09 11:42:10 +00:00
GitHub Action
ad30f8389b Added version 1.2.83.453.g1fae877b 2026-02-07 06:46:04 +00:00
GitHub Action
d10399826f Added version 1.2.82.428.g0ac8be2b 2026-01-27 05:44:45 +00:00
GitHub Action
90c98c0eb8 Added version 1.2.82.423.ga54eef9e 2026-01-24 06:44:18 +00:00
GitHub Action
12e0093856 Added version 1.2.82.409.g64a0a3c3 2026-01-23 08:43:19 +00:00
GitHub Action
80e6947c87 Added version 1.2.82.344.g681cb6a8 2026-01-22 08:45:05 +00:00
GitHub Action
88e997b6dd Added version 1.2.82.286.g8d25d435 2026-01-21 08:45:52 +00:00
GitHub Action
ca7de1e166 Added version 1.2.82.158.g070b7ef8 2026-01-16 08:45:14 +00:00
GitHub Action
7e70f4f339 Added version 1.2.81.264.gb4ad4efa 2026-01-13 05:44:27 +00:00
GitHub Actions
41d837b5b0 Added deb version 1.2.79.427 2026-01-12 11:15:43 +00:00
GitHub Action
f399b5cdd6 Added version 1.2.81.259.g857fba73 2026-01-12 06:44:17 +00:00
GitHub Action
7c071de816 Added version 1.2.81.258.g6cf93e85 2026-01-10 06:45:24 +00:00
GitHub Action
be781c007e Added version 1.2.81.241.g2943f0b9 2026-01-09 06:46:22 +00:00
GitHub Action
43e10f7e0a Added version 1.2.81.198.g3726460d 2026-01-08 08:43:23 +00:00
GitHub Action
392a0235f8 Added version 1.2.81.159.g5fbc5508 2026-01-07 08:42:37 +00:00
GitHub Action
b25f0cf848 Added version 1.2.81.131.gcbfb0261 2026-01-06 08:42:41 +00:00
GitHub Action
7c537a68a0 Added version 1.2.81.107.g22b83685 2026-01-05 08:46:19 +00:00
GitHub Action
3fd9169c15 Added version 1.2.81.104.g225ec0e6 2026-01-04 08:38:57 +00:00
GitHub Action
3bb8caf576 Added version 1.2.81.102.g3fe6da12 2026-01-03 08:38:46 +00:00
GitHub Action
fda738dde1 Added version 1.2.81.88.g9670c97c 2026-01-02 08:42:46 +00:00
GitHub Action
c255e9eab8 Added version 1.2.80.358.g74e46c21 2025-12-30 06:44:28 +00:00
GitHub Action
4e61673309 Added version 1.2.80.354.gc3785978 2025-12-27 06:44:56 +00:00
GitHub Action
e179da8e48 Added version 1.2.80.349.g2efc88b5 2025-12-26 06:44:11 +00:00
GitHub Action
9e079d0968 Added version 1.2.80.334.gb3f9fc96 2025-12-25 08:39:29 +00:00
GitHub Action
5fba045c3a Added version 1.2.80.315.g30b9cfbe 2025-12-24 08:41:17 +00:00
GitHub Action
41a318e417 Added version 1.2.80.290.gabebb6aa 2025-12-23 08:41:11 +00:00
GitHub Action
80bb785d07 Added version 1.2.80.247.g4c104b06 2025-12-22 08:41:46 +00:00
GitHub Action
319354b1ac Added version 1.2.80.236.gb661a706 2025-12-21 08:36:09 +00:00
GitHub Action
501a8c9877 Added version 1.2.80.232.gcd5eb6df 2025-12-20 08:37:27 +00:00
GitHub Action
88e67df823 Added version 1.2.80.191.g39184aea 2025-12-19 08:44:22 +00:00
GitHub Action
2cdf95f4ad Added version 1.2.79.427.g80eb4a07 2025-12-19 06:44:47 +00:00
GitHub Actions
84854e9bf3 Added deb version 1.2.79.425 2025-12-17 09:22:54 +00:00
GitHub Action
59e6d4f47b Added version 1.2.79.425.g1d0fcf61 2025-12-16 06:44:13 +00:00
GitHub Action
6a1219a76e Added version 1.2.79.418.g617d91ef 2025-12-15 06:45:22 +00:00
GitHub Action
5f3103d908 Added version 1.2.79.417.g0e9f05fc 2025-12-13 06:46:39 +00:00
dependabot[bot]
18e8130d04
Bump actions/cache from 4 to 5 (#15)
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-13 04:00:08 +03:00
dependabot[bot]
0874d7f305
Bump actions/download-artifact from 6 to 7 (#16)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-13 03:59:59 +03:00
dependabot[bot]
8d610ceaad
Bump actions/upload-artifact from 5 to 6 (#17)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-13 03:59:49 +03:00
GitHub Action
29b9b1e37e Added version 1.2.79.411.g33e8b03a 2025-12-12 06:45:08 +00:00
GitHub Action
18774fa88b Added version 1.2.79.378.gf06ca97d 2025-12-11 08:43:57 +00:00
GitHub Action
f450ca818c Added version 1.2.79.250.g72d95fb3 2025-12-07 08:34:52 +00:00
GitHub Action
97fff96bd7 Added version 1.2.79.246.g768eb550 2025-12-06 08:37:00 +00:00
GitHub Action
225d47e09e Added version 1.2.79.206.g6a84c90c 2025-12-05 08:43:48 +00:00
GitHub Action
1caa61daa7 Added version 1.2.78.418.gaeeb5ebd 2025-12-03 10:44:25 +00:00
GitHub Action
8b3fce1ec2 Added version 1.2.78.410.g8ed2b60a 2025-12-01 12:45:22 +00:00
GitHub Action
e060b29562 Added version 1.2.78.409.g6aead1f8 2025-11-29 06:44:19 +00:00
GitHub Action
47258b2b26 Added version 1.2.78.397.gc2b9a506 2025-11-28 06:44:15 +00:00
GitHub Action
ed8fbaf286 Added version 1.2.78.354.gcbda56e3 2025-11-27 13:02:43 +00:00
GitHub Actions
43ce7f9302 Added deb version 1.2.77.358 2025-11-25 11:14:16 +00:00
GitHub Action
27ecd89f27 Added version 1.2.78.174.g0ea26501 2025-11-22 08:33:46 +00:00
GitHub Action
f4200c56a6 Added version 1.2.78.145.g47f556f3 2025-11-21 08:42:07 +00:00
dependabot[bot]
ee2288360e
Bump actions/checkout from 5 to 6 (#14)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-20 22:52:55 +03:00
GitHub Action
27d16425b7 Added version 1.2.77.358.g4339a634 2025-11-19 05:44:32 +00:00
amd64fox
f7bc952f82 Reapply "Merge branch 'main' of https://github.com/LoaderSpot/LoaderSpot"
This reverts commit 07cf7fea90.
2025-11-19 02:13:21 +03:00
amd64fox
07cf7fea90 Revert "Merge branch 'main' of https://github.com/LoaderSpot/LoaderSpot"
This reverts commit e5cd965530, reversing
changes made to c77736ccc5.
2025-11-19 02:10:36 +03:00
amd64fox
e5cd965530 Merge branch 'main' of https://github.com/LoaderSpot/LoaderSpot 2025-11-19 02:09:26 +03:00
GitHub Action
43271f0004 Added version 1.2.56.259.g35fc40b3 2025-11-18 06:46:50 +00:00
GitHub Action
8d94b700f1 Added version 1.2.53.257.g47fa6c39 2025-11-18 06:44:53 +00:00
GitHub Action
ee6cc93a0b Added version 1.2.77.357.g6a28e474 2025-11-18 05:44:59 +00:00
GitHub Action
b89472d397 Added version 1.2.52.402.g4f67b282 2025-11-18 05:44:10 +00:00
GitHub Action
a505191e0e Added version 1.2.58.318.gc4769917 2025-11-18 04:46:26 +00:00
GitHub Action
387b329651 Added version 1.2.53.259.gecba76ba 2025-11-18 04:45:05 +00:00
GitHub Action
ca9b8c328b Added version 1.2.52.337.g72641da6 2025-11-18 04:44:09 +00:00
GitHub Action
9a887ca2d0 Added version 1.2.58.354.ga9e5d5dd 2025-11-18 03:46:58 +00:00
GitHub Action
947986fd6c Added version 1.2.57.301.gb804e5ce 2025-11-18 03:45:54 +00:00
GitHub Action
d6fb0f0483 Added version 1.2.52.297.g9ad95283 2025-11-18 03:45:05 +00:00
GitHub Action
1fb610a32e Added version 1.2.62.398.g54159197 2025-11-18 03:23:10 +00:00
GitHub Action
6e8aeba93d Added version 1.2.51.292.g9ae239fb 2025-11-18 03:22:03 +00:00
amd64fox
ff58033e51
Update update_version.py 2025-11-18 06:09:40 +03:00
amd64fox
1c69604346
Update version-check.yml 2025-11-18 06:08:50 +03:00
amd64fox
660715dd7b
Update version-check.yml 2025-11-18 06:07:29 +03:00
amd64fox
06ba92fc54
Update update_versions.yml 2025-11-18 06:06:50 +03:00
amd64fox
5717fb178c
Update version-check.yml 2025-11-18 06:01:23 +03:00
amd64fox
c77736ccc5 Revert "Update concurrency group for version check"
This reverts commit 1e1fbb6fcb.
2025-11-18 05:57:06 +03:00
amd64fox
549117406c
Increase MAX_RETRIES and update JSON handling 2025-11-18 05:51:09 +03:00
amd64fox
820937d353
Refactor update_version to accept JSON input directly 2025-11-18 05:50:24 +03:00
amd64fox
7af18cd8b2
Refactor JSON handling in update_versions.yml 2025-11-18 05:43:00 +03:00
amd64fox
815d92cd48
Refactor update_version to read JSON from file 2025-11-18 05:41:31 +03:00
amd64fox
4a0a7bb0de
Change JSON data passing method in workflow 2025-11-18 05:39:35 +03:00
amd64fox
d4981cf75c Revert "Added version 1.2.52.402.g4f67b282"
This reverts commit 871ebfb35f.
2025-11-18 05:32:31 +03:00
amd64fox
e61063df56 Revert "Added version 1.2.52.297.g9ad95283"
This reverts commit fff751fbcc.
2025-11-18 05:32:23 +03:00
amd64fox
a9433d2f73 Revert "Added version 1.2.57.301.gb804e5ce"
This reverts commit 39b110ff83.
2025-11-18 05:32:17 +03:00
amd64fox
5f4ac6a10c Revert "Added version 1.2.58.354.ga9e5d5dd"
This reverts commit e6b410b837.
2025-11-18 05:32:12 +03:00
amd64fox
8e49fe7dd7 Revert "Added version 1.2.51.292.g9ae239fb"
This reverts commit 259f31a1d3.
2025-11-18 05:32:02 +03:00
amd64fox
9046d7d8c2 Revert "Added version 1.2.56.259.g35fc40b3"
This reverts commit 4847fa31bd.
2025-11-18 05:31:50 +03:00
amd64fox
fe1af720e3 Revert "Added version 1.2.62.398.g54159197"
This reverts commit 0d00cac03f.
2025-11-18 05:31:37 +03:00
amd64fox
f3d0d53e20
Add retry logic for version update in workflow 2025-11-18 05:31:10 +03:00
amd64fox
0d00cac03f Added version 1.2.62.398.g54159197 2025-11-18 02:23:44 +00:00
amd64fox
4847fa31bd Added version 1.2.56.259.g35fc40b3 2025-11-18 02:23:20 +00:00
amd64fox
259f31a1d3 Added version 1.2.51.292.g9ae239fb 2025-11-18 02:22:59 +00:00
amd64fox
a2fd6913cc
Add push options to update_versions workflow 2025-11-18 05:20:30 +03:00
amd64fox
e6b410b837 Added version 1.2.58.354.ga9e5d5dd 2025-11-18 02:15:40 +00:00
amd64fox
39b110ff83 Added version 1.2.57.301.gb804e5ce 2025-11-18 02:15:21 +00:00
amd64fox
fff751fbcc Added version 1.2.52.297.g9ad95283 2025-11-18 02:15:03 +00:00
amd64fox
871ebfb35f Added version 1.2.52.402.g4f67b282 2025-11-18 02:14:45 +00:00
amd64fox
b8c01ad340
Refactor version update workflow to use auto-commit action 2025-11-18 05:11:59 +03:00
GitHub Action
dee58698c7 Added version 1.2.57.248.g99e4f91e 2025-11-18 01:57:56 +00:00
GitHub Action
f4b24453ac Added version 1.2.56.440.gb68df5d6 2025-11-18 01:57:35 +00:00
GitHub Action
d699d2ac8c Added version 1.2.52.290.gabcab511 2025-11-18 01:57:13 +00:00
GitHub Action
3f952f65bc Added version 1.2.51.251.g3824e6a9 2025-11-18 01:56:44 +00:00
amd64fox
1e1fbb6fcb
Update concurrency group for version check 2025-11-18 04:52:57 +03:00
GitHub Action
8dd85ffcfd Added version 1.2.63.212.g5da2018e 2025-11-18 01:48:09 +00:00
GitHub Action
2a7d1845eb Added version 1.2.61.199.g23513eeb 2025-11-18 01:46:41 +00:00
GitHub Action
172b066e95 Added version 1.2.51.210.ge411d4f7 2025-11-18 01:45:10 +00:00
GitHub Action
2c5e18b40c Added version 1.2.66.242.g7de5ae85 2025-11-18 01:01:49 +00:00
GitHub Action
f7ebdc8915 Added version 1.2.63.214.gb21d7c0d 2025-11-18 01:00:04 +00:00
GitHub Action
e7f647aa3c Added version 1.2.51.175.gfc0f5b10 2025-11-18 00:58:38 +00:00
amd64fox
4b936d86a4 add buildType 2025-11-16 14:54:10 +03:00
GitHub Action
26344b96b4 Added version 1.2.77.352.g26264c50 2025-11-16 06:44:44 +00:00
amd64fox
5b24d6d776 refactor search results to support multiple versions 2025-11-16 02:45:24 +03:00
GitHub Action
6cbe6ccdaf Added version 1.2.77.351.geb47b01f 2025-11-15 06:44:09 +00:00
amd64fox
6507e63825 fix buildType property assignment in version check script 2025-11-14 19:09:55 +03:00
amd64fox
d8e2291f0f refactor GAS submission logic in build version script 2025-11-14 19:00:24 +03:00
GitHub Action
3268102583 Added version 1.2.65.120.g1371365b 2025-11-14 04:15:48 +00:00
amd64fox
902b6f1223 Add buildType support to version update workflow 2025-11-14 07:01:40 +03:00
amd64fox
e6d3f0c76e remove redundant Write-Host debug messages 2025-11-14 05:40:41 +03:00
amd64fox
336fe44b5b replace Write-Output with Write-Host for messages 2025-11-14 05:33:05 +03:00
amd64fox
187fc4d439 refactor build info extraction to new script 2025-11-14 05:24:03 +03:00
amd64fox
5eee2572e2 remove debug output and minor message tweak 2025-11-14 02:19:30 +03:00
amd64fox
0eebb89e76 Combine build type detection and GAS sending steps 2025-11-14 02:15:12 +03:00
amd64fox
d15b5fbc32 update curl command to handle redirects in workflow 2025-11-14 02:04:00 +03:00
amd64fox
0398cb6ea1 Improve debug output for GAS POST request 2025-11-14 01:56:46 +03:00
amd64fox
d61a2e58db Improve GAS response handling in version-check workflow 2025-11-14 01:51:43 +03:00
amd64fox
e0cf1f94fc improve error handling for GAS response in workflow 2025-11-14 01:38:24 +03:00
amd64fox
430d5d40fc replace deprecated set-output with GITHUB_OUTPUT 2025-11-14 01:07:28 +03:00
amd64fox
ab8896961f refactor parameter handling in get-build-info.ps1 2025-11-14 00:43:49 +03:00
amd64fox
ba0af93cdc update version check output 2025-11-14 00:34:46 +03:00
amd64fox
86d02dde3c added build type processing 2025-11-14 00:17:15 +03:00
dependabot[bot]
31eeb1d86d
Bump actions/setup-python from 5 to 6 (#11)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-13 14:01:24 +03:00
dependabot[bot]
cb81917067
Bump actions/download-artifact from 4 to 6 (#12)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 6.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-13 14:01:01 +03:00
dependabot[bot]
4aeb2f150f
Bump actions/checkout from 4 to 5 (#13)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-13 14:00:44 +03:00
GitHub Action
817cefb17e Added version 1.2.77.288.g30addd9f 2025-11-13 10:32:33 +00:00
GitHub Action
90bc45dc0f Added version 1.2.77.250.g2d195c71 2025-11-12 17:49:09 +00:00
GitHub Action
51ff38d16f Added version 1.2.77.162.ga10209d6 2025-11-11 19:43:42 +00:00
GitHub Action
ade13834f9 Added version 1.2.76.298.g2316a870 2025-11-11 05:43:50 +00:00
GitHub Action
c1c0c715d1 Added version 1.2.70.253.g75b5048f 2025-11-10 12:11:45 +00:00
amd64fox
4e0009ce26 Improve error handling in update_version.py script 2025-11-10 15:10:22 +03:00
amd64fox
bffdc39cca refactor version update script and workflow 2025-11-10 15:03:30 +03:00
amd64fox
2d561118b9 refactor data input for version update workflow 2025-11-10 14:54:39 +03:00
amd64fox
70f3ac3249 fix DATA_JSON environment variable assignment 2025-11-10 14:51:50 +03:00
amd64fox
8806f679a9 fix DATA_JSON environment variable formatting 2025-11-10 14:48:00 +03:00
amd64fox
ea015c7280 read datajson input from environment variable 2025-11-10 14:43:40 +03:00
amd64fox
0b7ae2e90a Merge branch 'main' of https://github.com/LoaderSpot/LoaderSpot 2025-11-10 12:41:40 +03:00
amd64fox
e35724cf7f fix: increased web request timeout for gas 2025-11-10 12:41:38 +03:00
GitHub Action
6c882d93c6 Added version 1.2.77.159.gd5928880 2025-11-10 08:35:11 +00:00
amd64fox
4911d56f18 fix name 2025-11-08 15:17:41 +03:00
amd64fox
6a235cd0f5 take the compiled cli 2025-11-08 14:19:39 +03:00
amd64fox
9070044b8a updated name to loaderspot_ui 2025-11-08 11:43:38 +03:00
amd64fox
923b4b8d78 Update build-to-release.yml 2025-11-08 11:29:56 +03:00
amd64fox
4367d89cbe
Update build-to-release.yml 2025-11-08 11:21:35 +03:00
amd64fox
9053695b6b remove arm for Linux 2025-11-08 10:47:49 +03:00
amd64fox
467b7301ec added OpenSSL dependencies (arm64) 2025-11-08 10:42:38 +03:00
amd64fox
432ad0301e added OpenSSL dependencies 2025-11-08 10:36:21 +03:00
16 changed files with 2377 additions and 356 deletions

View file

@ -17,14 +17,24 @@ jobs:
rust_target: aarch64-pc-windows-msvc
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust toolchain and target
shell: pwsh
run: |
rustup toolchain install stable --profile minimal
rustup target add ${{ matrix.rust_target }}
rustup default stable
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.rust_target }}
- name: Cache cargo dependencies
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
LoaderSpot_UI/target
LoaderSpot_CLI/target
key: ${{ runner.os }}-cargo-${{ matrix.rust_target }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-${{ matrix.rust_target }}-
- name: Build release binaries
env:
@ -32,93 +42,87 @@ jobs:
ARCH: ${{ matrix.arch }}
shell: pwsh
run: |
# fail on first error
$ErrorActionPreference = "Stop"
# prepare artifacts folder
$out = "artifacts"
if (Test-Path $out) { Remove-Item $out -Recurse -Force }
New-Item -ItemType Directory -Path $out | Out-Null
# build UI
Write-Host "Building UI for target: $env:RUST_TARGET"
Write-Host "Building UI for $env:RUST_TARGET"
cargo build --manifest-path LoaderSpot_UI/Cargo.toml --release --target $env:RUST_TARGET
$exePathUI = Join-Path -Path "LoaderSpot_UI" -ChildPath (Join-Path -Path "target" -ChildPath (Join-Path -Path $env:RUST_TARGET -ChildPath "release"))
$exeUI = Get-ChildItem -Path $exePathUI -Filter "*.exe" -File | Select-Object -First 1
Copy-Item $exeUI.FullName -Destination "$out\loaderspot_ui_$env:ARCH.exe" -Force
Write-Host "UI executable ready: $out\loaderspot_ui_$env:ARCH.exe"
# build CLI
Write-Host "Building CLI for target: $env:RUST_TARGET"
$exeUI = Get-ChildItem "LoaderSpot_UI/target/$env:RUST_TARGET/release/*.exe" | Select-Object -First 1
Copy-Item $exeUI.FullName "$out\loaderspot-ui-win-$env:ARCH.exe"
Write-Host "UI executable ready: loaderspot-ui-win-$env:ARCH.exe"
Write-Host "Building CLI for $env:RUST_TARGET"
cargo build --manifest-path LoaderSpot_CLI/Cargo.toml --release --target $env:RUST_TARGET
$exePathCLI = Join-Path -Path "LoaderSpot_CLI" -ChildPath (Join-Path -Path "target" -ChildPath (Join-Path -Path $env:RUST_TARGET -ChildPath "release"))
$exeCLI = Get-ChildItem -Path $exePathCLI -Filter "*.exe" -File | Select-Object -First 1
Copy-Item $exeCLI.FullName -Destination "$out\loaderspot_cli_$env:ARCH.exe" -Force
Write-Host "CLI executable ready: $out\loaderspot_cli_$env:ARCH.exe"
$exeCLI = Get-ChildItem "LoaderSpot_CLI/target/$env:RUST_TARGET/release/*.exe" | Select-Object -First 1
Copy-Item $exeCLI.FullName "$out\loaderspot-cli-win-$env:ARCH.exe"
Write-Host "CLI executable ready: loaderspot-cli-win-$env:ARCH.exe"
- name: Upload Windows artifacts
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v7
with:
name: windows-artifacts-${{ matrix.arch }}
path: |
artifacts/loaderspot_ui_${{ matrix.arch }}.exe
artifacts/loaderspot_cli_${{ matrix.arch }}.exe
path: artifacts/*
build_linux:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- arch: x64
rust_target: x86_64-unknown-linux-gnu
- arch: arm64
rust_target: aarch64-unknown-linux-gnu
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Install Rust toolchain and target
run: |
rustup toolchain install stable --profile minimal
rustup target add ${{ matrix.rust_target }}
rustup default stable
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-unknown-linux-gnu
- name: Cache cargo dependencies
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
LoaderSpot_UI/target
LoaderSpot_CLI/target
key: ${{ runner.os }}-cargo-x86_64-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-x86_64-
- name: Install cross-compilation tools
- name: Install build dependencies
run: |
sudo apt-get update
if [ "${{ matrix.rust_target }}" = "aarch64-unknown-linux-gnu" ]; then
sudo apt-get install -y gcc-aarch64-linux-gnu
fi
sudo apt-get install -y pkg-config libssl-dev
- name: Build release binary
env:
RUST_TARGET: ${{ matrix.rust_target }}
ARCH: ${{ matrix.arch }}
- name: Build release binaries
run: |
# Set linker for cross-compilation
if [ "$RUST_TARGET" = "aarch64-unknown-linux-gnu" ]; then
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc
fi
mkdir -p artifacts
# build CLI
echo "Building CLI for target: $RUST_TARGET"
cargo build --manifest-path LoaderSpot_CLI/Cargo.toml --release --target $RUST_TARGET
cp "LoaderSpot_CLI/target/$RUST_TARGET/release/loaderspot_cli" "artifacts/loaderspot_cli_linux_$ARCH"
echo "CLI executable ready: artifacts/loaderspot_cli_linux_$ARCH"
# Build UI for Linux
echo "Building UI for x86_64-unknown-linux-gnu"
cargo build --manifest-path LoaderSpot_UI/Cargo.toml --release --target x86_64-unknown-linux-gnu
cp "LoaderSpot_UI/target/x86_64-unknown-linux-gnu/release/loaderspot_ui" "artifacts/loaderspot-ui-linux-x64"
echo "UI executable ready: loaderspot-ui-linux-x64"
# Build CLI for Linux
echo "Building CLI for x86_64-unknown-linux-gnu"
cargo build --manifest-path LoaderSpot_CLI/Cargo.toml --release --target x86_64-unknown-linux-gnu
cp "LoaderSpot_CLI/target/x86_64-unknown-linux-gnu/release/loaderspot_cli" "artifacts/loaderspot-cli-linux-x64"
echo "CLI executable ready: loaderspot-cli-linux-x64"
- name: Upload Linux artifact
uses: actions/upload-artifact@v5
- name: Upload Linux artifacts
uses: actions/upload-artifact@v7
with:
name: linux-artifacts-${{ matrix.arch }}
path: artifacts/loaderspot_cli_linux_${{ matrix.arch }}
name: linux-artifacts-x64
path: artifacts/*
release:
needs: [build_windows, build_linux]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
path: artifacts
@ -127,14 +131,15 @@ jobs:
uses: actions/github-script@v8
with:
script: |
const releases = await github.rest.repos.listReleases({
owner: context.repo.owner,
repo: context.repo.repo
});
if (!releases.data || releases.data.length === 0) {
core.setFailed('No releases found for repository');
try {
const release = await github.rest.repos.getLatestRelease({
owner: context.repo.owner,
repo: context.repo.repo
});
return release.data.id;
} catch (error) {
core.setFailed(`No releases found: ${error.message}`);
}
return releases.data[0].id;
- name: Upload assets to Release
uses: actions/github-script@v8
@ -164,7 +169,7 @@ jobs:
});
}
console.log(`Uploading new asset: ${fileName}`);
console.log(`Uploading: ${fileName}`);
await github.rest.repos.uploadReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
@ -174,10 +179,17 @@ jobs:
});
}
const files = [];
const artifactDirs = fs.readdirSync('artifacts');
for (const dir of artifactDirs) {
const files = fs.readdirSync(path.join('artifacts', dir));
for (const file of files) {
await uploadAsset(path.join('artifacts', dir, file));
const dirPath = path.join('artifacts', dir);
const dirFiles = fs.readdirSync(dirPath);
for (const file of dirFiles) {
files.push(path.join(dirPath, file));
}
}
for (let i = 0; i < files.length; i += 3) {
const batch = files.slice(i, i + 3);
await Promise.all(batch.map(uploadAsset));
}

View file

@ -13,7 +13,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
token: ${{ secrets.PERSONAL_TOKEN }}

View file

@ -1,8 +1,6 @@
name: check Spotify MS
on:
schedule:
- cron: '0 */5 * * *'
workflow_dispatch:
jobs:
@ -11,7 +9,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: run ms-spotify-check.ps1
shell: pwsh

View file

@ -1,13 +1,15 @@
name: Sync files to table
on:
push:
branches:
- main
paths:
- 'versions.json'
- 'versions_deb.json'
workflow_dispatch:
# Temporarily disabled automatic sync trigger.
# Uncomment this block to restore automatic runs on push to main.
# push:
# branches:
# - main
# paths:
# - 'versions.json'
# - 'versions_deb.json'
workflow_dispatch:
jobs:
sync-files:
@ -17,10 +19,10 @@ jobs:
steps:
- name: Checkout source repo
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Checkout target repo
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
repository: LoaderSpot/table
path: table

View file

@ -8,7 +8,11 @@ on:
required: true
version:
description: 'Another version parameter'
required: true
required: true
buildType:
description: 'Build type (e.g., Master, Release)'
required: false
default: 'false'
jobs:
update-version:
runs-on: ubuntu-latest
@ -17,7 +21,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
token: ${{ secrets.PERSONAL_TOKEN }}
@ -29,7 +33,8 @@ jobs:
- name: Update version in versions.json
run: |
data="${{ github.event.inputs.datajson }}"
python3 -c "import json, re; data=json.loads('$data'); file_data=json.load(open('versions.json')); file_data[list(data.keys())[0]] = data[list(data.keys())[0]]; file_data = dict(sorted(file_data.items(), key=lambda x: [int(part) if part.isdigit() else part for part in re.split(r'(\d+)', x[0])], reverse=True)); json.dump(file_data, open('versions.json', 'w'), indent=2)"
build_type="${{ github.event.inputs.buildType }}"
python3 update_version.py "$data" "$build_type"
- name: Commit changes
run: |

View file

@ -8,29 +8,47 @@ concurrency:
cancel-in-progress: false
jobs:
build:
version-search:
runs-on: ubuntu-latest
outputs:
versions: ${{ steps.search.outputs.versions }}
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache Cargo dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
LoaderSpot_CLI/target/
key: ${{ runner.os }}-cargo-${{ hashFiles('LoaderSpot_CLI/Cargo.lock') }}
- name: Build LoaderSpot_CLI
run: cd LoaderSpot_CLI && cargo build --release
- name: Download latest loaderspot_cli_linux_x64
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release download --repo ${{ github.repository }} --pattern "loaderspot-cli-linux-x64" --output loaderspot_cli
chmod +x loaderspot_cli
- name: Version search
run: ${{ github.workspace }}/LoaderSpot_CLI/target/release/loaderspot_cli --version "${{ github.event.client_payload.v }}" --source "${{ github.event.client_payload.s }}" --gas-url "${{ secrets.GOOGLE_APPS_URL }}" --connections 300
id: search
if: github.event.client_payload.v && github.event.client_payload.s
env:
v: ${{ github.event.client_payload.v }}
s: ${{ github.event.client_payload.s }}
run: |
{
echo 'versions<<EOF'
./loaderspot_cli --version "$v" --connections 300 --ladder-search
echo 'EOF'
} >> "$GITHUB_OUTPUT"
get-build-and-send:
runs-on: windows-latest
needs: version-search
if: success() && needs.version-search.outputs.versions
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Get build info and send to GAS
shell: pwsh
env:
GOOGLE_APPS_URL: ${{ secrets.GOOGLE_APPS_URL }}
VERSIONS: ${{ needs.version-search.outputs.versions }}
SOURCE: ${{ github.event.client_payload.s }}
run: |
./check-build-version.ps1 -versions "$env:VERSIONS" -source "$env:SOURCE" -googleAppsUrl "$env:GOOGLE_APPS_URL"

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/LoaderSpot_CLI/target
/LoaderSpot_UI/target

View file

@ -7,7 +7,6 @@ use std::sync::Arc;
use std::time::Duration;
use tokio::sync::Semaphore;
use regex::Regex;
use scraper::{Html, Selector};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
enum PlatformArch {
@ -149,13 +148,9 @@ struct Cli {
#[clap(long, default_value_t = 100)]
connections: usize,
/// URL to send the found versions to (Google Apps Script)
/// Use ladder search algorithm
#[clap(long)]
gas_url: Option<String>,
/// Source of the version
#[clap(long)]
source: Option<String>,
ladder_search: bool,
}
fn parse_range(range_str: &str) -> (i32, i32) {
@ -220,7 +215,7 @@ async fn main() {
let connections = cli.connections.clamp(50, 300);
let range = cli.range.clone();
let client = Client::builder()
.timeout(std::time::Duration::from_secs(10))
.timeout(std::time::Duration::from_secs(30))
.build()
.unwrap();
@ -231,43 +226,42 @@ async fn main() {
pb.set_style(spinner_style);
pb.enable_steady_tick(Duration::from_millis(80));
let version_clone = cli.version.clone();
let versions = cli.version.clone();
let client_clone = client.clone();
let gas_url_clone = cli.gas_url.clone();
let source_clone = cli.source.clone();
let search_task = tokio::spawn(async move {
let mut all_found_urls = Vec::new();
let ladder_search = cli.ladder_search;
let versions_clone_for_task = versions.clone();
for version in &version_clone {
let search_task = tokio::spawn(async move {
let mut results = Vec::new();
for version in &versions_clone_for_task {
let mut all_found_urls_for_version = Vec::new();
let mut arches_to_search = platform_arches.clone();
if !should_use_win_x86(version) && arches_to_search.contains(&PlatformArch::WinX86) {
if version_clone.len() == 1 && arches_to_search.len() == 1 {
if versions_clone_for_task.len() == 1 && arches_to_search.len() == 1 {
eprintln!("Warning: x86 architecture for Windows is no longer supported for versions newer than 1.2.53.");
continue;
}
arches_to_search.retain(|&p| p != PlatformArch::WinX86);
}
if gas_url_clone.is_some() && source_clone.is_some() {
// "Лесенка"
if ladder_search {
let mut start_number = 0;
let mut before_enter = 1000;
let additional_searches = 15;
let increment = 1000;
// Первый проход
for &platform_arch in &arches_to_search {
let found = search_installers(&client_clone, version, start_number, before_enter, platform_arch, connections).await;
all_found_urls.extend(found);
all_found_urls_for_version.extend(found);
}
// Дополнительные поиски
for _ in 0..additional_searches {
let latest_urls = get_latest_urls(&all_found_urls, &[version.to_string()], source_clone.as_deref().unwrap_or(""));
let latest_urls = get_latest_urls(&all_found_urls_for_version);
let target_len = arches_to_search.iter().filter(|&&p| p != PlatformArch::WinX86 || should_use_win_x86(version)).count();
if latest_urls.len() >= target_len + 2 { // +2 for version and source
if latest_urls.len() >= target_len {
break;
}
@ -280,55 +274,40 @@ async fn main() {
missing_arches.push(platform_arch);
}
}
for &platform_arch in &missing_arches {
let found = search_installers(&client_clone, version, start_number, before_enter, platform_arch, connections).await;
all_found_urls.extend(found);
all_found_urls_for_version.extend(found);
}
}
} else {
// Стандартный поиск
let (start, end) = parse_range(&range);
for &platform_arch in &arches_to_search {
let found = search_installers(&client_clone, version, start, end, platform_arch, connections).await;
all_found_urls.extend(found);
all_found_urls_for_version.extend(found);
}
}
let mut latest_urls = get_latest_urls(&all_found_urls_for_version);
latest_urls.insert("version".to_string(), version.clone());
results.push(latest_urls);
}
all_found_urls
results
});
let all_found_urls = search_task.await.unwrap();
let results = search_task.await.unwrap();
pb.finish_and_clear();
match (&cli.gas_url, &cli.source) {
(Some(gas_url), Some(source)) => {
let latest_urls = get_latest_urls(&all_found_urls, &cli.version, source);
send_to_gas(client, gas_url, latest_urls).await;
}
(Some(_), None) => {
eprintln!("Warning: --gas-url is provided, but --source is missing. Skipping sending to GAS.");
let json_output = serde_json::to_string_pretty(&all_found_urls).unwrap();
println!("{}", json_output);
}
(None, Some(_)) => {
eprintln!("Warning: --source is provided, but --gas-url is missing. Skipping sending to GAS.");
let json_output = serde_json::to_string_pretty(&all_found_urls).unwrap();
println!("{}", json_output);
}
(None, None) => {
let json_output = serde_json::to_string_pretty(&all_found_urls).unwrap();
println!("{}", json_output);
}
if versions.len() == 1 {
let json_output = serde_json::to_string_pretty(&results[0]).unwrap();
println!("{}", json_output);
} else {
let json_output = serde_json::to_string_pretty(&results).unwrap();
println!("{}", json_output);
}
}
fn get_latest_urls(
found_urls: &[(String, PlatformArch)],
versions: &[String],
source: &str,
) -> HashMap<String, String> {
fn get_latest_urls(found_urls: &[(String, PlatformArch)]) -> HashMap<String, String> {
let mut platform_urls = HashMap::new();
let version_pattern = Regex::new(r"-(\d+)\.(exe|tbz)$").unwrap();
@ -347,62 +326,16 @@ fn get_latest_urls(
}
}
let mut latest_urls: HashMap<String, String> = platform_urls
let latest_urls: HashMap<String, String> = platform_urls
.into_iter()
.map(|(k, (v, _))| (k, v))
.collect();
if !versions.is_empty() {
latest_urls.insert("version".to_string(), versions.join(", "));
}
latest_urls.insert("source".to_string(), source.to_string());
if latest_urls.is_empty() {
let mut empty_map = HashMap::new();
empty_map.insert("unknown".to_string(), "unknown".to_string());
if !versions.is_empty() {
empty_map.insert("version".to_string(), versions.join(", "));
}
empty_map.insert("source".to_string(), source.to_string());
return empty_map;
}
latest_urls
}
async fn send_to_gas(
client: Client,
gas_url: &str,
data: HashMap<String, String>,
) {
let cleaned_gas_url = gas_url.trim_matches('"');
match client.post(cleaned_gas_url).json(&data).send().await {
Ok(response) => {
if response.status().is_success() {
match response.text().await {
Ok(text) => {
if text.contains("<div") {
let soup = Html::parse_document(&text);
let selector = Selector::parse("div[style*='text-align:center']").unwrap();
if let Some(div) = soup.select(&selector).next() {
println!("Ответ от GAS: {}", div.text().collect::<String>().trim());
} else {
eprintln!("Не удалось извлечь ответ из HTML. Полный ответ:");
eprintln!("{}", text);
}
} else {
println!("Ответ от GAS: {}", text.trim());
}
}
Err(e) => eprintln!("Ошибка чтения ответа от GAS: {}", e),
}
} else {
eprintln!("Ошибка при отправке в GAS: {}", response.status());
}
}
Err(e) => eprintln!("Ошибка при отправке запроса в GAS: {}", e),
}
}

View file

@ -762,6 +762,15 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
@ -1892,6 +1901,21 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
[[package]]
name = "loaderspot_ui"
version = "0.1.0"
dependencies = [
"crossbeam-channel",
"eframe",
"egui",
"regex",
"reqwest",
"serde",
"serde_json",
"tokio",
"winapi",
]
[[package]]
name = "lock_api"
version = "0.4.14"
@ -3187,20 +3211,6 @@ dependencies = [
"bitflags 2.10.0",
]
[[package]]
name = "spotify_finder"
version = "0.1.0"
dependencies = [
"eframe",
"egui",
"regex",
"reqwest",
"serde",
"serde_json",
"tokio",
"winapi",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"

View file

@ -1,5 +1,5 @@
[package]
name = "spotify_finder"
name = "loaderspot_ui"
version = "0.1.0"
edition = "2021"
@ -11,4 +11,7 @@ reqwest = { version = "0.12", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
regex = "1.10"
winapi = { version = "0.3", features = ["winuser"] }
crossbeam-channel = "0.5"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["winuser"] }

View file

@ -1,13 +1,13 @@
// Hide console window on Windows
#![cfg_attr(target_os = "windows", windows_subsystem = "windows")]
use crossbeam_channel::{unbounded, Receiver, Sender};
use eframe::egui;
use reqwest::Client;
use serde::Deserialize;
use std::collections::{HashMap, VecDeque};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, OnceLock};
use std::time::Instant;
use tokio::runtime::Runtime;
@ -31,13 +31,13 @@ impl Platform {
}
}
fn path_template(&self) -> &str {
fn generate_path(&self, version: &str, number: i32) -> String {
match self {
Platform::WinX86 => "win32-x86/spotify_installer-{version}-{number}.exe",
Platform::WinX64 => "win32-x86_64/spotify_installer-{version}-{number}.exe",
Platform::WinArm64 => "win32-arm64/spotify_installer-{version}-{number}.exe",
Platform::MacOsIntel => "osx-x86_64/spotify-autoupdate-{version}-{number}.tbz",
Platform::MacOsArm64 => "osx-arm64/spotify-autoupdate-{version}-{number}.tbz",
Platform::WinX86 => format!("win32-x86/spotify_installer-{version}-{number}.exe"),
Platform::WinX64 => format!("win32-x86_64/spotify_installer-{version}-{number}.exe"),
Platform::WinArm64 => format!("win32-arm64/spotify_installer-{version}-{number}.exe"),
Platform::MacOsIntel => format!("osx-x86_64/spotify-autoupdate-{version}-{number}.tbz"),
Platform::MacOsArm64 => format!("osx-arm64/spotify-autoupdate-{version}-{number}.tbz"),
}
}
@ -63,11 +63,11 @@ impl UrlGenerator {
const BASE_URL: &'static str = "https://upgrade.scdn.co/upgrade/client/";
fn generate_url(platform: Platform, version: &str, number: i32) -> String {
let path = platform
.path_template()
.replace("{version}", version)
.replace("{number}", &number.to_string());
format!("{}{}", Self::BASE_URL, path)
format!(
"{}{}",
Self::BASE_URL,
platform.generate_path(version, number)
)
}
}
@ -83,15 +83,12 @@ fn extract_base_version(version: &str) -> String {
#[allow(dead_code)]
fn should_use_win_x86(version: &str) -> bool {
let base_version = extract_base_version(version);
let parts: Vec<&str> = base_version.split('.').collect();
let mut parts = version.split('.');
if parts.len() >= 3 {
if let (Ok(major), Ok(minor), Ok(patch)) = (
parts[0].parse::<u32>(),
parts[1].parse::<u32>(),
parts[2].parse::<u32>(),
) {
if let (Some(p1), Some(p2), Some(p3)) = (parts.next(), parts.next(), parts.next()) {
if let (Ok(major), Ok(minor), Ok(patch)) =
(p1.parse::<u32>(), p2.parse::<u32>(), p3.parse::<u32>())
{
return (major, minor, patch) <= (1, 2, 53);
}
}
@ -99,7 +96,8 @@ fn should_use_win_x86(version: &str) -> bool {
}
fn validate_version(version: &str) -> bool {
let re = regex::Regex::new(r"^\d+\.\d+\.\d+\.\d+\.g[0-9a-f]{8}$").unwrap();
static RE: OnceLock<regex::Regex> = OnceLock::new();
let re = RE.get_or_init(|| regex::Regex::new(r"^\d+\.\d+\.\d+\.\d+\.g[0-9a-f]{8}$").unwrap());
re.is_match(version)
}
@ -117,8 +115,23 @@ const MAX_CONNECTION_OPTIONS: [usize; 6] = [50, 100, 150, 200, 250, 300];
async fn check_url(client: &Client, url: String, platform: Platform) -> Option<(String, Platform)> {
match client.head(&url).send().await {
Ok(response) if response.status().is_success() => Some((url, platform)),
_ => None,
Ok(response) => {
if response.status().is_success() {
Some((url, platform))
} else if response.status() == reqwest::StatusCode::TOO_MANY_REQUESTS {
eprintln!(
"RATE LIMITED (429)! Server blocked the request for: {}",
url
);
None
} else {
None
}
}
Err(e) => {
eprintln!("Network error: {}", e);
None
}
}
}
@ -151,7 +164,7 @@ async fn check_version_and_submit(client: &Client, version: &str) {
let version_exists = versions_json
.values()
.any(|v| v.fullversion.as_ref().map_or(false, |fv| fv == version));
.any(|v| v.fullversion.as_ref().is_some_and(|fv| fv == version));
if !version_exists {
submit_to_google_form(client, version).await;
@ -159,16 +172,15 @@ async fn check_version_and_submit(client: &Client, version: &str) {
}
enum SearchMessage {
Progress(u64, u64),
Result(String, Platform),
Complete(String),
VersionStart(String, usize, usize),
CompleteAll,
}
async fn search_installers(
client: &Client,
version: &str,
struct SearchOptions {
client: Client,
version: String,
start: i32,
end: i32,
platforms: Vec<Platform>,
@ -176,45 +188,59 @@ async fn search_installers(
tx: Sender<SearchMessage>,
pause_flag: Arc<AtomicBool>,
cancel_flag: Arc<AtomicBool>,
) {
processed: Arc<AtomicU64>,
}
async fn search_installers(opts: SearchOptions) {
use tokio::sync::Semaphore;
let total = ((end - start + 1) * platforms.len() as i32) as u64;
let semaphore = Arc::new(Semaphore::new(max_connections));
let semaphore = Arc::new(Semaphore::new(opts.max_connections));
let mut tasks = Vec::new();
let processed = Arc::new(Mutex::new(0u64));
for platform in platforms {
for number in start..=end {
let url = UrlGenerator::generate_url(platform, version, number);
let client = client.clone();
let permit = semaphore.clone().acquire_owned().await.unwrap();
let tx_clone = tx.clone();
let processed_clone = processed.clone();
let pause_local = pause_flag.clone();
let cancel_local = cancel_flag.clone();
'outer: for platform in opts.platforms {
for number in opts.start..=opts.end {
if opts.cancel_flag.load(Ordering::Relaxed) {
break 'outer;
}
while opts.pause_flag.load(Ordering::Relaxed) {
if opts.cancel_flag.load(Ordering::Relaxed) {
break 'outer;
}
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}
let permit = match semaphore.clone().acquire_owned().await {
Ok(p) => p,
Err(_) => break 'outer,
};
while opts.pause_flag.load(Ordering::Relaxed) {
if opts.cancel_flag.load(Ordering::Relaxed) {
break 'outer;
}
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}
if opts.cancel_flag.load(Ordering::Relaxed) {
break 'outer;
}
let url = UrlGenerator::generate_url(platform, &opts.version, number);
let client = opts.client.clone();
let tx_clone = opts.tx.clone();
let processed_clone = opts.processed.clone();
let cancel_local = opts.cancel_flag.clone();
let task = tokio::spawn(async move {
let _permit = permit;
if cancel_local.load(Ordering::Relaxed) {
return;
}
while pause_local.load(Ordering::Relaxed) {
if cancel_local.load(Ordering::Relaxed) {
return;
}
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
}
let result = check_url(&client, url, platform).await;
drop(permit);
let mut p = processed_clone.lock().unwrap();
*p += 1;
let current = *p;
drop(p);
let _ = tx_clone.send(SearchMessage::Progress(current, total));
processed_clone.fetch_add(1, Ordering::Relaxed);
if let Some((url, platform)) = result {
let _ = tx_clone.send(SearchMessage::Result(url, platform));
@ -225,11 +251,17 @@ async fn search_installers(
}
}
if opts.cancel_flag.load(Ordering::Relaxed) {
for task in &tasks {
task.abort();
}
}
for task in tasks {
let _ = task.await;
}
let _ = tx.send(SearchMessage::Complete(version.to_string()));
let _ = opts.tx.send(SearchMessage::Complete(opts.version));
}
struct SpotifyFinderApp {
@ -247,7 +279,6 @@ struct SpotifyFinderApp {
report_unknown: bool,
search_results: String,
is_searching: bool,
is_paused: bool,
displayed_results: String,
@ -262,7 +293,8 @@ struct SpotifyFinderApp {
progress: f32,
progress_text: String,
total_work: u64,
processed_global: u64,
processed_global: Arc<AtomicU64>,
rx: Option<Receiver<SearchMessage>>,
found_urls: HashMap<Platform, Vec<String>>,
@ -284,7 +316,6 @@ impl Default for SpotifyFinderApp {
platform_macos_intel: false,
platform_macos_arm64: false,
report_unknown: false,
search_results: String::new(),
is_searching: false,
is_paused: false,
displayed_results: String::new(),
@ -296,7 +327,7 @@ impl Default for SpotifyFinderApp {
progress: 0.0,
progress_text: String::new(),
total_work: 0,
processed_global: 0,
processed_global: Arc::new(AtomicU64::new(0)),
rx: None,
found_urls: HashMap::new(),
pause_flag: Arc::new(AtomicBool::new(false)),
@ -309,7 +340,6 @@ impl Default for SpotifyFinderApp {
}
impl SpotifyFinderApp {
// Improved reveal logic: finish current item immediately and fast-forward the queue
fn advance_reveal(&mut self) {
if self.current_reveal.is_some() && !self.reveal_queue.is_empty() {
if let Some(cur) = self.current_reveal.take() {
@ -436,7 +466,6 @@ impl SpotifyFinderApp {
self.is_searching = true;
self.progress = 0.0;
self.progress_text = "Starting...".to_string();
self.search_results.clear();
self.displayed_results.clear();
self.reveal_queue.clear();
self.current_reveal = None;
@ -451,13 +480,15 @@ impl SpotifyFinderApp {
let per_version = ((end - start + 1) * cnt as i32) as u64;
total_work_calc = total_work_calc.saturating_add(per_version);
}
self.total_work = total_work_calc;
self.processed_global = 0;
self.processed_global.store(0, Ordering::Relaxed);
self.current_version = None;
self.current_version_index = 0;
self.total_versions = versions.len();
let (tx, rx) = channel();
let (tx, rx): (Sender<SearchMessage>, Receiver<SearchMessage>) = unbounded();
self.rx = Some(rx);
self.pause_flag.store(false, Ordering::Relaxed);
@ -469,6 +500,7 @@ impl SpotifyFinderApp {
let pause = self.pause_flag.clone();
let cancel = self.cancel_flag.clone();
let base_platforms_for_spawn = base_platforms.clone();
let processed_for_spawn = self.processed_global.clone();
self.runtime.spawn(async move {
let client = Client::builder()
@ -502,17 +534,18 @@ impl SpotifyFinderApp {
continue;
}
search_installers(
&client,
&version,
search_installers(SearchOptions {
client: client.clone(),
version,
start,
end,
platforms_for_version,
max_conn,
tx.clone(),
pause.clone(),
cancel.clone(),
)
platforms: platforms_for_version,
max_connections: max_conn,
tx: tx.clone(),
pause_flag: pause.clone(),
cancel_flag: cancel.clone(),
processed: processed_for_spawn.clone(),
})
.await;
if cancel.load(Ordering::Relaxed) {
@ -534,7 +567,6 @@ impl SpotifyFinderApp {
}
fn clear_results(&mut self) {
self.search_results.clear();
self.displayed_results.clear();
self.reveal_queue.clear();
self.current_reveal = None;
@ -542,49 +574,49 @@ impl SpotifyFinderApp {
self.progress = 0.0;
self.progress_text.clear();
self.total_work = 0;
self.processed_global = 0;
self.processed_global.store(0, Ordering::Relaxed);
}
fn update_search_progress(&mut self) {
let current_processed = self.processed_global.load(Ordering::Relaxed);
let denom = if self.total_work == 0 {
1
} else {
self.total_work
};
self.progress = current_processed as f32 / denom as f32;
if let Some(v) = &self.current_version {
let short = short_version(v);
if self.total_versions > 1 {
self.progress_text = format!(
"Checking: {}/{}, Version: {}, No. {}/{}",
current_processed,
denom,
short,
self.current_version_index,
self.total_versions
);
} else {
self.progress_text = format!(
"Checking: {}/{}, Version: {}",
current_processed, denom, short
);
}
} else if self.is_searching {
self.progress_text = format!("Checking: {}/{}", current_processed, denom);
}
if let Some(rx_owned) = self.rx.take() {
let rx = rx_owned;
let mut completed = false;
let mut processed_this_frame = 0;
while let Ok(msg) = rx_owned.try_recv() {
processed_this_frame += 1;
while let Ok(msg) = rx.try_recv() {
match msg {
SearchMessage::Progress(_current, _total) => {
self.processed_global = self.processed_global.saturating_add(1);
let denom = if self.total_work == 0 {
1
} else {
self.total_work
};
self.progress = self.processed_global as f32 / denom as f32;
if let Some(v) = &self.current_version {
let short = short_version(v);
if self.total_versions > 1 {
self.progress_text = format!(
"Checking: {}/{}, Version: {}, No. {}/{}",
self.processed_global,
denom,
short,
self.current_version_index,
self.total_versions
);
} else {
self.progress_text = format!(
"Checking: {}/{}, Version: {}",
self.processed_global, denom, short
);
}
} else {
self.progress_text =
format!("Checking: {}/{}", self.processed_global, denom);
}
}
SearchMessage::Result(url, platform) => {
let entry = self.found_urls.entry(platform).or_insert_with(Vec::new);
let entry = self.found_urls.entry(platform).or_default();
let first = entry.is_empty();
entry.push(url.clone());
@ -626,10 +658,14 @@ impl SpotifyFinderApp {
completed = true;
}
}
if processed_this_frame > 1000 && !completed {
break;
}
}
if !completed {
self.rx = Some(rx);
self.rx = Some(rx_owned);
}
}
}
@ -954,15 +990,13 @@ impl eframe::App for SpotifyFinderApp {
self.is_paused = false;
self.pause_flag.store(false, Ordering::Relaxed);
}
} else {
if ui
.add_sized(btn_size, egui::Button::new("⏸ Pause"))
.clicked()
{
self.is_paused = true;
self.pause_flag.store(true, Ordering::Relaxed);
self.progress_text = "Paused".to_string();
}
} else if ui
.add_sized(btn_size, egui::Button::new("⏸ Pause"))
.clicked()
{
self.is_paused = true;
self.pause_flag.store(true, Ordering::Relaxed);
self.progress_text = "Paused".to_string();
}
if ui
@ -971,13 +1005,11 @@ impl eframe::App for SpotifyFinderApp {
{
self.stop_search();
}
} else {
if ui
.add_sized(btn_size, egui::Button::new("▶ Start Search"))
.clicked()
{
self.start_search();
}
} else if ui
.add_sized(btn_size, egui::Button::new("▶ Start Search"))
.clicked()
{
self.start_search();
}
ui.add_enabled_ui(!self.is_searching, |ui| {

125
check-build-version.ps1 Normal file
View file

@ -0,0 +1,125 @@
param (
[string]$versions,
[string]$source,
[string]$googleAppsUrl
)
function Find-BuildInfo {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=0)]
[string]$Path
)
process {
try {
$fileContent = Get-Content -Path $Path -ErrorAction Stop
$found = $false
$regex = '(Master|Release|PR|Local) Build.+(?:cef_)?(\d+\.\d+\.\d+\+g[0-9a-f]+\+chromium-\d+\.\d+\.\d+\.\d+)'
foreach ($line in $fileContent) {
$match = [regex]::Match($line, $regex)
if ($match.Success) {
$buildType = $match.Groups[1].Value
Write-Host "build type: $buildType"
return $buildType
}
}
if (-not $found) {
Write-Host "Билд не найден"
return $false
}
}
catch {
Write-Error "Ошибка при чтении файла: $_"
return $false
}
}
}
function Get-BuildTypeFromUrl {
[CmdletBinding()]
param(
[string]$Url
)
$tempPath = Join-Path ([System.IO.Path]::GetTempPath()) "spotify"
$destinationPath = Join-Path $tempPath "unpacked"
$exePath = Join-Path $tempPath "SpotifySetup.exe"
if (-not (Test-Path -Path $tempPath)) {
New-Item -ItemType Directory -Path $tempPath | Out-Null
}
if (-not (Test-Path -Path $destinationPath)) {
New-Item -ItemType Directory -Path $destinationPath | Out-Null
}
try {
Write-Host "Скачивание файла из $Url..."
Invoke-WebRequest -Uri $Url -OutFile $exePath
Write-Host "Файл сохранен в $exePath"
Write-Host "Распаковка файла в $destinationPath"
Start-Process -Wait -FilePath $exePath -ArgumentList "/extract `"$destinationPath`""
$dllPath = Join-Path $destinationPath "Spotify.dll"
$exePathForAnalysis = Join-Path $destinationPath "Spotify.exe"
if (Test-Path $dllPath) {
return Find-BuildInfo -Path $dllPath
} elseif (Test-Path $exePathForAnalysis) {
return Find-BuildInfo -Path $exePathForAnalysis
}
else {
Write-Error "Файлы Spotify.dll и Spotify.exe не найдены в $destinationPath"
return $false
}
}
catch {
Write-Error "Произошла ошибка при скачивании или распаковке: $_"
return $false
}
}
if ([string]::IsNullOrEmpty($versions) -or [string]::IsNullOrEmpty($source) -or [string]::IsNullOrEmpty($googleAppsUrl)) {
Write-Error "Один или несколько обязательных параметров (versions, source, googleAppsUrl) не предоставлены."
exit 1
}
$versionsObj = $versions | ConvertFrom-Json
$win64Url = $versionsObj.WIN64
$buildType = $false
if ($win64Url) {
$buildType = Get-BuildTypeFromUrl -Url $win64Url
}
if ($buildType -eq $false) {
$versionsObj | Add-Member -NotePropertyName "buildType" -NotePropertyValue $false -Force
} else {
$versionsObj | Add-Member -NotePropertyName "buildType" -NotePropertyValue $buildType -Force
}
$versionsObj | Add-Member -NotePropertyName "source" -NotePropertyValue $source -Force
$finalJson = $versionsObj | ConvertTo-Json -Compress
Write-Host "Отправка данных на GAS..."
try {
$response = Invoke-WebRequest -Uri $googleAppsUrl `
-Method POST `
-ContentType "application/json" `
-Body $finalJson `
-UseBasicParsing -ErrorAction Stop
if ($response.StatusCode -eq 200) {
Write-Host "Ответ от GAS: $($response.Content)"
} else {
Write-Error "Ошибка при отправке в GAS. Статус: $($response.StatusCode). Ответ: $($response.Content)"
}
} catch {
Write-Error "Критическая ошибка при отправке в GAS: $_"
}

View file

@ -123,9 +123,9 @@ def commit_changes(new_versions):
# Формируем сообщение коммита в зависимости от количества новых версий
if new_versions:
commit_message = (
f"Added deb version {new_versions[0][0]}"
f"Added deb version {new_versions[0][1]}"
if len(new_versions) == 1
else f"Added deb versions: {', '.join(v[0] for v in new_versions)}"
else f"Added deb versions: {', '.join(v[1] for v in new_versions)}"
)
else:
commit_message = "Update Spotify versions"

63
update_version.py Normal file
View file

@ -0,0 +1,63 @@
#!/usr/bin/env python3
import json
import re
import sys
def update_version(data_json: str, build_type: str = None, version_file: str = "versions.json") -> bool:
try:
data = json.loads(data_json)
version_key = list(data.keys())[0]
version_data = data[version_key]
if build_type and build_type.lower() != 'false':
version_data = {'buildType': build_type, **version_data}
with open(version_file, 'r', encoding='utf-8') as f:
file_data = json.load(f)
file_data[version_key] = version_data
sorted_data = dict(
sorted(
file_data.items(),
key=lambda x: [
int(part) if part.isdigit() else part
for part in re.split(r'(\d+)', x[0])
],
reverse=True
)
)
with open(version_file, 'w', encoding='utf-8') as f:
json.dump(sorted_data, f, indent=2)
print(f"Version {version_key} added to {version_file}")
return True
except json.JSONDecodeError as e:
print(f"JSON parse error: {e}", file=sys.stderr)
return False
except FileNotFoundError:
print(f"File {version_file} not found", file=sys.stderr)
return False
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return False
def main():
if len(sys.argv) < 2:
print("Usage: python update_version.py '<json_data>' [build_type] [version_file]", file=sys.stderr)
sys.exit(1)
data_json = sys.argv[1]
build_type = sys.argv[2] if len(sys.argv) > 2 else None
version_file = sys.argv[3] if len(sys.argv) > 3 else "versions.json"
success = update_version(data_json, build_type, version_file)
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,44 @@
{
"1.2.84.476": {
"fullversion": "1.2.84.476.ga1ff6607",
"size": "157643240",
"data": "09.03.2026",
"links": {
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.84.476.ga1ff6607_amd64.deb"
}
},
"1.2.82.428": {
"fullversion": "1.2.82.428.g0ac8be2b",
"size": "156375346",
"data": "09.02.2026",
"links": {
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.82.428.g0ac8be2b_amd64.deb"
}
},
"1.2.79.427": {
"fullversion": "1.2.79.427.g80eb4a07",
"size": "155456648",
"data": "12.01.2026",
"links": {
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.79.427.g80eb4a07_amd64.deb"
}
},
"1.2.79.425": {
"fullversion": "1.2.79.425.g1d0fcf61",
"size": "155381166",
"data": "17.12.2025",
"links": {
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.79.425.g1d0fcf61_amd64.deb"
}
},
"1.2.77.358": {
"fullversion": "1.2.77.358.g4339a634",
"size": "155510286",
"data": "25.11.2025",
"links": {
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.77.358.g4339a634_amd64.deb"
}
},
"1.2.74.477": {
"fullversion": "1.2.74.477.g3be53afe",
"size": "154653044",