mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-24 18:26:49 +00:00
Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1c2b103b6 | ||
|
|
9f1734efb7 | ||
|
|
729c2e889c | ||
|
|
a9edc7ad7a | ||
|
|
08a6486c00 | ||
|
|
ca3bc1becd | ||
|
|
fe5028a399 | ||
|
|
c1d9d04071 | ||
|
|
e5ae2abbf0 | ||
|
|
41fa5de5a8 | ||
|
|
697b78aa21 | ||
|
|
1a7be0bac8 | ||
|
|
9d88972ae0 | ||
|
|
aec4130afe | ||
|
|
64228cd3d9 | ||
|
|
3568a0e528 | ||
|
|
38c2b51f2b | ||
|
|
450f365817 | ||
|
|
cf3f6aabdf | ||
|
|
842fb6e304 | ||
|
|
a1169331aa | ||
|
|
2fb7d62e06 | ||
|
|
4e57153e91 | ||
|
|
d2e78b6970 | ||
|
|
ccef6554fe | ||
|
|
97875a87c2 | ||
|
|
2684f99563 | ||
|
|
a50318dc8b | ||
|
|
eeccc36fe4 | ||
|
|
1ead134578 | ||
|
|
f03f8ebe70 | ||
|
|
aa8b360e1d | ||
|
|
8c22f9929c | ||
|
|
3795b2de3a | ||
|
|
f4650fe570 | ||
|
|
fec57e0fff | ||
|
|
8e47a5906f | ||
|
|
f728ddf737 | ||
|
|
173cec5ef7 | ||
|
|
d16c4822eb | ||
|
|
03e3d74e51 | ||
|
|
0a3d011e2e | ||
|
|
f112a05569 | ||
|
|
1c579d96ee | ||
|
|
19a0be1d26 | ||
|
|
a8de495c7c | ||
|
|
ab8ceab055 | ||
|
|
64b72d1e55 | ||
|
|
f807bdb660 | ||
|
|
5b66b70566 | ||
|
|
41c06851a5 | ||
|
|
4dbb964ba9 | ||
|
|
11471c759d | ||
|
|
2aa4ab7fe8 | ||
|
|
dfc055c066 | ||
|
|
1a0ac11f46 | ||
|
|
7266864b3b | ||
|
|
b52f746138 | ||
|
|
a2585ec8c9 | ||
|
|
37781698e0 | ||
|
|
2a8fe7611b | ||
|
|
8fdf14bb10 | ||
|
|
31db72b2d6 | ||
|
|
f28ae15cc9 | ||
|
|
6fcc64dad1 | ||
|
|
e18046084e | ||
|
|
c3c5f33289 | ||
|
|
e03734ef8f | ||
|
|
a7c2ca6bc5 | ||
|
|
171fca42f2 | ||
|
|
e90ac74b16 | ||
|
|
a5ce8bf3d7 | ||
|
|
aad77569a7 | ||
|
|
e74b3e4f78 | ||
|
|
7db44c5835 | ||
|
|
25a88f941d | ||
|
|
2eab00facc | ||
|
|
98e51a4543 | ||
|
|
2ee49db81d | ||
|
|
aab397dd2d | ||
|
|
7426fafcb0 | ||
|
|
270bd7ae26 | ||
|
|
4c3713c19f | ||
|
|
5791b1e552 | ||
|
|
5c59236a09 | ||
|
|
a7119bba89 | ||
|
|
3b64e14489 | ||
|
|
5f36ca91d7 | ||
|
|
5a2cd25fa1 | ||
|
|
20a5d509a7 | ||
|
|
0c457e7f3e | ||
|
|
4e6014d652 | ||
|
|
1436fb6d6a | ||
|
|
e2578c7960 | ||
|
|
8fb1a2e1ea |
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1.9.0
|
||||
uses: actions/setup-dotnet@v1.9.1
|
||||
with:
|
||||
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
- name: Upload latest strings for translation on Crowdin
|
||||
continue-on-error: true
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.configuration == 'Release' && startsWith(matrix.os, 'ubuntu-') }}
|
||||
uses: crowdin/github-action@1.4.6
|
||||
uses: crowdin/github-action@1.4.7
|
||||
with:
|
||||
crowdin_branch_name: main
|
||||
config: '.github/crowdin.yml'
|
||||
|
||||
2
.github/workflows/docker-ci.yml
vendored
2
.github/workflows/docker-ci.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v1.6.0
|
||||
|
||||
- name: Build ${{ matrix.configuration }} Docker image from ${{ matrix.file }}
|
||||
uses: docker/build-push-action@v2.8.0
|
||||
uses: docker/build-push-action@v2.9.0
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.file }}
|
||||
|
||||
6
.github/workflows/docker-publish-latest.yml
vendored
6
.github/workflows/docker-publish-latest.yml
vendored
@@ -23,14 +23,14 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v1.6.0
|
||||
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v1.12.0
|
||||
uses: docker/login-action@v1.13.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1.12.0
|
||||
uses: docker/login-action@v1.13.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -55,7 +55,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@v2.8.0
|
||||
uses: docker/build-push-action@v2.9.0
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile.Service
|
||||
|
||||
6
.github/workflows/docker-publish-main.yml
vendored
6
.github/workflows/docker-publish-main.yml
vendored
@@ -24,14 +24,14 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v1.6.0
|
||||
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v1.12.0
|
||||
uses: docker/login-action@v1.13.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1.12.0
|
||||
uses: docker/login-action@v1.13.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -55,7 +55,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@v2.8.0
|
||||
uses: docker/build-push-action@v2.9.0
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
|
||||
@@ -24,14 +24,14 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v1.6.0
|
||||
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v1.12.0
|
||||
uses: docker/login-action@v1.13.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1.12.0
|
||||
uses: docker/login-action@v1.13.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
@@ -56,7 +56,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@v2.8.0
|
||||
uses: docker/build-push-action@v2.9.0
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
|
||||
4
.github/workflows/publish.yml
vendored
4
.github/workflows/publish.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1.9.0
|
||||
uses: actions/setup-dotnet@v1.9.1
|
||||
with:
|
||||
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
run: dotnet --info
|
||||
|
||||
- name: Setup Node.js with npm
|
||||
uses: actions/setup-node@v2.5.1
|
||||
uses: actions/setup-node@v3.0.0
|
||||
with:
|
||||
check-latest: true
|
||||
node-version: ${{ env.NODE_JS_VERSION }}
|
||||
|
||||
2
.github/workflows/translations.yml
vendored
2
.github/workflows/translations.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
git reset --hard origin/master
|
||||
|
||||
- name: Download latest translations from Crowdin
|
||||
uses: crowdin/github-action@1.4.6
|
||||
uses: crowdin/github-action@1.4.7
|
||||
with:
|
||||
upload_sources: false
|
||||
download_translations: true
|
||||
|
||||
2
ASF-ui
2
ASF-ui
Submodule ASF-ui updated: 156992e88d...e35e350a24
@@ -97,7 +97,10 @@
|
||||
<value>Dokončeno načítání celkem {0} přístupových tokenů.</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>Načítání všech úložišť, celkem z {0} aplikací...</value>
|
||||
<comment>{0} will be replaced by the number (total count) of apps being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingAppInfos" xml:space="preserve">
|
||||
<value>Získávání {0} informací o aplikaci...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos being retrieved</comment>
|
||||
@@ -106,14 +109,37 @@
|
||||
<value>Načítání informací o aplikaci {0} bylo dokončeno.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="BotRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>Získávání {0} tokenů úložišť...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of depot keys being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>Načítání {0} přístupových tokenů bylo dokončeno.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of depot keys retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
|
||||
<value>Načítání všech tokenbů úložišť, celkem z {0} aplikací bylo dokončeno.</value>
|
||||
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>
|
||||
</data>
|
||||
<data name="SubmissionNoNewData" xml:space="preserve">
|
||||
<value>Nejsou k dispozici žádné nové údaje k odeslání, vše je aktuální.</value>
|
||||
</data>
|
||||
<data name="SubmissionNoContributorSet" xml:space="preserve">
|
||||
<value>Data nelze odeslat, protože neexistuje žádné platné SteamID, které bychom mohli klasifikovat jako přispěvatele. Zvažte nastavení {0} parametrů.</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>Odesílání celkem registrovaných aplikací/balíčků/úložišť: {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>Odeslání se nezdařilo z důvodu příliš mnoha odeslaných požadavků. Pokusíme se znovu přibližně za {0}.</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
|
||||
</data>
|
||||
<data name="SubmissionSuccessful" xml:space="preserve">
|
||||
<value>Data byla úspěšně odeslána. Server zaregistroval celkem nové aplikace/balíčky/úložiště: {0} ({1} ověřeno)/{2} ({3} ověřeno)/{4} ({5} ověřeno).</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>Nové aplikace: {0}</value>
|
||||
<comment>{0} will be replaced by list of the apps (IDs, numbers), separated by a comma</comment>
|
||||
@@ -130,9 +156,18 @@
|
||||
<value>Ověřené balíčky: {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>Nová úložiště: {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>Ověřená úložiště: {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} inicializován, žádný plugin nebude rozpoznávat: {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>Načítání globální mezipaměti STD...</value>
|
||||
</data>
|
||||
|
||||
@@ -171,6 +171,10 @@
|
||||
<data name="LoadingGlobalCache" xml:space="preserve">
|
||||
<value>Globaler STD-Cache wird geladen...</value>
|
||||
</data>
|
||||
|
||||
|
||||
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
|
||||
<value>Überprüfe STD globale Cache-Integrität...</value>
|
||||
</data>
|
||||
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
|
||||
<value>Fehler beim Überprüfen der globalen STD-Cache-Integrität. Dies deutet auf eine mögliche Datei-/Speicher-Beschädigung hin; stattdessen wird eine neue Instanz initialisiert.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -63,42 +63,42 @@
|
||||
</value>
|
||||
</resheader>
|
||||
<data name="PluginDisabledMissingBuildToken" xml:space="preserve">
|
||||
<value>{0} foi desativado devido à falta de um token de compilação</value>
|
||||
<value>O {0} foi desativado devido a um token de compilação ausente</value>
|
||||
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin")</comment>
|
||||
</data>
|
||||
<data name="PluginDisabledInConfig" xml:space="preserve">
|
||||
<value>{0} está desativado de acordo com sua configuração. Se você gostaria de ajudar o SteamDB no envio de dados, por favor, confira nosso wiki.</value>
|
||||
<value>O {0} está desativado de acordo com a sua configuração. Caso deseje ajudar o SteamDB com o envio de informações, dê uma olhada na nossa wiki.</value>
|
||||
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin")</comment>
|
||||
</data>
|
||||
<data name="PluginInitializedAndEnabled" xml:space="preserve">
|
||||
<value>{0} foi inicializado com sucesso, obrigado antecipadamente pela sua ajuda. O primeiro envio ocorrerá em aproximadamente {1} a partir de agora.</value>
|
||||
<value>O {0} foi inicializado com sucesso, agradecemos a sua ajuda. O primeiro envio ocorrerá em aproximadamente {1}.</value>
|
||||
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin"), {1} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
|
||||
</data>
|
||||
<data name="FileCouldNotBeLoadedFreshInit" xml:space="preserve">
|
||||
<value>{0} não pôde ser carregado, uma instância nova será inicializada...</value>
|
||||
<value>O {0} não pôde ser carregado, uma instância nova será inicializada...</value>
|
||||
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
|
||||
</data>
|
||||
<data name="BotNoAppsToRefresh" xml:space="preserve">
|
||||
<value>Não há aplicativos que necessitem de ser atualizados nesta instância de bot.</value>
|
||||
<value>Não há aplicativos que exijam atualizações na instância atual.</value>
|
||||
</data>
|
||||
<data name="BotRetrievingTotalAppAccessTokens" xml:space="preserve">
|
||||
<value>Recuperando um total de {0} tokens de acesso a aplicativos...</value>
|
||||
<value>Recuperando um total de {0} tokens de acesso para aplicativos...</value>
|
||||
<comment>{0} will be replaced by the number (total count) of app access tokens being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingAppAccessTokens" xml:space="preserve">
|
||||
<value>Recuperando {0} tokens de acesso a aplicativos...</value>
|
||||
<value>Recuperando {0} tokens...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app access tokens being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingAppAccessTokens" xml:space="preserve">
|
||||
<value>Concluímos a recuperação de {0} tokens de acesso ao aplicativo.</value>
|
||||
<value>Recuperamos {0} tokens.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app access tokens retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingTotalAppAccessTokens" xml:space="preserve">
|
||||
<value>Obtivemos um total de {0} tokens de acesso aos aplicativos.</value>
|
||||
<value>Recuperamos um total de {0} tokens de acesso.</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>Recuperando todos os depósitos por um total de {0} apps...</value>
|
||||
<value>Recuperando depots para todos os {0} aplicativos...</value>
|
||||
<comment>{0} will be replaced by the number (total count) of apps being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingAppInfos" xml:space="preserve">
|
||||
@@ -106,38 +106,38 @@
|
||||
<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>Concluímos a recuperação de {0} informações de aplicativo.</value>
|
||||
<value>Recuperamos um total de {0} informações de aplicativos.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>Recuperando {0} chaves de depósito...</value>
|
||||
<value>Recuperando {0} códigos de depots...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of depot keys being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>Terminamos de recuperar {0} chaves de depósito.</value>
|
||||
<value>Recuperamos códigos para {0} depots.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of depot keys retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
|
||||
<value>Terminamos de recuperar todas as chaves de depósito para um total de {0} apps.</value>
|
||||
<value>Recuperamos códigos de acesso para {0} aplicativos.</value>
|
||||
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>
|
||||
</data>
|
||||
<data name="SubmissionNoNewData" xml:space="preserve">
|
||||
<value>Não há novos dados para enviar, tudo está atualizado.</value>
|
||||
<value>Não há novos dados a serem enviados. Tudo está atualizado.</value>
|
||||
</data>
|
||||
<data name="SubmissionNoContributorSet" xml:space="preserve">
|
||||
<value>Não foi possível enviar os dados porque não há um conjunto SteamID válido que possamos classificar como colaborador. Considere configurar a propriedade {0}.</value>
|
||||
<value>Não foi possível enviar os dados porque não conseguimos identificar um ID Steam válido para definir como colaborador. Considere configurar a propriedade "{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>Enviando um total de apps/pacotes/pacotes registrados: {0}/{1}/{2}...</value>
|
||||
<value>Enviando um total de {0} aplicativos, {1} pacotes e {2} depots registrados...</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>O envio falhou devido a muitas solicitações enviadas, tentaremos novamente em aproximadamente {0} a partir de agora.</value>
|
||||
<value>O envio falhou porque muitas solicitações foram enviadas pelo cliente. Tentaremos novamente em aproximadamente {0}.</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
|
||||
</data>
|
||||
<data name="SubmissionSuccessful" xml:space="preserve">
|
||||
<value>Os dados foram enviados com sucesso. O servidor registrou um total de novos aplicativos/pacotes/depósitos: {0} ({1} verificado)/{2} ({3} verificado)/{4} ({5} verificado).</value>
|
||||
<value>Os dados foram enviados com sucesso. O servidor registrou um total de {0} aplicativos ({1} verificados), {2} pacotes ({3} verificados) e {4} depots ({5} verificados).</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">
|
||||
@@ -157,24 +157,24 @@
|
||||
<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>Novos depósitos: {0}</value>
|
||||
<value>Novos depots: {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>Depósitos verificados: {0}</value>
|
||||
<value>Depots verificados: {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} inicializado, o plugin não resolverá nenhum desses: {1}.</value>
|
||||
<value>{0} inicializado, o plugin ignorará os seguintes pacotes: {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>Carregando cache STD global...</value>
|
||||
<value>Carregando cache global do STD...</value>
|
||||
</data>
|
||||
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
|
||||
<value>Validando integridade do cache STD global...</value>
|
||||
<value>Validando integridade do cache global do STD...</value>
|
||||
</data>
|
||||
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
|
||||
<value>Falha ao verificar a integridade do cache STD global. Isso sugere uma potencial corrupção de arquivo/memória, uma instância nova será inicializada em vez disso.</value>
|
||||
<value>Falha ao verificar a integridade do cache global do STD. Isto pode indicar uma possível corrupção de arquivo/memória, uma nova instância será inicializada.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -62,34 +62,119 @@
|
||||
PublicKeyToken=b77a5c561934e089
|
||||
</value>
|
||||
</resheader>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="PluginDisabledMissingBuildToken" xml:space="preserve">
|
||||
<value>由於 {0} 缺少組建權杖而被停用</value>
|
||||
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin")</comment>
|
||||
</data>
|
||||
<data name="PluginDisabledInConfig" xml:space="preserve">
|
||||
<value>目前 {0} 已根據您的設定被停用。如果您想幫助 SteamDB 提交資料,請查看我們的 Wiki。</value>
|
||||
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin")</comment>
|
||||
</data>
|
||||
<data name="PluginInitializedAndEnabled" xml:space="preserve">
|
||||
<value>已成功初始化 {0},預先感謝您的幫助。第一次提交將大約在 {1} 後進行。</value>
|
||||
<comment>{0} will be replaced by the name of the plugin (e.g. "SteamTokenDumperPlugin"), {1} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
|
||||
</data>
|
||||
<data name="FileCouldNotBeLoadedFreshInit" xml:space="preserve">
|
||||
<value>無法載入 {0},將初始化一個新實例…</value>
|
||||
<comment>{0} will be replaced by the name of the file (e.g. "GlobalCache")</comment>
|
||||
</data>
|
||||
<data name="BotNoAppsToRefresh" xml:space="preserve">
|
||||
<value>此 Bot 實例中沒有需要再新的應用程式。</value>
|
||||
</data>
|
||||
<data name="BotRetrievingTotalAppAccessTokens" xml:space="preserve">
|
||||
<value>正在檢索共 {0} 個應用程式存取權杖…</value>
|
||||
<comment>{0} will be replaced by the number (total count) of app access tokens being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingAppAccessTokens" xml:space="preserve">
|
||||
<value>正在檢索 {0} 個應用程式存取權杖…</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app access tokens being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingAppAccessTokens" xml:space="preserve">
|
||||
<value>已完成檢索 {0} 個應用程式存取權杖。</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app access tokens retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingTotalAppAccessTokens" xml:space="preserve">
|
||||
<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} 個應用程式的 Depot…</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="BotRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>正在檢索 {0} 個應用程式的 Depot 金鑰…</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of depot keys being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>已完成檢索 {0} 個應用程式的 Depot。</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of depot keys retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
|
||||
<value>已完成檢索共 {0} 個應用程式的 Depot。</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>正在提交註冊的應用程式/程式包/Depot 共:{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>已成功提交資料。伺服器共已註冊了新的應用程式/程式包/Depot 共:{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>新的 Depot:{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>已被驗證的 Depot:{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>
|
||||
|
||||
@@ -475,7 +475,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotS
|
||||
return;
|
||||
}
|
||||
|
||||
ulong contributorSteamID = ASF.GlobalConfig is { SteamOwnerID: > 0 } && new SteamID(ASF.GlobalConfig.SteamOwnerID).IsIndividualAccount ? ASF.GlobalConfig.SteamOwnerID : Bot.Bots.Values.Where(static bot => bot.SteamID > 0).OrderByDescending(static bot => bot.OwnedPackageIDs.Count).FirstOrDefault()?.SteamID ?? 0;
|
||||
ulong contributorSteamID = ASF.GlobalConfig is { SteamOwnerID: > 0 } && new SteamID(ASF.GlobalConfig.SteamOwnerID).IsIndividualAccount ? ASF.GlobalConfig.SteamOwnerID : Bot.Bots.Values.Where(static bot => bot.SteamID > 0).MaxBy(static bot => bot.OwnedPackageIDs.Count)?.SteamID ?? 0;
|
||||
|
||||
if (contributorSteamID == 0) {
|
||||
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.SubmissionNoContributorSet, nameof(ASF.GlobalConfig.SteamOwnerID)));
|
||||
|
||||
@@ -302,6 +302,7 @@
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseNullPropagation/@EntryIndexedValue">SUGGESTION</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseNullPropagationWhenPossible/@EntryIndexedValue">SUGGESTION</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UsePositionalDeconstructionPattern/@EntryIndexedValue">SUGGESTION</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseStringInterpolationWhenPossible/@EntryIndexedValue">SUGGESTION</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseThrowIfNullMethod/@EntryIndexedValue">WARNING</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseVerbatimString/@EntryIndexedValue">SUGGESTION</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=WrongIndentSize/@EntryIndexedValue">WARNING</s:String>
|
||||
|
||||
126
ArchiSteamFarm/Collections/ObservableConcurrentDictionary.cs
Normal file
126
ArchiSteamFarm/Collections/ObservableConcurrentDictionary.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2022 Ł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;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.Collections;
|
||||
|
||||
public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue> where TKey : notnull {
|
||||
public event EventHandler? OnModified;
|
||||
|
||||
[PublicAPI]
|
||||
public int Count => BackingDictionary.Count;
|
||||
|
||||
[PublicAPI]
|
||||
public bool IsEmpty => BackingDictionary.IsEmpty;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
private readonly ConcurrentDictionary<TKey, TValue> BackingDictionary = new();
|
||||
|
||||
int ICollection<KeyValuePair<TKey, TValue>>.Count => BackingDictionary.Count;
|
||||
int IReadOnlyCollection<KeyValuePair<TKey, TValue>>.Count => BackingDictionary.Count;
|
||||
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => BackingDictionary.Keys;
|
||||
ICollection<TKey> IDictionary<TKey, TValue>.Keys => BackingDictionary.Keys;
|
||||
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => BackingDictionary.Values;
|
||||
ICollection<TValue> IDictionary<TKey, TValue>.Values => BackingDictionary.Values;
|
||||
|
||||
public TValue this[TKey key] {
|
||||
get => BackingDictionary[key];
|
||||
set {
|
||||
if (BackingDictionary.TryGetValue(key, out TValue? savedValue) && EqualityComparer<TValue>.Default.Equals(savedValue, value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
BackingDictionary[key] = value;
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<TKey, TValue> item) {
|
||||
(TKey key, TValue value) = item;
|
||||
|
||||
Add(key, value);
|
||||
}
|
||||
|
||||
public void Add(TKey key, TValue value) => TryAdd(key, value);
|
||||
|
||||
public void Clear() {
|
||||
if (BackingDictionary.IsEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
BackingDictionary.Clear();
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<TKey, TValue> item) => ((ICollection<KeyValuePair<TKey, TValue>>) BackingDictionary).Contains(item);
|
||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) => ((ICollection<KeyValuePair<TKey, TValue>>) BackingDictionary).CopyTo(array, arrayIndex);
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => BackingDictionary.GetEnumerator();
|
||||
|
||||
public bool Remove(KeyValuePair<TKey, TValue> item) {
|
||||
ICollection<KeyValuePair<TKey, TValue>> collection = BackingDictionary;
|
||||
|
||||
if (!collection.Remove(item)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Remove(TKey key) {
|
||||
if (!BackingDictionary.TryRemove(key, out _)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IDictionary<TKey, TValue>.ContainsKey(TKey key) => BackingDictionary.ContainsKey(key);
|
||||
bool IReadOnlyDictionary<TKey, TValue>.ContainsKey(TKey key) => BackingDictionary.ContainsKey(key);
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
bool IReadOnlyDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value) => BackingDictionary.TryGetValue(key, out value!);
|
||||
bool IDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value) => BackingDictionary.TryGetValue(key, out value!);
|
||||
|
||||
[PublicAPI]
|
||||
public bool TryAdd(TKey key, TValue value) {
|
||||
if (!BackingDictionary.TryAdd(key, value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public bool TryGetValue(TKey key, out TValue? value) => BackingDictionary.TryGetValue(key, out value);
|
||||
}
|
||||
@@ -41,7 +41,7 @@ using ArchiSteamFarm.Web;
|
||||
|
||||
namespace ArchiSteamFarm.Core;
|
||||
|
||||
internal sealed class Statistics : IAsyncDisposable {
|
||||
internal sealed class RemoteCommunication : IAsyncDisposable {
|
||||
private const ushort MaxItemsForFairBots = ArchiWebHandler.MaxItemsInSingleInventoryRequest * WebBrowser.MaxTries; // Determines which fair bots we'll deprioritize when matching due to excessive number of inventory requests they need to make, which are likely to fail in the process or cause excessive delays
|
||||
private const byte MaxMatchedBotsHard = 40; // Determines how many bots we can attempt to match in total, where match attempt is equal to analyzing bot's inventory
|
||||
private const byte MaxMatchingRounds = 10; // Determines maximum amount of matching rounds we're going to consider before leaving the rest of work for the next batch
|
||||
@@ -61,7 +61,7 @@ internal sealed class Statistics : IAsyncDisposable {
|
||||
private readonly SemaphoreSlim MatchActivelySemaphore = new(1, 1);
|
||||
|
||||
#pragma warning disable CA2213 // False positive, .NET Framework can't understand DisposeAsync()
|
||||
private readonly Timer MatchActivelyTimer;
|
||||
private readonly Timer? MatchActivelyTimer;
|
||||
#pragma warning restore CA2213 // False positive, .NET Framework can't understand DisposeAsync()
|
||||
|
||||
private readonly SemaphoreSlim RequestsSemaphore = new(1, 1);
|
||||
@@ -71,25 +71,33 @@ internal sealed class Statistics : IAsyncDisposable {
|
||||
private DateTime LastPersonaStateRequest;
|
||||
private bool ShouldSendHeartBeats;
|
||||
|
||||
internal Statistics(Bot bot) {
|
||||
internal RemoteCommunication(Bot bot) {
|
||||
Bot = bot ?? throw new ArgumentNullException(nameof(bot));
|
||||
|
||||
MatchActivelyTimer = new Timer(
|
||||
MatchActively,
|
||||
null,
|
||||
TimeSpan.FromHours(1) + TimeSpan.FromSeconds(ASF.LoadBalancingDelay * Bot.Bots?.Count ?? 0), // Delay
|
||||
TimeSpan.FromHours(8) // Period
|
||||
);
|
||||
if (Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchActively)) {
|
||||
MatchActivelyTimer = new Timer(
|
||||
MatchActively,
|
||||
null,
|
||||
TimeSpan.FromHours(1) + TimeSpan.FromSeconds(ASF.LoadBalancingDelay * Bot.Bots?.Count ?? 0), // Delay
|
||||
TimeSpan.FromHours(8) // Period
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync() {
|
||||
MatchActivelySemaphore.Dispose();
|
||||
RequestsSemaphore.Dispose();
|
||||
|
||||
await MatchActivelyTimer.DisposeAsync().ConfigureAwait(false);
|
||||
if (MatchActivelyTimer != null) {
|
||||
await MatchActivelyTimer.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task OnHeartBeat() {
|
||||
if (!Bot.BotConfig.RemoteCommunication.HasFlag(BotConfig.ERemoteCommunication.PublicListing)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Request persona update if needed
|
||||
if ((DateTime.UtcNow > LastPersonaStateRequest.AddHours(MinPersonaStateTTL)) && (DateTime.UtcNow > LastAnnouncementCheck.AddHours(MinAnnouncementCheckTTL))) {
|
||||
LastPersonaStateRequest = DateTime.UtcNow;
|
||||
@@ -125,12 +133,20 @@ internal sealed class Statistics : IAsyncDisposable {
|
||||
}
|
||||
|
||||
internal async Task OnLoggedOn() {
|
||||
if (!Bot.BotConfig.RemoteCommunication.HasFlag(BotConfig.ERemoteCommunication.SteamGroup)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await Bot.ArchiWebHandler.JoinGroup(SharedInfo.ASFGroupSteamID).ConfigureAwait(false)) {
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(ArchiWebHandler.JoinGroup)));
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task OnPersonaState(string? nickname = null, string? avatarHash = null) {
|
||||
if (!Bot.BotConfig.RemoteCommunication.HasFlag(BotConfig.ERemoteCommunication.PublicListing)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((DateTime.UtcNow < LastAnnouncementCheck.AddHours(MinAnnouncementCheckTTL)) && (ShouldSendHeartBeats || (LastHeartBeat == DateTime.MinValue))) {
|
||||
return;
|
||||
}
|
||||
@@ -229,12 +245,20 @@ internal sealed class Statistics : IAsyncDisposable {
|
||||
}
|
||||
|
||||
private async Task<bool?> IsEligibleForListing() {
|
||||
// Bot must be eligible for matching first
|
||||
bool? isEligibleForMatching = await IsEligibleForMatching().ConfigureAwait(false);
|
||||
|
||||
if (isEligibleForMatching != true) {
|
||||
return isEligibleForMatching;
|
||||
}
|
||||
|
||||
// Bot must have STM enabled in TradingPreferences
|
||||
if (!Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.SteamTradeMatcher)) {
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, $"{nameof(Bot.BotConfig.TradingPreferences)}: {Bot.BotConfig.TradingPreferences}"));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bot must have public inventory
|
||||
bool? hasPublicInventory = await Bot.HasPublicInventory().ConfigureAwait(false);
|
||||
|
||||
@@ -255,13 +279,6 @@ internal sealed class Statistics : IAsyncDisposable {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bot must have STM enable in TradingPreferences
|
||||
if (!Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.SteamTradeMatcher)) {
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, $"{nameof(Bot.BotConfig.TradingPreferences)}: {Bot.BotConfig.TradingPreferences}"));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bot must have at least one accepted matchable type set
|
||||
if ((Bot.BotConfig.MatchableTypes.Count == 0) || Bot.BotConfig.MatchableTypes.All(static type => !AcceptedMatchableTypes.Contains(type))) {
|
||||
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, $"{nameof(Bot.BotConfig.MatchableTypes)}: {string.Join(", ", Bot.BotConfig.MatchableTypes)}"));
|
||||
@@ -101,7 +101,9 @@
|
||||
<value>{0} е невалиден!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
|
||||
<data name="ErrorNoBotsDefined" xml:space="preserve">
|
||||
<value>Бота не е настроен. Забравихте ли да проверите конфигурацията на ASF? Следвайте инструкцийте в "wiki" ако срещате трудности.</value>
|
||||
</data>
|
||||
<data name="ErrorObjectIsNull" xml:space="preserve">
|
||||
<value>{0} е нулев!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
@@ -189,7 +191,10 @@
|
||||
<value>Моля въведете Вашият 2FA код от Steam authenticator приложението: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
|
||||
<data name="UserInputSteamGuard" xml:space="preserve">
|
||||
<value>Моля, въведете SteamGuard кода, изпратен на Вашият e-mail: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputSteamLogin" xml:space="preserve">
|
||||
<value>Моля, въведете Вашият Steam login: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
@@ -219,38 +224,86 @@
|
||||
<value>Не е намерен бот с името {0}!</value>
|
||||
<comment>{0} will be replaced by bot's name query (string)</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
<data name="BotStatusOverview" xml:space="preserve">
|
||||
<value>Има {0}/{1} работещи бота, с общо {2} игри ({3} карти) оставащи за "фармене".</value>
|
||||
<comment>{0} will be replaced by number of active bots, {1} will be replaced by total number of bots, {2} will be replaced by total number of games left to farm, {3} will be replaced by total number of cards left to farm</comment>
|
||||
</data>
|
||||
<data name="BotStatusIdling" xml:space="preserve">
|
||||
<value>Ботът фарми играта: {0} ({1}, {2} оставащи карти) от общо {3} игри ({4} карти) остават за "фармене" (~{5} оставащо време).</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by number of cards left to farm, {3} will be replaced by total number of games to farm, {4} will be replaced by total number of cards to farm, {5} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="BotStatusIdlingList" xml:space="preserve">
|
||||
<value>Ботът фарми игрите: {0} от общо {1} игри ({2} карти) остават за "фармене" (~{3} оставащо време).</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), {1} will be replaced by total number of games to farm, {2} will be replaced by total number of cards to farm, {3} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="CheckingFirstBadgePage" xml:space="preserve">
|
||||
<value>Проверяване на първата страница със значки...</value>
|
||||
</data>
|
||||
<data name="CheckingOtherBadgePages" xml:space="preserve">
|
||||
<value>Проверяване на други страници със значки...</value>
|
||||
</data>
|
||||
|
||||
<data name="ChosenFarmingAlgorithm" xml:space="preserve">
|
||||
<value>Избран алгоритъм за фармене: {0}</value>
|
||||
<comment>{0} will be replaced by the name of chosen farming algorithm</comment>
|
||||
</data>
|
||||
<data name="Done" xml:space="preserve">
|
||||
<value>Готово!</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="GamesToIdle" xml:space="preserve">
|
||||
<value>Остават общо {0} игри ({1} карти) за "фармене" (~{2} оставащо време)...</value>
|
||||
<comment>{0} will be replaced by number of games, {1} will be replaced by number of cards, {2} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="IdlingFinished" xml:space="preserve">
|
||||
<value>Фарменето на карти приключи!</value>
|
||||
</data>
|
||||
<data name="IdlingFinishedForGame" xml:space="preserve">
|
||||
<value>Фарменето на карти завърши: {0} ({1}) след {2} игра!</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="IdlingFinishedForGames" xml:space="preserve">
|
||||
<value>Фарменето на карти завърши за игрите: {0}</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="IdlingStatusForGame" xml:space="preserve">
|
||||
<value>Статус на фарменето за {0} ({1}): {2} карти остават</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by number of cards left to farm</comment>
|
||||
</data>
|
||||
<data name="IdlingStopped" xml:space="preserve">
|
||||
<value>Фарменето е спряно!</value>
|
||||
</data>
|
||||
<data name="IgnoredPermanentPauseEnabled" xml:space="preserve">
|
||||
<value>Отказване на това желание, тъй като е активирана постоянна пауза!</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
<data name="NothingToIdle" xml:space="preserve">
|
||||
<value>Няма нищо за фармене на този акаунт!</value>
|
||||
</data>
|
||||
<data name="NowIdling" xml:space="preserve">
|
||||
<value>Сега се фарми: {0} ({1})</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="NowIdlingList" xml:space="preserve">
|
||||
<value>Сега се фарми: {0}</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="PlayingNotAvailable" xml:space="preserve">
|
||||
<value>Игрането не е възможно в момента, ще пробваме по-късно!</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="StillIdling" xml:space="preserve">
|
||||
<value>Все още се фарми: {0} ({1})</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="StillIdlingList" xml:space="preserve">
|
||||
<value>Все още се фарми: {0}</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="StoppedIdling" xml:space="preserve">
|
||||
<value>Фарменето прекратено: {0} ({1})</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="StoppedIdlingList" xml:space="preserve">
|
||||
<value>Фарменето прекратено: {0}</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="UnknownCommand" xml:space="preserve">
|
||||
<value>Непозната команда!</value>
|
||||
</data>
|
||||
@@ -265,7 +318,9 @@
|
||||
<value>Приемане на подарък: {0}...</value>
|
||||
<comment>{0} will be replaced by giftID (number)</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotAccountLimited" xml:space="preserve">
|
||||
<value>Този акаунт е ограничен, фарменето е невъзможен докато ограничението не бъде премахнато!</value>
|
||||
</data>
|
||||
<data name="BotAddLicense" xml:space="preserve">
|
||||
<value>ID: {0} | Статус: {1}</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by status string</comment>
|
||||
@@ -287,10 +342,18 @@
|
||||
<value>2FA Token: {0}</value>
|
||||
<comment>{0} will be replaced by generated 2FA token (string)</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="BotAutomaticIdlingNowPaused" xml:space="preserve">
|
||||
<value>Автоматичното фармене е паузирано!</value>
|
||||
</data>
|
||||
<data name="BotAutomaticIdlingNowResumed" xml:space="preserve">
|
||||
<value>Автоматичното фармене е възобновено!</value>
|
||||
</data>
|
||||
<data name="BotAutomaticIdlingPausedAlready" xml:space="preserve">
|
||||
<value>Автоматичното фармене е вече паузирано!</value>
|
||||
</data>
|
||||
<data name="BotAutomaticIdlingResumedAlready" xml:space="preserve">
|
||||
<value>Автоматичното фармене е вече възобновено!</value>
|
||||
</data>
|
||||
<data name="BotConnected" xml:space="preserve">
|
||||
<value>Свързан към Steam!</value>
|
||||
</data>
|
||||
@@ -347,7 +410,10 @@
|
||||
<value>Вече се притежава: {0} | {1}</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotPointsBalance" xml:space="preserve">
|
||||
<value>Баланс на точките: {0}</value>
|
||||
<comment>{0} will be replaced by the points balance value (integer)</comment>
|
||||
</data>
|
||||
<data name="BotRateLimitExceeded" xml:space="preserve">
|
||||
<value>Ограничението надвишено, ще повторим след {0} на "отброяване"...</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "25 minutes")</comment>
|
||||
@@ -366,8 +432,12 @@
|
||||
<data name="BotRemovedExpiredLoginKey" xml:space="preserve">
|
||||
<value>Премахнат изтекъл ключ за вход!</value>
|
||||
</data>
|
||||
|
||||
|
||||
<data name="BotStatusNotIdling" xml:space="preserve">
|
||||
<value>Бота не фарми нищо.</value>
|
||||
</data>
|
||||
<data name="BotStatusLimited" xml:space="preserve">
|
||||
<value>Бота е ограничен и не може да "дропне" карти чрез фармене.</value>
|
||||
</data>
|
||||
<data name="BotStatusConnecting" xml:space="preserve">
|
||||
<value>Ботът се свързва към мрежата на Steam.</value>
|
||||
</data>
|
||||
@@ -399,8 +469,12 @@
|
||||
<data name="BotConnectionLost" xml:space="preserve">
|
||||
<value>Връзката с мрежата на Steam е загубена. Повторно свързване...</value>
|
||||
</data>
|
||||
|
||||
|
||||
<data name="BotAccountFree" xml:space="preserve">
|
||||
<value>Акаунтът вече не е зает: фарменето на карти е възобновено!</value>
|
||||
</data>
|
||||
<data name="BotAccountOccupied" xml:space="preserve">
|
||||
<value>Акаунтът в момента се ползва: ASF ще възобнови фарменето на карти, когато е свободен...</value>
|
||||
</data>
|
||||
<data name="BotConnecting" xml:space="preserve">
|
||||
<value>Свързване…</value>
|
||||
</data>
|
||||
@@ -438,7 +512,10 @@
|
||||
<value>ASF ще се опита да ползва вашият предпочитан {0} език, но преводът за този език е само {1} завършен. Може би може да ни помогнете да подобрим ASF с превод на вашия език?</value>
|
||||
<comment>{0} will be replaced by culture code, such as "en-US", {1} will be replaced by completeness percentage, such as "78.5%"</comment>
|
||||
</data>
|
||||
|
||||
<data name="IdlingGameNotPossible" xml:space="preserve">
|
||||
<value>Фарменето на {0} ({1}) е временно забранено, тъй като ASF не е в състояние да "играе" тази игра в момента.</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="WarningIdlingGameMismatch" xml:space="preserve">
|
||||
<value>ASF откри несъответствие на ID за {0} ({1}) и ще използва ID на {2} вместо това.</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by game's ID (number)</comment>
|
||||
@@ -447,8 +524,12 @@
|
||||
<value>{0} V{1}</value>
|
||||
<comment>{0} will be replaced by program's name (e.g. "ASF"), {1} will be replaced by program's version (e.g. "1.0.0.0"). This string typically has nothing to translate and you should leave it as it is, unless you need to change the format, e.g. in RTL languages.</comment>
|
||||
</data>
|
||||
|
||||
|
||||
<data name="BotAccountLocked" xml:space="preserve">
|
||||
<value>Този акаунт е заключен, фарменето на карти е недостъпно завинаги!</value>
|
||||
</data>
|
||||
<data name="BotStatusLocked" xml:space="preserve">
|
||||
<value>Бота е заключен и не може да "дропне" карти чрез фармене.</value>
|
||||
</data>
|
||||
<data name="ErrorFunctionOnlyInHeadlessMode" xml:space="preserve">
|
||||
<value>Тази функция е възможна само в headless режим!</value>
|
||||
</data>
|
||||
@@ -459,7 +540,9 @@
|
||||
<data name="ErrorAccessDenied" xml:space="preserve">
|
||||
<value>Достъпът отказан!</value>
|
||||
</data>
|
||||
|
||||
<data name="WarningPreReleaseVersion" xml:space="preserve">
|
||||
<value>Вие използвате версия, която е по-нова от последната версия за актуализация. Моля, имайте предвид, че предварителните версии са посветени на потребителите, които знаят как да докладват за бъгове, да се справят с проблеми и да дават обратна връзка - няма да ви бъде осигурена техническа подръжка.</value>
|
||||
</data>
|
||||
<data name="BotStats" xml:space="preserve">
|
||||
<value>Текущо използване на паметта: {0} MB.
|
||||
Работно време на процеса: {1}</value>
|
||||
@@ -562,7 +645,10 @@
|
||||
<value>Успешно извършени {0} потвърждения!</value>
|
||||
<comment>{0} will be replaced by number of confirmations</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotExtraIdlingCooldown" xml:space="preserve">
|
||||
<value>Изчакване до {0}, за да се гарантира, че е може да започне фарменето на карти...</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "1 minute")</comment>
|
||||
</data>
|
||||
<data name="UpdateCleanup" xml:space="preserve">
|
||||
<value>Изтриване на старите файлове след обновление...</value>
|
||||
</data>
|
||||
|
||||
@@ -195,7 +195,7 @@ StackTrace:
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputSteamGuard" xml:space="preserve">
|
||||
<value>Bitte gebe den SteamGuard Authentifizierungstoken ein, der an deine E-Mail Adresse geschickt wurde: </value>
|
||||
<value>Bitte gebe den SteamGuard Authentifizierungstoken ein, der an Ihre E-Mail Adresse geschickt wurde: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputSteamLogin" xml:space="preserve">
|
||||
@@ -445,10 +445,10 @@ StackTrace:
|
||||
<value>Bot stellt eine Verbindung zum Steam-Netzwerk her.</value>
|
||||
</data>
|
||||
<data name="BotStatusNotRunning" xml:space="preserve">
|
||||
<value>Bot läuft nicht.</value>
|
||||
<value>Bot ist nicht in Betrieb.</value>
|
||||
</data>
|
||||
<data name="BotStatusPaused" xml:space="preserve">
|
||||
<value>Bot ist pausiert oder läuft im manuellen Modus.</value>
|
||||
<value>Bot ist pausiert oder wird im manuellen Modus ausgeführt.</value>
|
||||
</data>
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Bot wird zurzeit benutzt.</value>
|
||||
@@ -476,7 +476,7 @@ StackTrace:
|
||||
<value>Benutzerkonto ist nicht mehr in Verwendung: Sammelprozess fortgesetzt!</value>
|
||||
</data>
|
||||
<data name="BotAccountOccupied" xml:space="preserve">
|
||||
<value>Benutzerkonto ist in Verwendung: ASF wird den Sammelprozess fortsetzen, wenn es wieder möglich ist...</value>
|
||||
<value>Benutzerkonto ist in Verwendung: ASF wird den Sammelprozess fortsetzen, sobald diese wieder verfügbar ist...</value>
|
||||
</data>
|
||||
<data name="BotConnecting" xml:space="preserve">
|
||||
<value>Verbinde...</value>
|
||||
@@ -506,7 +506,7 @@ StackTrace:
|
||||
<value>Bitte lesen Sie den Abschnitt zu unseren Datenschutzrichtlinien im Wiki, wenn Sie wissen möchten was ASF im Detail alles macht!</value>
|
||||
</data>
|
||||
<data name="Welcome" xml:space="preserve">
|
||||
<value>Es sieht so aus, als ob Sie das Programm zum ersten Mal gestartet haben, willkommen!</value>
|
||||
<value>Es sieht so aus, als ob Sie das Programm zum ersten Mal gestartet haben. Willkommen!</value>
|
||||
</data>
|
||||
<data name="ErrorInvalidCurrentCulture" xml:space="preserve">
|
||||
<value>Ihre angegebene CurrentCulture ist ungültig, ASF wird weiterhin mit dem Standard laufen!</value>
|
||||
|
||||
@@ -222,20 +222,34 @@ StackTrace: {2}</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
<data name="BotStatusIdlingList" xml:space="preserve">
|
||||
<value>A bot a {0} játékot farmolja. Összesen {1} játék ({2} kártya) maradt hátra (kb. {3} mire végez).</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), {1} will be replaced by total number of games to farm, {2} will be replaced by total number of cards to farm, {3} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="CheckingFirstBadgePage" xml:space="preserve">
|
||||
<value>A kitűzők első oldalának ellenőrzése...</value>
|
||||
</data>
|
||||
<data name="CheckingOtherBadgePages" xml:space="preserve">
|
||||
<value>A többi kitűző oldal ellenőrzése...</value>
|
||||
</data>
|
||||
|
||||
<data name="ChosenFarmingAlgorithm" xml:space="preserve">
|
||||
<value>A kiválasztott farmoló algoritmus: {0}</value>
|
||||
<comment>{0} will be replaced by the name of chosen farming algorithm</comment>
|
||||
</data>
|
||||
<data name="Done" xml:space="preserve">
|
||||
<value>Kész!</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
<data name="GamesToIdle" xml:space="preserve">
|
||||
<value>Még összesen {0} játék ({1} kártya) maradt (kb. {2} míg végez)...</value>
|
||||
<comment>{0} will be replaced by number of games, {1} will be replaced by number of cards, {2} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="IdlingFinished" xml:space="preserve">
|
||||
<value>Farmolás befejezve!</value>
|
||||
</data>
|
||||
<data name="IdlingFinishedForGame" xml:space="preserve">
|
||||
<value>Farmolás befejezve: {0} ({1}) készen van {2} játékidő után!</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
|
||||
<data name="IdlingStatusForGame" xml:space="preserve">
|
||||
<value>Farmolás állapota a következő a játékhoz: {0} ({1}): {2} kártya maradt</value>
|
||||
|
||||
@@ -105,7 +105,7 @@ StackTrace:
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorNoBotsDefined" xml:space="preserve">
|
||||
<value>Nenhum bot foi definido. Você esqueceu de configurar seu Archi Steam Farm? Siga o guia de "configuração" no wiki se você estiver com dúvidas.</value>
|
||||
<value>Nenhum bot definido. Você esqueceu de configurar o ASF? Confira o guia "Primeiros passos" na wiki caso esteja com dúvidas.</value>
|
||||
</data>
|
||||
<data name="ErrorObjectIsNull" xml:space="preserve">
|
||||
<value>{0} é nulo!</value>
|
||||
@@ -194,7 +194,10 @@ StackTrace:
|
||||
<value>Insira o código gerado pelo autenticador móvel do Steam: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
|
||||
<data name="UserInputSteamGuard" xml:space="preserve">
|
||||
<value>Insira o código do Steam Guard recebido por e-mail: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputSteamLogin" xml:space="preserve">
|
||||
<value>Insira o seu nome de usuário Steam: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
@@ -689,48 +692,48 @@ Tempo de execução: {1}</value>
|
||||
<comment>{0} will be replaced with the relative path to the affected config file</comment>
|
||||
</data>
|
||||
<data name="WarningWeakIPCPassword" xml:space="preserve">
|
||||
<value>Sua senha IPC parece muito fraca. Considere escolher uma senha mais forte para melhorar sua segurança. Detalhe: {0}</value>
|
||||
<value>A senha do servidor IPC é muito fraca. Escolha uma senha mais forte para aumentar a segurança. Detalhes: {0}</value>
|
||||
<comment>{0} will be replaced by additional details about the password being considered weak</comment>
|
||||
</data>
|
||||
<data name="WarningWeakSteamPassword" xml:space="preserve">
|
||||
<value>Sua senha Steam para '{0}' parece ser fraca. Considere escolher uma mais forte para aumentar a segurança. Detalhes: {1}</value>
|
||||
<value>A senha da conta Steam "{0}" é muito fraca. Escolha uma senha mais forte para aumentar a segurança. Detalhes: {0}</value>
|
||||
<comment>{0} will be replaced by either the affected bot name or the path to the bots configuration file, {1} will be replaced by additional details about the password being considered weak</comment>
|
||||
</data>
|
||||
<data name="WarningWeakCryptKey" xml:space="preserve">
|
||||
<value>Sua chave de criptografia parece ser fraca. Considere escolher uma mais forte para melhorar sua segurança. Detalhe: {0}</value>
|
||||
<value>A chave de criptografia é muito fraca. Escolha uma senha mais forte para aumentar a segurança. Detalhes: {0}</value>
|
||||
<comment>{0} will be replaced by additional details about the encryption key being considered weak</comment>
|
||||
</data>
|
||||
<data name="WarningTooShortCryptKey" xml:space="preserve">
|
||||
<value>Sua chave de criptografia é muito curta. É recomendado utilizar uma com no mínimo {0} caracteres.</value>
|
||||
<value>A chave de criptografia é muito curta. Recomendamos usar uma chave que tenha pelo menos {0} bytes (caracteres) de comprimento.</value>
|
||||
<comment>{0} will be replaced by the number of bytes (characters) recommended</comment>
|
||||
</data>
|
||||
<data name="WarningDefaultCryptKeyUsedForHashing" xml:space="preserve">
|
||||
<value>Você está utilizando {0} configuração de {1}, mas você não forneceu uma senha de criptografia personalizada. Você deve fornecer uma senha de criptografia para melhorar sua segurança.</value>
|
||||
<value>Você está usando a configuração {0} da propriedade {1} sem uma --cryptkey personalizada. Forneça uma --cryptkey personalizada para aumentar a segurança.</value>
|
||||
<comment>{0} will be replaced by the name of a particular setting (e.g. "SCrypt"), {1} will be replaced by the name of the property (e.g. "IPCPassword")</comment>
|
||||
</data>
|
||||
<data name="WarningDefaultCryptKeyUsedForEncryption" xml:space="preserve">
|
||||
<value>Você está utilizando {0} configuração de {1}, mas você não forneceu uma senha de criptografia personalizada. Isto anula totalmente a proteção, porque o Archi Steam Farm é forçado a utilizar sua própria senha (pública). Você deverá fornecer uma senha de criptografia personalizada para utilizar os benefícios de segurança fornecidos por esta configuração.</value>
|
||||
<value>Você está usando a configuração {0} da propriedade {1} sem uma --cryptkey personalizada. O ASF será forçado a usar a chave padrão, que é menos segura e inutiliza a proteção configurada. Forneça uma --cryptkey personalizada para se beneficiar da segurança adicional.</value>
|
||||
<comment>{0} will be replaced by the name of a particular setting (e.g. "AES"), {1} will be replaced by the name of the property (e.g. "SteamPassword")</comment>
|
||||
</data>
|
||||
<data name="WarningRunningAsRoot" xml:space="preserve">
|
||||
<value>Você está utilizando o Archi Steam Farm como administrador (Root). Isto não é necessário, e poderá trazer riscos a sua máquina. É recomendado rodar em modo usuário se possível.</value>
|
||||
<value>Você está tentando executar o ASF como administrador (root). Isto causa um risco de segurança significativo à sua máquina e já que o ASF não requer acesso root para o seu funcionamento, recomendamos executá-lo como usuário não-administrador, se possível.</value>
|
||||
</data>
|
||||
<data name="WarningRunningInUnsupportedEnvironment" xml:space="preserve">
|
||||
<value>Você está executando o Archi Steam Farm em um ambiente não suportado, utilizando o argumento --ignore-unported-environment. Por favor, note que não oferecemos qualquer tipo de apoio a este cenário, e você está fazendo isso inteiramente por sua conta e risco! Você foi avisado.</value>
|
||||
<value>Você está executando o ASF em um ambiente não suportado, utilizando o argumento --ignore-unported-environment. Por favor, note que não oferecemos qualquer tipo de apoio a este cenário, e você está fazendo isso inteiramente por sua conta e risco! Você foi avisado.</value>
|
||||
</data>
|
||||
<data name="FetchingChecksumFromRemoteServer" xml:space="preserve">
|
||||
<value>Obtendo checksum do servidor remoto...</value>
|
||||
</data>
|
||||
<data name="VerifyingChecksumWithRemoteServer" xml:space="preserve">
|
||||
<value>Comparando a checksum do binário baixado junto ao servidor remoto...</value>
|
||||
<value>Comparando a checksum do binário baixado com a do servidor remoto...</value>
|
||||
</data>
|
||||
<data name="ChecksumMissing" xml:space="preserve">
|
||||
<value>O servidor remoto não reconhece a versão para a qual estamos atualizando. Esta situação pode ser possível se a versão foi publicada recentemente, recusando-se a prosseguir imediatamente com o processo de atualização, como uma medida de segurança adicional.</value>
|
||||
<value>O servidor remoto não sabe nada sobre a versão para a qual estamos atualizando. Esta situação é possível se a atualização foi publicada recentemente - recusando-se a prosseguir com o processo de atualização imediatamente como uma medida de segurança adicional.</value>
|
||||
</data>
|
||||
<data name="ChecksumWrong" xml:space="preserve">
|
||||
<value>O servidor remoto retornou com uma chave de verificação diferente. Isto pode indicar que o download corrompeu, ou um ataque MITM, não será possível prosseguir com a atualização!</value>
|
||||
<value>O servidor remoto respondeu com um checksum diferente, isto pode indicar download corrompido ou um ataque MITM, recusando-se a prosseguir com o procedimento de atualização!</value>
|
||||
</data>
|
||||
<data name="PatchingFiles" xml:space="preserve">
|
||||
<value>Extraindo arquivos do Archi Steam Farm...</value>
|
||||
<value>Atualizando arquivos do ASF...</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -697,7 +697,7 @@ Process uptime: {1}</value>
|
||||
</data>
|
||||
<data name="WarningWeakSteamPassword" xml:space="preserve">
|
||||
<value>Your Steam password for '{0}' seems to be weak. Consider choosing a stronger one for increased security. Details: {1}</value>
|
||||
<comment>{0} will be replaced by either the affected bot name or the path to the bots configuration file, {1} will be replaced by additional details about the password being considered weak</comment>
|
||||
<comment>{0} will be replaced by the affected bot name, {1} will be replaced by additional details about the password being considered weak</comment>
|
||||
</data>
|
||||
<data name="WarningWeakCryptKey" xml:space="preserve">
|
||||
<value>Your encryption key seems to be weak. Consider choosing a stronger one for increased security. Details: {0}</value>
|
||||
|
||||
@@ -104,7 +104,9 @@
|
||||
<value>{0} невірний!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
|
||||
<data name="ErrorNoBotsDefined" xml:space="preserve">
|
||||
<value>Не налаштовано жодного боту. Можливо ви забули налаштувати ASF? Скористуйтеся розділом 'налаштування' у нашій wiki, якщо не знаєте, що робити.</value>
|
||||
</data>
|
||||
<data name="ErrorObjectIsNull" xml:space="preserve">
|
||||
<value>{0} має значення null!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
@@ -192,7 +194,10 @@
|
||||
<value>Будь ласка, введіть ваш код 2FA з додатку для автентифікації у Steam: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
|
||||
<data name="UserInputSteamGuard" xml:space="preserve">
|
||||
<value>Будь ласка, введіть код SteamGuard, який був надісланий вам на електронну пошту: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputSteamLogin" xml:space="preserve">
|
||||
<value>Будь ласка, введіть свій Steam логін: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
@@ -222,38 +227,86 @@
|
||||
<value>Не вдалося знайти бота з ім'ям {0}!</value>
|
||||
<comment>{0} will be replaced by bot's name query (string)</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
<data name="BotStatusOverview" xml:space="preserve">
|
||||
<value>Запущено {0}/{1} ботів, усього залишилось {2} ігор ({3} карток) які потрібно фармити.</value>
|
||||
<comment>{0} will be replaced by number of active bots, {1} will be replaced by total number of bots, {2} will be replaced by total number of games left to farm, {3} will be replaced by total number of cards left to farm</comment>
|
||||
</data>
|
||||
<data name="BotStatusIdling" xml:space="preserve">
|
||||
<value>Бот фармить гру: {0} ({1}, {2} карток залишилося обробити). Всього залишилося {3} ігор ({4} карток), (приблизно {5}) які треба фармити.</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by number of cards left to farm, {3} will be replaced by total number of games to farm, {4} will be replaced by total number of cards to farm, {5} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="BotStatusIdlingList" xml:space="preserve">
|
||||
<value>Бот фармить ігри: {0} з {1} ігор ({2} картки) які треба фармити (залишилось приблизно {3}).</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), {1} will be replaced by total number of games to farm, {2} will be replaced by total number of cards to farm, {3} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="CheckingFirstBadgePage" xml:space="preserve">
|
||||
<value>Перевірка першої сторінки зі значками...</value>
|
||||
</data>
|
||||
<data name="CheckingOtherBadgePages" xml:space="preserve">
|
||||
<value>Перевірка інших сторінок зі значками...</value>
|
||||
</data>
|
||||
|
||||
<data name="ChosenFarmingAlgorithm" xml:space="preserve">
|
||||
<value>Обраний алгоритм фарму: {0}</value>
|
||||
<comment>{0} will be replaced by the name of chosen farming algorithm</comment>
|
||||
</data>
|
||||
<data name="Done" xml:space="preserve">
|
||||
<value>Виконано!</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="GamesToIdle" xml:space="preserve">
|
||||
<value>Всього залишилось {0} ігор ({1} карток), які потрібно фармити (приблизно {2})...</value>
|
||||
<comment>{0} will be replaced by number of games, {1} will be replaced by number of cards, {2} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="IdlingFinished" xml:space="preserve">
|
||||
<value>Фарм закінчено!</value>
|
||||
</data>
|
||||
<data name="IdlingFinishedForGame" xml:space="preserve">
|
||||
<value>Закінчено фарм: {0} ({1}) після {2} ігрового часу!</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="IdlingFinishedForGames" xml:space="preserve">
|
||||
<value>Закінчено фарм ігор: {0}</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="IdlingStatusForGame" xml:space="preserve">
|
||||
<value>Статус фарма для {0} ({1}): залишилося {2} карт</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by number of cards left to farm</comment>
|
||||
</data>
|
||||
<data name="IdlingStopped" xml:space="preserve">
|
||||
<value>Фарм зупинено!</value>
|
||||
</data>
|
||||
<data name="IgnoredPermanentPauseEnabled" xml:space="preserve">
|
||||
<value>Ігнорування запиту, тому що ввімкнена постійна пауза!</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
<data name="NothingToIdle" xml:space="preserve">
|
||||
<value>На цьому акаунті нема чого фармити!</value>
|
||||
</data>
|
||||
<data name="NowIdling" xml:space="preserve">
|
||||
<value>Зараз фармиться: {0} ({1})</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="NowIdlingList" xml:space="preserve">
|
||||
<value>Зараз фармиться: {0}</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="PlayingNotAvailable" xml:space="preserve">
|
||||
<value>Зараз запустити ігру неможливо, спробуємо пізніше!</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="StillIdling" xml:space="preserve">
|
||||
<value>Все ще фармиться: {0} ({1})</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="StillIdlingList" xml:space="preserve">
|
||||
<value>Все ще фармиться: {0}</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="StoppedIdling" xml:space="preserve">
|
||||
<value>Зупинено фарм: {0} ({1})</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="StoppedIdlingList" xml:space="preserve">
|
||||
<value>Зупинено фарм: {0}</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="UnknownCommand" xml:space="preserve">
|
||||
<value>Невідома команда!</value>
|
||||
</data>
|
||||
@@ -268,7 +321,9 @@
|
||||
<value>Прийняття подарунку: {0}...</value>
|
||||
<comment>{0} will be replaced by giftID (number)</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotAccountLimited" xml:space="preserve">
|
||||
<value>Цей акаунт обмежений, фарм буде недоступним доки ви не позбудетесь обмежень!</value>
|
||||
</data>
|
||||
<data name="BotAddLicense" xml:space="preserve">
|
||||
<value>ID: {0} | Стан: {1}</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by status string</comment>
|
||||
|
||||
@@ -194,7 +194,10 @@ StackTrace:
|
||||
<value>Xin vui lòng nhập mã 2FA từ ứng dụng Steam Authenticator của bạn: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
|
||||
<data name="UserInputSteamGuard" xml:space="preserve">
|
||||
<value>Vui lòng nhập mã SteamGuard đã được gửi tới email của bạn: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputSteamLogin" xml:space="preserve">
|
||||
<value>Xin vui lòng nhập tài khoản Steam của bạn: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</data>
|
||||
<data name="ErrorNoBotsDefined" xml:space="preserve">
|
||||
<value>沒有設定任何機器人,您是否忘記設定 ASF 了?如果您不明白發生了什麼,請閱讀 Wiki 上的「安裝指南」。</value>
|
||||
<value>沒有設定任何 Bot。你是否忘記設定 ASF 了?如果你不明白發生了什麼,請閱讀 Wiki 上的「設定指南」。</value>
|
||||
</data>
|
||||
<data name="ErrorObjectIsNull" xml:space="preserve">
|
||||
<value>{0} 為空值!</value>
|
||||
@@ -128,7 +128,7 @@
|
||||
<value>無法進行更新,因為此版本沒有提供任何資產!</value>
|
||||
</data>
|
||||
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
|
||||
<value>收到一個使用者輸入請求,但行程目前正以無介面模式執行!</value>
|
||||
<value>收到一個使用者輸入請求,但進程目前正以無介面模式執行!</value>
|
||||
</data>
|
||||
<data name="Exiting" xml:space="preserve">
|
||||
<value>正在退出...</value>
|
||||
@@ -147,11 +147,11 @@
|
||||
<comment>{0} will be replaced by trade number</comment>
|
||||
</data>
|
||||
<data name="LoggingIn" xml:space="preserve">
|
||||
<value>正在登錄到 {0}...</value>
|
||||
<value>正在登入到 {0}…</value>
|
||||
<comment>{0} will be replaced by service's name</comment>
|
||||
</data>
|
||||
<data name="NoBotsAreRunning" xml:space="preserve">
|
||||
<value>沒有執行中的 Bot,正在退出……</value>
|
||||
<value>沒有執行中的 Bot,正在退出…</value>
|
||||
</data>
|
||||
<data name="RefreshingOurSession" xml:space="preserve">
|
||||
<value>更新工作階段 !</value>
|
||||
@@ -161,22 +161,22 @@
|
||||
<comment>{0} will be replaced by trade number</comment>
|
||||
</data>
|
||||
<data name="Restarting" xml:space="preserve">
|
||||
<value>正在重新啟動...</value>
|
||||
<value>正在重新啟動…</value>
|
||||
</data>
|
||||
<data name="Starting" xml:space="preserve">
|
||||
<value>正在啟動...</value>
|
||||
<value>正在啟動…</value>
|
||||
</data>
|
||||
<data name="Success" xml:space="preserve">
|
||||
<value>成功!</value>
|
||||
</data>
|
||||
<data name="UnlockingParentalAccount" xml:space="preserve">
|
||||
<value>正在解鎖家庭監護帳戶...</value>
|
||||
<value>正在解鎖家庭監護帳號…</value>
|
||||
</data>
|
||||
<data name="UpdateCheckingNewVersion" xml:space="preserve">
|
||||
<value>正在檢查新版本...</value>
|
||||
<value>正在檢查新版本…</value>
|
||||
</data>
|
||||
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
|
||||
<value>正在下載新版本:{0}({1} MB)……等待期間,如果喜歡這個軟體,請考慮捐助 ASF!:)</value>
|
||||
<value>正在下載新版本:{0} ({1} MB)…等待期間,如果喜歡這個軟體,請考慮捐助 ASF!:)</value>
|
||||
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
|
||||
</data>
|
||||
<data name="UpdateFinished" xml:space="preserve">
|
||||
@@ -190,12 +190,15 @@
|
||||
<comment>{0} will be replaced by current version, {1} will be replaced by remote version</comment>
|
||||
</data>
|
||||
<data name="UserInputSteam2FA" xml:space="preserve">
|
||||
<value>請輸入你的 Steam Guard 行動驗證器上的兩步驟驗證代碼: </value>
|
||||
<value>請輸入你的 Steam Guard 行動驗證器上的雙重驗證代碼: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputSteamGuard" xml:space="preserve">
|
||||
<value>請輸入寄送至你的電子信箱的 Steam Guard 驗證代碼: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
|
||||
<data name="UserInputSteamLogin" xml:space="preserve">
|
||||
<value>請輸入你的 Steam 帳戶:</value>
|
||||
<value>請輸入你的 Steam 帳號: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="UserInputSteamParentalCode" xml:space="preserve">
|
||||
@@ -214,7 +217,7 @@
|
||||
<value>IPC 伺服器已就緒!</value>
|
||||
</data>
|
||||
<data name="IPCStarting" xml:space="preserve">
|
||||
<value>IPC 伺服器啟動中……</value>
|
||||
<value>正在啟動 IPC 伺服器…</value>
|
||||
</data>
|
||||
<data name="BotAlreadyStopped" xml:space="preserve">
|
||||
<value>這個 Bot 已經停止了!</value>
|
||||
@@ -224,7 +227,7 @@
|
||||
<comment>{0} will be replaced by bot's name query (string)</comment>
|
||||
</data>
|
||||
<data name="BotStatusOverview" xml:space="preserve">
|
||||
<value>有 {0}/{1} 個 Bot 正在運行,總共有 {2} 個遊戲 ({3} 張卡片) 等待掛卡。</value>
|
||||
<value>有 {0}/{1} 個 Bot 正在執行,總共有 {2} 個遊戲 ({3} 張卡片) 等待掛卡。</value>
|
||||
<comment>{0} will be replaced by number of active bots, {1} will be replaced by total number of bots, {2} will be replaced by total number of games left to farm, {3} will be replaced by total number of cards left to farm</comment>
|
||||
</data>
|
||||
<data name="BotStatusIdling" xml:space="preserve">
|
||||
@@ -236,10 +239,10 @@
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), {1} will be replaced by total number of games to farm, {2} will be replaced by total number of cards to farm, {3} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="CheckingFirstBadgePage" xml:space="preserve">
|
||||
<value>正在檢查徽章頁面第一頁...</value>
|
||||
<value>正在檢查徽章頁面第一頁…</value>
|
||||
</data>
|
||||
<data name="CheckingOtherBadgePages" xml:space="preserve">
|
||||
<value>正在檢查其他徽章頁面...</value>
|
||||
<value>正在檢查其他徽章頁面…</value>
|
||||
</data>
|
||||
<data name="ChosenFarmingAlgorithm" xml:space="preserve">
|
||||
<value>選擇的掛卡演算法為:{0}</value>
|
||||
@@ -249,7 +252,7 @@
|
||||
<value>完成!</value>
|
||||
</data>
|
||||
<data name="GamesToIdle" xml:space="preserve">
|
||||
<value>總共有 {0} 個遊戲 (總數 {1} 張卡片) 需要掛卡 (仍需約 {2})...</value>
|
||||
<value>總共有 {0} 個遊戲 ({1} 張卡片) 需要掛卡 (仍需約 {2})…</value>
|
||||
<comment>{0} will be replaced by number of games, {1} will be replaced by number of cards, {2} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
|
||||
</data>
|
||||
<data name="IdlingFinished" xml:space="preserve">
|
||||
@@ -274,33 +277,33 @@
|
||||
<value>正在忽略此請求,因為強制暫停已啟用!</value>
|
||||
</data>
|
||||
<data name="NothingToIdle" xml:space="preserve">
|
||||
<value>本帳戶目前沒有需要掛卡的遊戲!</value>
|
||||
<value>本帳號目前沒有需要掛卡的遊戲!</value>
|
||||
</data>
|
||||
<data name="NowIdling" xml:space="preserve">
|
||||
<value>正在掛卡: {0} ({1})</value>
|
||||
<value>正在掛卡:{0} ({1})</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="NowIdlingList" xml:space="preserve">
|
||||
<value>正在掛卡: {0}</value>
|
||||
<value>正在掛卡:{0}</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="PlayingNotAvailable" xml:space="preserve">
|
||||
<value>目前無法執行,我們將稍後再試!</value>
|
||||
</data>
|
||||
<data name="StillIdling" xml:space="preserve">
|
||||
<value>仍在掛卡: {0} ({1})</value>
|
||||
<value>仍在掛卡:{0} ({1})</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="StillIdlingList" xml:space="preserve">
|
||||
<value>仍在掛卡: {0}</value>
|
||||
<value>仍在掛卡:{0}</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="StoppedIdling" xml:space="preserve">
|
||||
<value>停止掛卡: {0} ({1})</value>
|
||||
<value>停止掛卡:{0} ({1})</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="StoppedIdlingList" xml:space="preserve">
|
||||
<value>停止掛卡: {0}</value>
|
||||
<value>停止掛卡:{0}</value>
|
||||
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="UnknownCommand" xml:space="preserve">
|
||||
@@ -314,11 +317,11 @@
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="BotAcceptingGift" xml:space="preserve">
|
||||
<value>接受禮物:{0}……</value>
|
||||
<value>接受禮物:{0}…</value>
|
||||
<comment>{0} will be replaced by giftID (number)</comment>
|
||||
</data>
|
||||
<data name="BotAccountLimited" xml:space="preserve">
|
||||
<value>本帳戶為受限帳戶,在限制解除前將無法掛卡!</value>
|
||||
<value>本帳號為受限帳號,在限制解除前將無法掛卡!</value>
|
||||
</data>
|
||||
<data name="BotAddLicense" xml:space="preserve">
|
||||
<value>ID:{0} | 狀態:{1}</value>
|
||||
@@ -332,13 +335,13 @@
|
||||
<value>Bot 已在執行中!</value>
|
||||
</data>
|
||||
<data name="BotAuthenticatorConverting" xml:space="preserve">
|
||||
<value>正在將 .maFile 轉換成 ASF 的檔案格式……</value>
|
||||
<value>正在將 .maFile 轉換成 ASF 的檔案格式…</value>
|
||||
</data>
|
||||
<data name="BotAuthenticatorImportFinished" xml:space="preserve">
|
||||
<value>已成功匯入行動驗證器!</value>
|
||||
</data>
|
||||
<data name="BotAuthenticatorToken" xml:space="preserve">
|
||||
<value>兩步驟驗證權杖:{0}</value>
|
||||
<value>雙重驗證權杖:{0}</value>
|
||||
<comment>{0} will be replaced by generated 2FA token (string)</comment>
|
||||
</data>
|
||||
<data name="BotAutomaticIdlingNowPaused" xml:space="preserve">
|
||||
@@ -360,13 +363,13 @@
|
||||
<value>已與 Steam 中斷連線!</value>
|
||||
</data>
|
||||
<data name="BotDisconnecting" xml:space="preserve">
|
||||
<value>正在中斷連線...</value>
|
||||
<value>正在中斷連線…</value>
|
||||
</data>
|
||||
<data name="BotInstanceNotStartingBecauseDisabled" xml:space="preserve">
|
||||
<value>這個 Bot 將不會啟動,因為它在設定檔中被停用!</value>
|
||||
</data>
|
||||
<data name="BotInvalidAuthenticatorDuringLogin" xml:space="preserve">
|
||||
<value>已連續收到 {0} 次 TwoFactorCodeMismatch 錯誤訊息。你的兩步驟驗證憑證可能已失效,或者時間不同步,正在中止!</value>
|
||||
<value>已連續收到 {0} 次 TwoFactorCodeMismatch 錯誤訊息。你的雙重驗證憑證可能已失效,或者時間不同步,正在中止!</value>
|
||||
<comment>{0} will be replaced by maximum allowed number of failed 2FA attempts</comment>
|
||||
</data>
|
||||
<data name="BotLoggedOff" xml:space="preserve">
|
||||
@@ -378,16 +381,16 @@
|
||||
<comment>{0} will be replaced by steam ID (number)</comment>
|
||||
</data>
|
||||
<data name="BotLoggingIn" xml:space="preserve">
|
||||
<value>登入中...</value>
|
||||
<value>正在登入…</value>
|
||||
</data>
|
||||
<data name="BotLogonSessionReplaced" xml:space="preserve">
|
||||
<value>這個帳戶似乎正被另一個 ASF 使用中,這是未定義的行為,拒絕讓它繼續執行!</value>
|
||||
<value>這個帳號似乎正被另一個 ASF 使用中,這是未定義的行為,拒絕讓它繼續執行!</value>
|
||||
</data>
|
||||
<data name="BotLootingFailed" xml:space="preserve">
|
||||
<value>交易提案已失敗!</value>
|
||||
</data>
|
||||
<data name="BotLootingMasterNotDefined" xml:space="preserve">
|
||||
<value>無法發送交易提案,因為沒有帳戶設有 master 權限!</value>
|
||||
<value>無法發送交易提案,因為沒有帳號設有 master 權限!</value>
|
||||
</data>
|
||||
<data name="BotLootingSuccess" xml:space="preserve">
|
||||
<value>交易提案發送成功!</value>
|
||||
@@ -396,7 +399,7 @@
|
||||
<value>你無法對自己發出交易請求!</value>
|
||||
</data>
|
||||
<data name="BotNoASFAuthenticator" xml:space="preserve">
|
||||
<value>這個 Bot 並未啟用 ASF 兩步驟驗證!您是否忘記將兩步驟驗證導入至 ASF?</value>
|
||||
<value>這個 Bot 並未啟用 ASF 雙重驗證!您是否忘記將雙重驗證導入至 ASF?</value>
|
||||
</data>
|
||||
<data name="BotNotConnected" xml:space="preserve">
|
||||
<value>此 Bot 尚未連線!</value>
|
||||
@@ -414,11 +417,11 @@
|
||||
<comment>{0} will be replaced by the points balance value (integer)</comment>
|
||||
</data>
|
||||
<data name="BotRateLimitExceeded" xml:space="preserve">
|
||||
<value>超過頻率限制,我們將在 {0} 後重試……</value>
|
||||
<value>超過頻率限制,我們將在 {0} 後重試…</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "25 minutes")</comment>
|
||||
</data>
|
||||
<data name="BotReconnecting" xml:space="preserve">
|
||||
<value>正在重新連線...</value>
|
||||
<value>正在重新連線…</value>
|
||||
</data>
|
||||
<data name="BotRedeem" xml:space="preserve">
|
||||
<value>序號:{0} | 狀態:{1}</value>
|
||||
@@ -469,13 +472,13 @@
|
||||
<value>與 Steam 網路的連線中斷,正在重新連線……</value>
|
||||
</data>
|
||||
<data name="BotAccountFree" xml:space="preserve">
|
||||
<value>帳戶不再被佔用,已恢復掛卡!</value>
|
||||
<value>帳號不再被佔用,已恢復掛卡!</value>
|
||||
</data>
|
||||
<data name="BotAccountOccupied" xml:space="preserve">
|
||||
<value>帳號目前使用中,ASF 將在該帳號空閒時繼續掛卡……</value>
|
||||
<value>帳號目前使用中,ASF 將在該帳號空閒時繼續掛卡…</value>
|
||||
</data>
|
||||
<data name="BotConnecting" xml:space="preserve">
|
||||
<value>正在連線...</value>
|
||||
<value>正在連線…</value>
|
||||
</data>
|
||||
<data name="BotHeartBeatFailed" xml:space="preserve">
|
||||
<value>無法與用戶端中斷連線,正在中止此 Bot!</value>
|
||||
@@ -484,7 +487,7 @@
|
||||
<value>無法初始化 SteamDirectory,與 Steam 網路的連線可能需要更長的時間!</value>
|
||||
</data>
|
||||
<data name="BotStopping" xml:space="preserve">
|
||||
<value>正在停止...</value>
|
||||
<value>正在停止…</value>
|
||||
</data>
|
||||
<data name="ErrorBotConfigInvalid" xml:space="preserve">
|
||||
<value>你的 Bot 設定無效,請確認 {0} 的內容然後再試一次!</value>
|
||||
@@ -495,7 +498,7 @@
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
</data>
|
||||
<data name="Initializing" xml:space="preserve">
|
||||
<value>正在初始化 {0}...</value>
|
||||
<value>正在初始化 {0}…</value>
|
||||
<comment>{0} will be replaced by service name that is being initialized</comment>
|
||||
</data>
|
||||
<data name="WarningPrivacyPolicy" xml:space="preserve">
|
||||
@@ -524,7 +527,7 @@
|
||||
<comment>{0} will be replaced by program's name (e.g. "ASF"), {1} will be replaced by program's version (e.g. "1.0.0.0"). This string typically has nothing to translate and you should leave it as it is, unless you need to change the format, e.g. in RTL languages.</comment>
|
||||
</data>
|
||||
<data name="BotAccountLocked" xml:space="preserve">
|
||||
<value>此帳戶已被封鎖,永久無法掛卡!</value>
|
||||
<value>此帳號已被封鎖,永久無法掛卡!</value>
|
||||
</data>
|
||||
<data name="BotStatusLocked" xml:space="preserve">
|
||||
<value>Bot 已被封鎖,無法透過掛卡得到卡片。</value>
|
||||
@@ -548,7 +551,7 @@
|
||||
<comment>{0} will be replaced by number (in megabytes) of memory being used, {1} will be replaced by translated TimeSpan string (such as "25 minutes"). Please note that this string should include newlines for formatting.</comment>
|
||||
</data>
|
||||
<data name="ClearingDiscoveryQueue" xml:space="preserve">
|
||||
<value>正在瀏覽 Steam 探索佇列 #{0}……</value>
|
||||
<value>正在瀏覽 Steam 探索佇列 #{0}…</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
<data name="DoneClearingDiscoveryQueue" xml:space="preserve">
|
||||
@@ -556,11 +559,11 @@
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
<data name="BotOwnsOverviewPerGame" xml:space="preserve">
|
||||
<value>{0}/{1} 個 Bot 已經擁有遊戲 {2}。</value>
|
||||
<value>{0}/{1} 個 Bot 已擁有遊戲 {2}。</value>
|
||||
<comment>{0} will be replaced by number of bots that already own particular game being checked, {1} will be replaced by total number of bots that were checked during the process, {2} will be replaced by game's ID (number)</comment>
|
||||
</data>
|
||||
<data name="BotRefreshingPackagesData" xml:space="preserve">
|
||||
<value>更新套件資料中……</value>
|
||||
<value>更新套件資料中…</value>
|
||||
</data>
|
||||
<data name="WarningDeprecated" xml:space="preserve">
|
||||
<value>{0} 的用法已棄用,並且將在未來的版本中移除,請改用 {1}。</value>
|
||||
@@ -589,7 +592,7 @@
|
||||
<comment>{0} will be replaced by bot's level</comment>
|
||||
</data>
|
||||
<data name="ActivelyMatchingItems" xml:space="preserve">
|
||||
<value>正在比對 Steam 物品,第 #{0} 輪……</value>
|
||||
<value>正在比對 Steam 物品,第 #{0} 輪…</value>
|
||||
<comment>{0} will be replaced by round number</comment>
|
||||
</data>
|
||||
<data name="DoneActivelyMatchingItems" xml:space="preserve">
|
||||
@@ -604,7 +607,7 @@
|
||||
<comment>{0} will be replaced by number of sets traded</comment>
|
||||
</data>
|
||||
<data name="WarningExcessiveBotsCount" xml:space="preserve">
|
||||
<value>你執行的個人 Bot 帳戶數量超過我們的建議上限 ({0})。請留意,此設定不受支援,且可能會導致各種 Steam 相關問題,包括帳戶停權。請參閱常見問答了解詳情。</value>
|
||||
<value>你執行的個人 Bot 帳號數量超過我們的建議上限 ({0})。請留意,此設定不受支援,且可能會導致各種 Steam 相關問題,包括帳號停權。請參閱常見問答了解詳情。</value>
|
||||
<comment>{0} will be replaced by our maximum recommended bots count (number)</comment>
|
||||
</data>
|
||||
<data name="PluginLoaded" xml:space="preserve">
|
||||
@@ -612,7 +615,7 @@
|
||||
<comment>{0} will be replaced by the name of the custom ASF plugin</comment>
|
||||
</data>
|
||||
<data name="PluginLoading" xml:space="preserve">
|
||||
<value>正在載入 {0} V{1}……</value>
|
||||
<value>正在載入 {0} V{1}…</value>
|
||||
<comment>{0} will be replaced by the name of the custom ASF plugin, {1} will be replaced by its version</comment>
|
||||
</data>
|
||||
<data name="NothingFound" xml:space="preserve">
|
||||
@@ -622,13 +625,13 @@
|
||||
<value>你已載入一個以上的自訂外掛程式。由於我們無法支援修改過的配置,如遭遇任何問題,請向相關外掛程式的開發人員尋求協助。</value>
|
||||
</data>
|
||||
<data name="PleaseWait" xml:space="preserve">
|
||||
<value>請稍候⋯⋯</value>
|
||||
<value>請稍候…</value>
|
||||
</data>
|
||||
<data name="EnterCommand" xml:space="preserve">
|
||||
<value>輸入指令:</value>
|
||||
</data>
|
||||
<data name="Executing" xml:space="preserve">
|
||||
<value>執行中……</value>
|
||||
<value>正在執行…</value>
|
||||
</data>
|
||||
<data name="InteractiveConsoleEnabled" xml:space="preserve">
|
||||
<value>已開啟互動式主控台,按「C」進入指令模式</value>
|
||||
@@ -645,14 +648,14 @@
|
||||
<comment>{0} will be replaced by number of confirmations</comment>
|
||||
</data>
|
||||
<data name="BotExtraIdlingCooldown" xml:space="preserve">
|
||||
<value>最多等待 {0} 以確保我們可以開始掛卡……</value>
|
||||
<value>最多等待 {0} 以確保我們可以開始掛卡…</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "1 minute")</comment>
|
||||
</data>
|
||||
<data name="UpdateCleanup" xml:space="preserve">
|
||||
<value>正在清理更新後的過時檔案……</value>
|
||||
<value>正在清理更新後的過時檔案…</value>
|
||||
</data>
|
||||
<data name="BotGeneratingSteamParentalCode" xml:space="preserve">
|
||||
<value>正在產生 Steam 家庭監護代碼,這會需要一段時間,請考慮將它寫入設定檔中……</value>
|
||||
<value>正在產生 Steam 家庭監護代碼,這會需要一段時間,請考慮將它寫入設定檔中…</value>
|
||||
</data>
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>IPC 設定檔已變更!</value>
|
||||
@@ -662,7 +665,7 @@
|
||||
<comment>{0} will be replaced by trade offer ID (number), {1} will be replaced by internal ASF enum name, {2} will be replaced by technical reason why the trade was determined to be in this state</comment>
|
||||
</data>
|
||||
<data name="BotInvalidPasswordDuringLogin" xml:space="preserve">
|
||||
<value>連續收到 InvalidPassword 錯誤代碼 {0} 次。您的帳戶密碼大概是錯的,中止!</value>
|
||||
<value>連續收到 InvalidPassword 錯誤代碼 {0} 次。您的帳號密碼大概是錯的,中止!</value>
|
||||
<comment>{0} will be replaced by maximum allowed number of failed login attempts</comment>
|
||||
</data>
|
||||
<data name="Result" xml:space="preserve">
|
||||
@@ -684,40 +687,52 @@
|
||||
<comment>{0} will be replaced by internal name of the config property (e.g. "GamesPlayedWhileIdle"), {1} will be replaced by comma-separated list of appIDs that user has chosen</comment>
|
||||
</data>
|
||||
<data name="AutomaticFileMigration" xml:space="preserve">
|
||||
<value>{0} 設定檔將會改版成最新的語法……</value>
|
||||
<value>{0} 設定檔將會改版成最新的語法…</value>
|
||||
<comment>{0} will be replaced with the relative path to the affected config file</comment>
|
||||
</data>
|
||||
<data name="WarningWeakIPCPassword" xml:space="preserve">
|
||||
<value>你的IPC密碼看起來很弱 你可以考慮更換一個安全性更高的密碼 細節:{0}</value>
|
||||
<value>你的 IPC 密碼看起來很弱。請考慮換一個強度更高的密碼來增加安全性。詳細資訊: {0}</value>
|
||||
<comment>{0} will be replaced by additional details about the password being considered weak</comment>
|
||||
</data>
|
||||
<data name="WarningWeakSteamPassword" xml:space="preserve">
|
||||
<value>你的steam 密碼 {0} 看起來很弱 你可以考慮換一個 細節: {1}</value>
|
||||
<value>你的 Steam 密碼「{0}」看起來很弱。請考慮換一個強度更高的密碼來增加安全性。詳細資訊: {1}</value>
|
||||
<comment>{0} will be replaced by either the affected bot name or the path to the bots configuration file, {1} will be replaced by additional details about the password being considered weak</comment>
|
||||
</data>
|
||||
<data name="WarningWeakCryptKey" xml:space="preserve">
|
||||
<value>你的加密密鑰看起來很弱 可以考慮換一個 細節:{0}</value>
|
||||
<value>你的加密金鑰看起來很弱。請考慮換一個強度更高的金鑰來增加安全性。詳細資訊: {0}</value>
|
||||
<comment>{0} will be replaced by additional details about the encryption key being considered weak</comment>
|
||||
</data>
|
||||
<data name="WarningTooShortCryptKey" xml:space="preserve">
|
||||
<value>你的加密密鑰太短 我們推薦使用{0} 以上長度的密鑰</value>
|
||||
<value>你的加密金鑰太短。我們推薦使用 {0} 位元組 (字元) 以上長度的金鑰。</value>
|
||||
<comment>{0} will be replaced by the number of bytes (characters) recommended</comment>
|
||||
</data>
|
||||
<data name="WarningDefaultCryptKeyUsedForHashing" xml:space="preserve">
|
||||
<value>你正在使用 {0} 設置 {1} 屬性 但你沒有提供一個自定義的加密密鑰 你應該提供一個自定義的加密密鑰以提高安全性</value>
|
||||
<value>你正在使用 {0} 設定 {1} 屬性。但你沒有提供一個自定義的 --cryptkey。你應該提供自定義 --cryptkey 以提高安全性。</value>
|
||||
<comment>{0} will be replaced by the name of a particular setting (e.g. "SCrypt"), {1} will be replaced by the name of the property (e.g. "IPCPassword")</comment>
|
||||
</data>
|
||||
<data name="WarningDefaultCryptKeyUsedForEncryption" xml:space="preserve">
|
||||
<value>你正在使用 {0} 設置 {1} 屬性 但你沒有提供一個自定義的加密密鑰 這很不安全 你應該提供一個自定義的加密密鑰</value>
|
||||
<value>你正在使用 {0} 設定 {1} 屬性。但你沒有提供一個自定義的 --cryptkey。這完全破壞了保護,因為 ASF 被迫使用自己的 (已知) 金鑰。你應該提供自定義 --cryptkey 用於使用此設定提供的安全優勢。</value>
|
||||
<comment>{0} will be replaced by the name of a particular setting (e.g. "AES"), {1} will be replaced by the name of the property (e.g. "SteamPassword")</comment>
|
||||
</data>
|
||||
|
||||
<data name="WarningRunningInUnsupportedEnvironment" xml:space="preserve">
|
||||
<value>你在不支援的環境下提供了 --ignore-unsupported-environment 參數 請注意 我們不為這種情況提供任何支持 風險請完全由你自行承擔 我們已經警告過你了</value>
|
||||
<data name="WarningRunningAsRoot" xml:space="preserve">
|
||||
<value>你正在以管理員權限 (Root) 執行 ASF。這會給你的機器帶來重大的安全風險,且由於 ASF 的操作不需要 Root 權限,我們建議盡可能以非管理員使用者身份執行它。</value>
|
||||
</data>
|
||||
<data name="WarningRunningInUnsupportedEnvironment" xml:space="preserve">
|
||||
<value>您在不受支援的環境中執行 ASF,並提供 --ignore-unsupported-environment 引數。請注意,我們不對這種情況提供任何形式的支援,您完全需要自行承擔風險。你已經被警告過了。</value>
|
||||
</data>
|
||||
<data name="FetchingChecksumFromRemoteServer" xml:space="preserve">
|
||||
<value>正在從遠端伺服器擷取核對和…</value>
|
||||
</data>
|
||||
<data name="VerifyingChecksumWithRemoteServer" xml:space="preserve">
|
||||
<value>正在驗證已下載的二進制檔案與來自遠端伺服器的核對和…</value>
|
||||
</data>
|
||||
<data name="ChecksumMissing" xml:space="preserve">
|
||||
<value>遠端伺服器對我們要更新到的版本一無所知。如果該版本是最近發布的,則可能出現這種情況——立刻拒絕進行更新程序作為額外的安全措施。</value>
|
||||
</data>
|
||||
<data name="ChecksumWrong" xml:space="preserve">
|
||||
<value>遠端伺服器回覆了不同的核對和,這可能意味著下載檔案損毀或遭受中間人攻擊,拒絕繼續更新程序!</value>
|
||||
</data>
|
||||
<data name="PatchingFiles" xml:space="preserve">
|
||||
<value>正在修補 ASF 檔案…</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</root>
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2022 Ł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.Threading.Tasks;
|
||||
using ArchiSteamFarm.Steam;
|
||||
using ArchiSteamFarm.Storage;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace ArchiSteamFarm.Plugins.Interfaces;
|
||||
|
||||
[PublicAPI]
|
||||
[Obsolete($"Use {nameof(IBotCommand2)} instead, this one will be removed soon.", true)]
|
||||
public interface IBotCommand : IPlugin {
|
||||
/// <summary>
|
||||
/// ASF will call this method for unrecognized commands.
|
||||
/// </summary>
|
||||
/// <param name="bot">Bot object related to this callback.</param>
|
||||
/// <param name="steamID">64-bit long unsigned integer of steamID executing the command.</param>
|
||||
/// <param name="message">Command message in its raw format, stripped of <see cref="GlobalConfig.CommandPrefix" />.</param>
|
||||
/// <param name="args">Pre-parsed message using standard ASF delimiters.</param>
|
||||
/// <returns>Response to the command, or null/empty (as the task value) if the command isn't handled by this plugin.</returns>
|
||||
Task<string?> OnBotCommand(Bot bot, ulong steamID, string message, string[] args);
|
||||
}
|
||||
@@ -40,7 +40,6 @@ using ArchiSteamFarm.Steam;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using ArchiSteamFarm.Steam.Exchange;
|
||||
using ArchiSteamFarm.Steam.Integration.Callbacks;
|
||||
using ArchiSteamFarm.Storage;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SteamKit2;
|
||||
|
||||
@@ -297,36 +296,6 @@ internal static class PluginsCore {
|
||||
return null;
|
||||
}
|
||||
|
||||
ulong oldSteamID = steamID;
|
||||
|
||||
if (oldSteamID == 0) {
|
||||
oldSteamID = ASF.GlobalConfig?.SteamOwnerID ?? GlobalConfig.DefaultSteamOwnerID;
|
||||
}
|
||||
|
||||
if ((oldSteamID != 0) && new SteamID(oldSteamID).IsIndividualAccount) {
|
||||
IList<string?> oldResponses;
|
||||
|
||||
try {
|
||||
#pragma warning disable CS0618 // We intentionally support deprecated interface for a while longer
|
||||
oldResponses = await Utilities.InParallel(ActivePlugins.OfType<IBotCommand>().Select(plugin => plugin.OnBotCommand(bot, oldSteamID, message, args))).ConfigureAwait(false);
|
||||
#pragma warning restore CS0618 // We intentionally support deprecated interface for a while longer
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (oldResponses.Count > 0) {
|
||||
// Due to fact that responses is string[] array, not a List, we need to reinitialize it
|
||||
// Normally I'd wrote it differently but this is temporary code to be removed soon, so this will suffice
|
||||
responses = new List<string?>(responses);
|
||||
|
||||
foreach (string? oldResponse in oldResponses) {
|
||||
responses.Add(oldResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return string.Join(Environment.NewLine, responses.Where(static response => !string.IsNullOrEmpty(response)));
|
||||
}
|
||||
|
||||
|
||||
@@ -154,7 +154,6 @@ public sealed class Bot : IAsyncDisposable {
|
||||
private readonly SemaphoreSlim MessagingSemaphore = new(1, 1);
|
||||
private readonly ConcurrentDictionary<UserNotificationsCallback.EUserNotification, uint> PastNotifications = new();
|
||||
private readonly SemaphoreSlim SendCompleteTypesSemaphore = new(1, 1);
|
||||
private readonly Statistics? Statistics;
|
||||
private readonly SteamClient SteamClient;
|
||||
private readonly ConcurrentHashSet<ulong> SteamFamilySharingIDs = new();
|
||||
private readonly SteamUser SteamUser;
|
||||
@@ -250,6 +249,7 @@ public sealed class Bot : IAsyncDisposable {
|
||||
#pragma warning restore CA2213 // False positive, .NET Framework can't understand DisposeAsync()
|
||||
|
||||
private bool ReconnectOnUserInitiated;
|
||||
private RemoteCommunication? RemoteCommunication;
|
||||
private bool SendCompleteTypesScheduled;
|
||||
|
||||
#pragma warning disable CA2213 // False positive, .NET Framework can't understand DisposeAsync()
|
||||
@@ -339,10 +339,6 @@ public sealed class Bot : IAsyncDisposable {
|
||||
Commands = new Commands(this);
|
||||
Trading = new Trading(this);
|
||||
|
||||
if (!Debugging.IsDebugBuild && (ASF.GlobalConfig?.Statistics ?? GlobalConfig.DefaultStatistics)) {
|
||||
Statistics = new Statistics(this);
|
||||
}
|
||||
|
||||
HeartBeatTimer = new Timer(
|
||||
HeartBeat,
|
||||
null,
|
||||
@@ -383,8 +379,8 @@ public sealed class Bot : IAsyncDisposable {
|
||||
await SendItemsTimer.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (Statistics != null) {
|
||||
await Statistics.DisposeAsync().ConfigureAwait(false);
|
||||
if (RemoteCommunication != null) {
|
||||
await RemoteCommunication.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (SteamSaleEvent != null) {
|
||||
@@ -605,7 +601,7 @@ public sealed class Bot : IAsyncDisposable {
|
||||
}
|
||||
|
||||
if (itemsPerSet < itemsPerClassID.Count) {
|
||||
throw new InvalidOperationException($"{nameof(inventory)} && {nameof(amountsToExtract)}");
|
||||
throw new InvalidOperationException($"{nameof(itemsPerSet)} < {nameof(itemsPerClassID)}");
|
||||
}
|
||||
|
||||
if (itemsPerSet > itemsPerClassID.Count) {
|
||||
@@ -757,27 +753,6 @@ public sealed class Bot : IAsyncDisposable {
|
||||
return await ArchiWebHandler.GetTradeHoldDurationForTrade(tradeID).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
[Obsolete($"Use {nameof(GetAccess)} instead (if you still need it), this one will be removed soon.", true)]
|
||||
public bool HasAccess(ulong steamID, BotConfig.EAccess access) {
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||
}
|
||||
|
||||
if ((access == BotConfig.EAccess.None) || !Enum.IsDefined(access)) {
|
||||
throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(BotConfig.EAccess));
|
||||
}
|
||||
|
||||
if (ASF.IsOwner(steamID)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return access switch {
|
||||
BotConfig.EAccess.FamilySharing when SteamFamilySharingIDs.Contains(steamID) => true,
|
||||
_ => BotConfig.SteamUserPermissions.TryGetValue(steamID, out BotConfig.EAccess realPermission) && (realPermission >= access)
|
||||
};
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<Dictionary<uint, byte>?> LoadCardsPerSet(IReadOnlyCollection<uint> appIDs) {
|
||||
if ((appIDs == null) || (appIDs.Count == 0)) {
|
||||
@@ -1332,9 +1307,7 @@ public sealed class Bot : IAsyncDisposable {
|
||||
OrderedDictionary gamesToRedeemInBackground = new();
|
||||
|
||||
using (StreamReader reader = new(filePath)) {
|
||||
string? line;
|
||||
|
||||
while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) != null) {
|
||||
while (await reader.ReadLineAsync().ConfigureAwait(false) is { } line) {
|
||||
if (line.Length == 0) {
|
||||
continue;
|
||||
}
|
||||
@@ -1422,7 +1395,7 @@ public sealed class Bot : IAsyncDisposable {
|
||||
return;
|
||||
}
|
||||
|
||||
(BotConfig? botConfig, _) = await BotConfig.Load(configFile).ConfigureAwait(false);
|
||||
(BotConfig? botConfig, _) = await BotConfig.Load(configFile, BotName).ConfigureAwait(false);
|
||||
|
||||
if (botConfig == null) {
|
||||
await Destroy().ConfigureAwait(false);
|
||||
@@ -1524,7 +1497,7 @@ public sealed class Bot : IAsyncDisposable {
|
||||
return;
|
||||
}
|
||||
|
||||
(BotConfig? botConfig, string? latestJson) = await BotConfig.Load(configFilePath).ConfigureAwait(false);
|
||||
(BotConfig? botConfig, string? latestJson) = await BotConfig.Load(configFilePath, botName).ConfigureAwait(false);
|
||||
|
||||
if (botConfig == null) {
|
||||
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.ErrorBotConfigInvalid, configFilePath));
|
||||
@@ -1666,6 +1639,18 @@ public sealed class Bot : IAsyncDisposable {
|
||||
SteamFriends.RequestFriendInfo(SteamID, EClientPersonaStateFlag.PlayerName | EClientPersonaStateFlag.Presence);
|
||||
}
|
||||
|
||||
internal void ResetPersonaState() {
|
||||
if (BotConfig.OnlineStatus == EPersonaState.Offline) {
|
||||
return;
|
||||
}
|
||||
|
||||
SteamFriends.SetPersonaState(BotConfig.OnlineStatus);
|
||||
|
||||
if (BotConfig.OnlineFlags > 0) {
|
||||
ArchiHandler.SetPersonaState(BotConfig.OnlineStatus, BotConfig.OnlineFlags);
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<bool> SendTypingMessage(ulong steamID) {
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||
@@ -1844,9 +1829,7 @@ public sealed class Bot : IAsyncDisposable {
|
||||
try {
|
||||
using StreamReader reader = new(filePath);
|
||||
|
||||
string? line;
|
||||
|
||||
while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) != null) {
|
||||
while (await reader.ReadLineAsync().ConfigureAwait(false) is { } line) {
|
||||
if (line.Length == 0) {
|
||||
continue;
|
||||
}
|
||||
@@ -1962,8 +1945,8 @@ public sealed class Bot : IAsyncDisposable {
|
||||
|
||||
HeartBeatFailures = 0;
|
||||
|
||||
if (Statistics != null) {
|
||||
Utilities.InBackground(Statistics.OnHeartBeat);
|
||||
if (RemoteCommunication != null) {
|
||||
Utilities.InBackground(RemoteCommunication.OnHeartBeat);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericDebuggingException(e);
|
||||
@@ -2112,6 +2095,16 @@ public sealed class Bot : IAsyncDisposable {
|
||||
SteamSaleEvent = new SteamSaleEvent(this);
|
||||
}
|
||||
|
||||
if (RemoteCommunication != null) {
|
||||
await RemoteCommunication.DisposeAsync().ConfigureAwait(false);
|
||||
|
||||
RemoteCommunication = null;
|
||||
}
|
||||
|
||||
if (!Debugging.IsDebugBuild && (BotConfig.RemoteCommunication > BotConfig.ERemoteCommunication.None)) {
|
||||
RemoteCommunication = new RemoteCommunication(this);
|
||||
}
|
||||
|
||||
await PluginsCore.OnBotInitModules(this, BotConfig.AdditionalProperties).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -2866,13 +2859,11 @@ public sealed class Bot : IAsyncDisposable {
|
||||
|
||||
Utilities.InBackground(InitializeFamilySharing);
|
||||
|
||||
if (Statistics != null) {
|
||||
Utilities.InBackground(Statistics.OnLoggedOn);
|
||||
if (RemoteCommunication != null) {
|
||||
Utilities.InBackground(RemoteCommunication.OnLoggedOn);
|
||||
}
|
||||
|
||||
if (BotConfig.OnlineStatus != EPersonaState.Offline) {
|
||||
SteamFriends.SetPersonaState(BotConfig.OnlineStatus);
|
||||
}
|
||||
ResetPersonaState();
|
||||
|
||||
if (BotConfig.SteamMasterClanID != 0) {
|
||||
Utilities.InBackground(
|
||||
@@ -3042,8 +3033,8 @@ public sealed class Bot : IAsyncDisposable {
|
||||
AvatarHash = avatarHash;
|
||||
Nickname = callback.Name;
|
||||
|
||||
if (Statistics != null) {
|
||||
Utilities.InBackground(() => Statistics.OnPersonaState(callback.Name, avatarHash));
|
||||
if (RemoteCommunication != null) {
|
||||
Utilities.InBackground(() => RemoteCommunication.OnPersonaState(callback.Name, avatarHash));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3288,7 +3279,7 @@ public sealed class Bot : IAsyncDisposable {
|
||||
}
|
||||
|
||||
private async Task ResetGamesPlayed() {
|
||||
if (CardsFarmer.NowFarming) {
|
||||
if (!IsConnectedAndLoggedOn || CardsFarmer.NowFarming) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3300,10 +3291,24 @@ public sealed class Bot : IAsyncDisposable {
|
||||
// This function might be executed before PlayingSessionStateCallback/SharedLibraryLockStatusCallback, ensure proper delay in this case
|
||||
await Task.Delay(2000).ConfigureAwait(false);
|
||||
|
||||
if (CardsFarmer.NowFarming || !IsPlayingPossible) {
|
||||
if (!IsConnectedAndLoggedOn || CardsFarmer.NowFarming || !IsPlayingPossible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (PlayingWasBlocked) {
|
||||
byte minFarmingDelayAfterBlock = ASF.GlobalConfig?.MinFarmingDelayAfterBlock ?? GlobalConfig.DefaultMinFarmingDelayAfterBlock;
|
||||
|
||||
if (minFarmingDelayAfterBlock > 0) {
|
||||
for (byte i = 0; (i < minFarmingDelayAfterBlock) && IsConnectedAndLoggedOn && !CardsFarmer.NowFarming && IsPlayingPossible && PlayingWasBlocked; i++) {
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!IsConnectedAndLoggedOn || CardsFarmer.NowFarming || !IsPlayingPossible) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.BotIdlingSelectedGames, nameof(BotConfig.GamesPlayedWhileIdle), string.Join(", ", BotConfig.GamesPlayedWhileIdle)));
|
||||
}
|
||||
|
||||
@@ -3364,21 +3369,42 @@ public sealed class Bot : IAsyncDisposable {
|
||||
}
|
||||
|
||||
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), List<uint>> inventorySets = Trading.GetInventorySets(inventory);
|
||||
|
||||
// Filter appIDs that can't possibly be completed due to having less cards than smallest badges possible
|
||||
appIDs.IntersectWith(inventorySets.Where(static kv => kv.Value.Count >= MinCardsPerBadge).Select(static kv => kv.Key.RealAppID));
|
||||
|
||||
if (appIDs.Count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<uint, byte>? cardCountPerAppID = await LoadCardsPerSet(appIDs).ConfigureAwait(false);
|
||||
Dictionary<uint, byte>? cardsCountPerAppID = await LoadCardsPerSet(appIDs).ConfigureAwait(false);
|
||||
|
||||
if ((cardCountPerAppID == null) || (cardCountPerAppID.Count == 0)) {
|
||||
if (cardsCountPerAppID == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), (uint Sets, byte CardsPerSet)> itemsToTakePerInventorySet = inventorySets.Where(kv => appIDs.Contains(kv.Key.RealAppID)).ToDictionary(static kv => kv.Key, kv => (kv.Value[0], cardCountPerAppID[kv.Key.RealAppID]));
|
||||
Dictionary<(uint RealAppID, Asset.EType Type, Asset.ERarity Rarity), (uint Sets, byte CardsPerSet)> itemsToTakePerInventorySet = new();
|
||||
|
||||
if (itemsToTakePerInventorySet.Values.All(static value => value.Sets == 0)) {
|
||||
foreach (((uint RealAppID, Asset.EType Type, Asset.ERarity Rarity) key, List<uint>? amounts) in inventorySets.Where(set => appIDs.Contains(set.Key.RealAppID))) {
|
||||
if (!cardsCountPerAppID.TryGetValue(key.RealAppID, out byte cardsCount) || (cardsCount == 0)) {
|
||||
throw new InvalidOperationException(nameof(cardsCount));
|
||||
}
|
||||
|
||||
if (amounts.Count < cardsCount) {
|
||||
// Filter results that can't be completed due to not having enough cards available (now that we know how much exactly)
|
||||
continue;
|
||||
}
|
||||
|
||||
uint minimumOwnedAmount = amounts[0];
|
||||
|
||||
if (minimumOwnedAmount == 0) {
|
||||
throw new InvalidOperationException(nameof(minimumOwnedAmount));
|
||||
}
|
||||
|
||||
itemsToTakePerInventorySet[key] = (minimumOwnedAmount, cardsCount);
|
||||
}
|
||||
|
||||
if (itemsToTakePerInventorySet.Count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ using ArchiSteamFarm.Steam.Integration.CMsgs;
|
||||
using JetBrains.Annotations;
|
||||
using SteamKit2;
|
||||
using SteamKit2.Internal;
|
||||
using EPersonaStateFlag = SteamKit2.EPersonaStateFlag;
|
||||
|
||||
namespace ArchiSteamFarm.Steam.Integration;
|
||||
|
||||
@@ -129,6 +130,37 @@ public sealed class ArchiHandler : ClientMsgHandler {
|
||||
return body.games.ToDictionary(static game => (uint) game.appid, static game => game.name);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<string?> GetTradeToken() {
|
||||
if (Client == null) {
|
||||
throw new InvalidOperationException(nameof(Client));
|
||||
}
|
||||
|
||||
if (!Client.IsConnected) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CEcon_GetTradeOfferAccessToken_Request request = new();
|
||||
|
||||
SteamUnifiedMessages.ServiceMethodResponse response;
|
||||
|
||||
try {
|
||||
response = await UnifiedEconService.SendMessage(x => x.GetTradeOfferAccessToken(request)).ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (response.Result != EResult.OK) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CEcon_GetTradeOfferAccessToken_Response body = response.GetDeserializedResponse<CEcon_GetTradeOfferAccessToken_Response>();
|
||||
|
||||
return body.trade_offer_access_token;
|
||||
}
|
||||
|
||||
public override void HandleMsg(IPacketMsg packetMsg) {
|
||||
ArgumentNullException.ThrowIfNull(packetMsg);
|
||||
|
||||
@@ -400,36 +432,6 @@ public sealed class ArchiHandler : ClientMsgHandler {
|
||||
return body.privacy_settings;
|
||||
}
|
||||
|
||||
internal async Task<string?> GetTradeToken() {
|
||||
if (Client == null) {
|
||||
throw new InvalidOperationException(nameof(Client));
|
||||
}
|
||||
|
||||
if (!Client.IsConnected) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CEcon_GetTradeOfferAccessToken_Request request = new();
|
||||
|
||||
SteamUnifiedMessages.ServiceMethodResponse response;
|
||||
|
||||
try {
|
||||
response = await UnifiedEconService.SendMessage(x => x.GetTradeOfferAccessToken(request)).ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (response.Result != EResult.OK) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CEcon_GetTradeOfferAccessToken_Response body = response.GetDeserializedResponse<CEcon_GetTradeOfferAccessToken_Response>();
|
||||
|
||||
return body.trade_offer_access_token;
|
||||
}
|
||||
|
||||
internal async Task<string?> GetTwoFactorDeviceIdentifier(ulong steamID) {
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||
@@ -756,6 +758,33 @@ public sealed class ArchiHandler : ClientMsgHandler {
|
||||
Client.Send(request);
|
||||
}
|
||||
|
||||
internal void SetPersonaState(EPersonaState state, EPersonaStateFlag flags) {
|
||||
if (!Enum.IsDefined(state)) {
|
||||
throw new InvalidEnumArgumentException(nameof(state), (int) state, typeof(EPersonaState));
|
||||
}
|
||||
|
||||
if (flags < 0) {
|
||||
throw new InvalidEnumArgumentException(nameof(flags), (int) flags, typeof(EPersonaStateFlag));
|
||||
}
|
||||
|
||||
if (Client == null) {
|
||||
throw new InvalidOperationException(nameof(Client));
|
||||
}
|
||||
|
||||
if (!Client.IsConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
ClientMsgProtobuf<CMsgClientChangeStatus> request = new(EMsg.ClientChangeStatus) {
|
||||
Body = {
|
||||
persona_state = (uint) state,
|
||||
persona_state_flags = (uint) flags
|
||||
}
|
||||
};
|
||||
|
||||
Client.Send(request);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public enum EUserInterfaceMode : byte {
|
||||
Default = 0,
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel;
|
||||
@@ -67,8 +66,6 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
[PublicAPI]
|
||||
public static Uri SteamStoreURL => new("https://store.steampowered.com");
|
||||
|
||||
private static readonly ConcurrentDictionary<uint, byte> CachedCardCountsForGame = new();
|
||||
|
||||
private static ushort WebLimiterDelay => ASF.GlobalConfig?.WebLimiterDelay ?? GlobalConfig.DefaultWebLimiterDelay;
|
||||
|
||||
[PublicAPI]
|
||||
@@ -1705,7 +1702,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
throw new ArgumentOutOfRangeException(nameof(appID));
|
||||
}
|
||||
|
||||
if (CachedCardCountsForGame.TryGetValue(appID, out byte result)) {
|
||||
if (ASF.GlobalDatabase?.CardCountsPerGame.TryGetValue(appID, out byte result) == true) {
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1727,7 +1724,7 @@ public sealed class ArchiWebHandler : IDisposable {
|
||||
return 0;
|
||||
}
|
||||
|
||||
CachedCardCountsForGame.TryAdd(appID, result);
|
||||
ASF.GlobalDatabase?.CardCountsPerGame.TryAdd(appID, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -74,9 +74,7 @@ internal static class SteamChatMessage {
|
||||
|
||||
using StringReader stringReader = new(message);
|
||||
|
||||
string? line;
|
||||
|
||||
while ((line = await stringReader.ReadLineAsync().ConfigureAwait(false)) != null) {
|
||||
while (await stringReader.ReadLineAsync().ConfigureAwait(false) is { } line) {
|
||||
// Special case for empty newline
|
||||
if (line.Length == 0) {
|
||||
if (messagePart.Length > prefixLength) {
|
||||
|
||||
@@ -107,6 +107,19 @@ public sealed class Actions : IAsyncDisposable {
|
||||
return (success, token, success ? Strings.Success : Strings.WarningFailed);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public ulong GetFirstSteamMasterID() {
|
||||
ulong steamMasterID = Bot.BotConfig.SteamUserPermissions.Where(kv => (kv.Key > 0) && (kv.Key != Bot.SteamID) && new SteamID(kv.Key).IsIndividualAccount && (kv.Value == BotConfig.EAccess.Master)).Select(static kv => kv.Key).OrderBy(static steamID => steamID).FirstOrDefault();
|
||||
|
||||
if (steamMasterID > 0) {
|
||||
return steamMasterID;
|
||||
}
|
||||
|
||||
ulong steamOwnerID = ASF.GlobalConfig?.SteamOwnerID ?? GlobalConfig.DefaultSteamOwnerID;
|
||||
|
||||
return (steamOwnerID > 0) && new SteamID(steamOwnerID).IsIndividualAccount ? steamOwnerID : 0;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<IDisposable> GetTradingLock() {
|
||||
await TradingSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
@@ -506,18 +519,6 @@ public sealed class Actions : IAsyncDisposable {
|
||||
|
||||
internal void OnDisconnected() => HandledGifts.Clear();
|
||||
|
||||
private ulong GetFirstSteamMasterID() {
|
||||
ulong steamMasterID = Bot.BotConfig.SteamUserPermissions.Where(kv => (kv.Key > 0) && (kv.Key != Bot.SteamID) && new SteamID(kv.Key).IsIndividualAccount && (kv.Value == BotConfig.EAccess.Master)).Select(static kv => kv.Key).OrderBy(static steamID => steamID).FirstOrDefault();
|
||||
|
||||
if (steamMasterID > 0) {
|
||||
return steamMasterID;
|
||||
}
|
||||
|
||||
ulong steamOwnerID = ASF.GlobalConfig?.SteamOwnerID ?? GlobalConfig.DefaultSteamOwnerID;
|
||||
|
||||
return (steamOwnerID > 0) && new SteamID(steamOwnerID).IsIndividualAccount ? steamOwnerID : 0;
|
||||
}
|
||||
|
||||
private static async Task LimitGiftsRequestsAsync() {
|
||||
if (ASF.GiftsSemaphore == null) {
|
||||
throw new InvalidOperationException(nameof(ASF.GiftsSemaphore));
|
||||
|
||||
@@ -82,15 +82,27 @@ public sealed class Commands {
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
[Obsolete($"Use overload which accepts {nameof(EAccess)} instead, this one will be removed soon.", true)]
|
||||
public async Task<string?> Response(ulong steamID, string message) {
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
public static EAccess GetProxyAccess(Bot bot, EAccess access, ulong steamID = 0) {
|
||||
// The objective here should be simple, calculating effective access of the user
|
||||
// Problem is, history already proved nothing in this damn file is as simple as it seems
|
||||
// We use this function for proxying commands such as !status 2 sent to bot 1, which should use 2's user access instead
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
|
||||
if (!Enum.IsDefined(access)) {
|
||||
throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess));
|
||||
}
|
||||
|
||||
if ((steamID != 0) && !new SteamID(steamID).IsIndividualAccount) {
|
||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||
}
|
||||
|
||||
EAccess access = Bot.GetAccess(steamID);
|
||||
// If we got executed with owner access or lack steamID entirely, then this is effective access
|
||||
if ((access >= EAccess.Owner) || (steamID == 0)) {
|
||||
return access;
|
||||
}
|
||||
|
||||
return await Response(access, message, steamID).ConfigureAwait(false);
|
||||
// Otherwise, effective access is the access of the user on target bot, whatever that would be, not this one
|
||||
return bot.GetAccess(steamID);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
@@ -516,29 +528,6 @@ public sealed class Commands {
|
||||
return gamesOwned;
|
||||
}
|
||||
|
||||
private static EAccess ProxyAccess(Bot bot, EAccess access, ulong steamID = 0) {
|
||||
// The objective here should be simple, calculating effective access of the user
|
||||
// Problem is, history already proved nothing in this damn file is as simple as it seems
|
||||
// We use this function for proxying commands such as !status 2 sent to bot 1, which should use 2's user access instead
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
|
||||
if (!Enum.IsDefined(access)) {
|
||||
throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess));
|
||||
}
|
||||
|
||||
if ((steamID != 0) && !new SteamID(steamID).IsIndividualAccount) {
|
||||
throw new ArgumentOutOfRangeException(nameof(steamID));
|
||||
}
|
||||
|
||||
// If we got executed with owner access or lack steamID entirely, then this is effective access
|
||||
if ((access >= EAccess.Owner) || (steamID == 0)) {
|
||||
return access;
|
||||
}
|
||||
|
||||
// Otherwise, effective access is the access of the user on target bot, whatever that would be, not this one
|
||||
return bot.GetAccess(steamID);
|
||||
}
|
||||
|
||||
private async Task<string?> Response2FA(EAccess access) {
|
||||
if (!Enum.IsDefined(access)) {
|
||||
throw new InvalidEnumArgumentException(nameof(access), (int) access, typeof(EAccess));
|
||||
@@ -568,7 +557,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.Response2FA(ProxyAccess(bot, access, steamID)))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.Response2FA(GetProxyAccess(bot, access, steamID)))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -612,7 +601,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.Response2FAConfirm(ProxyAccess(bot, access, steamID), confirm))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.Response2FAConfirm(GetProxyAccess(bot, access, steamID), confirm))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -710,7 +699,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseAddLicense(ProxyAccess(bot, access, steamID), query))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseAddLicense(GetProxyAccess(bot, access, steamID), query))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -774,7 +763,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseAdvancedLoot(ProxyAccess(bot, access, steamID), appID, contextID))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseAdvancedLoot(GetProxyAccess(bot, access, steamID), appID, contextID))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -889,7 +878,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseAdvancedRedeem(ProxyAccess(bot, access, steamID), options, keys, steamID))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseAdvancedRedeem(GetProxyAccess(bot, access, steamID), options, keys, steamID))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -1003,7 +992,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNameTo)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseAdvancedTransfer(ProxyAccess(bot, access, steamID), appID, contextID, targetBot))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseAdvancedTransfer(GetProxyAccess(bot, access, steamID), appID, contextID, targetBot))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -1039,7 +1028,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseBackgroundGamesRedeemer(ProxyAccess(bot, access, steamID))))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseBackgroundGamesRedeemer(GetProxyAccess(bot, access, steamID))))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -1123,7 +1112,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseFarm(ProxyAccess(bot, access, steamID)))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseFarm(GetProxyAccess(bot, access, steamID)))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -1153,7 +1142,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseFarmingBlacklist(ProxyAccess(bot, access, steamID))))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseFarmingBlacklist(GetProxyAccess(bot, access, steamID))))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -1224,7 +1213,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseFarmingBlacklistAdd(ProxyAccess(bot, access, steamID), targetAppIDs)))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseFarmingBlacklistAdd(GetProxyAccess(bot, access, steamID), targetAppIDs)))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -1290,7 +1279,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseFarmingBlacklistRemove(ProxyAccess(bot, access, steamID), targetAppIDs)))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseFarmingBlacklistRemove(GetProxyAccess(bot, access, steamID), targetAppIDs)))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -1320,7 +1309,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseFarmingQueue(ProxyAccess(bot, access, steamID))))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseFarmingQueue(GetProxyAccess(bot, access, steamID))))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -1398,7 +1387,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseFarmingQueueAdd(ProxyAccess(bot, access, steamID), targetAppIDs)))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseFarmingQueueAdd(GetProxyAccess(bot, access, steamID), targetAppIDs)))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -1469,7 +1458,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseFarmingQueueRemove(ProxyAccess(bot, access, steamID), targetAppIDs)))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseFarmingQueueRemove(GetProxyAccess(bot, access, steamID), targetAppIDs)))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -1565,7 +1554,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseInput(ProxyAccess(bot, access, steamID), propertyName, inputValue)))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseInput(GetProxyAccess(bot, access, steamID), propertyName, inputValue)))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -1605,7 +1594,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseLevel(ProxyAccess(bot, access, steamID)))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseLevel(GetProxyAccess(bot, access, steamID)))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -1649,7 +1638,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseLoot(ProxyAccess(bot, access, steamID)))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseLoot(GetProxyAccess(bot, access, steamID)))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -1717,7 +1706,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseLootByRealAppIDs(ProxyAccess(bot, access, steamID), realAppIDsText, exclude))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseLootByRealAppIDs(GetProxyAccess(bot, access, steamID), realAppIDsText, exclude))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -1747,7 +1736,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseMatchActivelyBlacklist(ProxyAccess(bot, access, steamID))))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseMatchActivelyBlacklist(GetProxyAccess(bot, access, steamID))))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -1805,7 +1794,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseMatchActivelyBlacklistAdd(ProxyAccess(bot, access, steamID), targetAppIDs)))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseMatchActivelyBlacklistAdd(GetProxyAccess(bot, access, steamID), targetAppIDs)))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -1863,7 +1852,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseMatchActivelyBlacklistRemove(ProxyAccess(bot, access, steamID), targetAppIDs)))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseMatchActivelyBlacklistRemove(GetProxyAccess(bot, access, steamID), targetAppIDs)))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -1889,6 +1878,9 @@ public sealed class Commands {
|
||||
|
||||
Bot.SteamFriends.SetPersonaName(nickname);
|
||||
|
||||
// Nickname change affects the current persona state, reset it back to wanted one
|
||||
Bot.ResetPersonaState();
|
||||
|
||||
return FormatBotResponse(Strings.Done);
|
||||
}
|
||||
|
||||
@@ -1911,7 +1903,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseNickname(ProxyAccess(bot, access, steamID), nickname)))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseNickname(GetProxyAccess(bot, access, steamID), nickname)))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -2089,7 +2081,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<(string? Response, Dictionary<string, string>? OwnedGames)> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseOwns(ProxyAccess(bot, access, steamID), query))).ConfigureAwait(false);
|
||||
IList<(string? Response, Dictionary<string, string>? OwnedGames)> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseOwns(GetProxyAccess(bot, access, steamID), query))).ConfigureAwait(false);
|
||||
|
||||
List<(string Response, Dictionary<string, string> OwnedGames)> validResults = new(results.Where(static result => !string.IsNullOrEmpty(result.Response) && (result.OwnedGames != null))!);
|
||||
|
||||
@@ -2157,7 +2149,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponsePause(ProxyAccess(bot, access, steamID), permanent, resumeInSecondsText))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponsePause(GetProxyAccess(bot, access, steamID), permanent, resumeInSecondsText))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -2254,7 +2246,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponsePlay(ProxyAccess(bot, access, steamID), targetGameIDs))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponsePlay(GetProxyAccess(bot, access, steamID), targetGameIDs))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -2294,7 +2286,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponsePointsBalance(ProxyAccess(bot, access, steamID)))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponsePointsBalance(GetProxyAccess(bot, access, steamID)))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -2454,7 +2446,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponsePrivacy(ProxyAccess(bot, access, steamID), privacySettingsText))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponsePrivacy(GetProxyAccess(bot, access, steamID), privacySettingsText))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -2739,7 +2731,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseRedeem(ProxyAccess(bot, access, steamID), keysText, steamID, redeemFlags))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseRedeem(GetProxyAccess(bot, access, steamID), keysText, steamID, redeemFlags))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -2779,7 +2771,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseReset(ProxyAccess(bot, access, steamID)))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseReset(GetProxyAccess(bot, access, steamID)))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -2829,7 +2821,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseResume(ProxyAccess(bot, access, steamID))))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseResume(GetProxyAccess(bot, access, steamID))))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -2865,7 +2857,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseStart(ProxyAccess(bot, access, steamID))))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseStart(GetProxyAccess(bot, access, steamID))))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -2944,7 +2936,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<(string? Response, Bot Bot)> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseStatus(ProxyAccess(bot, access, steamID))))).ConfigureAwait(false);
|
||||
IList<(string? Response, Bot Bot)> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseStatus(GetProxyAccess(bot, access, steamID))))).ConfigureAwait(false);
|
||||
|
||||
List<(string Response, Bot Bot)> validResults = new(results.Where(static result => !string.IsNullOrEmpty(result.Response))!);
|
||||
|
||||
@@ -2988,7 +2980,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseStop(ProxyAccess(bot, access, steamID))))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseStop(GetProxyAccess(bot, access, steamID))))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -3018,7 +3010,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseTradingBlacklist(ProxyAccess(bot, access, steamID))))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseTradingBlacklist(GetProxyAccess(bot, access, steamID))))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -3076,7 +3068,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseTradingBlacklistAdd(ProxyAccess(bot, access, steamID), targetSteamIDs)))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseTradingBlacklistAdd(GetProxyAccess(bot, access, steamID), targetSteamIDs)))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -3134,7 +3126,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseTradingBlacklistRemove(ProxyAccess(bot, access, steamID), targetSteamIDs)))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseTradingBlacklistRemove(GetProxyAccess(bot, access, steamID), targetSteamIDs)))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -3200,7 +3192,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseTransfer(ProxyAccess(bot, access, steamID), botNameTo))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseTransfer(GetProxyAccess(bot, access, steamID), botNameTo))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -3330,7 +3322,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNameTo)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseTransferByRealAppIDs(ProxyAccess(bot, access, steamID), realAppIDs, targetBot, exclude))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseTransferByRealAppIDs(GetProxyAccess(bot, access, steamID), realAppIDs, targetBot, exclude))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -3396,7 +3388,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseUnpackBoosters(ProxyAccess(bot, access, steamID)))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => bot.Commands.ResponseUnpackBoosters(GetProxyAccess(bot, access, steamID)))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
@@ -3452,7 +3444,7 @@ public sealed class Commands {
|
||||
return access >= EAccess.Owner ? FormatStaticResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)) : null;
|
||||
}
|
||||
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseWalletBalance(ProxyAccess(bot, access, steamID))))).ConfigureAwait(false);
|
||||
IList<string?> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.Commands.ResponseWalletBalance(GetProxyAccess(bot, access, steamID))))).ConfigureAwait(false);
|
||||
|
||||
List<string> responses = new(results.Where(static result => !string.IsNullOrEmpty(result))!);
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ using ArchiSteamFarm.IPC.Integration;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using ArchiSteamFarm.Steam.Integration;
|
||||
using ArchiSteamFarm.Storage;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@@ -68,6 +69,9 @@ public sealed class BotConfig {
|
||||
[PublicAPI]
|
||||
public const byte DefaultHoursUntilCardDrops = 3;
|
||||
|
||||
[PublicAPI]
|
||||
public const EPersonaStateFlag DefaultOnlineFlags = 0;
|
||||
|
||||
[PublicAPI]
|
||||
public const EPersonaState DefaultOnlineStatus = EPersonaState.Online;
|
||||
|
||||
@@ -80,6 +84,9 @@ public sealed class BotConfig {
|
||||
[PublicAPI]
|
||||
public const ERedeemingPreferences DefaultRedeemingPreferences = ERedeemingPreferences.None;
|
||||
|
||||
[PublicAPI]
|
||||
public const ERemoteCommunication DefaultRemoteCommunication = ERemoteCommunication.All;
|
||||
|
||||
[PublicAPI]
|
||||
public const bool DefaultSendOnFarmingFinished = false;
|
||||
|
||||
@@ -183,6 +190,9 @@ public sealed class BotConfig {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public ImmutableHashSet<Asset.EType> MatchableTypes { get; private set; } = DefaultMatchableTypes;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public EPersonaStateFlag OnlineFlags { get; private set; } = DefaultOnlineFlags;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public EPersonaState OnlineStatus { get; private set; } = DefaultOnlineStatus;
|
||||
|
||||
@@ -195,6 +205,9 @@ public sealed class BotConfig {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public ERedeemingPreferences RedeemingPreferences { get; private set; } = DefaultRedeemingPreferences;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public ERemoteCommunication RemoteCommunication { get; private set; } = DefaultRemoteCommunication;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool SendOnFarmingFinished { get; private set; } = DefaultSendOnFarmingFinished;
|
||||
|
||||
@@ -339,6 +352,9 @@ public sealed class BotConfig {
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeMatchableTypes() => !Saving || ((MatchableTypes != DefaultMatchableTypes) && !MatchableTypes.SetEquals(DefaultMatchableTypes));
|
||||
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeOnlineFlags() => !Saving || (OnlineFlags != DefaultOnlineFlags);
|
||||
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeOnlineStatus() => !Saving || (OnlineStatus != DefaultOnlineStatus);
|
||||
|
||||
@@ -351,6 +367,9 @@ public sealed class BotConfig {
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeRedeemingPreferences() => !Saving || (RedeemingPreferences != DefaultRedeemingPreferences);
|
||||
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeRemoteCommunication() => !Saving || (RemoteCommunication != DefaultRemoteCommunication);
|
||||
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeSendOnFarmingFinished() => !Saving || (SendOnFarmingFinished != DefaultSendOnFarmingFinished);
|
||||
|
||||
@@ -456,6 +475,10 @@ public sealed class BotConfig {
|
||||
return (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(MatchableTypes), matchableType));
|
||||
}
|
||||
|
||||
if (OnlineFlags < 0) {
|
||||
return (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(OnlineFlags), OnlineFlags));
|
||||
}
|
||||
|
||||
if (!Enum.IsDefined(OnlineStatus)) {
|
||||
return (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(OnlineStatus), OnlineStatus));
|
||||
}
|
||||
@@ -517,7 +540,7 @@ public sealed class BotConfig {
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static async Task<(BotConfig? BotConfig, string? LatestJson)> Load(string filePath) {
|
||||
internal static async Task<(BotConfig? BotConfig, string? LatestJson)> Load(string filePath, string? botName = null) {
|
||||
if (string.IsNullOrEmpty(filePath)) {
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
}
|
||||
@@ -577,7 +600,11 @@ public sealed class BotConfig {
|
||||
(bool isWeak, string? reason) = Utilities.TestPasswordStrength(decryptedSteamPassword!, disallowedValues);
|
||||
|
||||
if (isWeak) {
|
||||
ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningWeakSteamPassword, !string.IsNullOrEmpty(botConfig.SteamLogin) ? botConfig.SteamLogin! : filePath, reason));
|
||||
if (string.IsNullOrEmpty(botName)) {
|
||||
botName = Path.GetFileNameWithoutExtension(filePath);
|
||||
}
|
||||
|
||||
ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningWeakSteamPassword, botName, reason));
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -594,7 +621,18 @@ public sealed class BotConfig {
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: Pending removal, Statistics -> RemoteCommunication migration
|
||||
if ((ASF.GlobalConfig?.Statistics == false) && (botConfig.RemoteCommunication == DefaultRemoteCommunication)) {
|
||||
botConfig.RemoteCommunication = ERemoteCommunication.None;
|
||||
}
|
||||
|
||||
if (!Program.ConfigMigrate) {
|
||||
// TODO: Pending removal, warning for people that disabled config migrate, they need to migrate themselves
|
||||
if (ASF.GlobalConfig?.Statistics == false) {
|
||||
ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(Program.ConfigMigrate)));
|
||||
ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningDeprecated, nameof(GlobalConfig.Statistics), nameof(RemoteCommunication)));
|
||||
}
|
||||
|
||||
return (botConfig, null);
|
||||
}
|
||||
|
||||
@@ -662,6 +700,14 @@ public sealed class BotConfig {
|
||||
All = Forwarding | Distributing | KeepMissingGames | AssumeWalletKeyOnBadActivationCode
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ERemoteCommunication : byte {
|
||||
None = 0,
|
||||
SteamGroup = 1,
|
||||
PublicListing = 2,
|
||||
All = SteamGroup | PublicListing
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ETradingPreferences : byte {
|
||||
None = 0,
|
||||
|
||||
@@ -94,48 +94,6 @@ internal sealed class BotDatabase : SerializableFile {
|
||||
[JsonProperty(PropertyName = $"_{nameof(MobileAuthenticator)}")]
|
||||
private MobileAuthenticator? BackingMobileAuthenticator;
|
||||
|
||||
private bool SaveNeededDueToMigration;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
[Obsolete("Available for limited time and only to migrate existing databases")]
|
||||
private ConcurrentHashSet<ulong> BlacklistedFromTradesSteamIDs {
|
||||
set {
|
||||
if (TradingBlacklistSteamIDs.AddRange(value) && string.IsNullOrEmpty(FilePath)) {
|
||||
SaveNeededDueToMigration = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
[Obsolete("Available for limited time and only to migrate existing databases")]
|
||||
private ConcurrentHashSet<uint> IdlingBlacklistedAppIDs {
|
||||
set {
|
||||
if (FarmingBlacklistAppIDs.AddRange(value) && string.IsNullOrEmpty(FilePath)) {
|
||||
SaveNeededDueToMigration = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
[Obsolete("Available for limited time and only to migrate existing databases")]
|
||||
private ConcurrentHashSet<uint> IdlingPriorityAppIDs {
|
||||
set {
|
||||
if (FarmingPriorityQueueAppIDs.AddRange(value) && string.IsNullOrEmpty(FilePath)) {
|
||||
SaveNeededDueToMigration = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
[Obsolete("Available for limited time and only to migrate existing databases")]
|
||||
private ConcurrentHashSet<uint> MatchActivelyBlacklistedAppIDs {
|
||||
set {
|
||||
if (MatchActivelyBlacklistAppIDs.AddRange(value) && string.IsNullOrEmpty(FilePath)) {
|
||||
SaveNeededDueToMigration = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BotDatabase(string filePath) {
|
||||
if (string.IsNullOrEmpty(filePath)) {
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
@@ -243,12 +201,6 @@ internal sealed class BotDatabase : SerializableFile {
|
||||
|
||||
botDatabase.FilePath = filePath;
|
||||
|
||||
if (botDatabase.SaveNeededDueToMigration) {
|
||||
botDatabase.SaveNeededDueToMigration = false;
|
||||
|
||||
Utilities.InBackground(botDatabase.Save);
|
||||
}
|
||||
|
||||
return botDatabase;
|
||||
}
|
||||
|
||||
|
||||
@@ -99,9 +99,6 @@ public sealed class GlobalConfig {
|
||||
[PublicAPI]
|
||||
public const EOptimizationMode DefaultOptimizationMode = EOptimizationMode.MaxPerformance;
|
||||
|
||||
[PublicAPI]
|
||||
public const bool DefaultStatistics = true;
|
||||
|
||||
[PublicAPI]
|
||||
public const string? DefaultSteamMessagePrefix = "/me ";
|
||||
|
||||
@@ -259,9 +256,6 @@ public sealed class GlobalConfig {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public EOptimizationMode OptimizationMode { get; private set; } = DefaultOptimizationMode;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool Statistics { get; private set; } = DefaultStatistics;
|
||||
|
||||
[JsonProperty]
|
||||
[MaxLength(SteamChatMessage.MaxMessagePrefixBytes / SteamChatMessage.ReservedEscapeMessageBytes)]
|
||||
public string? SteamMessagePrefix { get; private set; } = DefaultSteamMessagePrefix;
|
||||
@@ -303,6 +297,10 @@ public sealed class GlobalConfig {
|
||||
|
||||
internal bool Saving { get; set; }
|
||||
|
||||
// TODO: Pending removal, Statistics property which got changed into RemoteConnection
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal bool Statistics { get; private set; } = true;
|
||||
|
||||
[JsonProperty]
|
||||
internal string? WebProxyPassword {
|
||||
get => BackingWebProxyPassword;
|
||||
@@ -398,8 +396,11 @@ public sealed class GlobalConfig {
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeSSteamOwnerID() => !Saving;
|
||||
|
||||
// TODO: Pending removal, Statistics property which got changed into RemoteConnection, we never serialize it after update
|
||||
#pragma warning disable CA1822 // We can't mark it as static
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeStatistics() => !Saving || (Statistics != DefaultStatistics);
|
||||
public bool ShouldSerializeStatistics() => false;
|
||||
#pragma warning restore CA1822 // We can't mark it as static
|
||||
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeSteamMessagePrefix() => !Saving || (SteamMessagePrefix != DefaultSteamMessagePrefix);
|
||||
|
||||
@@ -28,6 +28,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Collections;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Helpers;
|
||||
using ArchiSteamFarm.Localization;
|
||||
@@ -48,6 +49,9 @@ public sealed class GlobalDatabase : SerializableFile {
|
||||
[PublicAPI]
|
||||
public IReadOnlyDictionary<uint, (uint ChangeNumber, ImmutableHashSet<uint>? AppIDs)> PackagesDataReadOnly => PackagesData;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly ObservableConcurrentDictionary<uint, byte> CardCountsPerGame = new();
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly InMemoryServerListProvider ServerListProvider = new();
|
||||
|
||||
@@ -107,7 +111,10 @@ public sealed class GlobalDatabase : SerializableFile {
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
private GlobalDatabase() => ServerListProvider.ServerListUpdated += OnObjectModified;
|
||||
private GlobalDatabase() {
|
||||
CardCountsPerGame.OnModified += OnObjectModified;
|
||||
ServerListProvider.ServerListUpdated += OnObjectModified;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public void DeleteFromJsonStorage(string key) {
|
||||
@@ -159,6 +166,9 @@ public sealed class GlobalDatabase : SerializableFile {
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeBackingLastChangeNumber() => LastChangeNumber != 0;
|
||||
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeCardCountsPerGame() => !CardCountsPerGame.IsEmpty;
|
||||
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeKeyValueJsonStorage() => !KeyValueJsonStorage.IsEmpty;
|
||||
|
||||
@@ -174,6 +184,7 @@ public sealed class GlobalDatabase : SerializableFile {
|
||||
protected override void Dispose(bool disposing) {
|
||||
if (disposing) {
|
||||
// Events we registered
|
||||
CardCountsPerGame.OnModified -= OnObjectModified;
|
||||
ServerListProvider.ServerListUpdated -= OnObjectModified;
|
||||
|
||||
// Those are objects that are always being created if constructor doesn't throw exception
|
||||
|
||||
@@ -10,4 +10,11 @@
|
||||
<assembly fullname="System.Composition.AttributedModel">
|
||||
<type fullname="System.Composition.ExportAttribute" />
|
||||
</assembly>
|
||||
|
||||
<!-- This is needed for our STD plugin -->
|
||||
<assembly fullname="System.Linq">
|
||||
<type fullname="System.Linq.Enumerable">
|
||||
<method name="MaxBy" />
|
||||
</type>
|
||||
</assembly>
|
||||
</linker>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>5.2.2.5</Version>
|
||||
<Version>5.2.3.5</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
<PackageVersion Include="AngleSharp.XPath" Version="1.1.7" />
|
||||
<PackageVersion Include="ConfigureAwaitChecker.Analyzer" Version="5.0.0" />
|
||||
<PackageVersion Include="CryptSharpStandard" Version="1.0.0" />
|
||||
<PackageVersion Include="Humanizer" Version="2.13.14" />
|
||||
<PackageVersion Include="Humanizer" Version="2.14.1" />
|
||||
<PackageVersion Include="JetBrains.Annotations" Version="2021.3.0" />
|
||||
<PackageVersion Include="Markdig.Signed" Version="0.26.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||
<PackageVersion Include="Markdig.Signed" Version="0.27.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||
<PackageVersion Include="MSTest.TestAdapter" Version="2.2.8" />
|
||||
<PackageVersion Include="MSTest.TestFramework" Version="2.2.8" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
@@ -18,7 +18,7 @@
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.2.3" />
|
||||
<PackageVersion Include="System.Composition" Version="6.0.0" />
|
||||
<PackageVersion Include="System.Composition.AttributedModel" Version="6.0.0" />
|
||||
<PackageVersion Include="System.Linq.Async" Version="5.1.0" />
|
||||
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
|
||||
<PackageVersion Include="zxcvbn-core" Version="7.0.92" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net48'">
|
||||
<PackageVersion Include="JustArchiNET.Madness" Version="3.2.1" />
|
||||
<PackageVersion Include="JustArchiNET.Madness" Version="3.3.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Cors" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Diagnostics" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" />
|
||||
|
||||
10
README.md
10
README.md
@@ -43,20 +43,20 @@
|
||||
|
||||
## Description
|
||||
|
||||
ASF is a C# application with primary purpose of idling Steam cards from multiple accounts simultaneously. Unlike Idle Master which works only for one account at given time, while requiring Steam client running in the background and launching additional processes imitating "game playing" status, ASF doesn't require any Steam client running in the background, doesn't launch any additional processes and is made to handle unlimited Steam accounts at once. In addition to that, it's meant to be run on servers or other desktop-less machines, and features full cross-OS support, which makes it possible to launch on any operating system with .NET Core runtime, such as Windows, Linux and OS X. ASF is possible thanks to gigantic amount of work done in marvelous **[SteamKit2](https://github.com/SteamRE/SteamKit)** library.
|
||||
ASF is a C# application with primary purpose of farming Steam cards from multiple accounts simultaneously. Unlike Idle Master which works only for one account at given time, while requiring Steam client running in the background and launching additional processes imitating "game playing" status, ASF doesn't require any Steam client running in the background, doesn't launch any additional processes and is made to handle unlimited Steam accounts at once. In addition to that, it's meant to be run on servers or other desktop-less machines, and features full cross-OS support, which makes it possible to launch on any operating system with .NET Core runtime, such as Windows, Linux and OS X. ASF is possible thanks to gigantic amount of work done in marvelous **[SteamKit2](https://github.com/SteamRE/SteamKit)** library.
|
||||
|
||||
Today, ASF is one of the most versatile Steam power tools, allowing you to make use of many features that were implemented over time. Apart from idling Steam cards, which remains the primary focus, ASF includes bunch of features on its own, such as a possibility to use it as Steam authenticator or chat logger. In addition to that, ASF includes plugin system, thanks to which anybody can further extend it to his/her needs.
|
||||
Today, ASF is one of the most versatile Steam power tools, allowing you to make use of many features that were implemented over time. Apart from farming Steam cards, which remains the primary focus, ASF includes bunch of features on its own, such as a possibility to use it as Steam authenticator or chat logger. In addition to that, ASF includes plugin system, thanks to which anybody can further extend it to his/her needs.
|
||||
|
||||
---
|
||||
|
||||
### Core features
|
||||
|
||||
- Automatic idling of available games with card drops using any number of active accounts
|
||||
- Automatic farming of available games with card drops using any number of active accounts
|
||||
- No requirement of running or even having official Steam client installed
|
||||
- Guarantee of being VAC-free, focus on security and privacy
|
||||
- Complex error-reporting mechanism, reliability even during Steam issues and other networking quirks
|
||||
- Flexible cards idling algorithm, pushing the performance to the maximum while still allowing a lot of customization
|
||||
- Offline idling, enabling you to skip in-game status and stop confusing your friends with fake playing status
|
||||
- Flexible cards farming algorithm, pushing the performance to the maximum while still allowing a lot of customization
|
||||
- Offline farming, enabling you to skip in-game status and stop confusing your friends with fake playing status
|
||||
- Advanced support for Steam accounts, including ability to redeem keys, redeem gifts, accept trades, send messages and more
|
||||
- Support for latest Steam security features, including SteamGuard, SteamParental and 2-factor authentication
|
||||
- Unique ASF 2FA mechanism allowing ASF to act as a mobile authenticator, if needed
|
||||
|
||||
2
wiki
2
wiki
Submodule wiki updated: 44c9c35ee3...fd3f70c226
Reference in New Issue
Block a user