Compare commits
430 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa537c63d9 | ||
|
|
3f6cba6bdd | ||
|
|
65dcb761bd | ||
|
|
df4be06104 | ||
|
|
a6c9716521 | ||
|
|
86ea359ab0 | ||
|
|
2f207d2667 | ||
|
|
d26ef77ead | ||
|
|
a174eda2e0 | ||
|
|
cc256be6ac | ||
|
|
2892713442 | ||
|
|
acab906723 | ||
|
|
7728100b3b | ||
|
|
6ccbafbb47 | ||
|
|
162fa66840 | ||
|
|
7d8b10ba7d | ||
|
|
fcc8e2b822 | ||
|
|
fe889d1026 | ||
|
|
8728576c0c | ||
|
|
3a6a75235c | ||
|
|
c197397e58 | ||
|
|
9c21bf44bf | ||
|
|
f8fc5f57a0 | ||
|
|
3b72261622 | ||
|
|
61c8175e2a | ||
|
|
5002fadff1 | ||
|
|
45bc01e8bd | ||
|
|
5618c88aaf | ||
|
|
72054c2fec | ||
|
|
c73b6f8c90 | ||
|
|
02c04bbbda | ||
|
|
a5b9f1ff60 | ||
|
|
d683626a49 | ||
|
|
9b82042f98 | ||
|
|
0618b8d452 | ||
|
|
ad30f8389b | ||
|
|
d10399826f | ||
|
|
90c98c0eb8 | ||
|
|
12e0093856 | ||
|
|
80e6947c87 | ||
|
|
88e997b6dd | ||
|
|
ca7de1e166 | ||
|
|
7e70f4f339 | ||
|
|
41d837b5b0 | ||
|
|
f399b5cdd6 | ||
|
|
7c071de816 | ||
|
|
be781c007e | ||
|
|
43e10f7e0a | ||
|
|
392a0235f8 | ||
|
|
b25f0cf848 | ||
|
|
7c537a68a0 | ||
|
|
3fd9169c15 | ||
|
|
3bb8caf576 | ||
|
|
fda738dde1 | ||
|
|
c255e9eab8 | ||
|
|
4e61673309 | ||
|
|
e179da8e48 | ||
|
|
9e079d0968 | ||
|
|
5fba045c3a | ||
|
|
41a318e417 | ||
|
|
80bb785d07 | ||
|
|
319354b1ac | ||
|
|
501a8c9877 | ||
|
|
88e67df823 | ||
|
|
2cdf95f4ad | ||
|
|
84854e9bf3 | ||
|
|
59e6d4f47b | ||
|
|
6a1219a76e | ||
|
|
5f3103d908 | ||
|
|
18e8130d04 | ||
|
|
0874d7f305 | ||
|
|
8d610ceaad | ||
|
|
29b9b1e37e | ||
|
|
18774fa88b | ||
|
|
f450ca818c | ||
|
|
97fff96bd7 | ||
|
|
225d47e09e | ||
|
|
1caa61daa7 | ||
|
|
8b3fce1ec2 | ||
|
|
e060b29562 | ||
|
|
47258b2b26 | ||
|
|
ed8fbaf286 | ||
|
|
43ce7f9302 | ||
|
|
27ecd89f27 | ||
|
|
f4200c56a6 | ||
|
|
ee2288360e | ||
|
|
27d16425b7 | ||
|
|
f7bc952f82 | ||
|
|
07cf7fea90 | ||
|
|
e5cd965530 | ||
|
|
43271f0004 | ||
|
|
8d94b700f1 | ||
|
|
ee6cc93a0b | ||
|
|
b89472d397 | ||
|
|
a505191e0e | ||
|
|
387b329651 | ||
|
|
ca9b8c328b | ||
|
|
9a887ca2d0 | ||
|
|
947986fd6c | ||
|
|
d6fb0f0483 | ||
|
|
1fb610a32e | ||
|
|
6e8aeba93d | ||
|
|
ff58033e51 | ||
|
|
1c69604346 | ||
|
|
660715dd7b | ||
|
|
06ba92fc54 | ||
|
|
5717fb178c | ||
|
|
c77736ccc5 | ||
|
|
549117406c | ||
|
|
820937d353 | ||
|
|
7af18cd8b2 | ||
|
|
815d92cd48 | ||
|
|
4a0a7bb0de | ||
|
|
d4981cf75c | ||
|
|
e61063df56 | ||
|
|
a9433d2f73 | ||
|
|
5f4ac6a10c | ||
|
|
8e49fe7dd7 | ||
|
|
9046d7d8c2 | ||
|
|
fe1af720e3 | ||
|
|
f3d0d53e20 | ||
|
|
0d00cac03f | ||
|
|
4847fa31bd | ||
|
|
259f31a1d3 | ||
|
|
a2fd6913cc | ||
|
|
e6b410b837 | ||
|
|
39b110ff83 | ||
|
|
fff751fbcc | ||
|
|
871ebfb35f | ||
|
|
b8c01ad340 | ||
|
|
dee58698c7 | ||
|
|
f4b24453ac | ||
|
|
d699d2ac8c | ||
|
|
3f952f65bc | ||
|
|
1e1fbb6fcb | ||
|
|
8dd85ffcfd | ||
|
|
2a7d1845eb | ||
|
|
172b066e95 | ||
|
|
2c5e18b40c | ||
|
|
f7ebdc8915 | ||
|
|
e7f647aa3c | ||
|
|
4b936d86a4 | ||
|
|
26344b96b4 | ||
|
|
5b24d6d776 | ||
|
|
6cbe6ccdaf | ||
|
|
6507e63825 | ||
|
|
d8e2291f0f | ||
|
|
3268102583 | ||
|
|
902b6f1223 | ||
|
|
e6d3f0c76e | ||
|
|
336fe44b5b | ||
|
|
187fc4d439 | ||
|
|
5eee2572e2 | ||
|
|
0eebb89e76 | ||
|
|
d15b5fbc32 | ||
|
|
0398cb6ea1 | ||
|
|
d61a2e58db | ||
|
|
e0cf1f94fc | ||
|
|
430d5d40fc | ||
|
|
ab8896961f | ||
|
|
ba0af93cdc | ||
|
|
86d02dde3c | ||
|
|
31eeb1d86d | ||
|
|
cb81917067 | ||
|
|
4aeb2f150f | ||
|
|
817cefb17e | ||
|
|
90bc45dc0f | ||
|
|
51ff38d16f | ||
|
|
ade13834f9 | ||
|
|
c1c0c715d1 | ||
|
|
4e0009ce26 | ||
|
|
bffdc39cca | ||
|
|
2d561118b9 | ||
|
|
70f3ac3249 | ||
|
|
8806f679a9 | ||
|
|
ea015c7280 | ||
|
|
0b7ae2e90a | ||
|
|
e35724cf7f | ||
|
|
6c882d93c6 | ||
|
|
4911d56f18 | ||
|
|
6a235cd0f5 | ||
|
|
9070044b8a | ||
|
|
923b4b8d78 | ||
|
|
4367d89cbe | ||
|
|
9053695b6b | ||
|
|
467b7301ec | ||
|
|
432ad0301e | ||
|
|
b4d199ea7a | ||
|
|
0eb2d0af45 | ||
|
|
d894e361dd | ||
|
|
a9e9a984fd | ||
|
|
d508cb5e40 | ||
|
|
8b44677f43 | ||
|
|
1e0a1f09ac | ||
|
|
65e1f39299 | ||
|
|
f0d0a70f98 | ||
|
|
d24b63e43a | ||
|
|
8f67fb3aff | ||
|
|
8aa7ad5534 | ||
|
|
cfbf7b4d69 | ||
|
|
3ed340b20f | ||
|
|
dc7df6292f | ||
|
|
eeea3304d8 | ||
|
|
9bfacab976 | ||
|
|
91d87ed7ef | ||
|
|
a660738738 | ||
|
|
4df6648885 | ||
|
|
3185f44d21 | ||
|
|
8cc9141369 | ||
|
|
7ff5e1f0ee | ||
|
|
d78141cc92 | ||
|
|
d02d1be468 | ||
|
|
b434d93a7d | ||
|
|
e67831bc47 | ||
|
|
0d58298f6e | ||
|
|
036f7c81cf | ||
|
|
4039246aca | ||
|
|
e6dd76fa72 | ||
|
|
2dbac74551 | ||
|
|
5688c02e92 | ||
|
|
3aa3fb4921 | ||
|
|
33d9b0ddac | ||
|
|
8529cfc34b | ||
|
|
9cdc08b322 | ||
|
|
c068487050 | ||
|
|
3d47c833eb | ||
|
|
63899de3d6 | ||
|
|
b317b8168f | ||
|
|
d603039c00 | ||
|
|
868972a47e | ||
|
|
9326f6bfd3 | ||
|
|
16356e1543 | ||
|
|
ff9382d4ea | ||
|
|
8a53dc3c18 | ||
|
|
1f48146372 | ||
|
|
87b943bc48 | ||
|
|
750b857544 | ||
|
|
7e64bacd03 | ||
|
|
faf0558dde | ||
|
|
d9146507b9 | ||
|
|
057009caf3 | ||
|
|
1b359e22e4 | ||
|
|
7e736a646e | ||
|
|
9f7390a779 | ||
|
|
396f0697b3 | ||
|
|
b482d719ed | ||
|
|
50604a491e | ||
|
|
bb9e86176f | ||
|
|
c91456058b | ||
|
|
7cedf1ac49 | ||
|
|
8e0464d47f | ||
|
|
986a582902 | ||
|
|
1b80fb3367 | ||
|
|
df15ae250c | ||
|
|
f164dba453 | ||
|
|
d026e42c1f | ||
|
|
2af6b7f87c | ||
|
|
6f6542d717 | ||
|
|
d41ec2ac5a | ||
|
|
8ffbd3dd53 | ||
|
|
5622bc72ca | ||
|
|
e3c69f7781 | ||
|
|
00024a87c3 | ||
|
|
af0e8147f1 | ||
|
|
619108c571 | ||
|
|
69dd050d4a | ||
|
|
f0e4936a4b | ||
|
|
ab157bd4b6 | ||
|
|
00aa0f57e2 | ||
|
|
b7433c2dbf | ||
|
|
5f6989deb4 | ||
|
|
959cc1927a | ||
|
|
85a31493d6 | ||
|
|
2f3fb1f893 | ||
|
|
bca898a0a1 | ||
|
|
b6af069283 | ||
|
|
b38f7ca469 | ||
|
|
b3f627d1c7 | ||
|
|
e05e9b7b7c | ||
|
|
58d07c5029 | ||
|
|
f5867e41f9 | ||
|
|
a24607c368 | ||
|
|
3d578b8565 | ||
|
|
09f0f59b2e | ||
|
|
b4a7f1a595 | ||
|
|
5893e42c70 | ||
|
|
e9034dd202 | ||
|
|
b76d0b7cbd | ||
|
|
4289b220c1 | ||
|
|
9ecfee6004 | ||
|
|
446cc5327b | ||
|
|
eecc8af715 | ||
|
|
2fc035a8ee | ||
|
|
4034d310c1 | ||
|
|
28c04bebeb | ||
|
|
a5ea5f01ae | ||
|
|
9a54376530 | ||
|
|
bd8d49c80e | ||
|
|
4ef145a9e3 | ||
|
|
321b1d2858 | ||
|
|
1432956c49 | ||
|
|
a1fddeb973 | ||
|
|
90c227d1ba | ||
|
|
b35667cf69 | ||
|
|
822cdaa3eb | ||
|
|
97abe7aa40 | ||
|
|
8d56b0d775 | ||
|
|
fb47f6d480 | ||
|
|
9df55c5494 | ||
|
|
7d9aa0c7eb | ||
|
|
7112698513 | ||
|
|
72b7d6b5dd | ||
|
|
6624bc5020 | ||
|
|
f52dd7c1fa | ||
|
|
82fbeacf5a | ||
|
|
6526d71161 | ||
|
|
8adc107785 | ||
|
|
59be936f6d | ||
|
|
88b31c9130 | ||
|
|
8d231decf4 | ||
|
|
72d5ff7480 | ||
|
|
de365aa8a7 | ||
|
|
09601cdb73 | ||
|
|
2cad3be3d8 | ||
|
|
13d2d94011 | ||
|
|
f12d3342ee | ||
|
|
9c6804ffb5 | ||
|
|
933d268ef3 | ||
|
|
fc17cc1e63 | ||
|
|
6f702f7258 | ||
|
|
5a8f446821 | ||
|
|
ce1b0d89e8 | ||
|
|
3d03b9591c | ||
|
|
e931f71eff | ||
|
|
8c74f13626 | ||
|
|
fb79b33669 | ||
|
|
0b7d8c06c8 | ||
|
|
7ef4fabd64 | ||
|
|
61b9865e6f | ||
|
|
a8167d45a0 | ||
|
|
12f2c7cda1 | ||
|
|
91dd2af0c0 | ||
|
|
1232c65cec | ||
|
|
901ab98fba | ||
|
|
ab8f9e1b1a | ||
|
|
dc220b0dd3 | ||
|
|
2a169e4168 | ||
|
|
b0bdf02380 | ||
|
|
ead0411e5c | ||
|
|
b54270ecf8 | ||
|
|
59d583053c | ||
|
|
10ae98ed1c | ||
|
|
dae2ad2a54 | ||
|
|
dccc2d1c6f | ||
|
|
a9cc95f110 | ||
|
|
e6372b70f5 | ||
|
|
b888225855 | ||
|
|
b7d471e831 | ||
|
|
64b444a33f | ||
|
|
f33cb61fa6 | ||
|
|
c8be325e45 | ||
|
|
bff7965979 | ||
|
|
3407540b01 | ||
|
|
2bec3b86ba | ||
|
|
27ae092fa8 | ||
|
|
a654a55191 | ||
|
|
e991f56ff2 | ||
|
|
354a4b0d80 | ||
|
|
6dfb99885b | ||
|
|
1cdbd7ee53 | ||
|
|
4d509c9a2a | ||
|
|
8d1fcd9210 | ||
|
|
7f3bd4f94d | ||
|
|
102a942749 | ||
|
|
791fe33406 | ||
|
|
adf4578dfb | ||
|
|
ebbf366d95 | ||
|
|
1eb8cce2c7 | ||
|
|
eb1d9cc174 | ||
|
|
059fea7014 | ||
|
|
fbf444aec3 | ||
|
|
d1432bafd2 | ||
|
|
63b10a1080 | ||
|
|
314526bfed | ||
|
|
3ca30a5d7e | ||
|
|
9cea64a5f8 | ||
|
|
b52b4bab42 | ||
|
|
d18c2b9f48 | ||
|
|
fde54ca814 | ||
|
|
9afa0f0cdf | ||
|
|
842dbae48f | ||
|
|
ea37771cec | ||
|
|
17c42a43d1 | ||
|
|
3546abbb5f | ||
|
|
431a1f23b5 | ||
|
|
5e753fad2c | ||
|
|
f293bac367 | ||
|
|
7ea7f74551 | ||
|
|
07d26f15f5 | ||
|
|
5e8d7ca767 | ||
|
|
f88ef8cc97 | ||
|
|
dd6925d55c | ||
|
|
3545cad272 | ||
|
|
30ed09822c | ||
|
|
26e17bedf3 | ||
|
|
a65648bd92 | ||
|
|
8a2a7a168f | ||
|
|
0c2c5674a9 | ||
|
|
9fe899186b | ||
|
|
7373b01696 | ||
|
|
9dbcd4521b | ||
|
|
f3611aba92 | ||
|
|
c46510406b | ||
|
|
d0c8af3b1e | ||
|
|
3d0b5c4760 | ||
|
|
594d9b4147 | ||
|
|
3ea6da3258 | ||
|
|
d28b01fde0 | ||
|
|
89a1196978 | ||
|
|
33b35d456f | ||
|
|
55fa746179 | ||
|
|
74858bdc7d | ||
|
|
9b17670c88 | ||
|
|
3182f39f9d | ||
|
|
02bbfc9ef5 | ||
|
|
10ecaea773 | ||
|
|
2eea070a78 | ||
|
|
bd71193036 | ||
|
|
7e85e8dbec | ||
|
|
74cb634169 |
22 changed files with 15070 additions and 118 deletions
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
195
.github/workflows/build-to-release.yml
vendored
Normal file
195
.github/workflows/build-to-release.yml
vendored
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
name: Build LoaderSpot
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_windows:
|
||||||
|
runs-on: windows-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- arch: x86
|
||||||
|
rust_target: i686-pc-windows-msvc
|
||||||
|
- arch: x64
|
||||||
|
rust_target: x86_64-pc-windows-msvc
|
||||||
|
- arch: arm64
|
||||||
|
rust_target: aarch64-pc-windows-msvc
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- 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:
|
||||||
|
RUST_TARGET: ${{ matrix.rust_target }}
|
||||||
|
ARCH: ${{ matrix.arch }}
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$out = "artifacts"
|
||||||
|
if (Test-Path $out) { Remove-Item $out -Recurse -Force }
|
||||||
|
New-Item -ItemType Directory -Path $out | Out-Null
|
||||||
|
|
||||||
|
Write-Host "Building UI for $env:RUST_TARGET"
|
||||||
|
cargo build --manifest-path LoaderSpot_UI/Cargo.toml --release --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
|
||||||
|
$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@v7
|
||||||
|
with:
|
||||||
|
name: windows-artifacts-${{ matrix.arch }}
|
||||||
|
path: artifacts/*
|
||||||
|
|
||||||
|
build_linux:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- 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 build dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y pkg-config libssl-dev
|
||||||
|
|
||||||
|
- name: Build release binaries
|
||||||
|
run: |
|
||||||
|
mkdir -p artifacts
|
||||||
|
|
||||||
|
# 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 artifacts
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
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@v8
|
||||||
|
with:
|
||||||
|
path: artifacts
|
||||||
|
|
||||||
|
- name: Get latest release
|
||||||
|
id: latest_release
|
||||||
|
uses: actions/github-script@v8
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Upload assets to Release
|
||||||
|
uses: actions/github-script@v8
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const releaseId = parseInt('${{ steps.latest_release.outputs.result }}', 10);
|
||||||
|
|
||||||
|
async function uploadAsset(filePath) {
|
||||||
|
const fileName = path.basename(filePath);
|
||||||
|
console.log(`Processing ${fileName}`);
|
||||||
|
|
||||||
|
const assets = await github.rest.repos.listReleaseAssets({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
release_id: releaseId
|
||||||
|
});
|
||||||
|
|
||||||
|
const existingAsset = assets.data.find(a => a.name === fileName);
|
||||||
|
if (existingAsset) {
|
||||||
|
console.log(`Deleting existing asset: ${fileName}`);
|
||||||
|
await github.rest.repos.deleteReleaseAsset({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
asset_id: existingAsset.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Uploading: ${fileName}`);
|
||||||
|
await github.rest.repos.uploadReleaseAsset({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
release_id: releaseId,
|
||||||
|
name: fileName,
|
||||||
|
data: fs.readFileSync(filePath)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = [];
|
||||||
|
const artifactDirs = fs.readdirSync('artifacts');
|
||||||
|
for (const dir of artifactDirs) {
|
||||||
|
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));
|
||||||
|
}
|
||||||
34
.github/workflows/check-deb-versions.yml
vendored
Normal file
34
.github/workflows/check-deb-versions.yml
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
name: Checking deb versions
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 * * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-and-update:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.PERSONAL_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: 3.12.2
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install requests packaging
|
||||||
|
|
||||||
|
- name: Run version checker
|
||||||
|
env:
|
||||||
|
PERSONAL_TOKEN: ${{ secrets.PERSONAL_TOKEN }}
|
||||||
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||||
|
run: python check_deb_versions.py
|
||||||
18
.github/workflows/ms-spotify.yml
vendored
Normal file
18
.github/workflows/ms-spotify.yml
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
name: check Spotify MS
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: windows-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: run ms-spotify-check.ps1
|
||||||
|
shell: pwsh
|
||||||
|
run: ./ms-spotify-check.ps1
|
||||||
|
env:
|
||||||
|
Token: ${{ secrets.Token }}
|
||||||
51
.github/workflows/sync-files.yml
vendored
Normal file
51
.github/workflows/sync-files.yml
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
name: Sync files to table
|
||||||
|
|
||||||
|
on:
|
||||||
|
# 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:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout source repo
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Checkout target repo
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
repository: LoaderSpot/table
|
||||||
|
path: table
|
||||||
|
token: ${{ secrets.PERSONAL_TOKEN }}
|
||||||
|
|
||||||
|
- name: Copy files
|
||||||
|
run: |
|
||||||
|
mkdir -p table/table
|
||||||
|
cp versions.json table/table/
|
||||||
|
cp versions_deb.json table/table/
|
||||||
|
|
||||||
|
- name: Commit and push changes
|
||||||
|
env:
|
||||||
|
TARGET_REPO: "https://${{ secrets.PERSONAL_TOKEN }}@github.com/LoaderSpot/table.git"
|
||||||
|
run: |
|
||||||
|
cd table
|
||||||
|
git config --global user.name "GitHub Actions"
|
||||||
|
git config --global user.email "actions@github.com"
|
||||||
|
|
||||||
|
if ! git diff --exit-code; then
|
||||||
|
git add table/
|
||||||
|
git commit -m "Auto-sync from LoaderSpot"
|
||||||
|
git push "$TARGET_REPO" main
|
||||||
|
else
|
||||||
|
echo "No changes to commit"
|
||||||
|
fi
|
||||||
45
.github/workflows/update_versions.yml
vendored
Normal file
45
.github/workflows/update_versions.yml
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
name: Update Version
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
datajson:
|
||||||
|
description: 'Version to update'
|
||||||
|
required: true
|
||||||
|
version:
|
||||||
|
description: 'Another version parameter'
|
||||||
|
required: true
|
||||||
|
buildType:
|
||||||
|
description: 'Build type (e.g., Master, Release)'
|
||||||
|
required: false
|
||||||
|
default: 'false'
|
||||||
|
jobs:
|
||||||
|
update-version:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.PERSONAL_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: 3.12.2
|
||||||
|
|
||||||
|
- name: Update version in versions.json
|
||||||
|
run: |
|
||||||
|
data="${{ github.event.inputs.datajson }}"
|
||||||
|
build_type="${{ github.event.inputs.buildType }}"
|
||||||
|
python3 update_version.py "$data" "$build_type"
|
||||||
|
|
||||||
|
- name: Commit changes
|
||||||
|
run: |
|
||||||
|
git config --local user.email "action@github.com"
|
||||||
|
git config --local user.name "GitHub Action"
|
||||||
|
git add versions.json
|
||||||
|
git commit -m "Added version ${{ github.event.inputs.version }}"
|
||||||
|
git push "https://${{ secrets.PERSONAL_TOKEN }}@github.com/${{ github.repository }}.git" HEAD:main
|
||||||
54
.github/workflows/version-check.yml
vendored
Normal file
54
.github/workflows/version-check.yml
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
name: Version Check
|
||||||
|
on:
|
||||||
|
repository_dispatch:
|
||||||
|
types: [webhook-event]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: delayed-version-check
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
version-search:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
versions: ${{ steps.search.outputs.versions }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- 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
|
||||||
|
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
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/LoaderSpot_CLI/target
|
||||||
|
/LoaderSpot_UI/target
|
||||||
108
LoaderSpot.ps1
108
LoaderSpot.ps1
|
|
@ -1,108 +0,0 @@
|
||||||
$version_spoti = Read-Host -Prompt "Enter the Spotify version, for example 1.1.68.632.g2b11de83"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if ($version_spoti -match '^\d.\d.\d{1,2}.\d{1,5}.[a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z]$') {
|
|
||||||
$before_enter = Read-Host -Prompt "Enter the number to stop at (e.g. 99)"
|
|
||||||
if ($before_enter -match '^\d{1,4}$') {
|
|
||||||
$numbers = 0
|
|
||||||
|
|
||||||
"Search..."
|
|
||||||
While ($numbers -le $before_enter) {
|
|
||||||
|
|
||||||
$_URL = "https://upgrade.scdn.co/upgrade/client/win32-x86/spotify_installer-$version_spoti-$numbers.exe"
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
|
||||||
$request = [System.Net.WebRequest]::Create($_URL)
|
|
||||||
$response = $request.getResponse()
|
|
||||||
|
|
||||||
if ($response.StatusCode -eq "200") {
|
|
||||||
$response.ResponseUri.OriginalString
|
|
||||||
$find_url += [System.Environment]::NewLine + $response.ResponseUri.OriginalString
|
|
||||||
$response.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
$numbers
|
|
||||||
}
|
|
||||||
|
|
||||||
$numbers++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Host "Invalid number, Stop ... " -ForegroundColor Red
|
|
||||||
pause
|
|
||||||
exit
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
if ($version_spoti -notmatch '^\d.\d.\d{1,2}.\d{1,5}.[a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z]$') {
|
|
||||||
Write-Host "Version entered incorrectly, please try again" -ForegroundColor Red
|
|
||||||
$version_spoti = Read-Host -Prompt "Enter the Spotify version, for example 1.1.68.632.g2b11de83"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if ($version_spoti -match '^\d.\d.\d{1,2}.\d{1,5}.[a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z]$') {
|
|
||||||
$before_enter = Read-Host -Prompt "Enter the number to stop at (e.g. 99)"
|
|
||||||
if ($before_enter -match '^\d{1,4}$') {
|
|
||||||
$numbers = 0
|
|
||||||
|
|
||||||
"Search..."
|
|
||||||
While ($numbers -le $before_enter) {
|
|
||||||
|
|
||||||
$_URL = "https://upgrade.scdn.co/upgrade/client/win32-x86/spotify_installer-$version_spoti-$numbers.exe"
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
|
||||||
$request = [System.Net.WebRequest]::Create($_URL)
|
|
||||||
$response = $request.getResponse()
|
|
||||||
|
|
||||||
if ($response.StatusCode -eq "200") {
|
|
||||||
$response.ResponseUri.OriginalString
|
|
||||||
$find_url += [System.Environment]::NewLine + $response.ResponseUri.OriginalString
|
|
||||||
$response.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
$numbers
|
|
||||||
}
|
|
||||||
|
|
||||||
$numbers++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Host "Invalid number, Stop ... " -ForegroundColor Red
|
|
||||||
pause
|
|
||||||
exit
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Host "Unsuccessful again, script stopped ..." -ForegroundColor Red
|
|
||||||
pause
|
|
||||||
exit
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
write-host "`n"
|
|
||||||
"Search completed"
|
|
||||||
write-host "`n"
|
|
||||||
if ($find_url) {
|
|
||||||
"Found links :"
|
|
||||||
$find_url
|
|
||||||
}
|
|
||||||
if (!($find_url)) {
|
|
||||||
"Nothing found, please increase your search range."
|
|
||||||
}
|
|
||||||
write-host "`n"
|
|
||||||
pause
|
|
||||||
exit
|
|
||||||
2118
LoaderSpot_CLI/Cargo.lock
generated
Normal file
2118
LoaderSpot_CLI/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
14
LoaderSpot_CLI/Cargo.toml
Normal file
14
LoaderSpot_CLI/Cargo.toml
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "loaderspot_cli"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
reqwest = { version = "0.11", features = ["json"] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
clap = { version = "4.0", features = ["derive"] }
|
||||||
|
indicatif = "0.17.7"
|
||||||
|
regex = "1"
|
||||||
|
scraper = "0.19.0"
|
||||||
341
LoaderSpot_CLI/src/main.rs
Normal file
341
LoaderSpot_CLI/src/main.rs
Normal file
|
|
@ -0,0 +1,341 @@
|
||||||
|
use clap::Parser;
|
||||||
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
|
use reqwest::Client;
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::sync::Semaphore;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
|
||||||
|
enum PlatformArch {
|
||||||
|
WinX86,
|
||||||
|
WinX64,
|
||||||
|
WinArm64,
|
||||||
|
MacOsIntel,
|
||||||
|
MacOsArm64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlatformArch {
|
||||||
|
fn path_template(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
PlatformArch::WinX86 => "win32-x86/spotify_installer-{version}-{number}.exe",
|
||||||
|
PlatformArch::WinX64 => "win32-x86_64/spotify_installer-{version}-{number}.exe",
|
||||||
|
PlatformArch::WinArm64 => "win32-arm64/spotify_installer-{version}-{number}.exe",
|
||||||
|
PlatformArch::MacOsIntel => "osx-x86_64/spotify-autoupdate-{version}-{number}.tbz",
|
||||||
|
PlatformArch::MacOsArm64 => "osx-arm64/spotify-autoupdate-{version}-{number}.tbz",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_string(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
PlatformArch::WinX86 => "WIN32",
|
||||||
|
PlatformArch::WinX64 => "WIN64",
|
||||||
|
PlatformArch::WinArm64 => "WIN-ARM64",
|
||||||
|
PlatformArch::MacOsIntel => "OSX",
|
||||||
|
PlatformArch::MacOsArm64 => "OSX-ARM64",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UrlGenerator;
|
||||||
|
|
||||||
|
impl UrlGenerator {
|
||||||
|
const BASE_URL: &'static str = "https://upgrade.scdn.co/upgrade/client/";
|
||||||
|
|
||||||
|
fn generate_url(platform: PlatformArch, version: &str, number: i32) -> String {
|
||||||
|
let path = platform
|
||||||
|
.path_template()
|
||||||
|
.replace("{version}", version)
|
||||||
|
.replace("{number}", &number.to_string());
|
||||||
|
format!("{}{}", Self::BASE_URL, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_base_version(version: &str) -> String {
|
||||||
|
let parts: Vec<&str> = version.split('.').collect();
|
||||||
|
if parts.len() >= 3 {
|
||||||
|
format!("{}.{}.{}", parts[0], parts[1], parts[2])
|
||||||
|
} else {
|
||||||
|
version.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_use_win_x86(version: &str) -> bool {
|
||||||
|
let base_version = extract_base_version(version);
|
||||||
|
let parts: Vec<&str> = base_version.split('.').collect();
|
||||||
|
|
||||||
|
if parts.len() >= 3 {
|
||||||
|
if let (Ok(major), Ok(minor), Ok(patch)) = (
|
||||||
|
parts[0].parse::<u32>(),
|
||||||
|
parts[1].parse::<u32>(),
|
||||||
|
parts[2].parse::<u32>(),
|
||||||
|
) {
|
||||||
|
return (major, minor, patch) <= (1, 2, 53);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn check_url(client: &Client, url: String, platform: PlatformArch) -> Option<(String, PlatformArch)> {
|
||||||
|
match client.head(&url).send().await {
|
||||||
|
Ok(response) if response.status().is_success() => Some((url, platform)),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn search_installers(
|
||||||
|
client: &Client,
|
||||||
|
version: &str,
|
||||||
|
start: i32,
|
||||||
|
end: i32,
|
||||||
|
platform: PlatformArch,
|
||||||
|
max_connections: usize,
|
||||||
|
) -> Vec<(String, PlatformArch)> {
|
||||||
|
let semaphore = Arc::new(Semaphore::new(max_connections));
|
||||||
|
let mut tasks = Vec::new();
|
||||||
|
let found_urls = Arc::new(tokio::sync::Mutex::new(Vec::new()));
|
||||||
|
|
||||||
|
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 found_urls_clone = found_urls.clone();
|
||||||
|
|
||||||
|
let task = tokio::spawn(async move {
|
||||||
|
let result = check_url(&client, url, platform).await;
|
||||||
|
drop(permit);
|
||||||
|
|
||||||
|
if let Some(found_data) = result {
|
||||||
|
let mut guard = found_urls_clone.lock().await;
|
||||||
|
guard.push(found_data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tasks.push(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
for task in tasks {
|
||||||
|
let _ = task.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let guard = found_urls.lock().await;
|
||||||
|
guard.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[clap(author, version, about, long_about = None, disable_version_flag = true)]
|
||||||
|
struct Cli {
|
||||||
|
/// Spotify version(s) to search for
|
||||||
|
#[clap(long, required = true, use_value_delimiter = true, value_delimiter = ',')]
|
||||||
|
version: Vec<String>,
|
||||||
|
|
||||||
|
/// Range of build numbers to check (e.g., 0-5000)
|
||||||
|
#[clap(long, default_value = "0-5000")]
|
||||||
|
range: String,
|
||||||
|
|
||||||
|
/// Architecture(s)
|
||||||
|
#[clap(long, use_value_delimiter = true, value_delimiter = ',', value_parser = ["x86", "x64", "arm64", "intel", "all"], default_value = "all")]
|
||||||
|
arch: Vec<String>,
|
||||||
|
|
||||||
|
/// Platform(s)
|
||||||
|
#[clap(long, name = "os", use_value_delimiter = true, value_delimiter = ',', value_parser = ["win", "mac", "all"], default_value = "all")]
|
||||||
|
platform: Vec<String>,
|
||||||
|
|
||||||
|
/// Number of concurrent connections
|
||||||
|
#[clap(long, default_value_t = 100)]
|
||||||
|
connections: usize,
|
||||||
|
|
||||||
|
/// Use ladder search algorithm
|
||||||
|
#[clap(long)]
|
||||||
|
ladder_search: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_range(range_str: &str) -> (i32, i32) {
|
||||||
|
let parts: Vec<&str> = range_str.split('-').collect();
|
||||||
|
if parts.len() == 2 {
|
||||||
|
let start = parts[0].parse().unwrap_or(0);
|
||||||
|
let end = parts[1].parse().unwrap_or(5000);
|
||||||
|
(start, end)
|
||||||
|
} else {
|
||||||
|
(0, 5000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let cli = Cli::parse();
|
||||||
|
|
||||||
|
let platforms = if cli.platform.contains(&"all".to_string()) {
|
||||||
|
vec!["win", "mac"]
|
||||||
|
} else {
|
||||||
|
cli.platform.iter().map(|s| s.as_str()).collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
let arches = if cli.arch.contains(&"all".to_string()) {
|
||||||
|
if platforms.contains(&"win") && platforms.contains(&"mac") {
|
||||||
|
vec!["x86", "x64", "arm64", "intel", "arm64"]
|
||||||
|
} else if platforms.contains(&"win") {
|
||||||
|
vec!["x86", "x64", "arm64"]
|
||||||
|
} else if platforms.contains(&"mac") {
|
||||||
|
vec!["intel", "arm64"]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cli.arch.iter().map(|s| s.as_str()).collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut platform_arches = Vec::new();
|
||||||
|
for platform in &platforms {
|
||||||
|
for arch in &arches {
|
||||||
|
let platform_arch = match (*platform, *arch) {
|
||||||
|
("win", "x86") => Some(PlatformArch::WinX86),
|
||||||
|
("win", "x64") => Some(PlatformArch::WinX64),
|
||||||
|
("win", "arm64") => Some(PlatformArch::WinArm64),
|
||||||
|
("mac", "intel") => Some(PlatformArch::MacOsIntel),
|
||||||
|
("mac", "arm64") => Some(PlatformArch::MacOsArm64),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(pa) = platform_arch {
|
||||||
|
if !platform_arches.contains(&pa) {
|
||||||
|
platform_arches.push(pa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if platform_arches.is_empty() {
|
||||||
|
eprintln!("Error: No valid platform and architecture combinations provided.");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let connections = cli.connections.clamp(50, 300);
|
||||||
|
let range = cli.range.clone();
|
||||||
|
let client = Client::builder()
|
||||||
|
.timeout(std::time::Duration::from_secs(30))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let spinner_style = ProgressStyle::with_template("{spinner}")
|
||||||
|
.unwrap()
|
||||||
|
.tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"); // A classic rotating spinner
|
||||||
|
let pb = ProgressBar::new_spinner();
|
||||||
|
pb.set_style(spinner_style);
|
||||||
|
pb.enable_steady_tick(Duration::from_millis(80));
|
||||||
|
|
||||||
|
let versions = cli.version.clone();
|
||||||
|
let client_clone = client.clone();
|
||||||
|
let ladder_search = cli.ladder_search;
|
||||||
|
let versions_clone_for_task = versions.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 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 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_for_version.extend(found);
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 0..additional_searches {
|
||||||
|
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 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
start_number = before_enter + 1;
|
||||||
|
before_enter += increment;
|
||||||
|
|
||||||
|
let mut missing_arches = Vec::new();
|
||||||
|
for &platform_arch in &arches_to_search {
|
||||||
|
if !latest_urls.contains_key(platform_arch.to_string()) {
|
||||||
|
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_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_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);
|
||||||
|
}
|
||||||
|
results
|
||||||
|
});
|
||||||
|
|
||||||
|
let results = search_task.await.unwrap();
|
||||||
|
pb.finish_and_clear();
|
||||||
|
|
||||||
|
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)]) -> HashMap<String, String> {
|
||||||
|
let mut platform_urls = HashMap::new();
|
||||||
|
let version_pattern = Regex::new(r"-(\d+)\.(exe|tbz)$").unwrap();
|
||||||
|
|
||||||
|
for (url, platform) in found_urls {
|
||||||
|
if let Some(captures) = version_pattern.captures(url) {
|
||||||
|
if let Some(version_number_match) = captures.get(1) {
|
||||||
|
let version_number = version_number_match.as_str().parse::<u32>().unwrap();
|
||||||
|
let platform_key = platform.to_string().to_string();
|
||||||
|
|
||||||
|
let entry = platform_urls.entry(platform_key).or_insert_with(|| (url.clone(), version_number));
|
||||||
|
|
||||||
|
if version_number > entry.1 {
|
||||||
|
*entry = (url.clone(), version_number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let latest_urls: HashMap<String, String> = platform_urls
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, (v, _))| (k, v))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if latest_urls.is_empty() {
|
||||||
|
let mut empty_map = HashMap::new();
|
||||||
|
empty_map.insert("unknown".to_string(), "unknown".to_string());
|
||||||
|
return empty_map;
|
||||||
|
}
|
||||||
|
|
||||||
|
latest_urls
|
||||||
|
}
|
||||||
4803
LoaderSpot_UI/Cargo.lock
generated
Normal file
4803
LoaderSpot_UI/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
17
LoaderSpot_UI/Cargo.toml
Normal file
17
LoaderSpot_UI/Cargo.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "loaderspot_ui"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
eframe = "0.29"
|
||||||
|
egui = "0.29"
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
reqwest = { version = "0.12", features = ["json"] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
regex = "1.10"
|
||||||
|
crossbeam-channel = "0.5"
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
winapi = { version = "0.3", features = ["winuser"] }
|
||||||
1088
LoaderSpot_UI/src/main.rs
Normal file
1088
LoaderSpot_UI/src/main.rs
Normal file
File diff suppressed because it is too large
Load diff
16
README.md
16
README.md
|
|
@ -1,11 +1,9 @@
|
||||||
<center>
|
<p align="center">
|
||||||
<h1 align="center">LoaderSpot
|
<a href="https://loadspot.pages.dev"><img src="https://img.shields.io/badge/Excel%20table--brightgreen.svg?style=flat&logo=microsoftexcel&label=Table"></a>
|
||||||
</h1>
|
<a href="https://t.me/OfficialSpotifyUpdates"><img src="https://img.shields.io/badge/Telegram- -blue?logo=telegram" alt="Telegram"></a>
|
||||||
<h4 align="center">A tool for downloading the full Spotify Windows Desktop client.</h4
|
</p>
|
||||||
</center>
|
|
||||||
|
|
||||||
|
<h1 align="center">LoaderSpot</h1>
|
||||||
|
<h2 align="center">This tool finds full Spotify installers and also collects them</h2>
|
||||||
|
|
||||||
|
<p align="center">Thanks for the idea <a href="https://github.com/nick-botticelli/SpotifyUpgradeFinder">nick-botticelli</a></p>
|
||||||
|
|
||||||
Download and run [LoaderSpot.bat](https://cutt.ly/JExUy35)
|
|
||||||
|
|
||||||
|
|
|
||||||
125
check-build-version.ps1
Normal file
125
check-build-version.ps1
Normal 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: $_"
|
||||||
|
}
|
||||||
282
check_deb_versions.py
Normal file
282
check_deb_versions.py
Normal file
|
|
@ -0,0 +1,282 @@
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
from packaging import version
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
from requests.exceptions import RequestException
|
||||||
|
import time
|
||||||
|
|
||||||
|
VERSIONS_JSON_FILE = "versions_deb.json"
|
||||||
|
SPOTIFY_REPO_URL = (
|
||||||
|
"https://repository-origin.spotify.com/pool/non-free/s/spotify-client/"
|
||||||
|
)
|
||||||
|
REPO_UPLOAD = "LoaderSpot/deb-builds"
|
||||||
|
|
||||||
|
|
||||||
|
def get_spotify_versions_from_repo():
|
||||||
|
urls = [
|
||||||
|
"https://repository.spotify.com/dists/testing/non-free/binary-amd64/Packages",
|
||||||
|
SPOTIFY_REPO_URL,
|
||||||
|
]
|
||||||
|
|
||||||
|
version_pattern = r"(\d+\.\d+\.\d+\.\d+\.g[0-9a-f]{8})"
|
||||||
|
versions = set()
|
||||||
|
|
||||||
|
for url in urls:
|
||||||
|
try:
|
||||||
|
response = requests.get(url)
|
||||||
|
response.raise_for_status()
|
||||||
|
found_versions = re.findall(version_pattern, response.text)
|
||||||
|
versions.update(found_versions)
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"Ошибка при запросе {url}: {e}")
|
||||||
|
|
||||||
|
min_version = version.parse("1.1.58")
|
||||||
|
return [
|
||||||
|
v for v in versions if version.parse(".".join(v.split(".")[:3])) > min_version
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_existing_versions():
|
||||||
|
try:
|
||||||
|
with open(VERSIONS_JSON_FILE, "r", encoding="utf-8") as f:
|
||||||
|
return json.load(f)
|
||||||
|
except (FileNotFoundError, json.JSONDecodeError) as e:
|
||||||
|
print(f"Ошибка при чтении файла {VERSIONS_JSON_FILE}: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_version_key(full_version):
|
||||||
|
match = re.match(r"(\d+\.\d+\.\d+\.\d+)\..*", full_version)
|
||||||
|
return match.group(1) if match else None
|
||||||
|
|
||||||
|
|
||||||
|
def find_new_versions(repo_versions, existing_data):
|
||||||
|
new_versions = []
|
||||||
|
|
||||||
|
for full_version in repo_versions:
|
||||||
|
version_key = parse_version_key(full_version)
|
||||||
|
if version_key and version_key not in existing_data:
|
||||||
|
new_versions.append((version_key, full_version))
|
||||||
|
|
||||||
|
return new_versions
|
||||||
|
|
||||||
|
|
||||||
|
def get_file_info(full_version):
|
||||||
|
url = f"{SPOTIFY_REPO_URL}spotify-client_{full_version}_amd64.deb"
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.head(url, timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
size = response.headers.get("Content-Length", "0")
|
||||||
|
|
||||||
|
last_modified = response.headers.get("Last-Modified", "")
|
||||||
|
if last_modified:
|
||||||
|
date_obj = datetime.strptime(last_modified, "%a, %d %b %Y %H:%M:%S %Z")
|
||||||
|
formatted_date = date_obj.strftime("%d.%m.%Y")
|
||||||
|
else:
|
||||||
|
formatted_date = ""
|
||||||
|
|
||||||
|
return {"size": size, "data": formatted_date}
|
||||||
|
else:
|
||||||
|
print(f"Ошибка HEAD запроса к {url}: {response.status_code}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка при запросе информации о файле: {e}")
|
||||||
|
|
||||||
|
return {"size": "0", "data": ""}
|
||||||
|
|
||||||
|
|
||||||
|
def sort_versions(versions_dict):
|
||||||
|
def version_key(item):
|
||||||
|
return tuple(map(int, item[0].split(".")))
|
||||||
|
|
||||||
|
return dict(sorted(versions_dict.items(), key=version_key, reverse=True))
|
||||||
|
|
||||||
|
|
||||||
|
def update_json_file(sorted_data):
|
||||||
|
try:
|
||||||
|
with open(VERSIONS_JSON_FILE, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(sorted_data, f, indent=2)
|
||||||
|
print(f"Файл {VERSIONS_JSON_FILE} успешно обновлен")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка при обновлении файла {VERSIONS_JSON_FILE}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def commit_changes(new_versions):
|
||||||
|
try:
|
||||||
|
github_actions = os.environ.get("GITHUB_ACTIONS") == "true"
|
||||||
|
|
||||||
|
if github_actions:
|
||||||
|
subprocess.run(["git", "config", "--global", "user.name", "GitHub Actions"])
|
||||||
|
subprocess.run(
|
||||||
|
["git", "config", "--global", "user.email", "actions@github.com"]
|
||||||
|
)
|
||||||
|
|
||||||
|
subprocess.run(["git", "add", VERSIONS_JSON_FILE])
|
||||||
|
|
||||||
|
# Формируем сообщение коммита в зависимости от количества новых версий
|
||||||
|
if new_versions:
|
||||||
|
commit_message = (
|
||||||
|
f"Added deb version {new_versions[0][1]}"
|
||||||
|
if len(new_versions) == 1
|
||||||
|
else f"Added deb versions: {', '.join(v[1] for v in new_versions)}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
commit_message = "Update Spotify versions"
|
||||||
|
|
||||||
|
# Создаем коммит
|
||||||
|
subprocess.run(["git", "commit", "-m", commit_message])
|
||||||
|
|
||||||
|
if github_actions:
|
||||||
|
token = get_github_token()
|
||||||
|
if token:
|
||||||
|
repo_url = f"https://{token}@github.com/{os.environ.get('GITHUB_REPOSITORY')}.git"
|
||||||
|
subprocess.run(["git", "push", repo_url, "HEAD:main"])
|
||||||
|
else:
|
||||||
|
subprocess.run(["git", "push"])
|
||||||
|
else:
|
||||||
|
subprocess.run(["git", "push"])
|
||||||
|
|
||||||
|
print("Изменения успешно отправлены в репозиторий")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка при отправке изменений в репозиторий: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def download_deb_file(full_version):
|
||||||
|
url = f"{SPOTIFY_REPO_URL}spotify-client_{full_version}_amd64.deb"
|
||||||
|
filename = f"spotify-client_{full_version}_amd64.deb"
|
||||||
|
|
||||||
|
try:
|
||||||
|
print(f"Скачивание {filename}...")
|
||||||
|
with tempfile.NamedTemporaryFile(delete=False, suffix=".deb") as temp_file:
|
||||||
|
response = requests.get(url, stream=True, timeout=60)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
for chunk in response.iter_content(chunk_size=8192):
|
||||||
|
if chunk:
|
||||||
|
temp_file.write(chunk)
|
||||||
|
|
||||||
|
return temp_file.name, filename
|
||||||
|
except RequestException as e:
|
||||||
|
print(f"Ошибка при скачивании файла: {e}")
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
def get_github_token():
|
||||||
|
token = os.environ.get("PERSONAL_TOKEN")
|
||||||
|
if not token:
|
||||||
|
print("ОШИБКА: Не найден PERSONAL_TOKEN в переменных окружения")
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
|
def get_latest_release(token):
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"token {token}",
|
||||||
|
"Accept": "application/vnd.github.v3+json",
|
||||||
|
}
|
||||||
|
|
||||||
|
url = f"https://api.github.com/repos/{REPO_UPLOAD}/releases/latest"
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
except RequestException as e:
|
||||||
|
print(f"Ошибка при получении последнего релиза: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def upload_file_to_release(token, release_id, file_path, file_name):
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"token {token}",
|
||||||
|
"Accept": "application/vnd.github.v3+json",
|
||||||
|
"Content-Type": "application/octet-stream",
|
||||||
|
}
|
||||||
|
|
||||||
|
url = f"https://uploads.github.com/repos/{REPO_UPLOAD}/releases/{release_id}/assets?name={file_name}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(file_path, "rb") as file:
|
||||||
|
print(f"Загрузка {file_name} в релиз...")
|
||||||
|
response = requests.post(url, headers=headers, data=file)
|
||||||
|
response.raise_for_status()
|
||||||
|
print(f"Файл {file_name} успешно загружен в релиз")
|
||||||
|
return True
|
||||||
|
except RequestException as e:
|
||||||
|
print(f"Ошибка при загрузке файла в релиз: {e}")
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
# Удаляем временный файл
|
||||||
|
try:
|
||||||
|
os.remove(file_path)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Не удалось удалить временный файл {file_path}: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Получаем версии из репозитория Spotify
|
||||||
|
spotify_versions = get_spotify_versions_from_repo()
|
||||||
|
|
||||||
|
# Получаем существующие версии из JSON
|
||||||
|
existing_data = get_existing_versions()
|
||||||
|
|
||||||
|
# Находим новые версии
|
||||||
|
new_versions = find_new_versions(spotify_versions, existing_data)
|
||||||
|
|
||||||
|
if not new_versions:
|
||||||
|
print("Новых версий не найдено")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(f"Найдено новых версий: {len(new_versions)}")
|
||||||
|
|
||||||
|
# Если есть новые версии, получаем токен GitHub и загружаем файлы
|
||||||
|
github_token = get_github_token()
|
||||||
|
if github_token:
|
||||||
|
latest_release = get_latest_release(github_token)
|
||||||
|
if latest_release:
|
||||||
|
release_id = latest_release["id"]
|
||||||
|
|
||||||
|
# Скачиваем и загружаем каждую новую версию
|
||||||
|
for version_key, full_version in new_versions:
|
||||||
|
temp_file_path, file_name = download_deb_file(full_version)
|
||||||
|
if temp_file_path:
|
||||||
|
time.sleep(2)
|
||||||
|
upload_file_to_release(
|
||||||
|
github_token, release_id, temp_file_path, file_name
|
||||||
|
)
|
||||||
|
|
||||||
|
# Добавляем новые версии в существующие данные
|
||||||
|
for version_key, full_version in new_versions:
|
||||||
|
file_info = get_file_info(full_version)
|
||||||
|
|
||||||
|
existing_data[version_key] = {
|
||||||
|
"fullversion": full_version,
|
||||||
|
"size": file_info["size"],
|
||||||
|
"data": file_info["data"],
|
||||||
|
"links": {
|
||||||
|
"amd64": f"https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_{full_version}_amd64.deb"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Сортируем и обновляем данные
|
||||||
|
sorted_data = sort_versions(existing_data)
|
||||||
|
if update_json_file(sorted_data):
|
||||||
|
# Если мы находимся в GitHub Actions, выполняем коммит
|
||||||
|
if os.environ.get("GITHUB_ACTIONS") == "true":
|
||||||
|
commit_changes(new_versions)
|
||||||
|
print("Обновление выполнено успешно")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
sys.exit(0)
|
||||||
280
ms-spotify-check.ps1
Normal file
280
ms-spotify-check.ps1
Normal file
|
|
@ -0,0 +1,280 @@
|
||||||
|
# Suppress verbose output
|
||||||
|
$VerbosePreference = 'SilentlyContinue'
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param (
|
||||||
|
[string]$Message
|
||||||
|
)
|
||||||
|
Write-Host "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - $Message"
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-WithRetry {
|
||||||
|
param (
|
||||||
|
[ScriptBlock]$ScriptBlock,
|
||||||
|
[int]$MaxAttempts = 6,
|
||||||
|
[int]$DelaySeconds = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
$attempt = 1
|
||||||
|
$success = $false
|
||||||
|
|
||||||
|
while (-not $success -and $attempt -le $MaxAttempts) {
|
||||||
|
try {
|
||||||
|
$result = & $ScriptBlock
|
||||||
|
$success = $true
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log "Attempt $attempt failed: $_"
|
||||||
|
if ($attempt -lt $MaxAttempts) {
|
||||||
|
Write-Log "Retrying in $DelaySeconds seconds..."
|
||||||
|
Start-Sleep -Seconds $DelaySeconds
|
||||||
|
}
|
||||||
|
$attempt++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $success) {
|
||||||
|
Write-Log "All $MaxAttempts attempts failed."
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-LatestSpotifyVersion {
|
||||||
|
Write-Log "Getting data from rg-adguard..."
|
||||||
|
|
||||||
|
$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
|
||||||
|
$session.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36"
|
||||||
|
|
||||||
|
$url = "https://ru.store.rg-adguard.net/api/GetFiles"
|
||||||
|
$body = "type=url&url=https://apps.microsoft.com/detail/9ncbcszsjrsb&gl=US&ring=RP&lang=en"
|
||||||
|
$headers = @{
|
||||||
|
"authority" = "ru.store.rg-adguard.net"
|
||||||
|
"method" = "POST"
|
||||||
|
"path" = "/api/GetFiles"
|
||||||
|
"scheme" = "https"
|
||||||
|
"accept" = "*/*"
|
||||||
|
"accept-language" = "ru,ru-RU;q=0.9,en;q=0.8"
|
||||||
|
"dnt" = "1"
|
||||||
|
"origin" = "https://ru.store.rg-adguard.net"
|
||||||
|
"priority" = "u=1, i"
|
||||||
|
"referer" = "https://ru.store.rg-adguard.net/"
|
||||||
|
"sec-ch-ua" = '"Google Chrome";v="129", "Not=A?Brand";v="8", "Chromium";v="129"'
|
||||||
|
"sec-ch-ua-mobile" = "?0"
|
||||||
|
"sec-ch-ua-platform" = '"Windows"'
|
||||||
|
"sec-fetch-dest" = "empty"
|
||||||
|
"sec-fetch-mode" = "cors"
|
||||||
|
"sec-fetch-site" = "same-origin"
|
||||||
|
}
|
||||||
|
|
||||||
|
$ProgressPreference = 'SilentlyContinue'
|
||||||
|
|
||||||
|
$lastResponse = $null
|
||||||
|
$response = Invoke-WithRetry -ScriptBlock {
|
||||||
|
$resp = Invoke-WebRequest -Uri $url -Method "POST" -Body $body -WebSession $session -Headers $headers -UseBasicParsing
|
||||||
|
$html = $resp.Content
|
||||||
|
$script:lastResponse = $html # Store the last response
|
||||||
|
$trContents = [regex]::Matches($html, '(?s)<tr.*?>(.*?)</tr>') | ForEach-Object { $_.Groups[1].Value }
|
||||||
|
$results = @()
|
||||||
|
foreach ($trContent in $trContents) {
|
||||||
|
if ($trContent -match '<a href="(http://tlu\.dl\.delivery\.mp\.microsoft\.com[^"]+)"[^>]*>([^<]+)</a>') {
|
||||||
|
$url = $matches[1]
|
||||||
|
$fileName = $matches[2]
|
||||||
|
}
|
||||||
|
if ($trContent -match '>(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} GMT)<') {
|
||||||
|
$dateTime = [DateTime]::ParseExact($matches[1], "yyyy-MM-dd HH:mm:ss 'GMT'", $null)
|
||||||
|
}
|
||||||
|
if ($trContent -match '>(\d+(?:\.\d+)?)\s*MB<') {
|
||||||
|
$size = $matches[1] + " MB"
|
||||||
|
}
|
||||||
|
$results += [PSCustomObject]@{
|
||||||
|
FileName = $fileName
|
||||||
|
DateTime = $dateTime
|
||||||
|
Size = $size
|
||||||
|
Url = $url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$filteredResults = $results | Where-Object { $_.FileName -match 'SpotifyAB\.SpotifyMusic_.+x64.+\.appx' }
|
||||||
|
$latestFile = $filteredResults | Sort-Object -Property DateTime -Descending | Select-Object -First 1
|
||||||
|
|
||||||
|
if (-not $latestFile) {
|
||||||
|
throw "No matching Spotify file found"
|
||||||
|
}
|
||||||
|
|
||||||
|
return $latestFile
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($response) {
|
||||||
|
Write-Log "Found: $($response.FileName)"
|
||||||
|
return $response
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Log "Failed to find matching Spotify file after multiple attempts."
|
||||||
|
Write-Log "Response:"
|
||||||
|
Write-Log $script:lastResponse
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Download-SpotifyAppx {
|
||||||
|
param ($downloadUrl, $filePath)
|
||||||
|
if (-not $downloadUrl) {
|
||||||
|
Write-Log "Error: Download URL is empty or null"
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
Write-Log "Downloading appx file..."
|
||||||
|
$ProgressPreference = 'SilentlyContinue'
|
||||||
|
|
||||||
|
$success = Invoke-WithRetry -ScriptBlock {
|
||||||
|
Invoke-WebRequest -Uri $downloadUrl -OutFile $filePath
|
||||||
|
$true
|
||||||
|
}
|
||||||
|
|
||||||
|
return $success
|
||||||
|
}
|
||||||
|
|
||||||
|
function Extract-SpotifyAppx {
|
||||||
|
param ($filePath, $extractPath)
|
||||||
|
Write-Log "Extracting files from archive..."
|
||||||
|
Add-Type -AssemblyName "System.IO.Compression.FileSystem"
|
||||||
|
try {
|
||||||
|
$zip = [System.IO.Compression.ZipFile]::OpenRead($filePath)
|
||||||
|
$zip.Entries | Where-Object { $_.FullName -notlike '*/*' } | ForEach-Object {
|
||||||
|
$destPath = Join-Path $extractPath $_.Name
|
||||||
|
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, $destPath, $true)
|
||||||
|
}
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log "Error extracting files: $_"
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if ($zip -ne $null) {
|
||||||
|
$zip.Dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-SpotifyExeVersion {
|
||||||
|
param ($spotifyExePath)
|
||||||
|
Write-Log "Getting version from $([System.IO.Path]::GetFileName($spotifyExePath))..."
|
||||||
|
$exeContent = Get-Content -Path $spotifyExePath -Raw
|
||||||
|
$regex = '(?<![\w\-])(\d+)\.(\d+)\.(\d+)\.(\d+)(\.g[0-9a-f]{8})(?![\w\-])'
|
||||||
|
if ($exeContent -match $regex) {
|
||||||
|
Write-Log "Version received successfully: $($matches[0])"
|
||||||
|
return $matches[0]
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
function Compare-SpotifyVersions {
|
||||||
|
param ($version, $jsonUrl)
|
||||||
|
Write-Log "Comparison of versions..."
|
||||||
|
$ProgressPreference = 'SilentlyContinue'
|
||||||
|
|
||||||
|
$jsonContent = Invoke-WithRetry -ScriptBlock {
|
||||||
|
Invoke-WebRequest -Uri $jsonUrl | ConvertFrom-Json
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $jsonContent) {
|
||||||
|
Write-Log "Failed to fetch version information after multiple attempts."
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($jsonVersion in $jsonContent.PSObject.Properties) {
|
||||||
|
if ($jsonVersion.Value.fullversion -eq $version) {
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
function Trigger-GitAction {
|
||||||
|
param (
|
||||||
|
[string]$v,
|
||||||
|
[string]$s
|
||||||
|
)
|
||||||
|
|
||||||
|
$apiUrl = "https://api.github.com/repos/amd64fox/LoaderSpot/dispatches"
|
||||||
|
|
||||||
|
$payload = @{
|
||||||
|
event_type = "webhook-event"
|
||||||
|
client_payload = @{
|
||||||
|
v = $v
|
||||||
|
s = $s
|
||||||
|
}
|
||||||
|
} | ConvertTo-Json
|
||||||
|
|
||||||
|
$headers = @{
|
||||||
|
"Accept" = "application/vnd.github.everest-preview+json"
|
||||||
|
"Authorization" = "Bearer " + $env:Token
|
||||||
|
"Content-Type" = "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
$success = Invoke-WithRetry -ScriptBlock {
|
||||||
|
Invoke-RestMethod -Uri $apiUrl -Method Post -Headers $headers -Body $payload
|
||||||
|
$true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($success) {
|
||||||
|
Write-Log "Successfully triggered Git action"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Log "Failed to trigger Git action after multiple attempts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Main {
|
||||||
|
$latestFile = Get-LatestSpotifyVersion
|
||||||
|
if (-not $latestFile) {
|
||||||
|
Write-Log "Failed to get latest Spotify version"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$filePath = Join-Path $spotifyTempDir $latestFile.FileName
|
||||||
|
if (-not (Download-SpotifyAppx -downloadUrl $latestFile.Url -filePath $filePath)) {
|
||||||
|
Write-Log "Failed to download Spotify appx"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Extract-SpotifyAppx -filePath $filePath -extractPath $spotifyTempDir)) {
|
||||||
|
Write-Log "Failed to extract Spotify appx"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$spotifyExePath = Get-ChildItem -Path $spotifyTempDir -Filter "Spotify.dll" -Recurse | Select-Object -First 1
|
||||||
|
if (-not $spotifyExePath) {
|
||||||
|
Write-Log "Spotify.exe not found"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$version = Get-SpotifyExeVersion -spotifyExePath $spotifyExePath.FullName
|
||||||
|
if (-not $version) {
|
||||||
|
Write-Log "Version not found in Spotify.exe"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$jsonUrl = "https://raw.githubusercontent.com/amd64fox/LoaderSpot/refs/heads/main/versions.json"
|
||||||
|
$versionExists = Compare-SpotifyVersions -version $version -jsonUrl $jsonUrl
|
||||||
|
|
||||||
|
switch ($versionExists) {
|
||||||
|
$false {
|
||||||
|
Write-Log "New version found"
|
||||||
|
Trigger-GitAction -v $version -s "[Microsoft Store](https://apps.microsoft.com/detail/9ncbcszsjrsb)"
|
||||||
|
Write-Log "Sent for search and processing in GAS"
|
||||||
|
}
|
||||||
|
$null {
|
||||||
|
Write-Log "Error comparing versions"
|
||||||
|
}
|
||||||
|
Default {
|
||||||
|
Write-Log "No new version found"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$tempPath = [System.IO.Path]::GetTempPath()
|
||||||
|
$spotifyTempDir = Join-Path $tempPath "Spotify"
|
||||||
|
New-Item -Path $spotifyTempDir -ItemType Directory -Force | Out-Null
|
||||||
|
|
||||||
|
Main
|
||||||
63
update_version.py
Normal file
63
update_version.py
Normal 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()
|
||||||
5252
versions.json
Normal file
5252
versions.json
Normal file
File diff suppressed because it is too large
Load diff
274
versions_deb.json
Normal file
274
versions_deb.json
Normal file
|
|
@ -0,0 +1,274 @@
|
||||||
|
{
|
||||||
|
"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",
|
||||||
|
"data": "20.10.2025",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.74.477.g3be53afe_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.63.394": {
|
||||||
|
"fullversion": "1.2.63.394.g126b0d89",
|
||||||
|
"size": "147984434",
|
||||||
|
"data": "19.05.2025",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.63.394.g126b0d89_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.60.564": {
|
||||||
|
"fullversion": "1.2.60.564.gcc6305cb",
|
||||||
|
"size": "144421884",
|
||||||
|
"data": "07.04.2025",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.60.564.gcc6305cb_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.59.514": {
|
||||||
|
"fullversion": "1.2.59.514.g834e17d4",
|
||||||
|
"size": "143491584",
|
||||||
|
"data": "13.03.2025",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.59.514.g834e17d4_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.59.513": {
|
||||||
|
"fullversion": "1.2.59.513.g9e00425b",
|
||||||
|
"size": "143470202",
|
||||||
|
"data": "12.03.2025",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.59.513.g9e00425b_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.56.502": {
|
||||||
|
"fullversion": "1.2.56.502.ga68d2d4f",
|
||||||
|
"size": "142990750",
|
||||||
|
"data": "28.01.2025",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.56.502.ga68d2d4f_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.53.440": {
|
||||||
|
"fullversion": "1.2.53.440.g7b2f582a",
|
||||||
|
"size": "143507202",
|
||||||
|
"data": "02.01.2025",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.53.440.g7b2f582a_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.52.442": {
|
||||||
|
"fullversion": "1.2.52.442.g01893f92",
|
||||||
|
"size": "143523470",
|
||||||
|
"data": "02.12.2024",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.52.442.g01893f92_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.50.335": {
|
||||||
|
"fullversion": "1.2.50.335.g5e2860a8",
|
||||||
|
"size": "143617148",
|
||||||
|
"data": "11.11.2024",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.50.335.g5e2860a8_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.48.405": {
|
||||||
|
"fullversion": "1.2.48.405.gf2c48e6f",
|
||||||
|
"size": "143394450",
|
||||||
|
"data": "08.10.2024",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.48.405.gf2c48e6f_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.47.364": {
|
||||||
|
"fullversion": "1.2.47.364.gf06e5cee",
|
||||||
|
"size": "109100472",
|
||||||
|
"data": "20.09.2024",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.47.364.gf06e5cee_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.45.454": {
|
||||||
|
"fullversion": "1.2.45.454.gc16ec9f6",
|
||||||
|
"size": "141143192",
|
||||||
|
"data": "26.08.2024",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.45.454.gc16ec9f6_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.42.290": {
|
||||||
|
"fullversion": "1.2.42.290.g242057a2",
|
||||||
|
"size": "140926882",
|
||||||
|
"data": "12.07.2024",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.42.290.g242057a2_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.40.599": {
|
||||||
|
"fullversion": "1.2.40.599.g606b7f29",
|
||||||
|
"size": "140956866",
|
||||||
|
"data": "17.06.2024",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.40.599.g606b7f29_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.37.701": {
|
||||||
|
"fullversion": "1.2.37.701.ge66eb7bc",
|
||||||
|
"size": "137805232",
|
||||||
|
"data": "03.05.2024",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.37.701.ge66eb7bc_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.31.1205": {
|
||||||
|
"fullversion": "1.2.31.1205.g4d59ad7c",
|
||||||
|
"size": "135223836",
|
||||||
|
"data": "09.02.2024",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.31.1205.g4d59ad7c_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.26.1187": {
|
||||||
|
"fullversion": "1.2.26.1187.g36b715a1",
|
||||||
|
"size": "133729904",
|
||||||
|
"data": "04.12.2023",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.26.1187.g36b715a1_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.25.1011": {
|
||||||
|
"fullversion": "1.2.25.1011.g0348b2ea",
|
||||||
|
"size": "132587766",
|
||||||
|
"data": "20.11.2023",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.25.1011.g0348b2ea_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.25.1009": {
|
||||||
|
"fullversion": "1.2.25.1009.g075ce884",
|
||||||
|
"size": "132533054",
|
||||||
|
"data": "17.11.2023",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.25.1009.g075ce884_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.22.982": {
|
||||||
|
"fullversion": "1.2.22.982.g794acc0a",
|
||||||
|
"size": "124729466",
|
||||||
|
"data": "06.10.2023",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.22.982.g794acc0a_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.20.1210": {
|
||||||
|
"fullversion": "1.2.20.1210.g2a8a8a57",
|
||||||
|
"size": "124183006",
|
||||||
|
"data": "08.09.2023",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.20.1210.g2a8a8a57_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.18.999": {
|
||||||
|
"fullversion": "1.2.18.999.g9b38fc27",
|
||||||
|
"size": "122391766",
|
||||||
|
"data": "14.08.2023",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.18.999.g9b38fc27_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.13.661": {
|
||||||
|
"fullversion": "1.2.13.661.ga588f749",
|
||||||
|
"size": "121793588",
|
||||||
|
"data": "05.06.2023",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.13.661.ga588f749_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.11.916": {
|
||||||
|
"fullversion": "1.2.11.916.geb595a67",
|
||||||
|
"size": "121053308",
|
||||||
|
"data": "11.05.2023",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.11.916.geb595a67_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.2.8.923": {
|
||||||
|
"fullversion": "1.2.8.923.g4f94bf0d",
|
||||||
|
"size": "119841576",
|
||||||
|
"data": "27.03.2023",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.2.8.923.g4f94bf0d_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.1.84.716": {
|
||||||
|
"fullversion": "1.1.84.716.gc5f8b819",
|
||||||
|
"size": "119770140",
|
||||||
|
"data": "22.04.2022",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.1.84.716.gc5f8b819_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.1.72.439": {
|
||||||
|
"fullversion": "1.1.72.439.gc253025e",
|
||||||
|
"size": "117803268",
|
||||||
|
"data": "10.11.2021",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.1.72.439.gc253025e_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.1.68.632": {
|
||||||
|
"fullversion": "1.1.68.632.g2b11de83",
|
||||||
|
"size": "112586002",
|
||||||
|
"data": "20.09.2021",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.1.68.632.g2b11de83_amd64.deb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1.1.67.586": {
|
||||||
|
"fullversion": "1.1.67.586.gbb5ef64e",
|
||||||
|
"size": "118938508",
|
||||||
|
"data": "31.08.2021",
|
||||||
|
"links": {
|
||||||
|
"amd64": "https://github.com/LoaderSpot/deb-builds/releases/download/builds/spotify-client_1.1.67.586.gbb5ef64e_amd64.deb"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue