mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-24 02:06:47 +00:00
Compare commits
104 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5009dad31 | ||
|
|
9682d1d5ef | ||
|
|
a4e8fca784 | ||
|
|
8f25040bf5 | ||
|
|
34beec8d6b | ||
|
|
6c0fd638a5 | ||
|
|
0b94213af7 | ||
|
|
f874598fd8 | ||
|
|
a1adcd3a5a | ||
|
|
1596b98e25 | ||
|
|
9b0ad5c545 | ||
|
|
6a47b30987 | ||
|
|
79594a9d00 | ||
|
|
38a9818482 | ||
|
|
a407d538f5 | ||
|
|
44bea85296 | ||
|
|
915d26adfa | ||
|
|
3dd4248587 | ||
|
|
7969e58fbf | ||
|
|
7043ebda23 | ||
|
|
4c0a5b7553 | ||
|
|
533fbe0c2f | ||
|
|
437dfd5f02 | ||
|
|
5f4962ddcc | ||
|
|
7efa609a13 | ||
|
|
0d7049ea7c | ||
|
|
997e7f0420 | ||
|
|
96619b9565 | ||
|
|
4c2a786e54 | ||
|
|
469b1571e1 | ||
|
|
3bc66e0d27 | ||
|
|
9a4fac600a | ||
|
|
546ba8b68f | ||
|
|
ed29c0633f | ||
|
|
35b1135b50 | ||
|
|
90403c98c9 | ||
|
|
71a1c0574a | ||
|
|
ba64a50bb5 | ||
|
|
05131b2b76 | ||
|
|
0894eaee28 | ||
|
|
6f2fd4eccc | ||
|
|
04b534bda1 | ||
|
|
3620796d6d | ||
|
|
a321a38ccd | ||
|
|
ea965dfa85 | ||
|
|
f381106de2 | ||
|
|
b9ab3d6490 | ||
|
|
4cf1d1e08b | ||
|
|
cba6e4df64 | ||
|
|
0dd6f38748 | ||
|
|
970ba437a0 | ||
|
|
e0bbbe3894 | ||
|
|
504791b5b6 | ||
|
|
6a6d0b48b9 | ||
|
|
84ff83bbe2 | ||
|
|
787bcc3546 | ||
|
|
fd811d8cf4 | ||
|
|
3fa743f64b | ||
|
|
ec374c050a | ||
|
|
5a07f8a2a3 | ||
|
|
91b09dc43f | ||
|
|
8d6d355a3b | ||
|
|
da6fd69398 | ||
|
|
0e623cfd15 | ||
|
|
1c01d8f59f | ||
|
|
5723ee7b19 | ||
|
|
1d85292451 | ||
|
|
0d65f5174b | ||
|
|
b7f34f0d5d | ||
|
|
4ffcea72b0 | ||
|
|
9c5fade596 | ||
|
|
331ecc1cc8 | ||
|
|
6428e5abd1 | ||
|
|
b86f83a634 | ||
|
|
ff55e09783 | ||
|
|
d7d24d5e47 | ||
|
|
48a14136a9 | ||
|
|
c9acbb7bf2 | ||
|
|
f98a159799 | ||
|
|
184232995d | ||
|
|
aedede3ba4 | ||
|
|
c874779c0d | ||
|
|
4a2457d571 | ||
|
|
65fdd4196a | ||
|
|
19eefff525 | ||
|
|
626c18ba23 | ||
|
|
d9c8dc1e2d | ||
|
|
25fa5cabbb | ||
|
|
21c9dac593 | ||
|
|
617dccbd9a | ||
|
|
2f2665e2ad | ||
|
|
fcc0d70cd1 | ||
|
|
06b2cf4ff5 | ||
|
|
8642b0775e | ||
|
|
c193858973 | ||
|
|
362e921a27 | ||
|
|
e21fa45718 | ||
|
|
caae270ea8 | ||
|
|
4843d539f0 | ||
|
|
fa8e649288 | ||
|
|
20708ad900 | ||
|
|
5e0b9551da | ||
|
|
e43adf4de7 | ||
|
|
afe16b8eb1 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
|
||||
- name: Upload latest strings for translation on Crowdin
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.configuration == 'Release' && startsWith(matrix.os, 'ubuntu-') }}
|
||||
uses: crowdin/github-action@v1.19.0
|
||||
uses: crowdin/github-action@v1.20.0
|
||||
with:
|
||||
crowdin_branch_name: main
|
||||
config: '.github/crowdin.yml'
|
||||
|
||||
2
.github/workflows/code-quality.yml
vendored
2
.github/workflows/code-quality.yml
vendored
@@ -40,6 +40,6 @@ jobs:
|
||||
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
|
||||
|
||||
- name: Report Qodana results to GitHub
|
||||
uses: github/codeql-action/upload-sarif@v3.24.6
|
||||
uses: github/codeql-action/upload-sarif@v3.24.8
|
||||
with:
|
||||
sarif_file: ${{ runner.temp }}/qodana/results/qodana.sarif.json
|
||||
|
||||
4
.github/workflows/docker-ci.yml
vendored
4
.github/workflows/docker-ci.yml
vendored
@@ -25,10 +25,10 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.1.0
|
||||
uses: docker/setup-buildx-action@v3.2.0
|
||||
|
||||
- name: Build ${{ matrix.configuration }} Docker image from ${{ matrix.file }}
|
||||
uses: docker/build-push-action@v5.2.0
|
||||
uses: docker/build-push-action@v5.3.0
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.file }}
|
||||
|
||||
8
.github/workflows/docker-publish-latest.yml
vendored
8
.github/workflows/docker-publish-latest.yml
vendored
@@ -24,17 +24,17 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.1.0
|
||||
uses: docker/setup-buildx-action@v3.2.0
|
||||
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v3.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v3.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
echo "DH_REPOSITORY=$(echo ${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build and publish Docker image from Dockerfile.Service
|
||||
uses: docker/build-push-action@v5.2.0
|
||||
uses: docker/build-push-action@v5.3.0
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile.Service
|
||||
|
||||
8
.github/workflows/docker-publish-main.yml
vendored
8
.github/workflows/docker-publish-main.yml
vendored
@@ -25,17 +25,17 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.1.0
|
||||
uses: docker/setup-buildx-action@v3.2.0
|
||||
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v3.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v3.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
echo "DH_REPOSITORY=$(echo ${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build and publish Docker image from Dockerfile
|
||||
uses: docker/build-push-action@v5.2.0
|
||||
uses: docker/build-push-action@v5.3.0
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
|
||||
@@ -25,17 +25,17 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.1.0
|
||||
uses: docker/setup-buildx-action@v3.2.0
|
||||
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v3.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3.0.0
|
||||
uses: docker/login-action@v3.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
echo "DH_REPOSITORY=$(echo ${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build and publish Docker image from Dockerfile
|
||||
uses: docker/build-push-action@v5.2.0
|
||||
uses: docker/build-push-action@v5.3.0
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
|
||||
3
.github/workflows/publish.yml
vendored
3
.github/workflows/publish.yml
vendored
@@ -504,9 +504,12 @@ jobs:
|
||||
- name: Create ArchiSteamFarm GitHub release
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
with:
|
||||
allowUpdates: true
|
||||
artifactErrorsFailBuild: true
|
||||
artifacts: "out/*"
|
||||
bodyFile: .github/RELEASE_TEMPLATE.md
|
||||
makeLatest: false
|
||||
name: ArchiSteamFarm V${{ github.ref_name }}
|
||||
prerelease: true
|
||||
token: ${{ secrets.ARCHIBOT_GITHUB_TOKEN }}
|
||||
updateOnlyUnreleased: true
|
||||
|
||||
2
.github/workflows/translations.yml
vendored
2
.github/workflows/translations.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
git reset --hard origin/master
|
||||
|
||||
- name: Download latest translations from Crowdin
|
||||
uses: crowdin/github-action@v1.19.0
|
||||
uses: crowdin/github-action@v1.20.0
|
||||
with:
|
||||
upload_sources: false
|
||||
download_translations: true
|
||||
|
||||
2
ASF-ui
2
ASF-ui
Submodule ASF-ui updated: 3c96528592...2f7a98e85e
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
//
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// |
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// |
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
//
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// |
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// |
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
//
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// |
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// |
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
//
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// |
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// |
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
//
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// |
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// |
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -41,7 +43,7 @@ using SteamKit2;
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
|
||||
|
||||
internal static class Backend {
|
||||
internal static async Task<ObjectResponse<GenericResponse<BackgroundTaskResponse>>?> AnnounceDiffForListing(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<Asset.EType> acceptedMatchableTypes, uint totalInventoryCount, bool matchEverything, string tradeToken, IReadOnlyCollection<AssetForListing> inventoryRemoved, string? previousInventoryChecksum, string? nickname = null, string? avatarHash = null) {
|
||||
internal static async Task<ObjectResponse<GenericResponse<BackgroundTaskResponse>>?> AnnounceDiffForListing(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<EAssetType> acceptedMatchableTypes, uint totalInventoryCount, bool matchEverything, string tradeToken, IReadOnlyCollection<AssetForListing> inventoryRemoved, string? previousInventoryChecksum, string? nickname = null, string? avatarHash = null) {
|
||||
ArgumentNullException.ThrowIfNull(webBrowser);
|
||||
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
@@ -72,7 +74,7 @@ internal static class Backend {
|
||||
return await webBrowser.UrlPostToJsonObject<GenericResponse<BackgroundTaskResponse>, AnnouncementDiffRequest>(request, data: data, requestOptions: WebBrowser.ERequestOptions.ReturnRedirections | WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors | WebBrowser.ERequestOptions.CompressRequest).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async Task<ObjectResponse<GenericResponse<BackgroundTaskResponse>>?> AnnounceForListing(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<Asset.EType> acceptedMatchableTypes, uint totalInventoryCount, bool matchEverything, string tradeToken, string? nickname = null, string? avatarHash = null) {
|
||||
internal static async Task<ObjectResponse<GenericResponse<BackgroundTaskResponse>>?> AnnounceForListing(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<EAssetType> acceptedMatchableTypes, uint totalInventoryCount, bool matchEverything, string tradeToken, string? nickname = null, string? avatarHash = null) {
|
||||
ArgumentNullException.ThrowIfNull(webBrowser);
|
||||
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
@@ -129,7 +131,7 @@ internal static class Backend {
|
||||
return response?.StatusCode;
|
||||
}
|
||||
|
||||
internal static async Task<(HttpStatusCode StatusCode, ImmutableHashSet<ListedUser> Users)?> GetListedUsersForMatching(Guid licenseID, Bot bot, WebBrowser webBrowser, IReadOnlyCollection<Asset> inventory, IReadOnlyCollection<Asset.EType> acceptedMatchableTypes) {
|
||||
internal static async Task<(HttpStatusCode StatusCode, ImmutableHashSet<ListedUser> Users)?> GetListedUsersForMatching(Guid licenseID, Bot bot, WebBrowser webBrowser, IReadOnlyCollection<Asset> inventory, IReadOnlyCollection<EAssetType> acceptedMatchableTypes) {
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(licenseID, Guid.Empty);
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
ArgumentNullException.ThrowIfNull(webBrowser);
|
||||
@@ -156,10 +158,10 @@ internal static class Backend {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (response.StatusCode, response.Content?.Result ?? ImmutableHashSet<ListedUser>.Empty);
|
||||
return (response.StatusCode, response.Content?.Result ?? []);
|
||||
}
|
||||
|
||||
internal static async Task<ObjectResponse<GenericResponse<ImmutableHashSet<SetPart>>>?> GetSetParts(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection<Asset.EType> matchableTypes, IReadOnlyCollection<uint> realAppIDs, CancellationToken cancellationToken = default) {
|
||||
internal static async Task<ObjectResponse<GenericResponse<ImmutableHashSet<SetPart>>>?> GetSetParts(WebBrowser webBrowser, ulong steamID, IReadOnlyCollection<EAssetType> matchableTypes, IReadOnlyCollection<uint> realAppIDs, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(webBrowser);
|
||||
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -38,7 +40,7 @@ internal sealed class AnnouncementDiffRequest : AnnouncementRequest {
|
||||
[JsonRequired]
|
||||
private string PreviousInventoryChecksum { get; init; }
|
||||
|
||||
internal AnnouncementDiffRequest(Guid guid, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<Asset.EType> matchableTypes, uint totalInventoryCount, bool matchEverything, byte maxTradeHoldDuration, string tradeToken, IReadOnlyCollection<AssetForListing> inventoryRemoved, string previousInventoryChecksum, string? nickname = null, string? avatarHash = null) : base(guid, steamID, inventory, inventoryChecksum, matchableTypes, totalInventoryCount, matchEverything, maxTradeHoldDuration, tradeToken, nickname, avatarHash) {
|
||||
internal AnnouncementDiffRequest(Guid guid, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<EAssetType> matchableTypes, uint totalInventoryCount, bool matchEverything, byte maxTradeHoldDuration, string tradeToken, IReadOnlyCollection<AssetForListing> inventoryRemoved, string previousInventoryChecksum, string? nickname = null, string? avatarHash = null) : base(guid, steamID, inventory, inventoryChecksum, matchableTypes, totalInventoryCount, matchEverything, maxTradeHoldDuration, tradeToken, nickname, avatarHash) {
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
|
||||
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -48,7 +50,7 @@ internal class AnnouncementRequest {
|
||||
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
private ImmutableHashSet<Asset.EType> MatchableTypes { get; init; }
|
||||
private ImmutableHashSet<EAssetType> MatchableTypes { get; init; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
@@ -73,7 +75,7 @@ internal class AnnouncementRequest {
|
||||
[JsonRequired]
|
||||
private string TradeToken { get; init; }
|
||||
|
||||
internal AnnouncementRequest(Guid guid, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<Asset.EType> matchableTypes, uint totalInventoryCount, bool matchEverything, byte maxTradeHoldDuration, string tradeToken, string? nickname = null, string? avatarHash = null) {
|
||||
internal AnnouncementRequest(Guid guid, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<EAssetType> matchableTypes, uint totalInventoryCount, bool matchEverything, byte maxTradeHoldDuration, string tradeToken, string? nickname = null, string? avatarHash = null) {
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
|
||||
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -39,7 +41,7 @@ internal class AssetForMatching {
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("r")]
|
||||
[JsonRequired]
|
||||
internal Asset.ERarity Rarity { get; private init; }
|
||||
internal EAssetRarity Rarity { get; private init; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("e")]
|
||||
@@ -54,7 +56,7 @@ internal class AssetForMatching {
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("p")]
|
||||
[JsonRequired]
|
||||
internal Asset.EType Type { get; private init; }
|
||||
internal EAssetType Type { get; private init; }
|
||||
|
||||
[JsonConstructor]
|
||||
protected AssetForMatching() { }
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -40,5 +42,5 @@ internal class AssetInInventory : AssetForMatching {
|
||||
AssetID = asset.AssetID;
|
||||
}
|
||||
|
||||
internal Asset ToAsset() => new(Asset.SteamAppID, Asset.SteamCommunityContextID, ClassID, Amount, tradable: Tradable, assetID: AssetID, realAppID: RealAppID, type: Type, rarity: Rarity);
|
||||
internal Asset ToAsset() => new(Asset.SteamAppID, Asset.SteamCommunityContextID, ClassID, Amount, new InventoryDescription(Asset.SteamAppID, ClassID, tradable: false, realAppID: RealAppID, type: Type, rarity: Rarity), AssetID);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -40,13 +42,13 @@ internal sealed class InventoriesRequest {
|
||||
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<Asset.EType> MatchableTypes { get; private init; }
|
||||
internal ImmutableHashSet<EAssetType> MatchableTypes { get; private init; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ulong SteamID { get; private init; }
|
||||
|
||||
internal InventoriesRequest(Guid guid, ulong steamID, IReadOnlyCollection<Asset> inventory, IReadOnlyCollection<Asset.EType> matchableTypes) {
|
||||
internal InventoriesRequest(Guid guid, ulong steamID, IReadOnlyCollection<Asset> inventory, IReadOnlyCollection<EAssetType> matchableTypes) {
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
|
||||
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -31,11 +33,11 @@ namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
internal sealed class ListedUser {
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<AssetInInventory> Assets { get; private init; } = ImmutableHashSet<AssetInInventory>.Empty;
|
||||
internal ImmutableHashSet<AssetInInventory> Assets { get; private init; } = [];
|
||||
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<Asset.EType> MatchableTypes { get; private init; } = ImmutableHashSet<Asset.EType>.Empty;
|
||||
internal ImmutableHashSet<EAssetType> MatchableTypes { get; private init; } = [];
|
||||
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -36,7 +38,7 @@ internal sealed class SetPart {
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("r")]
|
||||
[JsonRequired]
|
||||
internal Asset.ERarity Rarity { get; private init; }
|
||||
internal EAssetRarity Rarity { get; private init; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("e")]
|
||||
@@ -46,7 +48,7 @@ internal sealed class SetPart {
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("p")]
|
||||
[JsonRequired]
|
||||
internal Asset.EType Type { get; private init; }
|
||||
internal EAssetType Type { get; private init; }
|
||||
|
||||
[JsonConstructor]
|
||||
private SetPart() { }
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -35,7 +37,7 @@ internal sealed class SetPartsRequest {
|
||||
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<Asset.EType> MatchableTypes { get; private init; }
|
||||
internal ImmutableHashSet<EAssetType> MatchableTypes { get; private init; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
@@ -45,7 +47,7 @@ internal sealed class SetPartsRequest {
|
||||
[JsonRequired]
|
||||
internal ulong SteamID { get; private init; }
|
||||
|
||||
internal SetPartsRequest(Guid guid, ulong steamID, IReadOnlyCollection<Asset.EType> matchableTypes, IReadOnlyCollection<uint> realAppIDs) {
|
||||
internal SetPartsRequest(Guid guid, ulong steamID, IReadOnlyCollection<EAssetType> matchableTypes, IReadOnlyCollection<uint> realAppIDs) {
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
|
||||
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
191
ArchiSteamFarm.OfficialPlugins.ItemsMatcher/MatchingUtilities.cs
Normal file
191
ArchiSteamFarm.OfficialPlugins.ItemsMatcher/MatchingUtilities.cs
Normal file
@@ -0,0 +1,191 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// |
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// |
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
|
||||
|
||||
internal static class MatchingUtilities {
|
||||
internal static (Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> FullState, Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> TradableState) GetDividedInventoryState(IReadOnlyCollection<Asset> inventory) {
|
||||
if ((inventory == null) || (inventory.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(inventory));
|
||||
}
|
||||
|
||||
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> fullState = new();
|
||||
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> tradableState = new();
|
||||
|
||||
foreach (Asset item in inventory) {
|
||||
(uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
|
||||
|
||||
if (fullState.TryGetValue(key, out Dictionary<ulong, uint>? fullSet)) {
|
||||
fullSet[item.ClassID] = fullSet.GetValueOrDefault(item.ClassID) + item.Amount;
|
||||
} else {
|
||||
fullState[key] = new Dictionary<ulong, uint> { { item.ClassID, item.Amount } };
|
||||
}
|
||||
|
||||
if (!item.Tradable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tradableState.TryGetValue(key, out Dictionary<ulong, uint>? tradableSet)) {
|
||||
tradableSet[item.ClassID] = tradableSet.GetValueOrDefault(item.ClassID) + item.Amount;
|
||||
} else {
|
||||
tradableState[key] = new Dictionary<ulong, uint> { { item.ClassID, item.Amount } };
|
||||
}
|
||||
}
|
||||
|
||||
return (fullState, tradableState);
|
||||
}
|
||||
|
||||
internal static Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> GetTradableInventoryState(IReadOnlyCollection<Asset> inventory) {
|
||||
if ((inventory == null) || (inventory.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(inventory));
|
||||
}
|
||||
|
||||
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> tradableState = new();
|
||||
|
||||
foreach (Asset item in inventory.Where(static item => item.Tradable)) {
|
||||
(uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
|
||||
|
||||
if (tradableState.TryGetValue(key, out Dictionary<ulong, uint>? tradableSet)) {
|
||||
tradableSet[item.ClassID] = tradableSet.GetValueOrDefault(item.ClassID) + item.Amount;
|
||||
} else {
|
||||
tradableState[key] = new Dictionary<ulong, uint> { { item.ClassID, item.Amount } };
|
||||
}
|
||||
}
|
||||
|
||||
return tradableState;
|
||||
}
|
||||
|
||||
internal static HashSet<Asset> GetTradableItemsFromInventory(IReadOnlyCollection<Asset> inventory, IReadOnlyDictionary<ulong, uint> classIDs, bool randomize = false) {
|
||||
if ((inventory == null) || (inventory.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(inventory));
|
||||
}
|
||||
|
||||
if ((classIDs == null) || (classIDs.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(classIDs));
|
||||
}
|
||||
|
||||
// We need a copy of classIDs passed since we're going to manipulate them
|
||||
Dictionary<ulong, uint> classIDsState = classIDs.ToDictionary();
|
||||
|
||||
HashSet<Asset> result = [];
|
||||
|
||||
IEnumerable<Asset> items = inventory.Where(static item => item.Tradable);
|
||||
|
||||
// Randomization helps to decrease "items no longer available" in regards to sending offers to other users
|
||||
if (randomize) {
|
||||
#pragma warning disable CA5394 // This call isn't used in a security-sensitive manner
|
||||
items = items.Where(item => classIDsState.ContainsKey(item.ClassID)).OrderBy(static _ => Random.Shared.Next());
|
||||
#pragma warning restore CA5394 // This call isn't used in a security-sensitive manner
|
||||
}
|
||||
|
||||
foreach (Asset item in items) {
|
||||
if (!classIDsState.TryGetValue(item.ClassID, out uint amount)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (amount >= item.Amount) {
|
||||
result.Add(item);
|
||||
|
||||
if (amount > item.Amount) {
|
||||
classIDsState[item.ClassID] = amount - item.Amount;
|
||||
} else {
|
||||
classIDsState.Remove(item.ClassID);
|
||||
|
||||
if (classIDsState.Count == 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Asset itemToAdd = item.DeepClone();
|
||||
|
||||
itemToAdd.Amount = amount;
|
||||
|
||||
result.Add(itemToAdd);
|
||||
|
||||
classIDsState.Remove(itemToAdd.ClassID);
|
||||
|
||||
if (classIDsState.Count == 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we got here it means we still have classIDs to match
|
||||
throw new InvalidOperationException(nameof(classIDs));
|
||||
}
|
||||
|
||||
internal static bool IsEmptyForMatching(IReadOnlyDictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> fullState, IReadOnlyDictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> tradableState) {
|
||||
ArgumentNullException.ThrowIfNull(fullState);
|
||||
ArgumentNullException.ThrowIfNull(tradableState);
|
||||
|
||||
foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) set, IReadOnlyDictionary<ulong, uint> state) in tradableState) {
|
||||
if (!fullState.TryGetValue(set, out Dictionary<ulong, uint>? fullSet) || (fullSet.Count == 0)) {
|
||||
throw new InvalidOperationException(nameof(fullSet));
|
||||
}
|
||||
|
||||
if (!IsEmptyForMatching(fullSet, state)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// We didn't find any matchable combinations, so this inventory is empty
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool IsEmptyForMatching(IReadOnlyDictionary<ulong, uint> fullSet, IReadOnlyDictionary<ulong, uint> tradableSet) {
|
||||
ArgumentNullException.ThrowIfNull(fullSet);
|
||||
ArgumentNullException.ThrowIfNull(tradableSet);
|
||||
|
||||
foreach ((ulong classID, uint amount) in tradableSet) {
|
||||
switch (amount) {
|
||||
case 0:
|
||||
// No tradable items, this should never happen, dictionary should not have this key to begin with
|
||||
throw new InvalidOperationException(nameof(amount));
|
||||
case 1:
|
||||
// Single tradable item, can be matchable or not depending on the rest of the inventory
|
||||
if (!fullSet.TryGetValue(classID, out uint fullAmount) || (fullAmount == 0)) {
|
||||
throw new InvalidOperationException(nameof(fullAmount));
|
||||
}
|
||||
|
||||
if (fullAmount > 1) {
|
||||
// If we have a single tradable item but more than 1 in total, this is matchable
|
||||
return false;
|
||||
}
|
||||
|
||||
// A single exclusive tradable item is not matchable, continue
|
||||
continue;
|
||||
default:
|
||||
// Any other combination of tradable items is always matchable
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// We didn't find any matchable combinations, so this inventory is empty
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -61,11 +63,11 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
private const byte MinimumSteamGuardEnabledDays = 15; // As imposed by Steam limits
|
||||
private const byte MinPersonaStateTTL = 5; // Minimum amount of minutes we must wait before requesting persona state update
|
||||
|
||||
private static readonly FrozenSet<Asset.EType> AcceptedMatchableTypes = new HashSet<Asset.EType>(4) {
|
||||
Asset.EType.Emoticon,
|
||||
Asset.EType.FoilTradingCard,
|
||||
Asset.EType.ProfileBackground,
|
||||
Asset.EType.TradingCard
|
||||
private static readonly FrozenSet<EAssetType> AcceptedMatchableTypes = new HashSet<EAssetType>(4) {
|
||||
EAssetType.Emoticon,
|
||||
EAssetType.FoilTradingCard,
|
||||
EAssetType.ProfileBackground,
|
||||
EAssetType.TradingCard
|
||||
}.ToFrozenSet();
|
||||
|
||||
private readonly Bot Bot;
|
||||
@@ -229,7 +231,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
return;
|
||||
}
|
||||
|
||||
HashSet<Asset.EType> acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet();
|
||||
HashSet<EAssetType> acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet();
|
||||
|
||||
if (acceptedMatchableTypes.Count == 0) {
|
||||
throw new InvalidOperationException(nameof(acceptedMatchableTypes));
|
||||
@@ -250,7 +252,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
List<Asset> inventory;
|
||||
|
||||
try {
|
||||
inventory = await Bot.ArchiWebHandler.GetInventoryAsync().ToListAsync().ConfigureAwait(false);
|
||||
inventory = await Bot.ArchiHandler.GetMyInventoryAsync().ToListAsync().ConfigureAwait(false);
|
||||
} catch (HttpRequestException e) {
|
||||
// This is actually a network failure, so we'll stop sending heartbeats but not record it as valid check
|
||||
ShouldSendHeartBeats = false;
|
||||
@@ -282,10 +284,10 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
|
||||
List<AssetForListing> assetsForListing = [];
|
||||
|
||||
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), bool> tradableSets = new();
|
||||
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), bool> tradableSets = new();
|
||||
|
||||
foreach (Asset item in inventory) {
|
||||
if (item is { AssetID: > 0, Amount: > 0, ClassID: > 0, RealAppID: > 0, Type: > Asset.EType.Unknown, Rarity: > Asset.ERarity.Unknown, IsSteamPointsShopItem: false } && acceptedMatchableTypes.Contains(item.Type)) {
|
||||
if (item is { AssetID: > 0, Amount: > 0, ClassID: > 0, RealAppID: > 0, Type: > EAssetType.Unknown, Rarity: > EAssetRarity.Unknown, IsSteamPointsShopItem: false } && acceptedMatchableTypes.Contains(item.Type)) {
|
||||
// Only tradable assets matter for MatchEverything bots
|
||||
if (!matchEverything || item.Tradable) {
|
||||
assetsForListing.Add(new AssetForListing(item, index, previousAssetID));
|
||||
@@ -293,7 +295,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
|
||||
// But even for Fair bots, we should track and skip sets where we don't have any item to trade with
|
||||
if (!matchEverything) {
|
||||
(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
|
||||
(uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
|
||||
|
||||
if (tradableSets.TryGetValue(key, out bool tradable)) {
|
||||
if (!tradable && item.Tradable) {
|
||||
@@ -375,12 +377,12 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
if (!matchEverything) {
|
||||
// We should deduplicate our sets before sending them to the server, for doing that we'll use ASFB set parts data
|
||||
HashSet<uint> realAppIDs = [];
|
||||
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> state = new();
|
||||
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> state = new();
|
||||
|
||||
foreach (AssetForListing asset in assetsForListing) {
|
||||
realAppIDs.Add(asset.RealAppID);
|
||||
|
||||
(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity);
|
||||
(uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity);
|
||||
|
||||
if (state.TryGetValue(key, out Dictionary<ulong, uint>? set)) {
|
||||
set[asset.ClassID] = set.GetValueOrDefault(asset.ClassID) + asset.Amount;
|
||||
@@ -446,11 +448,11 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), HashSet<ulong>> databaseSets = setPartsResponse.Content.Result.GroupBy(static setPart => (setPart.RealAppID, setPart.Type, setPart.Rarity)).ToDictionary(static group => group.Key, static group => group.Select(static setPart => setPart.ClassID).ToHashSet());
|
||||
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), HashSet<ulong>> databaseSets = setPartsResponse.Content.Result.GroupBy(static setPart => (setPart.RealAppID, setPart.Type, setPart.Rarity)).ToDictionary(static group => group.Key, static group => group.Select(static setPart => setPart.ClassID).ToHashSet());
|
||||
|
||||
Dictionary<ulong, uint> setCopy = [];
|
||||
|
||||
foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key, Dictionary<ulong, uint> set) in state) {
|
||||
foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) key, Dictionary<ulong, uint> set) in state) {
|
||||
if (!databaseSets.TryGetValue(key, out HashSet<ulong>? databaseSet)) {
|
||||
// We have no clue about this set, we can't do any optimization
|
||||
continue;
|
||||
@@ -488,7 +490,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
HashSet<AssetForListing> assetsForListingFiltered = [];
|
||||
|
||||
foreach (AssetForListing asset in assetsForListing.Where(asset => state.TryGetValue((asset.RealAppID, asset.Type, asset.Rarity), out Dictionary<ulong, uint>? setState) && setState.TryGetValue(asset.ClassID, out uint targetAmount) && (targetAmount > 0)).OrderByDescending(static asset => asset.Tradable).ThenByDescending(static asset => asset.Index)) {
|
||||
(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity);
|
||||
(uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity);
|
||||
|
||||
if (!state.TryGetValue(key, out Dictionary<ulong, uint>? setState) || !setState.TryGetValue(asset.ClassID, out uint targetAmount) || (targetAmount == 0)) {
|
||||
// We're not interested in this combination
|
||||
@@ -901,7 +903,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
return;
|
||||
}
|
||||
|
||||
HashSet<Asset.EType> acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet();
|
||||
HashSet<EAssetType> acceptedMatchableTypes = Bot.BotConfig.MatchableTypes.Where(AcceptedMatchableTypes.Contains).ToHashSet();
|
||||
|
||||
if (acceptedMatchableTypes.Count == 0) {
|
||||
Bot.ArchiLogger.LogNullError(acceptedMatchableTypes);
|
||||
@@ -937,7 +939,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
HashSet<Asset> assetsForMatching;
|
||||
|
||||
try {
|
||||
assetsForMatching = await Bot.ArchiWebHandler.GetInventoryAsync().Where(item => item is { AssetID: > 0, Amount: > 0, ClassID: > 0, RealAppID: > 0, Type: > Asset.EType.Unknown, Rarity: > Asset.ERarity.Unknown, IsSteamPointsShopItem: false } && acceptedMatchableTypes.Contains(item.Type) && !Bot.BotDatabase.MatchActivelyBlacklistAppIDs.Contains(item.RealAppID)).ToHashSetAsync().ConfigureAwait(false);
|
||||
assetsForMatching = await Bot.ArchiHandler.GetMyInventoryAsync().Where(item => item is { AssetID: > 0, Amount: > 0, ClassID: > 0, RealAppID: > 0, Type: > EAssetType.Unknown, Rarity: > EAssetRarity.Unknown, IsSteamPointsShopItem: false } && acceptedMatchableTypes.Contains(item.Type) && !Bot.BotDatabase.MatchActivelyBlacklistAppIDs.Contains(item.RealAppID)).ToHashSetAsync().ConfigureAwait(false);
|
||||
} catch (HttpRequestException e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(assetsForMatching)));
|
||||
@@ -957,7 +959,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
}
|
||||
|
||||
// Remove from our inventory items that can't be possibly matched due to no dupes to offer available
|
||||
HashSet<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity)> setsToKeep = Trading.GetInventorySets(assetsForMatching).Where(static set => set.Value.Any(static amount => amount > 1)).Select(static set => set.Key).ToHashSet();
|
||||
HashSet<(uint RealAppID, EAssetType Type, EAssetRarity Rarity)> setsToKeep = Trading.GetInventorySets(assetsForMatching).Where(static set => set.Value.Any(static amount => amount > 1)).Select(static set => set.Key).ToHashSet();
|
||||
|
||||
if (assetsForMatching.RemoveWhere(item => !setsToKeep.Contains((item.RealAppID, item.Type, item.Rarity))) > 0) {
|
||||
if (assetsForMatching.Count == 0) {
|
||||
@@ -969,12 +971,12 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
|
||||
// We should deduplicate our sets before sending them to the server, for doing that we'll use ASFB set parts data
|
||||
HashSet<uint> realAppIDs = [];
|
||||
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> setsState = new();
|
||||
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> setsState = new();
|
||||
|
||||
foreach (Asset asset in assetsForMatching) {
|
||||
realAppIDs.Add(asset.RealAppID);
|
||||
|
||||
(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity);
|
||||
(uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity);
|
||||
|
||||
if (setsState.TryGetValue(key, out Dictionary<ulong, uint>? set)) {
|
||||
set[asset.ClassID] = set.GetValueOrDefault(asset.ClassID) + asset.Amount;
|
||||
@@ -1031,11 +1033,11 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), HashSet<ulong>> databaseSets = setPartsResponse.Content.Result.GroupBy(static setPart => (setPart.RealAppID, setPart.Type, setPart.Rarity)).ToDictionary(static group => group.Key, static group => group.Select(static setPart => setPart.ClassID).ToHashSet());
|
||||
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), HashSet<ulong>> databaseSets = setPartsResponse.Content.Result.GroupBy(static setPart => (setPart.RealAppID, setPart.Type, setPart.Rarity)).ToDictionary(static group => group.Key, static group => group.Select(static setPart => setPart.ClassID).ToHashSet());
|
||||
|
||||
Dictionary<ulong, uint> setCopy = [];
|
||||
|
||||
foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key, Dictionary<ulong, uint> set) in setsState) {
|
||||
foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) key, Dictionary<ulong, uint> set) in setsState) {
|
||||
uint minimumAmount = uint.MaxValue;
|
||||
uint maximumAmount = uint.MinValue;
|
||||
|
||||
@@ -1094,7 +1096,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
HashSet<Asset> assetsForMatchingFiltered = [];
|
||||
|
||||
foreach (Asset asset in assetsForMatching.Where(asset => setsState.TryGetValue((asset.RealAppID, asset.Type, asset.Rarity), out Dictionary<ulong, uint>? setState) && setState.TryGetValue(asset.ClassID, out uint targetAmount) && (targetAmount > 0)).OrderByDescending(static asset => asset.Tradable)) {
|
||||
(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity);
|
||||
(uint RealAppID, EAssetType Type, EAssetRarity Rarity) key = (asset.RealAppID, asset.Type, asset.Rarity);
|
||||
|
||||
if (!setsState.TryGetValue(key, out Dictionary<ulong, uint>? setState) || !setState.TryGetValue(asset.ClassID, out uint targetAmount) || (targetAmount == 0)) {
|
||||
// We're not interested in this combination
|
||||
@@ -1165,7 +1167,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> MatchActively(IReadOnlyCollection<ListedUser> listedUsers, IReadOnlyCollection<Asset> ourAssets, IReadOnlyCollection<Asset.EType> acceptedMatchableTypes) {
|
||||
private async Task<bool> MatchActively(IReadOnlyCollection<ListedUser> listedUsers, IReadOnlyCollection<Asset> ourAssets, IReadOnlyCollection<EAssetType> acceptedMatchableTypes) {
|
||||
if ((listedUsers == null) || (listedUsers.Count == 0)) {
|
||||
throw new ArgumentNullException(nameof(listedUsers));
|
||||
}
|
||||
@@ -1178,9 +1180,9 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
throw new ArgumentNullException(nameof(acceptedMatchableTypes));
|
||||
}
|
||||
|
||||
(Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> ourFullState, Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> ourTradableState) = Trading.GetDividedInventoryState(ourAssets);
|
||||
(Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> ourFullState, Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> ourTradableState) = MatchingUtilities.GetDividedInventoryState(ourAssets);
|
||||
|
||||
if (Trading.IsEmptyForMatching(ourFullState, ourTradableState)) {
|
||||
if (MatchingUtilities.IsEmptyForMatching(ourFullState, ourTradableState)) {
|
||||
// User doesn't have any more dupes in the inventory
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, $"{nameof(ourFullState)} || {nameof(ourTradableState)}"));
|
||||
|
||||
@@ -1248,6 +1250,14 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
byte failuresInRow = 0;
|
||||
uint matchedSets = 0;
|
||||
|
||||
HashSet<(uint RealAppID, EAssetType Type, EAssetRarity Rarity)> skippedSetsThisUser = [];
|
||||
HashSet<(uint RealAppID, EAssetType Type, EAssetRarity Rarity)> skippedSetsThisTrade = [];
|
||||
|
||||
Dictionary<ulong, uint> classIDsToGive = new();
|
||||
Dictionary<ulong, uint> classIDsToReceive = new();
|
||||
Dictionary<ulong, uint> fairClassIDsToGive = new();
|
||||
Dictionary<ulong, uint> fairClassIDsToReceive = new();
|
||||
|
||||
foreach (ListedUser listedUser in listedUsers.Where(listedUser => (listedUser.SteamID != Bot.SteamID) && acceptedMatchableTypes.Any(listedUser.MatchableTypes.Contains) && !Bot.IsBlacklistedFromTrades(listedUser.SteamID)).OrderByDescending(listedUser => !deprioritizedSteamIDs.Contains(listedUser.SteamID)).ThenByDescending(static listedUser => listedUser.TotalGamesCount > 1).ThenByDescending(static listedUser => listedUser.MatchEverything).ThenBy(static listedUser => listedUser.TotalInventoryCount)) {
|
||||
if (failuresInRow >= WebBrowser.MaxTries) {
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, $"{nameof(failuresInRow)} >= {WebBrowser.MaxTries}"));
|
||||
@@ -1261,7 +1271,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
break;
|
||||
}
|
||||
|
||||
HashSet<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity)> wantedSets = ourTradableState.Keys.Where(set => listedUser.MatchableTypes.Contains(set.Type)).ToHashSet();
|
||||
HashSet<(uint RealAppID, EAssetType Type, EAssetRarity Rarity)> wantedSets = ourTradableState.Keys.Where(set => listedUser.MatchableTypes.Contains(set.Type)).ToHashSet();
|
||||
|
||||
if (wantedSets.Count == 0) {
|
||||
continue;
|
||||
@@ -1282,26 +1292,27 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
continue;
|
||||
}
|
||||
|
||||
HashSet<Asset> theirInventory = listedUser.Assets.Where(item => (!listedUser.MatchEverything || item.Tradable) && wantedSets.Contains((item.RealAppID, item.Type, item.Rarity)) && ((tradeHoldDuration.Value == 0) || !(item.Type is Asset.EType.FoilTradingCard or Asset.EType.TradingCard && CardsFarmer.SalesBlacklist.Contains(item.RealAppID)))).Select(static asset => asset.ToAsset()).ToHashSet();
|
||||
HashSet<Asset> theirInventory = listedUser.Assets.Where(item => (!listedUser.MatchEverything || item.Tradable) && wantedSets.Contains((item.RealAppID, item.Type, item.Rarity)) && ((tradeHoldDuration.Value == 0) || !(item.Type is EAssetType.FoilTradingCard or EAssetType.TradingCard && CardsFarmer.SalesBlacklist.Contains(item.RealAppID)))).Select(static asset => asset.ToAsset()).ToHashSet();
|
||||
|
||||
if (theirInventory.Count == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
HashSet<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity)> skippedSetsThisUser = [];
|
||||
skippedSetsThisUser.Clear();
|
||||
|
||||
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), Dictionary<ulong, uint>> theirTradableState = Trading.GetTradableInventoryState(theirInventory);
|
||||
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), Dictionary<ulong, uint>> theirTradableState = MatchingUtilities.GetTradableInventoryState(theirInventory);
|
||||
|
||||
for (byte i = 0; i < Trading.MaxTradesPerAccount; i++) {
|
||||
byte itemsInTrade = 0;
|
||||
HashSet<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity)> skippedSetsThisTrade = [];
|
||||
|
||||
Dictionary<ulong, uint> classIDsToGive = new();
|
||||
Dictionary<ulong, uint> classIDsToReceive = new();
|
||||
Dictionary<ulong, uint> fairClassIDsToGive = new();
|
||||
Dictionary<ulong, uint> fairClassIDsToReceive = new();
|
||||
skippedSetsThisTrade.Clear();
|
||||
|
||||
foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) set, Dictionary<ulong, uint> ourFullItems) in ourFullState.Where(set => !skippedSetsThisUser.Contains(set.Key) && listedUser.MatchableTypes.Contains(set.Key.Type) && set.Value.Values.Any(static count => count > 1))) {
|
||||
classIDsToGive.Clear();
|
||||
classIDsToReceive.Clear();
|
||||
fairClassIDsToGive.Clear();
|
||||
fairClassIDsToReceive.Clear();
|
||||
|
||||
foreach (((uint RealAppID, EAssetType Type, EAssetRarity Rarity) set, Dictionary<ulong, uint> ourFullItems) in ourFullState.Where(set => !skippedSetsThisUser.Contains(set.Key) && listedUser.MatchableTypes.Contains(set.Key.Type) && set.Value.Values.Any(static count => count > 1))) {
|
||||
if (!ourTradableState.TryGetValue(set, out Dictionary<ulong, uint>? ourTradableItems) || (ourTradableItems.Count == 0)) {
|
||||
// We may have no more tradable items from this set
|
||||
continue;
|
||||
@@ -1312,14 +1323,14 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Trading.IsEmptyForMatching(ourFullItems, ourTradableItems)) {
|
||||
if (MatchingUtilities.IsEmptyForMatching(ourFullItems, ourTradableItems)) {
|
||||
// We may have no more matchable items from this set
|
||||
continue;
|
||||
}
|
||||
|
||||
// Those 2 collections are on user-basis since we can't be sure that the trade passes through (and therefore we need to keep original state in case of a failure)
|
||||
Dictionary<ulong, uint> ourFullSet = new(ourFullItems);
|
||||
Dictionary<ulong, uint> ourTradableSet = new(ourTradableItems);
|
||||
Dictionary<ulong, uint> ourFullSet = ourFullItems.ToDictionary();
|
||||
Dictionary<ulong, uint> ourTradableSet = ourTradableItems.ToDictionary();
|
||||
|
||||
bool match;
|
||||
|
||||
@@ -1345,13 +1356,13 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
fairClassIDsToReceive[theirItem] = ++fairReceivedAmount;
|
||||
|
||||
// Filter their inventory for the sets we're trading or have traded with this user
|
||||
HashSet<Asset> fairFiltered = theirInventory.Where(item => ((item.RealAppID == set.RealAppID) && (item.Type == set.Type) && (item.Rarity == set.Rarity)) || skippedSetsThisTrade.Contains((item.RealAppID, item.Type, item.Rarity))).Select(static item => item.CreateShallowCopy()).ToHashSet();
|
||||
HashSet<Asset> fairFiltered = theirInventory.Where(item => ((item.RealAppID == set.RealAppID) && (item.Type == set.Type) && (item.Rarity == set.Rarity)) || skippedSetsThisTrade.Contains((item.RealAppID, item.Type, item.Rarity))).ToHashSet();
|
||||
|
||||
// Copy list to HashSet<Steam.Asset>
|
||||
HashSet<Asset> fairItemsToGive = Trading.GetTradableItemsFromInventory(ourInventory.Values.Where(item => ((item.RealAppID == set.RealAppID) && (item.Type == set.Type) && (item.Rarity == set.Rarity)) || skippedSetsThisTrade.Contains((item.RealAppID, item.Type, item.Rarity))).Select(static item => item.CreateShallowCopy()).ToHashSet(), fairClassIDsToGive.ToDictionary(static classID => classID.Key, static classID => classID.Value));
|
||||
HashSet<Asset> fairItemsToReceive = Trading.GetTradableItemsFromInventory(fairFiltered.Select(static item => item.CreateShallowCopy()).ToHashSet(), fairClassIDsToReceive.ToDictionary(static classID => classID.Key, static classID => classID.Value));
|
||||
// Get tradable items from our and their inventory
|
||||
HashSet<Asset> fairItemsToGive = MatchingUtilities.GetTradableItemsFromInventory(ourInventory.Values.Where(item => ((item.RealAppID == set.RealAppID) && (item.Type == set.Type) && (item.Rarity == set.Rarity)) || skippedSetsThisTrade.Contains((item.RealAppID, item.Type, item.Rarity))).ToHashSet(), fairClassIDsToGive);
|
||||
HashSet<Asset> fairItemsToReceive = MatchingUtilities.GetTradableItemsFromInventory(fairFiltered, fairClassIDsToReceive);
|
||||
|
||||
// Actual check
|
||||
// Actual check, since we do this against remote user, we flip places for items
|
||||
if (!Trading.IsTradeNeutralOrBetter(fairFiltered, fairItemsToReceive, fairItemsToGive)) {
|
||||
// Revert the changes
|
||||
if (fairGivenAmount > 1) {
|
||||
@@ -1419,8 +1430,8 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
}
|
||||
|
||||
// Remove the items from inventories
|
||||
HashSet<Asset> itemsToGive = Trading.GetTradableItemsFromInventory(ourInventory.Values, classIDsToGive);
|
||||
HashSet<Asset> itemsToReceive = Trading.GetTradableItemsFromInventory(theirInventory, classIDsToReceive, true);
|
||||
HashSet<Asset> itemsToGive = MatchingUtilities.GetTradableItemsFromInventory(ourInventory.Values, classIDsToGive);
|
||||
HashSet<Asset> itemsToReceive = MatchingUtilities.GetTradableItemsFromInventory(theirInventory, classIDsToReceive, true);
|
||||
|
||||
if ((itemsToGive.Count != itemsToReceive.Count) || !Trading.IsFairExchange(itemsToGive, itemsToReceive)) {
|
||||
// Failsafe
|
||||
@@ -1504,10 +1515,14 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
// However, since this is only an assumption, we must mark newly acquired items as untradable so we're sure that they're not considered for trading, only for matching
|
||||
foreach (Asset itemToReceive in itemsToReceive) {
|
||||
if (ourInventory.TryGetValue(itemToReceive.AssetID, out Asset? item)) {
|
||||
item.Tradable = false;
|
||||
item.Description ??= new InventoryDescription(itemToReceive.AppID, itemToReceive.ClassID, itemToReceive.InstanceID, realAppID: itemToReceive.RealAppID, type: itemToReceive.Type, rarity: itemToReceive.Rarity);
|
||||
|
||||
item.Description.Body.tradable = false;
|
||||
item.Amount += itemToReceive.Amount;
|
||||
} else {
|
||||
itemToReceive.Tradable = false;
|
||||
itemToReceive.Description ??= new InventoryDescription(itemToReceive.AppID, itemToReceive.ClassID, itemToReceive.InstanceID, realAppID: itemToReceive.RealAppID, type: itemToReceive.Type, rarity: itemToReceive.Rarity);
|
||||
|
||||
itemToReceive.Description.Body.tradable = false;
|
||||
ourInventory[itemToReceive.AssetID] = itemToReceive;
|
||||
}
|
||||
|
||||
@@ -1528,7 +1543,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
|
||||
matchedSets += (uint) skippedSetsThisUser.Count;
|
||||
|
||||
if (Trading.IsEmptyForMatching(ourFullState, ourTradableState)) {
|
||||
if (MatchingUtilities.IsEmptyForMatching(ourFullState, ourTradableState)) {
|
||||
// User doesn't have any more dupes in the inventory
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -36,8 +38,6 @@ using SteamKit2.Internal;
|
||||
namespace ArchiSteamFarm.OfficialPlugins.MobileAuthenticator;
|
||||
|
||||
internal static class Commands {
|
||||
private const byte MaxFinalizationAttempts = 900 / Steam.Security.MobileAuthenticator.CodeInterval;
|
||||
|
||||
internal static async Task<string?> OnBotCommand(Bot bot, EAccess access, string message, string[] args, ulong steamID = 0) {
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
|
||||
@@ -144,43 +144,22 @@ internal static class Commands {
|
||||
|
||||
ulong steamTime = await mobileAuthenticator.GetSteamTime().ConfigureAwait(false);
|
||||
|
||||
bool successFinalizing = false;
|
||||
string? code = mobileAuthenticator.GenerateTokenForTime(steamTime);
|
||||
|
||||
for (byte i = 0; i < MaxFinalizationAttempts; i++) {
|
||||
if (i > 0) {
|
||||
steamTime += Steam.Security.MobileAuthenticator.CodeInterval;
|
||||
}
|
||||
|
||||
string? code = mobileAuthenticator.GenerateTokenForTime(steamTime);
|
||||
|
||||
if (string.IsNullOrEmpty(code)) {
|
||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(mobileAuthenticator.GenerateTokenForTime)));
|
||||
}
|
||||
|
||||
CTwoFactor_FinalizeAddAuthenticator_Response? response = await mobileAuthenticatorHandler.FinalizeAuthenticator(bot.SteamID, activationCode, code, steamTime).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(mobileAuthenticatorHandler.FinalizeAuthenticator)));
|
||||
}
|
||||
|
||||
if (response.want_more) {
|
||||
// OK, whatever
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!response.success) {
|
||||
EResult result = (EResult) response.status;
|
||||
|
||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, result));
|
||||
}
|
||||
|
||||
successFinalizing = true;
|
||||
|
||||
break;
|
||||
if (string.IsNullOrEmpty(code)) {
|
||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(mobileAuthenticator.GenerateTokenForTime)));
|
||||
}
|
||||
|
||||
if (!successFinalizing) {
|
||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, MaxFinalizationAttempts));
|
||||
CTwoFactor_FinalizeAddAuthenticator_Response? response = await mobileAuthenticatorHandler.FinalizeAuthenticator(bot.SteamID, activationCode, code, steamTime).ConfigureAwait(false);
|
||||
|
||||
if (response == null) {
|
||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(mobileAuthenticatorHandler.FinalizeAuthenticator)));
|
||||
}
|
||||
|
||||
if (!response.success) {
|
||||
EResult result = (EResult) response.status;
|
||||
|
||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, result));
|
||||
}
|
||||
|
||||
if (!bot.TryImportAuthenticator(mobileAuthenticator)) {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -61,7 +63,6 @@ internal sealed class MobileAuthenticatorHandler : ClientMsgHandler {
|
||||
authenticator_type = 1,
|
||||
authenticator_time = Utilities.GetUnixTime(),
|
||||
device_identifier = deviceID,
|
||||
sms_phone_id = "1",
|
||||
steamid = steamID
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -21,21 +23,17 @@
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Data;
|
||||
|
||||
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
internal sealed class SubmitResponse {
|
||||
internal sealed class SubmitResponse : BooleanResponse {
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("data")]
|
||||
internal SubmitResponseData? Data { get; private init; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("success")]
|
||||
[JsonRequired]
|
||||
internal bool Success { get; private init; }
|
||||
|
||||
[JsonConstructor]
|
||||
private SubmitResponse() { }
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -29,31 +31,31 @@ internal sealed class SubmitResponseData {
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("new_apps")]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<uint> NewApps { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
internal ImmutableHashSet<uint> NewApps { get; private init; } = [];
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("new_depots")]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<uint> NewDepots { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
internal ImmutableHashSet<uint> NewDepots { get; private init; } = [];
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("new_subs")]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<uint> NewPackages { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
internal ImmutableHashSet<uint> NewPackages { get; private init; } = [];
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("verified_apps")]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<uint> VerifiedApps { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
internal ImmutableHashSet<uint> VerifiedApps { get; private init; } = [];
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("verified_depots")]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<uint> VerifiedDepots { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
internal ImmutableHashSet<uint> VerifiedDepots { get; private init; } = [];
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("verified_subs")]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<uint> VerifiedPackages { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
internal ImmutableHashSet<uint> VerifiedPackages { get; private init; } = [];
|
||||
}
|
||||
#pragma warning restore CA1812 // False positive, the class is used during json deserialization
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>{0} de {1} chaves do depósito recuperadas com sucesso.</value>
|
||||
<value>{0} de {1} chaves de depots recuperadas com sucesso.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of depot keys that were successfully retrieved, {1} will be replaced by the number (count this batch) of depot keys that were supposed to be retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
|
||||
|
||||
@@ -97,24 +97,80 @@
|
||||
<value>Закінчено отримання всього {0} токенів доступу.</value>
|
||||
<comment>{0} will be replaced by the number (total count) of app access tokens retrieved</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="BotRetrievingTotalDepots" xml:space="preserve">
|
||||
<value>Отримання всіх депо для загалом {0} програм...</value>
|
||||
<comment>{0} will be replaced by the number (total count) of apps being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingAppInfos" xml:space="preserve">
|
||||
<value>Отримання {0} інформації про програму...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingAppInfos" xml:space="preserve">
|
||||
<value>Завершено отримання {0} інформації про програму.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>Успішно отримано {0} з {1} ключів депо.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of depot keys that were successfully retrieved, {1} will be replaced by the number (count this batch) of depot keys that were supposed to be retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
|
||||
<value>Завершено отримання всіх ключів депо для {0} програм.</value>
|
||||
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>
|
||||
</data>
|
||||
<data name="SubmissionNoNewData" xml:space="preserve">
|
||||
<value>Ніяких нових даних подавати не потрібно, все актуально.</value>
|
||||
</data>
|
||||
<data name="SubmissionNoContributorSet" xml:space="preserve">
|
||||
<value>Не вдалося надіслати дані, оскільки немає дійсного набору SteamID, який ми могли б класифікувати як дописувача. Подумайте про встановлення властивості {0}.</value>
|
||||
<comment>{0} will be replaced by the name of the config property (e.g. "SteamOwnerID") that the user is expected to set</comment>
|
||||
</data>
|
||||
<data name="SubmissionInProgress" xml:space="preserve">
|
||||
<value>Надіслати загальну кількість зареєстрованих програм/пакетів/депо: {0}/{1}/{2}...</value>
|
||||
<comment>{0} will be replaced by the number of app access tokens being submitted, {1} will be replaced by the number of package access tokens being submitted, {2} will be replaced by the number of depot keys being submitted</comment>
|
||||
</data>
|
||||
<data name="SubmissionFailedTooManyRequests" xml:space="preserve">
|
||||
<value>Відправлення не вдалося через занадто велику кількість надісланих запитів, ми спробуємо ще раз приблизно через {0} відтепер.</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
|
||||
</data>
|
||||
<data name="SubmissionSuccessful" xml:space="preserve">
|
||||
<value>Дані успішно надіслано. На сервері зареєстровано всього нових програм/пакунків/сховищ: {0} ({1} перевірено)/{2} ({3} перевірено)/{4} ({5} перевірено).</value>
|
||||
<comment>{0} will be replaced by the number of new app access tokens that the server has registered, {1} will be replaced by the number of verified app access tokens that the server has registered, {2} will be replaced by the number of new package access tokens that the server has registered, {3} will be replaced by the number of verified package access tokens that the server has registered, {4} will be replaced by the number of new depot keys that the server has registered, {5} will be replaced by the number of verified depot keys that the server has registered</comment>
|
||||
</data>
|
||||
<data name="SubmissionSuccessfulNewApps" xml:space="preserve">
|
||||
<value>Нові програми: {0}</value>
|
||||
<comment>{0} will be replaced by list of the apps (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="SubmissionSuccessfulVerifiedApps" xml:space="preserve">
|
||||
<value>Перевірених програм: {0}</value>
|
||||
<comment>{0} will be replaced by list of the apps (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="SubmissionSuccessfulNewPackages" xml:space="preserve">
|
||||
<value>Нові пакунки: {0}</value>
|
||||
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="SubmissionSuccessfulVerifiedPackages" xml:space="preserve">
|
||||
<value>Перевірені пакунки: {0}</value>
|
||||
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="SubmissionSuccessfulNewDepots" xml:space="preserve">
|
||||
<value>Нові склади: {0}</value>
|
||||
<comment>{0} will be replaced by list of the depots (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="SubmissionSuccessfulVerifiedDepots" xml:space="preserve">
|
||||
<value>Перевірені склади: {0}</value>
|
||||
<comment>{0} will be replaced by list of the depots (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="PluginSecretListInitialized" xml:space="preserve">
|
||||
<value>{0} ініціалізовано, плагін не буде вирішувати жодного з них: {1}.</value>
|
||||
<comment>{0} will be replaced by the name of the config property (e.g. "SecretPackageIDs"), {1} will be replaced by list of the objects (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="LoadingGlobalCache" xml:space="preserve">
|
||||
<value>Завантаження глобального кешу STD...</value>
|
||||
</data>
|
||||
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
|
||||
<value>Перевірка цілісності глобального кешу STD...</value>
|
||||
</data>
|
||||
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
|
||||
<value>Не вдалося перевірити цілісність глобального кешу STD. Це свідчить про можливе пошкодження файлу/пам'яті, замість нього буде ініціалізовано новий екземпляр.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -33,17 +35,17 @@ public sealed class SteamTokenDumperConfig {
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
[SwaggerItemsMinMax(MinimumUint = 1, MaximumUint = uint.MaxValue)]
|
||||
public ImmutableHashSet<uint> SecretAppIDs { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
public ImmutableHashSet<uint> SecretAppIDs { get; private init; } = [];
|
||||
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
[SwaggerItemsMinMax(MinimumUint = 1, MaximumUint = uint.MaxValue)]
|
||||
public ImmutableHashSet<uint> SecretDepotIDs { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
public ImmutableHashSet<uint> SecretDepotIDs { get; private init; } = [];
|
||||
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
[SwaggerItemsMinMax(MinimumUint = 1, MaximumUint = uint.MaxValue)]
|
||||
public ImmutableHashSet<uint> SecretPackageIDs { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
public ImmutableHashSet<uint> SecretPackageIDs { get; private init; } = [];
|
||||
|
||||
[JsonInclude]
|
||||
public bool SkipAutoGrantPackages { get; private init; } = true;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -43,7 +45,7 @@ public sealed class Bot {
|
||||
|
||||
foreach ((uint appID, byte cards) in itemsPerSet) {
|
||||
for (byte i = 1; i <= cards; i++) {
|
||||
items.Add(CreateCard(i, appID));
|
||||
items.Add(CreateCard(i, realAppID: appID));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,8 +61,8 @@ public sealed class Bot {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID),
|
||||
CreateCard(2, appID)
|
||||
CreateCard(1, realAppID: appID),
|
||||
CreateCard(2, realAppID: appID)
|
||||
];
|
||||
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => GetItemsForFullBadge(items, 2, appID, MinCardsPerBadge - 1));
|
||||
@@ -71,10 +73,10 @@ public sealed class Bot {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID),
|
||||
CreateCard(1, appID),
|
||||
CreateCard(2, appID),
|
||||
CreateCard(3, appID)
|
||||
CreateCard(1, realAppID: appID),
|
||||
CreateCard(1, realAppID: appID),
|
||||
CreateCard(2, realAppID: appID),
|
||||
CreateCard(3, realAppID: appID)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
||||
@@ -93,10 +95,10 @@ public sealed class Bot {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID),
|
||||
CreateCard(1, appID),
|
||||
CreateCard(2, appID),
|
||||
CreateCard(2, appID)
|
||||
CreateCard(1, realAppID: appID),
|
||||
CreateCard(1, realAppID: appID),
|
||||
CreateCard(2, realAppID: appID),
|
||||
CreateCard(2, realAppID: appID)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||
@@ -114,9 +116,9 @@ public sealed class Bot {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID, 2),
|
||||
CreateCard(2, appID),
|
||||
CreateCard(2, appID)
|
||||
CreateCard(1, amount: 2, realAppID: appID),
|
||||
CreateCard(2, realAppID: appID),
|
||||
CreateCard(2, realAppID: appID)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||
@@ -134,27 +136,27 @@ public sealed class Bot {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Common),
|
||||
CreateCard(2, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Common),
|
||||
CreateCard(1, realAppID: appID, type: EAssetType.TradingCard, rarity: EAssetRarity.Common),
|
||||
CreateCard(2, realAppID: appID, type: EAssetType.TradingCard, rarity: EAssetRarity.Common),
|
||||
|
||||
CreateCard(1, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Uncommon),
|
||||
CreateCard(2, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Uncommon),
|
||||
CreateCard(1, realAppID: appID, type: EAssetType.FoilTradingCard, rarity: EAssetRarity.Uncommon),
|
||||
CreateCard(2, realAppID: appID, type: EAssetType.FoilTradingCard, rarity: EAssetRarity.Uncommon),
|
||||
|
||||
CreateCard(1, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Rare),
|
||||
CreateCard(2, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Rare),
|
||||
CreateCard(1, realAppID: appID, type: EAssetType.FoilTradingCard, rarity: EAssetRarity.Rare),
|
||||
CreateCard(2, realAppID: appID, type: EAssetType.FoilTradingCard, rarity: EAssetRarity.Rare),
|
||||
|
||||
// for better readability and easier verification when thinking about this test the items that shall be selected for sending are the ones below this comment
|
||||
CreateCard(1, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Uncommon),
|
||||
CreateCard(2, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Uncommon),
|
||||
CreateCard(3, appID, type: Asset.EType.TradingCard, rarity: Asset.ERarity.Uncommon),
|
||||
CreateCard(1, realAppID: appID, type: EAssetType.TradingCard, rarity: EAssetRarity.Uncommon),
|
||||
CreateCard(2, realAppID: appID, type: EAssetType.TradingCard, rarity: EAssetRarity.Uncommon),
|
||||
CreateCard(3, realAppID: appID, type: EAssetType.TradingCard, rarity: EAssetRarity.Uncommon),
|
||||
|
||||
CreateCard(1, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Common),
|
||||
CreateCard(3, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Common),
|
||||
CreateCard(7, appID, type: Asset.EType.FoilTradingCard, rarity: Asset.ERarity.Common),
|
||||
CreateCard(1, realAppID: appID, type: EAssetType.FoilTradingCard, rarity: EAssetRarity.Common),
|
||||
CreateCard(3, realAppID: appID, type: EAssetType.FoilTradingCard, rarity: EAssetRarity.Common),
|
||||
CreateCard(7, realAppID: appID, type: EAssetType.FoilTradingCard, rarity: EAssetRarity.Common),
|
||||
|
||||
CreateCard(2, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare),
|
||||
CreateCard(3, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare),
|
||||
CreateCard(4, appID, type: Asset.EType.Unknown, rarity: Asset.ERarity.Rare)
|
||||
CreateCard(2, realAppID: appID, type: EAssetType.Unknown, rarity: EAssetRarity.Rare),
|
||||
CreateCard(3, realAppID: appID, type: EAssetType.Unknown, rarity: EAssetRarity.Rare),
|
||||
CreateCard(4, realAppID: appID, type: EAssetType.Unknown, rarity: EAssetRarity.Rare)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
||||
@@ -175,8 +177,8 @@ public sealed class Bot {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID),
|
||||
CreateCard(2, appID)
|
||||
CreateCard(1, realAppID: appID),
|
||||
CreateCard(2, realAppID: appID)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
||||
@@ -190,8 +192,8 @@ public sealed class Bot {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID),
|
||||
CreateCard(2, appID)
|
||||
CreateCard(1, realAppID: appID),
|
||||
CreateCard(2, realAppID: appID)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||
@@ -210,8 +212,8 @@ public sealed class Bot {
|
||||
const uint appID1 = 43;
|
||||
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID0),
|
||||
CreateCard(1, appID1)
|
||||
CreateCard(1, realAppID: appID0),
|
||||
CreateCard(1, realAppID: appID1)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(
|
||||
@@ -235,8 +237,8 @@ public sealed class Bot {
|
||||
const uint appID1 = 43;
|
||||
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID0),
|
||||
CreateCard(1, appID1)
|
||||
CreateCard(1, realAppID: appID0),
|
||||
CreateCard(1, realAppID: appID1)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(
|
||||
@@ -258,12 +260,12 @@ public sealed class Bot {
|
||||
const uint appID2 = 44;
|
||||
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID0),
|
||||
CreateCard(2, appID0),
|
||||
CreateCard(1, realAppID: appID0),
|
||||
CreateCard(2, realAppID: appID0),
|
||||
|
||||
CreateCard(1, appID1),
|
||||
CreateCard(2, appID1),
|
||||
CreateCard(3, appID1)
|
||||
CreateCard(1, realAppID: appID1),
|
||||
CreateCard(2, realAppID: appID1),
|
||||
CreateCard(3, realAppID: appID1)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(
|
||||
@@ -288,8 +290,8 @@ public sealed class Bot {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID, rarity: Asset.ERarity.Common),
|
||||
CreateCard(1, appID, rarity: Asset.ERarity.Rare)
|
||||
CreateCard(1, realAppID: appID, rarity: EAssetRarity.Common),
|
||||
CreateCard(1, realAppID: appID, rarity: EAssetRarity.Rare)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 1, appID);
|
||||
@@ -306,8 +308,8 @@ public sealed class Bot {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID, rarity: Asset.ERarity.Common),
|
||||
CreateCard(1, appID, rarity: Asset.ERarity.Rare)
|
||||
CreateCard(1, realAppID: appID, rarity: EAssetRarity.Common),
|
||||
CreateCard(1, realAppID: appID, rarity: EAssetRarity.Rare)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||
@@ -322,11 +324,11 @@ public sealed class Bot {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID, rarity: Asset.ERarity.Common),
|
||||
CreateCard(2, appID, rarity: Asset.ERarity.Common),
|
||||
CreateCard(1, appID, rarity: Asset.ERarity.Uncommon),
|
||||
CreateCard(2, appID, rarity: Asset.ERarity.Uncommon),
|
||||
CreateCard(3, appID, rarity: Asset.ERarity.Uncommon)
|
||||
CreateCard(1, realAppID: appID, rarity: EAssetRarity.Common),
|
||||
CreateCard(2, realAppID: appID, rarity: EAssetRarity.Common),
|
||||
CreateCard(1, realAppID: appID, rarity: EAssetRarity.Uncommon),
|
||||
CreateCard(2, realAppID: appID, rarity: EAssetRarity.Uncommon),
|
||||
CreateCard(3, realAppID: appID, rarity: EAssetRarity.Uncommon)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
||||
@@ -345,8 +347,8 @@ public sealed class Bot {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID, type: Asset.EType.TradingCard),
|
||||
CreateCard(1, appID, type: Asset.EType.FoilTradingCard)
|
||||
CreateCard(1, realAppID: appID, type: EAssetType.TradingCard),
|
||||
CreateCard(1, realAppID: appID, type: EAssetType.FoilTradingCard)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 1, appID);
|
||||
@@ -363,8 +365,8 @@ public sealed class Bot {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID, type: Asset.EType.TradingCard),
|
||||
CreateCard(1, appID, type: Asset.EType.FoilTradingCard)
|
||||
CreateCard(1, realAppID: appID, type: EAssetType.TradingCard),
|
||||
CreateCard(1, realAppID: appID, type: EAssetType.FoilTradingCard)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||
@@ -379,11 +381,11 @@ public sealed class Bot {
|
||||
const uint appID = 42;
|
||||
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID, type: Asset.EType.TradingCard),
|
||||
CreateCard(2, appID, type: Asset.EType.TradingCard),
|
||||
CreateCard(1, appID, type: Asset.EType.FoilTradingCard),
|
||||
CreateCard(2, appID, type: Asset.EType.FoilTradingCard),
|
||||
CreateCard(3, appID, type: Asset.EType.FoilTradingCard)
|
||||
CreateCard(1, realAppID: appID, type: EAssetType.TradingCard),
|
||||
CreateCard(2, realAppID: appID, type: EAssetType.TradingCard),
|
||||
CreateCard(1, realAppID: appID, type: EAssetType.FoilTradingCard),
|
||||
CreateCard(2, realAppID: appID, type: EAssetType.FoilTradingCard),
|
||||
CreateCard(3, realAppID: appID, type: EAssetType.FoilTradingCard)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 3, appID);
|
||||
@@ -402,8 +404,8 @@ public sealed class Bot {
|
||||
const uint appID0 = 42;
|
||||
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID0, 2),
|
||||
CreateCard(2, appID0)
|
||||
CreateCard(1, amount: 2, realAppID: appID0),
|
||||
CreateCard(2, realAppID: appID0)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID0);
|
||||
@@ -423,8 +425,8 @@ public sealed class Bot {
|
||||
HashSet<Asset> items = [];
|
||||
|
||||
for (byte i = 0; i < Steam.Exchange.Trading.MaxItemsPerTrade; i++) {
|
||||
items.Add(CreateCard(1, appID));
|
||||
items.Add(CreateCard(2, appID));
|
||||
items.Add(CreateCard(1, realAppID: appID));
|
||||
items.Add(CreateCard(2, realAppID: appID));
|
||||
}
|
||||
|
||||
HashSet<Asset> itemsToSend = GetItemsForFullBadge(items, 2, appID);
|
||||
@@ -440,10 +442,10 @@ public sealed class Bot {
|
||||
HashSet<Asset> items = [];
|
||||
|
||||
for (byte i = 0; i < 100; i++) {
|
||||
items.Add(CreateCard(1, appID0));
|
||||
items.Add(CreateCard(2, appID0));
|
||||
items.Add(CreateCard(1, appID1));
|
||||
items.Add(CreateCard(2, appID1));
|
||||
items.Add(CreateCard(1, realAppID: appID0));
|
||||
items.Add(CreateCard(2, realAppID: appID0));
|
||||
items.Add(CreateCard(1, realAppID: appID1));
|
||||
items.Add(CreateCard(2, realAppID: appID1));
|
||||
}
|
||||
|
||||
Dictionary<uint, byte> itemsPerSet = new() {
|
||||
@@ -463,10 +465,10 @@ public sealed class Bot {
|
||||
const uint appID2 = 44;
|
||||
|
||||
HashSet<Asset> items = [
|
||||
CreateCard(1, appID0),
|
||||
CreateCard(2, appID0),
|
||||
CreateCard(3, appID0),
|
||||
CreateCard(4, appID0)
|
||||
CreateCard(1, realAppID: appID0),
|
||||
CreateCard(2, realAppID: appID0),
|
||||
CreateCard(3, realAppID: appID0),
|
||||
CreateCard(4, realAppID: appID0)
|
||||
];
|
||||
|
||||
Assert.ThrowsException<InvalidOperationException>(
|
||||
@@ -489,12 +491,12 @@ public sealed class Bot {
|
||||
Assert.IsTrue(expectedResult.All(expectation => realResult.TryGetValue(expectation.Key, out long reality) && (expectation.Value == reality)));
|
||||
}
|
||||
|
||||
private static Asset CreateCard(ulong classID, uint realAppID, uint amount = 1, Asset.EType type = Asset.EType.TradingCard, Asset.ERarity rarity = Asset.ERarity.Common) => new(Asset.SteamAppID, Asset.SteamCommunityContextID, classID, amount, realAppID: realAppID, type: type, rarity: rarity);
|
||||
private static Asset CreateCard(ulong classID, ulong instanceID = 0, uint amount = 1, bool marketable = false, bool tradable = false, uint realAppID = Asset.SteamAppID, EAssetType type = EAssetType.TradingCard, EAssetRarity rarity = EAssetRarity.Common) => new(Asset.SteamAppID, Asset.SteamCommunityContextID, classID, amount, new InventoryDescription(Asset.SteamAppID, classID, instanceID, marketable, tradable, realAppID, type, rarity));
|
||||
|
||||
private static HashSet<Asset> GetItemsForFullBadge(IReadOnlyCollection<Asset> inventory, byte cardsPerSet, uint appID, ushort maxItems = Steam.Exchange.Trading.MaxItemsPerTrade) => GetItemsForFullBadge(inventory, new Dictionary<uint, byte> { { appID, cardsPerSet } }, maxItems);
|
||||
|
||||
private static HashSet<Asset> GetItemsForFullBadge(IReadOnlyCollection<Asset> inventory, IDictionary<uint, byte> cardsPerSet, ushort maxItems = Steam.Exchange.Trading.MaxItemsPerTrade) {
|
||||
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), List<uint>> inventorySets = Steam.Exchange.Trading.GetInventorySets(inventory);
|
||||
Dictionary<(uint RealAppID, EAssetType Type, EAssetRarity Rarity), List<uint>> inventorySets = Steam.Exchange.Trading.GetInventorySets(inventory);
|
||||
|
||||
return GetItemsForFullSets(inventory, inventorySets.ToDictionary(static kv => kv.Key, kv => (SetsToExtract: inventorySets[kv.Key][0], cardsPerSet[kv.Key.RealAppID])), maxItems).ToHashSet();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -31,18 +33,18 @@ public sealed class Trading {
|
||||
[TestMethod]
|
||||
public void ExploitingNewSetsIsFairButNotNeutral() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, 40),
|
||||
CreateItem(2, 10),
|
||||
CreateItem(3, 10)
|
||||
CreateItem(1, amount: 40),
|
||||
CreateItem(2, amount: 10),
|
||||
CreateItem(3, amount: 10)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(2, 5),
|
||||
CreateItem(3, 5)
|
||||
CreateItem(2, amount: 5),
|
||||
CreateItem(3, amount: 5)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(1, 9),
|
||||
CreateItem(1, amount: 9),
|
||||
CreateItem(4)
|
||||
];
|
||||
|
||||
@@ -52,24 +54,39 @@ public sealed class Trading {
|
||||
|
||||
[TestMethod]
|
||||
public void MismatchRarityIsNotFair() {
|
||||
HashSet<Asset> itemsToGive = [CreateItem(1, rarity: Asset.ERarity.Rare)];
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1, rarity: EAssetRarity.Rare)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(2)
|
||||
];
|
||||
|
||||
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MismatchRealAppIDsIsNotFair() {
|
||||
HashSet<Asset> itemsToGive = [CreateItem(1, realAppID: 570)];
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1, realAppID: 570)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(2)
|
||||
];
|
||||
|
||||
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void MismatchTypesIsNotFair() {
|
||||
HashSet<Asset> itemsToGive = [CreateItem(1, type: Asset.EType.Emoticon)];
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1, type: EAssetType.Emoticon)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(2)
|
||||
];
|
||||
|
||||
Assert.IsFalse(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
}
|
||||
@@ -77,19 +94,19 @@ public sealed class Trading {
|
||||
[TestMethod]
|
||||
public void MultiGameMultiTypeBadReject() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, 9),
|
||||
CreateItem(3, 9, 730, Asset.EType.Emoticon),
|
||||
CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon)
|
||||
CreateItem(1, amount: 9),
|
||||
CreateItem(3, amount: 9, realAppID: 730, type: EAssetType.Emoticon),
|
||||
CreateItem(4, realAppID: 730, type: EAssetType.Emoticon)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1),
|
||||
CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon)
|
||||
CreateItem(4, realAppID: 730, type: EAssetType.Emoticon)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(2),
|
||||
CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon)
|
||||
CreateItem(3, realAppID: 730, type: EAssetType.Emoticon)
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
@@ -99,18 +116,18 @@ public sealed class Trading {
|
||||
[TestMethod]
|
||||
public void MultiGameMultiTypeNeutralAccept() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, 9),
|
||||
CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon)
|
||||
CreateItem(1, amount: 9),
|
||||
CreateItem(3, realAppID: 730, type: EAssetType.Emoticon)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1),
|
||||
CreateItem(3, realAppID: 730, type: Asset.EType.Emoticon)
|
||||
CreateItem(3, realAppID: 730, type: EAssetType.Emoticon)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(2),
|
||||
CreateItem(4, realAppID: 730, type: Asset.EType.Emoticon)
|
||||
CreateItem(4, realAppID: 730, type: EAssetType.Emoticon)
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
@@ -120,7 +137,7 @@ public sealed class Trading {
|
||||
[TestMethod]
|
||||
public void MultiGameSingleTypeBadReject() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, 9),
|
||||
CreateItem(1, amount: 9),
|
||||
CreateItem(3, realAppID: 730),
|
||||
CreateItem(4, realAppID: 730)
|
||||
];
|
||||
@@ -142,7 +159,7 @@ public sealed class Trading {
|
||||
[TestMethod]
|
||||
public void MultiGameSingleTypeNeutralAccept() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, 2),
|
||||
CreateItem(1, amount: 2),
|
||||
CreateItem(3, realAppID: 730)
|
||||
];
|
||||
|
||||
@@ -164,15 +181,19 @@ public sealed class Trading {
|
||||
public void SingleGameAbrynosWasWrongNeutralAccept() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1),
|
||||
CreateItem(2, 2),
|
||||
CreateItem(2, amount: 2),
|
||||
CreateItem(3),
|
||||
CreateItem(4),
|
||||
CreateItem(5)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [CreateItem(2)];
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(2)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(3)];
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(3)
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -180,13 +201,17 @@ public sealed class Trading {
|
||||
|
||||
[TestMethod]
|
||||
public void SingleGameDonationAccept() {
|
||||
HashSet<Asset> inventory = [CreateItem(1)];
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [CreateItem(1)];
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(2),
|
||||
CreateItem(3, type: Asset.EType.SteamGems)
|
||||
CreateItem(3, type: EAssetType.SteamGems)
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
@@ -196,19 +221,19 @@ public sealed class Trading {
|
||||
[TestMethod]
|
||||
public void SingleGameMultiTypeBadReject() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, 9),
|
||||
CreateItem(3, 9, type: Asset.EType.Emoticon),
|
||||
CreateItem(4, type: Asset.EType.Emoticon)
|
||||
CreateItem(1, amount: 9),
|
||||
CreateItem(3, amount: 9, type: EAssetType.Emoticon),
|
||||
CreateItem(4, type: EAssetType.Emoticon)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1),
|
||||
CreateItem(4, type: Asset.EType.Emoticon)
|
||||
CreateItem(4, type: EAssetType.Emoticon)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(2),
|
||||
CreateItem(3, type: Asset.EType.Emoticon)
|
||||
CreateItem(3, type: EAssetType.Emoticon)
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
@@ -218,18 +243,18 @@ public sealed class Trading {
|
||||
[TestMethod]
|
||||
public void SingleGameMultiTypeNeutralAccept() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, 9),
|
||||
CreateItem(3, type: Asset.EType.Emoticon)
|
||||
CreateItem(1, amount: 9),
|
||||
CreateItem(3, type: EAssetType.Emoticon)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1),
|
||||
CreateItem(3, type: Asset.EType.Emoticon)
|
||||
CreateItem(3, type: EAssetType.Emoticon)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(2),
|
||||
CreateItem(4, type: Asset.EType.Emoticon)
|
||||
CreateItem(4, type: EAssetType.Emoticon)
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
@@ -250,7 +275,9 @@ public sealed class Trading {
|
||||
CreateItem(3)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(4, 3)];
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(4, amount: 3)
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -260,15 +287,17 @@ public sealed class Trading {
|
||||
public void SingleGameQuantityBadReject2() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1),
|
||||
CreateItem(2, 2)
|
||||
CreateItem(2, amount: 2)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1),
|
||||
CreateItem(2, 2)
|
||||
CreateItem(2, amount: 2)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(3, 3)];
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(3, amount: 3)
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -277,7 +306,7 @@ public sealed class Trading {
|
||||
[TestMethod]
|
||||
public void SingleGameQuantityNeutralAccept() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, 2),
|
||||
CreateItem(1, amount: 2),
|
||||
CreateItem(2)
|
||||
];
|
||||
|
||||
@@ -286,7 +315,9 @@ public sealed class Trading {
|
||||
CreateItem(2)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(3, 2)];
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(3, amount: 2)
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -299,8 +330,13 @@ public sealed class Trading {
|
||||
CreateItem(2)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [CreateItem(1)];
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(2)
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsFalse(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -309,12 +345,14 @@ public sealed class Trading {
|
||||
[TestMethod]
|
||||
public void SingleGameSingleTypeBadWithOverpayingReject() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, 2),
|
||||
CreateItem(2, 2),
|
||||
CreateItem(3, 2)
|
||||
CreateItem(1, amount: 2),
|
||||
CreateItem(2, amount: 2),
|
||||
CreateItem(3, amount: 2)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [CreateItem(2)];
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(2)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(1),
|
||||
@@ -329,12 +367,17 @@ public sealed class Trading {
|
||||
public void SingleGameSingleTypeBigDifferenceAccept() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1),
|
||||
CreateItem(2, 5),
|
||||
CreateItem(2, amount: 5),
|
||||
CreateItem(3)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [CreateItem(2)];
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(3)];
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(2)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(3)
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -344,10 +387,10 @@ public sealed class Trading {
|
||||
public void SingleGameSingleTypeBigDifferenceReject() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1),
|
||||
CreateItem(2, 2),
|
||||
CreateItem(3, 2),
|
||||
CreateItem(4, 3),
|
||||
CreateItem(5, 10)
|
||||
CreateItem(2, amount: 2),
|
||||
CreateItem(3, amount: 2),
|
||||
CreateItem(4, amount: 3),
|
||||
CreateItem(5, amount: 10)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [
|
||||
@@ -366,9 +409,17 @@ public sealed class Trading {
|
||||
|
||||
[TestMethod]
|
||||
public void SingleGameSingleTypeGoodAccept() {
|
||||
HashSet<Asset> inventory = [CreateItem(1, 2)];
|
||||
HashSet<Asset> itemsToGive = [CreateItem(1)];
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, amount: 2)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(2)
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -376,9 +427,17 @@ public sealed class Trading {
|
||||
|
||||
[TestMethod]
|
||||
public void SingleGameSingleTypeNeutralAccept() {
|
||||
HashSet<Asset> inventory = [CreateItem(1)];
|
||||
HashSet<Asset> itemsToGive = [CreateItem(1)];
|
||||
HashSet<Asset> itemsToReceive = [CreateItem(2)];
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(1)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(2)
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
@@ -387,11 +446,13 @@ public sealed class Trading {
|
||||
[TestMethod]
|
||||
public void SingleGameSingleTypeNeutralWithOverpayingAccept() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, 2),
|
||||
CreateItem(2, 2)
|
||||
CreateItem(1, amount: 2),
|
||||
CreateItem(2, amount: 2)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [CreateItem(2)];
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(2)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(1),
|
||||
@@ -405,26 +466,28 @@ public sealed class Trading {
|
||||
[TestMethod]
|
||||
public void TakingExcessiveAmountOfSingleCardCanStillBeFairAndNeutral() {
|
||||
HashSet<Asset> inventory = [
|
||||
CreateItem(1, 52),
|
||||
CreateItem(2, 73),
|
||||
CreateItem(3, 52),
|
||||
CreateItem(4, 47),
|
||||
CreateItem(1, amount: 52),
|
||||
CreateItem(2, amount: 73),
|
||||
CreateItem(3, amount: 52),
|
||||
CreateItem(4, amount: 47),
|
||||
CreateItem(5)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToGive = [CreateItem(2, 73)];
|
||||
HashSet<Asset> itemsToGive = [
|
||||
CreateItem(2, amount: 73)
|
||||
];
|
||||
|
||||
HashSet<Asset> itemsToReceive = [
|
||||
CreateItem(1, 9),
|
||||
CreateItem(3, 9),
|
||||
CreateItem(4, 8),
|
||||
CreateItem(5, 24),
|
||||
CreateItem(6, 23)
|
||||
CreateItem(1, amount: 9),
|
||||
CreateItem(3, amount: 9),
|
||||
CreateItem(4, amount: 8),
|
||||
CreateItem(5, amount: 24),
|
||||
CreateItem(6, amount: 23)
|
||||
];
|
||||
|
||||
Assert.IsTrue(IsFairExchange(itemsToGive, itemsToReceive));
|
||||
Assert.IsTrue(IsTradeNeutralOrBetter(inventory, itemsToGive, itemsToReceive));
|
||||
}
|
||||
|
||||
private static Asset CreateItem(ulong classID, uint amount = 1, uint realAppID = Asset.SteamAppID, Asset.EType type = Asset.EType.TradingCard, Asset.ERarity rarity = Asset.ERarity.Common) => new(Asset.SteamAppID, Asset.SteamCommunityContextID, classID, amount, realAppID: realAppID, type: type, rarity: rarity);
|
||||
private static Asset CreateItem(ulong classID, ulong instanceID = 0, uint amount = 1, bool marketable = false, bool tradable = false, uint realAppID = Asset.SteamAppID, EAssetType type = EAssetType.TradingCard, EAssetRarity rarity = EAssetRarity.Common) => new(Asset.SteamAppID, Asset.SteamCommunityContextID, classID, amount, new InventoryDescription(Asset.SteamAppID, classID, instanceID, marketable, tradable, realAppID, type, rarity));
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -712,11 +712,13 @@
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/EditorConfig/EnableClangFormatSupport/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/EditorConfig/EnableEditorConfigSupport/@EntryValue">False</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue"> _ _ _ ____ _ _____
|
||||
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">----------------------------------------------------------------------------------------------
|
||||
_ _ _ ____ _ _____
|
||||
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
----------------------------------------------------------------------------------------------
|
||||
|
|
||||
Copyright 2015-${CurrentDate.Year} Łukasz "JustArchi" Domeradzki
|
||||
Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -98,7 +100,9 @@ public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<T
|
||||
public void Add(TKey key, TValue value) {
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
TryAdd(key, value);
|
||||
if (!TryAdd(key, value)) {
|
||||
throw new ArgumentException($"An item with the same key has already been added. Key: {key}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -41,6 +43,8 @@ using ArchiSteamFarm.Steam;
|
||||
using ArchiSteamFarm.Steam.Integration;
|
||||
using ArchiSteamFarm.Storage;
|
||||
using ArchiSteamFarm.Web;
|
||||
using ArchiSteamFarm.Web.GitHub;
|
||||
using ArchiSteamFarm.Web.GitHub.Data;
|
||||
using ArchiSteamFarm.Web.Responses;
|
||||
using JetBrains.Annotations;
|
||||
using SteamKit2;
|
||||
@@ -113,6 +117,10 @@ public static class ASF {
|
||||
|
||||
WebBrowser = new WebBrowser(ArchiLogger, GlobalConfig.WebProxy, true);
|
||||
|
||||
if (!await PluginsCore.InitPlugins().ConfigureAwait(false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await UpdateAndRestart().ConfigureAwait(false);
|
||||
|
||||
if (!Program.IgnoreUnsupportedEnvironment && !await ProtectAgainstCrashes().ConfigureAwait(false)) {
|
||||
@@ -123,10 +131,6 @@ public static class ASF {
|
||||
|
||||
Program.AllowCrashFileRemoval = true;
|
||||
|
||||
if (!await PluginsCore.InitPlugins().ConfigureAwait(false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await PluginsCore.OnASFInitModules(GlobalConfig.AdditionalProperties).ConfigureAwait(false);
|
||||
await InitRateLimiters().ConfigureAwait(false);
|
||||
|
||||
@@ -185,212 +189,23 @@ public static class ASF {
|
||||
}
|
||||
}
|
||||
|
||||
internal static async Task<Version?> Update(GlobalConfig.EUpdateChannel? channel = null, bool updateOverride = false) {
|
||||
if (channel.HasValue && !Enum.IsDefined(channel.Value)) {
|
||||
throw new InvalidEnumArgumentException(nameof(channel), (int) channel, typeof(GlobalConfig.EUpdateChannel));
|
||||
internal static async Task<(bool Updated, Version? NewVersion)> Update(GlobalConfig.EUpdateChannel? updateChannel = null, bool updateOverride = false, bool forced = false) {
|
||||
if (updateChannel.HasValue && !Enum.IsDefined(updateChannel.Value)) {
|
||||
throw new InvalidEnumArgumentException(nameof(updateChannel), (int) updateChannel, typeof(GlobalConfig.EUpdateChannel));
|
||||
}
|
||||
|
||||
if (GlobalConfig == null) {
|
||||
throw new InvalidOperationException(nameof(GlobalConfig));
|
||||
}
|
||||
|
||||
if (WebBrowser == null) {
|
||||
throw new InvalidOperationException(nameof(WebBrowser));
|
||||
(bool updated, Version? newVersion) = await UpdateASF(updateChannel, updateOverride, forced).ConfigureAwait(false);
|
||||
|
||||
if (!updated) {
|
||||
// ASF wasn't updated as part of the process, update the plugins alone
|
||||
updated = await PluginsCore.UpdatePlugins(SharedInfo.Version, false, updateChannel, updateOverride, forced).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
channel ??= GlobalConfig.UpdateChannel;
|
||||
|
||||
if (!SharedInfo.BuildInfo.CanUpdate || (channel == GlobalConfig.EUpdateChannel.None)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
await UpdateSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
// If backup directory from previous update exists, it's a good idea to purge it now
|
||||
string backupDirectory = Path.Combine(SharedInfo.HomeDirectory, SharedInfo.UpdateDirectory);
|
||||
|
||||
if (Directory.Exists(backupDirectory)) {
|
||||
ArchiLogger.LogGenericInfo(Strings.UpdateCleanup);
|
||||
|
||||
for (byte i = 0; (i < WebBrowser.MaxTries) && Directory.Exists(backupDirectory); i++) {
|
||||
if (i > 0) {
|
||||
// It's entirely possible that old process is still running, wait a short moment for eventual cleanup
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
try {
|
||||
Directory.Delete(backupDirectory, true);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericDebuggingException(e);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (Directory.Exists(backupDirectory)) {
|
||||
ArchiLogger.LogGenericError(Strings.WarningFailed);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericInfo(Strings.Done);
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericInfo(Strings.UpdateCheckingNewVersion);
|
||||
|
||||
GitHub.ReleaseResponse? releaseResponse = await GitHub.GetLatestRelease(channel == GlobalConfig.EUpdateChannel.Stable).ConfigureAwait(false);
|
||||
|
||||
if (releaseResponse == null) {
|
||||
ArchiLogger.LogGenericWarning(Strings.ErrorUpdateCheckFailed);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(releaseResponse.Tag)) {
|
||||
ArchiLogger.LogGenericWarning(Strings.ErrorUpdateCheckFailed);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Version newVersion = new(releaseResponse.Tag);
|
||||
|
||||
ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.UpdateVersionInfo, SharedInfo.Version, newVersion));
|
||||
|
||||
if (SharedInfo.Version >= newVersion) {
|
||||
return newVersion;
|
||||
}
|
||||
|
||||
if (!updateOverride && (GlobalConfig.UpdatePeriod == 0)) {
|
||||
ArchiLogger.LogGenericInfo(Strings.UpdateNewVersionAvailable);
|
||||
await Task.Delay(SharedInfo.ShortInformationDelay).ConfigureAwait(false);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Auto update logic starts here
|
||||
if (releaseResponse.Assets.IsEmpty) {
|
||||
ArchiLogger.LogGenericWarning(Strings.ErrorUpdateNoAssets);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
string targetFile = $"{SharedInfo.ASF}-{SharedInfo.BuildInfo.Variant}.zip";
|
||||
GitHub.ReleaseResponse.Asset? binaryAsset = releaseResponse.Assets.FirstOrDefault(asset => !string.IsNullOrEmpty(asset.Name) && asset.Name.Equals(targetFile, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (binaryAsset == null) {
|
||||
ArchiLogger.LogGenericWarning(Strings.ErrorUpdateNoAssetForThisVersion);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (binaryAsset.DownloadURL == null) {
|
||||
ArchiLogger.LogNullError(binaryAsset.DownloadURL);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericInfo(Strings.FetchingChecksumFromRemoteServer);
|
||||
|
||||
string? remoteChecksum = await ArchiNet.FetchBuildChecksum(newVersion, SharedInfo.BuildInfo.Variant).ConfigureAwait(false);
|
||||
|
||||
switch (remoteChecksum) {
|
||||
case null:
|
||||
// Timeout or error, refuse to update as a security measure
|
||||
return null;
|
||||
case "":
|
||||
// Unknown checksum, release too new or actual malicious build published, no need to scare the user as it's 99.99% the first
|
||||
ArchiLogger.LogGenericWarning(Strings.ChecksumMissing);
|
||||
|
||||
return SharedInfo.Version;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(releaseResponse.ChangelogPlainText)) {
|
||||
ArchiLogger.LogGenericInfo(releaseResponse.ChangelogPlainText);
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.UpdateDownloadingNewVersion, newVersion, binaryAsset.Size / 1024 / 1024));
|
||||
|
||||
Progress<byte> progressReporter = new();
|
||||
|
||||
progressReporter.ProgressChanged += OnProgressChanged;
|
||||
|
||||
BinaryResponse? response;
|
||||
|
||||
try {
|
||||
response = await WebBrowser.UrlGetToBinary(binaryAsset.DownloadURL, progressReporter: progressReporter).ConfigureAwait(false);
|
||||
} finally {
|
||||
progressReporter.ProgressChanged -= OnProgressChanged;
|
||||
}
|
||||
|
||||
if (response?.Content == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericInfo(Strings.VerifyingChecksumWithRemoteServer);
|
||||
|
||||
byte[] responseBytes = response.Content as byte[] ?? response.Content.ToArray();
|
||||
|
||||
string checksum = Utilities.GenerateChecksumFor(responseBytes);
|
||||
|
||||
if (!checksum.Equals(remoteChecksum, StringComparison.OrdinalIgnoreCase)) {
|
||||
ArchiLogger.LogGenericError(Strings.ChecksumWrong);
|
||||
|
||||
return SharedInfo.Version;
|
||||
}
|
||||
|
||||
await PluginsCore.OnUpdateProceeding(newVersion).ConfigureAwait(false);
|
||||
|
||||
bool kestrelWasRunning = ArchiKestrel.IsRunning;
|
||||
|
||||
if (kestrelWasRunning) {
|
||||
// We disable ArchiKestrel here as the update process moves the core files and might result in IPC crash
|
||||
// TODO: It might fail if the update was triggered from the API, this should be something to improve in the future, by changing the structure into request -> return response -> finish update
|
||||
try {
|
||||
await ArchiKestrel.Stop().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
}
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericInfo(Strings.PatchingFiles);
|
||||
|
||||
MemoryStream ms = new(responseBytes);
|
||||
|
||||
try {
|
||||
await using (ms.ConfigureAwait(false)) {
|
||||
using ZipArchive zipArchive = new(ms);
|
||||
|
||||
if (!UpdateFromArchive(zipArchive, SharedInfo.HomeDirectory)) {
|
||||
ArchiLogger.LogGenericError(Strings.WarningFailed);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericException(e);
|
||||
|
||||
if (kestrelWasRunning) {
|
||||
// We've temporarily disabled ArchiKestrel but the update has failed, let's bring it back up
|
||||
// We can't even be sure if it's possible to bring it back up in this state, but it's worth trying anyway
|
||||
try {
|
||||
await ArchiKestrel.Start().ConfigureAwait(false);
|
||||
} catch (Exception ex) {
|
||||
ArchiLogger.LogGenericWarningException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericInfo(Strings.UpdateFinished);
|
||||
|
||||
await PluginsCore.OnUpdateFinished(newVersion).ConfigureAwait(false);
|
||||
|
||||
return newVersion;
|
||||
} finally {
|
||||
UpdateSemaphore.Release();
|
||||
}
|
||||
return (updated, newVersion);
|
||||
}
|
||||
|
||||
private static async Task<bool> CanHandleWriteEvent(string filePath) {
|
||||
@@ -800,16 +615,6 @@ public static class ASF {
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnProgressChanged(object? sender, byte progressPercentage) {
|
||||
const byte printEveryPercentage = 10;
|
||||
|
||||
if (progressPercentage % printEveryPercentage != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericDebug($"{progressPercentage}%...");
|
||||
}
|
||||
|
||||
private static async void OnRenamed(object sender, RenamedEventArgs e) {
|
||||
// This function can be called with a possibility of OldName or (new) Name being null, we have to take it into account
|
||||
ArgumentNullException.ThrowIfNull(sender);
|
||||
@@ -915,7 +720,7 @@ public static class ASF {
|
||||
throw new InvalidOperationException(nameof(GlobalConfig));
|
||||
}
|
||||
|
||||
if (!SharedInfo.BuildInfo.CanUpdate || (GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.None)) {
|
||||
if (GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.None) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -932,14 +737,11 @@ public static class ASF {
|
||||
ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.AutoUpdateCheckInfo, autoUpdatePeriod.ToHumanReadable()));
|
||||
}
|
||||
|
||||
Version? newVersion = await Update().ConfigureAwait(false);
|
||||
(bool updated, Version? newVersion) = await Update().ConfigureAwait(false);
|
||||
|
||||
if (newVersion == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (SharedInfo.Version >= newVersion) {
|
||||
if (SharedInfo.Version > newVersion) {
|
||||
if (!updated) {
|
||||
if ((newVersion != null) && (SharedInfo.Version > newVersion)) {
|
||||
// User is running version newer than their channel allows
|
||||
ArchiLogger.LogGenericWarning(Strings.WarningPreReleaseVersion);
|
||||
await Task.Delay(SharedInfo.InformationDelay).ConfigureAwait(false);
|
||||
}
|
||||
@@ -953,9 +755,224 @@ public static class ASF {
|
||||
await RestartOrExit().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static bool UpdateFromArchive(ZipArchive archive, string targetDirectory) {
|
||||
ArgumentNullException.ThrowIfNull(archive);
|
||||
ArgumentException.ThrowIfNullOrEmpty(targetDirectory);
|
||||
private static async Task<(bool Updated, Version? NewVersion)> UpdateASF(GlobalConfig.EUpdateChannel? channel = null, bool updateOverride = false, bool forced = false) {
|
||||
if (channel.HasValue && !Enum.IsDefined(channel.Value)) {
|
||||
throw new InvalidEnumArgumentException(nameof(channel), (int) channel, typeof(GlobalConfig.EUpdateChannel));
|
||||
}
|
||||
|
||||
if (GlobalConfig == null) {
|
||||
throw new InvalidOperationException(nameof(GlobalConfig));
|
||||
}
|
||||
|
||||
if (WebBrowser == null) {
|
||||
throw new InvalidOperationException(nameof(WebBrowser));
|
||||
}
|
||||
|
||||
channel ??= GlobalConfig.UpdateChannel;
|
||||
|
||||
if (!SharedInfo.BuildInfo.CanUpdate || (channel == GlobalConfig.EUpdateChannel.None)) {
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
string targetFile;
|
||||
|
||||
await UpdateSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
// If backup directory from previous update exists, it's a good idea to purge it now
|
||||
string backupDirectory = Path.Combine(SharedInfo.HomeDirectory, SharedInfo.UpdateDirectory);
|
||||
|
||||
if (Directory.Exists(backupDirectory)) {
|
||||
ArchiLogger.LogGenericInfo(Strings.UpdateCleanup);
|
||||
|
||||
for (byte i = 0; (i < WebBrowser.MaxTries) && Directory.Exists(backupDirectory); i++) {
|
||||
if (i > 0) {
|
||||
// It's entirely possible that old process is still running, wait a short moment for eventual cleanup
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
try {
|
||||
Directory.Delete(backupDirectory, true);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericDebuggingException(e);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (Directory.Exists(backupDirectory)) {
|
||||
ArchiLogger.LogGenericError(Strings.WarningFailed);
|
||||
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericInfo(Strings.Done);
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericInfo(Strings.UpdateCheckingNewVersion);
|
||||
|
||||
ReleaseResponse? releaseResponse = await GitHubService.GetLatestRelease(SharedInfo.GithubRepo, channel == GlobalConfig.EUpdateChannel.Stable).ConfigureAwait(false);
|
||||
|
||||
if (releaseResponse == null) {
|
||||
ArchiLogger.LogGenericWarning(Strings.ErrorUpdateCheckFailed);
|
||||
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(releaseResponse.Tag)) {
|
||||
ArchiLogger.LogGenericWarning(Strings.ErrorUpdateCheckFailed);
|
||||
|
||||
return (false, null);
|
||||
}
|
||||
|
||||
Version newVersion = new(releaseResponse.Tag);
|
||||
|
||||
ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.UpdateVersionInfo, SharedInfo.Version, newVersion));
|
||||
|
||||
if (!forced && (SharedInfo.Version >= newVersion)) {
|
||||
return (false, newVersion);
|
||||
}
|
||||
|
||||
if (!updateOverride && (GlobalConfig.UpdatePeriod == 0)) {
|
||||
ArchiLogger.LogGenericInfo(Strings.UpdateNewVersionAvailable);
|
||||
await Task.Delay(SharedInfo.ShortInformationDelay).ConfigureAwait(false);
|
||||
|
||||
return (false, newVersion);
|
||||
}
|
||||
|
||||
// Auto update logic starts here
|
||||
if (releaseResponse.Assets.IsEmpty) {
|
||||
ArchiLogger.LogGenericWarning(Strings.ErrorUpdateNoAssets);
|
||||
|
||||
return (false, newVersion);
|
||||
}
|
||||
|
||||
targetFile = $"{SharedInfo.ASF}-{SharedInfo.BuildInfo.Variant}.zip";
|
||||
ReleaseAsset? binaryAsset = releaseResponse.Assets.FirstOrDefault(asset => !string.IsNullOrEmpty(asset.Name) && asset.Name.Equals(targetFile, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (binaryAsset == null) {
|
||||
ArchiLogger.LogGenericWarning(Strings.ErrorUpdateNoAssetForThisVersion);
|
||||
|
||||
return (false, newVersion);
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericInfo(Strings.FetchingChecksumFromRemoteServer);
|
||||
|
||||
string? remoteChecksum = await ArchiNet.FetchBuildChecksum(newVersion, SharedInfo.BuildInfo.Variant).ConfigureAwait(false);
|
||||
|
||||
switch (remoteChecksum) {
|
||||
case null:
|
||||
// Timeout or error, refuse to update as a security measure
|
||||
return (false, newVersion);
|
||||
case "":
|
||||
// Unknown checksum, release too new or actual malicious build published, no need to scare the user as it's 99.99% the first
|
||||
ArchiLogger.LogGenericWarning(Strings.ChecksumMissing);
|
||||
|
||||
return (false, newVersion);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(releaseResponse.ChangelogPlainText)) {
|
||||
ArchiLogger.LogGenericInfo(releaseResponse.ChangelogPlainText);
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.UpdateDownloadingNewVersion, newVersion, binaryAsset.Size / 1024 / 1024));
|
||||
|
||||
Progress<byte> progressReporter = new();
|
||||
|
||||
progressReporter.ProgressChanged += onProgressChanged;
|
||||
|
||||
BinaryResponse? response;
|
||||
|
||||
try {
|
||||
response = await WebBrowser.UrlGetToBinary(binaryAsset.DownloadURL, progressReporter: progressReporter).ConfigureAwait(false);
|
||||
} finally {
|
||||
progressReporter.ProgressChanged -= onProgressChanged;
|
||||
}
|
||||
|
||||
if (response?.Content == null) {
|
||||
return (false, newVersion);
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericInfo(Strings.VerifyingChecksumWithRemoteServer);
|
||||
|
||||
byte[] responseBytes = response.Content as byte[] ?? response.Content.ToArray();
|
||||
|
||||
string checksum = Utilities.GenerateChecksumFor(responseBytes);
|
||||
|
||||
if (!checksum.Equals(remoteChecksum, StringComparison.OrdinalIgnoreCase)) {
|
||||
ArchiLogger.LogGenericError(Strings.ChecksumWrong);
|
||||
|
||||
return (false, newVersion);
|
||||
}
|
||||
|
||||
await PluginsCore.OnUpdateProceeding(newVersion).ConfigureAwait(false);
|
||||
|
||||
bool kestrelWasRunning = ArchiKestrel.IsRunning;
|
||||
|
||||
if (kestrelWasRunning) {
|
||||
// We disable ArchiKestrel here as the update process moves the core files and might result in IPC crash
|
||||
// TODO: It might fail if the update was triggered from the API, this should be something to improve in the future, by changing the structure into request -> return response -> finish update
|
||||
try {
|
||||
await ArchiKestrel.Stop().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
}
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericInfo(Strings.PatchingFiles);
|
||||
|
||||
try {
|
||||
MemoryStream memoryStream = new(responseBytes);
|
||||
|
||||
await using (memoryStream.ConfigureAwait(false)) {
|
||||
using ZipArchive zipArchive = new(memoryStream);
|
||||
|
||||
if (!await UpdateFromArchive(newVersion, channel.Value, updateOverride, forced, zipArchive).ConfigureAwait(false)) {
|
||||
ArchiLogger.LogGenericError(Strings.WarningFailed);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericException(e);
|
||||
|
||||
if (kestrelWasRunning) {
|
||||
// We've temporarily disabled ArchiKestrel but the update has failed, let's bring it back up
|
||||
// We can't even be sure if it's possible to bring it back up in this state, but it's worth trying anyway
|
||||
try {
|
||||
await ArchiKestrel.Start().ConfigureAwait(false);
|
||||
} catch (Exception ex) {
|
||||
ArchiLogger.LogGenericWarningException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
return (false, newVersion);
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericInfo(Strings.UpdateFinished);
|
||||
|
||||
await PluginsCore.OnUpdateFinished(newVersion).ConfigureAwait(false);
|
||||
|
||||
return (true, newVersion);
|
||||
} finally {
|
||||
UpdateSemaphore.Release();
|
||||
}
|
||||
|
||||
void onProgressChanged(object? sender, byte progressPercentage) {
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(progressPercentage, 100);
|
||||
|
||||
Utilities.OnProgressChanged(targetFile, progressPercentage);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<bool> UpdateFromArchive(Version newVersion, GlobalConfig.EUpdateChannel updateChannel, bool updateOverride, bool forced, ZipArchive zipArchive) {
|
||||
ArgumentNullException.ThrowIfNull(newVersion);
|
||||
|
||||
if (!Enum.IsDefined(updateChannel)) {
|
||||
throw new InvalidEnumArgumentException(nameof(updateChannel), (int) updateChannel, typeof(GlobalConfig.EUpdateChannel));
|
||||
}
|
||||
|
||||
ArgumentNullException.ThrowIfNull(zipArchive);
|
||||
|
||||
if (SharedInfo.HomeDirectory == AppContext.BaseDirectory) {
|
||||
// We're running a build that includes our dependencies in ASF's home
|
||||
@@ -968,114 +985,10 @@ public static class ASF {
|
||||
LoadAssembliesNeededBeforeUpdate();
|
||||
}
|
||||
|
||||
// Firstly we'll move all our existing files to a backup directory
|
||||
string backupDirectory = Path.Combine(targetDirectory, SharedInfo.UpdateDirectory);
|
||||
// We're ready to start update process, handle any plugin updates ready for new version
|
||||
await PluginsCore.UpdatePlugins(newVersion, true, updateChannel, updateOverride, forced).ConfigureAwait(false);
|
||||
|
||||
foreach (string file in Directory.EnumerateFiles(targetDirectory, "*", SearchOption.AllDirectories)) {
|
||||
string fileName = Path.GetFileName(file);
|
||||
|
||||
if (string.IsNullOrEmpty(fileName)) {
|
||||
ArchiLogger.LogNullError(fileName);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
string relativeFilePath = Path.GetRelativePath(targetDirectory, file);
|
||||
|
||||
if (string.IsNullOrEmpty(relativeFilePath)) {
|
||||
ArchiLogger.LogNullError(relativeFilePath);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
string? relativeDirectoryName = Path.GetDirectoryName(relativeFilePath);
|
||||
|
||||
switch (relativeDirectoryName) {
|
||||
case null:
|
||||
ArchiLogger.LogNullError(relativeDirectoryName);
|
||||
|
||||
return false;
|
||||
case "":
|
||||
// No directory, root folder
|
||||
switch (fileName) {
|
||||
case Logging.NLogConfigurationFile:
|
||||
case SharedInfo.LogFile:
|
||||
// Files with those names in root directory we want to keep
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
case SharedInfo.ArchivalLogsDirectory:
|
||||
case SharedInfo.ConfigDirectory:
|
||||
case SharedInfo.DebugDirectory:
|
||||
case SharedInfo.PluginsDirectory:
|
||||
case SharedInfo.UpdateDirectory:
|
||||
// Files in those directories we want to keep in their current place
|
||||
continue;
|
||||
default:
|
||||
// Files in subdirectories of those directories we want to keep as well
|
||||
if (Utilities.RelativeDirectoryStartsWith(relativeDirectoryName, SharedInfo.ArchivalLogsDirectory, SharedInfo.ConfigDirectory, SharedInfo.DebugDirectory, SharedInfo.PluginsDirectory, SharedInfo.UpdateDirectory)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
string targetBackupDirectory = relativeDirectoryName.Length > 0 ? Path.Combine(backupDirectory, relativeDirectoryName) : backupDirectory;
|
||||
Directory.CreateDirectory(targetBackupDirectory);
|
||||
|
||||
string targetBackupFile = Path.Combine(targetBackupDirectory, fileName);
|
||||
|
||||
File.Move(file, targetBackupFile, true);
|
||||
}
|
||||
|
||||
// We can now get rid of directories that are empty
|
||||
Utilities.DeleteEmptyDirectoriesRecursively(targetDirectory);
|
||||
|
||||
if (!Directory.Exists(targetDirectory)) {
|
||||
Directory.CreateDirectory(targetDirectory);
|
||||
}
|
||||
|
||||
// Now enumerate over files in the zip archive, skip directory entries that we're not interested in (we can create them ourselves if needed)
|
||||
foreach (ZipArchiveEntry zipFile in archive.Entries.Where(static zipFile => !string.IsNullOrEmpty(zipFile.Name))) {
|
||||
string file = Path.GetFullPath(Path.Combine(targetDirectory, zipFile.FullName));
|
||||
|
||||
if (!file.StartsWith(targetDirectory, StringComparison.Ordinal)) {
|
||||
throw new InvalidOperationException(nameof(file));
|
||||
}
|
||||
|
||||
if (File.Exists(file)) {
|
||||
// This is possible only with files that we decided to leave in place during our backup function
|
||||
string targetBackupFile = $"{file}.bak";
|
||||
|
||||
File.Move(file, targetBackupFile, true);
|
||||
}
|
||||
|
||||
// Check if this file requires its own folder
|
||||
if (zipFile.Name != zipFile.FullName) {
|
||||
string? directory = Path.GetDirectoryName(file);
|
||||
|
||||
if (string.IsNullOrEmpty(directory)) {
|
||||
ArchiLogger.LogNullError(directory);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(directory)) {
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
// We're not interested in extracting placeholder files (but we still want directories created for them, done above)
|
||||
switch (zipFile.Name) {
|
||||
case ".gitkeep":
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
zipFile.ExtractToFile(file);
|
||||
}
|
||||
|
||||
return true;
|
||||
return Utilities.UpdateFromArchive(zipArchive, SharedInfo.HomeDirectory);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -24,21 +26,17 @@ using System.Text.RegularExpressions;
|
||||
namespace ArchiSteamFarm.Core;
|
||||
|
||||
internal static partial class GeneratedRegexes {
|
||||
private const string CdKeyPattern = @"^[0-9A-Z]{4,7}-[0-9A-Z]{4,7}-[0-9A-Z]{4,7}(?:(?:-[0-9A-Z]{4,7})?(?:-[0-9A-Z]{4,7}))?$";
|
||||
private const string DecimalPattern = @"[0-9\.,]+";
|
||||
private const RegexOptions DefaultOptions = RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase;
|
||||
private const string DigitsPattern = @"\d+";
|
||||
private const string NonAsciiPattern = @"[^\u0000-\u007F]+";
|
||||
|
||||
[GeneratedRegex(CdKeyPattern, DefaultOptions)]
|
||||
[GeneratedRegex(@"^[0-9A-Z]{4,7}-[0-9A-Z]{4,7}-[0-9A-Z]{4,7}(?:(?:-[0-9A-Z]{4,7})?(?:-[0-9A-Z]{4,7}))?$", DefaultOptions)]
|
||||
internal static partial Regex CdKey();
|
||||
|
||||
[GeneratedRegex(DecimalPattern, DefaultOptions)]
|
||||
[GeneratedRegex(@"[0-9\.,]+", DefaultOptions)]
|
||||
internal static partial Regex Decimal();
|
||||
|
||||
[GeneratedRegex(DigitsPattern, DefaultOptions)]
|
||||
[GeneratedRegex(@"\d+", DefaultOptions)]
|
||||
internal static partial Regex Digits();
|
||||
|
||||
[GeneratedRegex(NonAsciiPattern, DefaultOptions)]
|
||||
[GeneratedRegex(@"[^\u0000-\u007F]+", DefaultOptions)]
|
||||
internal static partial Regex NonAscii();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -27,38 +29,44 @@ namespace ArchiSteamFarm.Core;
|
||||
|
||||
internal static partial class NativeMethods {
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[LibraryImport("user32.dll")]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static partial void FlashWindowEx(ref FlashWindowInfo pwfi);
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[LibraryImport("kernel32.dll")]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
internal static partial bool GetConsoleMode(nint hConsoleHandle, out EConsoleMode lpMode);
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[LibraryImport("libc", EntryPoint = "geteuid", SetLastError = true)]
|
||||
[SupportedOSPlatform("FreeBSD")]
|
||||
[SupportedOSPlatform("Linux")]
|
||||
[SupportedOSPlatform("MacOS")]
|
||||
[LibraryImport("libc", EntryPoint = "geteuid", SetLastError = true)]
|
||||
internal static partial uint GetEuid();
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
[LibraryImport("kernel32.dll")]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
internal static partial nint GetStdHandle(EStandardHandle nStdHandle);
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[LibraryImport("kernel32.dll")]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
[LibraryImport("kernel32.dll")]
|
||||
internal static partial bool SetConsoleMode(nint hConsoleHandle, EConsoleMode dwMode);
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
[LibraryImport("kernel32.dll")]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
internal static partial EExecutionState SetThreadExecutionState(EExecutionState executionState);
|
||||
|
||||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
|
||||
[LibraryImport("user32.dll")]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
[LibraryImport("user32.dll")]
|
||||
internal static partial void ShowWindow(nint hWnd, EShowWindow nCmdShow);
|
||||
|
||||
[Flags]
|
||||
@@ -77,6 +85,16 @@ internal static partial class NativeMethods {
|
||||
Awake = SystemRequired | AwayModeRequired | Continuous
|
||||
}
|
||||
|
||||
[Flags]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
internal enum EFlashFlags : uint {
|
||||
Stop = 0,
|
||||
Caption = 1,
|
||||
Tray = 2,
|
||||
All = Caption | Tray,
|
||||
Timer = 4
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("Windows")]
|
||||
internal enum EShowWindow : uint {
|
||||
Minimize = 6
|
||||
@@ -86,4 +104,16 @@ internal static partial class NativeMethods {
|
||||
internal enum EStandardHandle {
|
||||
Input = -10
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
[SupportedOSPlatform("Windows")]
|
||||
internal struct FlashWindowInfo {
|
||||
#pragma warning disable Reordering // TODO: This silly pragma doesn't do anything, but it stops Rider from reordering, we may be able to get rid of it later
|
||||
public uint StructSize;
|
||||
public nint WindowHandle;
|
||||
public EFlashFlags Flags;
|
||||
public uint Count;
|
||||
public uint TimeoutBetweenFlashes;
|
||||
#pragma warning restore Reordering // TODO: This silly pragma doesn't do anything, but it stops Rider from reordering, we may be able to get rid of it later
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -84,11 +86,11 @@ internal static class OS {
|
||||
private static Mutex? SingleInstance;
|
||||
|
||||
internal static void CoreInit(bool minimized, bool systemRequired) {
|
||||
if (OperatingSystem.IsWindows()) {
|
||||
if (minimized) {
|
||||
WindowsMinimizeConsoleWindow();
|
||||
}
|
||||
if (minimized) {
|
||||
MinimizeConsoleWindow();
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsWindows()) {
|
||||
if (systemRequired) {
|
||||
WindowsKeepSystemActive();
|
||||
}
|
||||
@@ -227,6 +229,73 @@ internal static class OS {
|
||||
return false;
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("Windows")]
|
||||
internal static void WindowsStartFlashingConsoleWindow() {
|
||||
if (!OperatingSystem.IsWindows()) {
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
|
||||
using Process currentProcess = Process.GetCurrentProcess();
|
||||
|
||||
nint handle = currentProcess.MainWindowHandle;
|
||||
|
||||
if (handle == nint.Zero) {
|
||||
return;
|
||||
}
|
||||
|
||||
NativeMethods.FlashWindowInfo flashInfo = new() {
|
||||
StructSize = (uint) Marshal.SizeOf<NativeMethods.FlashWindowInfo>(),
|
||||
Flags = NativeMethods.EFlashFlags.All | NativeMethods.EFlashFlags.Timer,
|
||||
WindowHandle = handle,
|
||||
Count = uint.MaxValue
|
||||
};
|
||||
|
||||
NativeMethods.FlashWindowEx(ref flashInfo);
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("Windows")]
|
||||
internal static void WindowsStopFlashingConsoleWindow() {
|
||||
if (!OperatingSystem.IsWindows()) {
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
|
||||
using Process currentProcess = Process.GetCurrentProcess();
|
||||
nint handle = currentProcess.MainWindowHandle;
|
||||
|
||||
if (handle == nint.Zero) {
|
||||
return;
|
||||
}
|
||||
|
||||
NativeMethods.FlashWindowInfo flashInfo = new() {
|
||||
StructSize = (uint) Marshal.SizeOf<NativeMethods.FlashWindowInfo>(),
|
||||
Flags = NativeMethods.EFlashFlags.Stop,
|
||||
WindowHandle = handle
|
||||
};
|
||||
|
||||
NativeMethods.FlashWindowEx(ref flashInfo);
|
||||
}
|
||||
|
||||
private static void MinimizeConsoleWindow() {
|
||||
(_, int top) = Console.GetCursorPosition();
|
||||
|
||||
// Will work if the terminal supports XTWINOPS "iconify" escape sequence, reference: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||
Console.Write('\x1b' + @"[2;0;0t");
|
||||
|
||||
// Reset cursor position if terminal outputs escape sequences as-is
|
||||
Console.SetCursorPosition(0, top);
|
||||
|
||||
// Fallback if we're using conhost on Windows
|
||||
if (OperatingSystem.IsWindows()) {
|
||||
using Process process = Process.GetCurrentProcess();
|
||||
|
||||
nint windowHandle = process.MainWindowHandle;
|
||||
|
||||
if (windowHandle != nint.Zero) {
|
||||
NativeMethods.ShowWindow(windowHandle, NativeMethods.EShowWindow.Minimize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("Windows")]
|
||||
private static void WindowsDisableQuickEditMode() {
|
||||
if (!OperatingSystem.IsWindows()) {
|
||||
@@ -264,15 +333,4 @@ internal static class OS {
|
||||
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, result));
|
||||
}
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("Windows")]
|
||||
private static void WindowsMinimizeConsoleWindow() {
|
||||
if (!OperatingSystem.IsWindows()) {
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
|
||||
using Process process = Process.GetCurrentProcess();
|
||||
|
||||
NativeMethods.ShowWindow(process.MainWindowHandle, NativeMethods.EShowWindow.Minimize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -26,6 +28,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Resources;
|
||||
@@ -35,6 +38,7 @@ using System.Threading.Tasks;
|
||||
using AngleSharp.Dom;
|
||||
using AngleSharp.XPath;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using ArchiSteamFarm.NLog;
|
||||
using ArchiSteamFarm.Storage;
|
||||
using Humanizer;
|
||||
using Humanizer.Localisation;
|
||||
@@ -263,26 +267,6 @@ public static class Utilities {
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void DeleteEmptyDirectoriesRecursively(string directory) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(directory);
|
||||
|
||||
if (!Directory.Exists(directory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
foreach (string subDirectory in Directory.EnumerateDirectories(directory)) {
|
||||
DeleteEmptyDirectoriesRecursively(subDirectory);
|
||||
}
|
||||
|
||||
if (!Directory.EnumerateFileSystemEntries(directory).Any()) {
|
||||
Directory.Delete(directory);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
}
|
||||
}
|
||||
|
||||
internal static ulong MathAdd(ulong first, int second) {
|
||||
if (second >= 0) {
|
||||
return first + (uint) second;
|
||||
@@ -291,16 +275,17 @@ public static class Utilities {
|
||||
return first - (uint) -second;
|
||||
}
|
||||
|
||||
internal static bool RelativeDirectoryStartsWith(string directory, params string[] prefixes) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(directory);
|
||||
internal static void OnProgressChanged(string fileName, byte progressPercentage) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(fileName);
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(progressPercentage, 100);
|
||||
|
||||
#pragma warning disable CA1508 // False positive, params could be null when explicitly set
|
||||
if ((prefixes == null) || (prefixes.Length == 0)) {
|
||||
#pragma warning restore CA1508 // False positive, params could be null when explicitly set
|
||||
throw new ArgumentNullException(nameof(prefixes));
|
||||
const byte printEveryPercentage = 10;
|
||||
|
||||
if (progressPercentage % printEveryPercentage != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (from prefix in prefixes where directory.Length > prefix.Length let pathSeparator = directory[prefix.Length] where (pathSeparator == Path.DirectorySeparatorChar) || (pathSeparator == Path.AltDirectorySeparatorChar) select prefix).Any(prefix => directory.StartsWith(prefix, StringComparison.Ordinal));
|
||||
ASF.ArchiLogger.LogGenericDebug($"{fileName} {progressPercentage}%...");
|
||||
}
|
||||
|
||||
internal static (bool IsWeak, string? Reason) TestPasswordStrength(string password, ISet<string>? additionallyForbiddenPhrases = null) {
|
||||
@@ -337,6 +322,120 @@ public static class Utilities {
|
||||
return (result.Score < 4, suggestions is { Count: > 0 } ? string.Join(' ', suggestions.Where(static suggestion => suggestion.Length > 0)) : null);
|
||||
}
|
||||
|
||||
internal static bool UpdateFromArchive(ZipArchive zipArchive, string targetDirectory) {
|
||||
ArgumentNullException.ThrowIfNull(zipArchive);
|
||||
ArgumentException.ThrowIfNullOrEmpty(targetDirectory);
|
||||
|
||||
// Firstly we'll move all our existing files to a backup directory
|
||||
string backupDirectory = Path.Combine(targetDirectory, SharedInfo.UpdateDirectory);
|
||||
|
||||
foreach (string file in Directory.EnumerateFiles(targetDirectory, "*", SearchOption.AllDirectories)) {
|
||||
string fileName = Path.GetFileName(file);
|
||||
|
||||
if (string.IsNullOrEmpty(fileName)) {
|
||||
ASF.ArchiLogger.LogNullError(fileName);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
string relativeFilePath = Path.GetRelativePath(targetDirectory, file);
|
||||
|
||||
if (string.IsNullOrEmpty(relativeFilePath)) {
|
||||
ASF.ArchiLogger.LogNullError(relativeFilePath);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
string? relativeDirectoryName = Path.GetDirectoryName(relativeFilePath);
|
||||
|
||||
switch (relativeDirectoryName) {
|
||||
case null:
|
||||
ASF.ArchiLogger.LogNullError(relativeDirectoryName);
|
||||
|
||||
return false;
|
||||
case "":
|
||||
// No directory, root folder
|
||||
switch (fileName) {
|
||||
case Logging.NLogConfigurationFile:
|
||||
case SharedInfo.LogFile:
|
||||
// Files with those names in root directory we want to keep
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
case SharedInfo.ArchivalLogsDirectory:
|
||||
case SharedInfo.ConfigDirectory:
|
||||
case SharedInfo.DebugDirectory:
|
||||
case SharedInfo.PluginsDirectory:
|
||||
case SharedInfo.UpdateDirectory:
|
||||
// Files in those directories we want to keep in their current place
|
||||
continue;
|
||||
default:
|
||||
// Files in subdirectories of those directories we want to keep as well
|
||||
if (RelativeDirectoryStartsWith(relativeDirectoryName, SharedInfo.ArchivalLogsDirectory, SharedInfo.ConfigDirectory, SharedInfo.DebugDirectory, SharedInfo.PluginsDirectory, SharedInfo.UpdateDirectory)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
string targetBackupDirectory = relativeDirectoryName.Length > 0 ? Path.Combine(backupDirectory, relativeDirectoryName) : backupDirectory;
|
||||
Directory.CreateDirectory(targetBackupDirectory);
|
||||
|
||||
string targetBackupFile = Path.Combine(targetBackupDirectory, fileName);
|
||||
|
||||
File.Move(file, targetBackupFile, true);
|
||||
}
|
||||
|
||||
// We can now get rid of directories that are empty
|
||||
DeleteEmptyDirectoriesRecursively(targetDirectory);
|
||||
|
||||
if (!Directory.Exists(targetDirectory)) {
|
||||
Directory.CreateDirectory(targetDirectory);
|
||||
}
|
||||
|
||||
// Now enumerate over files in the zip archive, skip directory entries that we're not interested in (we can create them ourselves if needed)
|
||||
foreach (ZipArchiveEntry zipFile in zipArchive.Entries.Where(static zipFile => !string.IsNullOrEmpty(zipFile.Name))) {
|
||||
string file = Path.GetFullPath(Path.Combine(targetDirectory, zipFile.FullName));
|
||||
|
||||
if (!file.StartsWith(targetDirectory, StringComparison.Ordinal)) {
|
||||
throw new InvalidOperationException(nameof(file));
|
||||
}
|
||||
|
||||
if (File.Exists(file)) {
|
||||
// This is possible only with files that we decided to leave in place during our backup function
|
||||
string targetBackupFile = $"{file}.bak";
|
||||
|
||||
File.Move(file, targetBackupFile, true);
|
||||
}
|
||||
|
||||
// Check if this file requires its own folder
|
||||
if (zipFile.Name != zipFile.FullName) {
|
||||
string? directory = Path.GetDirectoryName(file);
|
||||
|
||||
if (string.IsNullOrEmpty(directory)) {
|
||||
ASF.ArchiLogger.LogNullError(directory);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(directory)) {
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
// We're not interested in extracting placeholder files (but we still want directories created for them, done above)
|
||||
switch (zipFile.Name) {
|
||||
case ".gitkeep":
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
zipFile.ExtractToFile(file);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void WarnAboutIncompleteTranslation(ResourceManager resourceManager) {
|
||||
ArgumentNullException.ThrowIfNull(resourceManager);
|
||||
|
||||
@@ -391,4 +490,38 @@ public static class Utilities {
|
||||
ASF.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.TranslationIncomplete, $"{CultureInfo.CurrentUICulture.Name} ({CultureInfo.CurrentUICulture.EnglishName})", translationCompleteness.ToString("P1", CultureInfo.CurrentCulture)));
|
||||
}
|
||||
}
|
||||
|
||||
private static void DeleteEmptyDirectoriesRecursively(string directory) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(directory);
|
||||
|
||||
if (!Directory.Exists(directory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
foreach (string subDirectory in Directory.EnumerateDirectories(directory)) {
|
||||
DeleteEmptyDirectoriesRecursively(subDirectory);
|
||||
}
|
||||
|
||||
if (!Directory.EnumerateFileSystemEntries(directory).Any()) {
|
||||
Directory.Delete(directory);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool RelativeDirectoryStartsWith(string directory, params string[] prefixes) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(directory);
|
||||
|
||||
#pragma warning disable CA1508 // False positive, params could be null when explicitly set
|
||||
if ((prefixes == null) || (prefixes.Length == 0)) {
|
||||
#pragma warning restore CA1508 // False positive, params could be null when explicitly set
|
||||
throw new ArgumentNullException(nameof(prefixes));
|
||||
}
|
||||
|
||||
HashSet<char> separators = [Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar];
|
||||
|
||||
return prefixes.Where(prefix => (directory.Length > prefix.Length) && separators.Contains(directory[prefix.Length])).Any(prefix => directory.StartsWith(prefix, StringComparison.Ordinal));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
46
ArchiSteamFarm/Helpers/Json/BooleanNumberConverter.cs
Normal file
46
ArchiSteamFarm/Helpers/Json/BooleanNumberConverter.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// |
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// |
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace ArchiSteamFarm.Helpers.Json;
|
||||
|
||||
[PublicAPI]
|
||||
public sealed class BooleanNumberConverter : JsonConverter<bool> {
|
||||
public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
|
||||
reader.TokenType switch {
|
||||
JsonTokenType.True => true,
|
||||
JsonTokenType.False => false,
|
||||
JsonTokenType.Number => reader.GetByte() == 1,
|
||||
_ => throw new JsonException()
|
||||
};
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) {
|
||||
ArgumentNullException.ThrowIfNull(writer);
|
||||
|
||||
writer.WriteNumberValue(value ? 1 : 0);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -22,6 +24,7 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace ArchiSteamFarm.Helpers.Json;
|
||||
|
||||
@@ -30,9 +33,8 @@ namespace ArchiSteamFarm.Helpers.Json;
|
||||
/// TODO: This class exists purely because STJ can't deserialize Guid in other formats than default, at least for now
|
||||
/// https://github.com/dotnet/runtime/issues/30692
|
||||
/// </summary>
|
||||
internal sealed class GuidJsonConverter : JsonConverter<Guid> {
|
||||
internal static readonly GuidJsonConverter Shared = new();
|
||||
|
||||
[PublicAPI]
|
||||
public sealed class GuidJsonConverter : JsonConverter<Guid> {
|
||||
public override Guid Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
||||
if (reader.TryGetGuid(out Guid result)) {
|
||||
// Great, we can work with it
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -111,7 +113,6 @@ public static class JsonUtilities {
|
||||
private static JsonSerializerOptions CreateDefaultJsonSerializerOptions(bool writeIndented = false) =>
|
||||
new() {
|
||||
AllowTrailingCommas = true,
|
||||
Converters = { GuidJsonConverter.Shared },
|
||||
PropertyNamingPolicy = null,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip,
|
||||
TypeInfoResolver = new DefaultJsonTypeInfoResolver { Modifiers = { ApplyCustomModifiers } },
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -29,7 +31,6 @@ using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Helpers.Json;
|
||||
@@ -172,8 +173,7 @@ internal static class ArchiKestrel {
|
||||
string? assemblyDirectory = Path.GetDirectoryName(plugin.GetType().Assembly.Location);
|
||||
|
||||
if (string.IsNullOrEmpty(assemblyDirectory)) {
|
||||
// Invalid path provided
|
||||
continue;
|
||||
throw new InvalidOperationException(nameof(assemblyDirectory));
|
||||
}
|
||||
|
||||
physicalPath = Path.Combine(assemblyDirectory, plugin.PhysicalPath);
|
||||
@@ -387,10 +387,6 @@ internal static class ArchiKestrel {
|
||||
static options => {
|
||||
JsonSerializerOptions jsonSerializerOptions = Debugging.IsUserDebugging ? JsonUtilities.IndentedJsonSerialierOptions : JsonUtilities.DefaultJsonSerialierOptions;
|
||||
|
||||
foreach (JsonConverter converter in jsonSerializerOptions.Converters) {
|
||||
options.JsonSerializerOptions.Converters.Add(converter);
|
||||
}
|
||||
|
||||
options.JsonSerializerOptions.PropertyNamingPolicy = jsonSerializerOptions.PropertyNamingPolicy;
|
||||
options.JsonSerializerOptions.TypeInfoResolver = jsonSerializerOptions.TypeInfoResolver;
|
||||
options.JsonSerializerOptions.WriteIndented = jsonSerializerOptions.WriteIndented;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -180,7 +182,7 @@ public sealed class ASFController : ArchiController {
|
||||
return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(request.Channel))));
|
||||
}
|
||||
|
||||
(bool success, string? message, Version? version) = await Actions.Update(request.Channel).ConfigureAwait(false);
|
||||
(bool success, string? message, Version? version) = await Actions.Update(request.Channel, request.Forced).ConfigureAwait(false);
|
||||
|
||||
if (string.IsNullOrEmpty(message)) {
|
||||
message = success ? Strings.Success : Strings.WarningFailed;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
@@ -29,6 +31,8 @@ using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.IPC.Responses;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using ArchiSteamFarm.Web;
|
||||
using ArchiSteamFarm.Web.GitHub;
|
||||
using ArchiSteamFarm.Web.GitHub.Data;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace ArchiSteamFarm.IPC.Controllers.Api;
|
||||
@@ -47,7 +51,7 @@ public sealed class GitHubController : ArchiController {
|
||||
public async Task<ActionResult<GenericResponse>> GitHubReleaseGet() {
|
||||
CancellationToken cancellationToken = HttpContext.RequestAborted;
|
||||
|
||||
GitHub.ReleaseResponse? releaseResponse = await GitHub.GetLatestRelease(false, cancellationToken).ConfigureAwait(false);
|
||||
ReleaseResponse? releaseResponse = await GitHubService.GetLatestRelease(SharedInfo.GithubRepo, false, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return releaseResponse != null ? Ok(new GenericResponse<GitHubReleaseResponse>(new GitHubReleaseResponse(releaseResponse))) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries)));
|
||||
}
|
||||
@@ -67,11 +71,11 @@ public sealed class GitHubController : ArchiController {
|
||||
|
||||
CancellationToken cancellationToken = HttpContext.RequestAborted;
|
||||
|
||||
GitHub.ReleaseResponse? releaseResponse;
|
||||
ReleaseResponse? releaseResponse;
|
||||
|
||||
switch (version.ToUpperInvariant()) {
|
||||
case "LATEST":
|
||||
releaseResponse = await GitHub.GetLatestRelease(cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
releaseResponse = await GitHubService.GetLatestRelease(SharedInfo.GithubRepo, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
break;
|
||||
default:
|
||||
@@ -79,7 +83,7 @@ public sealed class GitHubController : ArchiController {
|
||||
return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(version))));
|
||||
}
|
||||
|
||||
releaseResponse = await GitHub.GetRelease(parsedVersion.ToString(4), cancellationToken).ConfigureAwait(false);
|
||||
releaseResponse = await GitHubService.GetRelease(SharedInfo.GithubRepo, parsedVersion.ToString(4), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -102,7 +106,7 @@ public sealed class GitHubController : ArchiController {
|
||||
|
||||
CancellationToken cancellationToken = HttpContext.RequestAborted;
|
||||
|
||||
Dictionary<string, DateTime>? revisions = await GitHub.GetWikiHistory(page, cancellationToken).ConfigureAwait(false);
|
||||
Dictionary<string, DateTime>? revisions = await GitHubService.GetWikiHistory(page, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return revisions != null ? revisions.Count > 0 ? Ok(new GenericResponse<ImmutableDictionary<string, DateTime>>(revisions.ToImmutableDictionary())) : BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(page)))) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries)));
|
||||
}
|
||||
@@ -123,7 +127,7 @@ public sealed class GitHubController : ArchiController {
|
||||
|
||||
CancellationToken cancellationToken = HttpContext.RequestAborted;
|
||||
|
||||
string? html = await GitHub.GetWikiPage(page, revision, cancellationToken).ConfigureAwait(false);
|
||||
string? html = await GitHubService.GetWikiPage(page, revision, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return html switch {
|
||||
null => StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries))),
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user