mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2026-01-01 06:00:46 +00:00
Merge branch 'main' of https://github.com/JustArchiNET/ArchiSteamFarm
This commit is contained in:
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/code-quality.yml
vendored
2
.github/workflows/code-quality.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
|
|
||||||
- name: Run Qodana scan
|
- name: Run Qodana scan
|
||||||
uses: JetBrains/qodana-action@v2023.1.0
|
uses: JetBrains/qodana-action@v2023.1.0
|
||||||
|
|||||||
6
.github/workflows/docker-ci.yml
vendored
6
.github/workflows/docker-ci.yml
vendored
@@ -17,15 +17,15 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2.5.0
|
uses: docker/setup-buildx-action@v2.6.0
|
||||||
|
|
||||||
- name: Build ${{ matrix.configuration }} Docker image from ${{ matrix.file }}
|
- name: Build ${{ matrix.configuration }} Docker image from ${{ matrix.file }}
|
||||||
uses: docker/build-push-action@v4.0.0
|
uses: docker/build-push-action@v4.1.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ${{ matrix.file }}
|
file: ${{ matrix.file }}
|
||||||
|
|||||||
10
.github/workflows/docker-publish-latest.yml
vendored
10
.github/workflows/docker-publish-latest.yml
vendored
@@ -15,22 +15,22 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2.5.0
|
uses: docker/setup-buildx-action@v2.6.0
|
||||||
|
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2.1.0
|
uses: docker/login-action@v2.2.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v2.1.0
|
uses: docker/login-action@v2.2.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
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"
|
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
|
- name: Build and publish Docker image from Dockerfile.Service
|
||||||
uses: docker/build-push-action@v4.0.0
|
uses: docker/build-push-action@v4.1.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: Dockerfile.Service
|
file: Dockerfile.Service
|
||||||
|
|||||||
10
.github/workflows/docker-publish-main.yml
vendored
10
.github/workflows/docker-publish-main.yml
vendored
@@ -16,22 +16,22 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2.5.0
|
uses: docker/setup-buildx-action@v2.6.0
|
||||||
|
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2.1.0
|
uses: docker/login-action@v2.2.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v2.1.0
|
uses: docker/login-action@v2.2.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
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"
|
echo "DH_REPOSITORY=$(echo ${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Build and publish Docker image from Dockerfile
|
- name: Build and publish Docker image from Dockerfile
|
||||||
uses: docker/build-push-action@v4.0.0
|
uses: docker/build-push-action@v4.1.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: ${{ env.PLATFORMS }}
|
platforms: ${{ env.PLATFORMS }}
|
||||||
|
|||||||
10
.github/workflows/docker-publish-released.yml
vendored
10
.github/workflows/docker-publish-released.yml
vendored
@@ -16,22 +16,22 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2.5.0
|
uses: docker/setup-buildx-action@v2.6.0
|
||||||
|
|
||||||
- name: Login to ghcr.io
|
- name: Login to ghcr.io
|
||||||
uses: docker/login-action@v2.1.0
|
uses: docker/login-action@v2.2.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v2.1.0
|
uses: docker/login-action@v2.2.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
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"
|
echo "DH_REPOSITORY=$(echo ${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Build and publish Docker image from Dockerfile
|
- name: Build and publish Docker image from Dockerfile
|
||||||
uses: docker/build-push-action@v4.0.0
|
uses: docker/build-push-action@v4.1.0
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: ${{ env.PLATFORMS }}
|
platforms: ${{ env.PLATFORMS }}
|
||||||
|
|||||||
6
.github/workflows/publish.yml
vendored
6
.github/workflows/publish.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
|
|
||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v3.2.0
|
uses: actions/setup-dotnet@v3.2.0
|
||||||
@@ -417,7 +417,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
|
|
||||||
- name: Download ASF-generic artifact from ubuntu-latest
|
- name: Download ASF-generic artifact from ubuntu-latest
|
||||||
uses: actions/download-artifact@v3.0.2
|
uses: actions/download-artifact@v3.0.2
|
||||||
|
|||||||
2
.github/workflows/translations.yml
vendored
2
.github/workflows/translations.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3.5.2
|
uses: actions/checkout@v3.5.3
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
token: ${{ secrets.ARCHIBOT_GITHUB_TOKEN }}
|
token: ${{ secrets.ARCHIBOT_GITHUB_TOKEN }}
|
||||||
|
|||||||
2
ASF-ui
2
ASF-ui
Submodule ASF-ui updated: 899f7630aa...2087d1701d
@@ -408,13 +408,11 @@ public sealed class Trading : IDisposable {
|
|||||||
HandledTradeOfferIDs.IntersectWith(tradeOffers.Select(static tradeOffer => tradeOffer.TradeOfferID));
|
HandledTradeOfferIDs.IntersectWith(tradeOffers.Select(static tradeOffer => tradeOffer.TradeOfferID));
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerable<Task<ParseTradeResult?>> tasks = tradeOffers.Where(tradeOffer => !HandledTradeOfferIDs.Contains(tradeOffer.TradeOfferID)).Select(ParseTrade);
|
IEnumerable<Task<ParseTradeResult>> tasks = tradeOffers.Where(tradeOffer => (tradeOffer.State == ETradeOfferState.Active) && HandledTradeOfferIDs.Add(tradeOffer.TradeOfferID)).Select(ParseTrade);
|
||||||
IList<ParseTradeResult?> results = await Utilities.InParallel(tasks).ConfigureAwait(false);
|
IList<ParseTradeResult> results = await Utilities.InParallel(tasks).ConfigureAwait(false);
|
||||||
|
|
||||||
HashSet<ParseTradeResult> validTradeResults = results.Where(static result => result != null).Select(static result => result!).ToHashSet();
|
|
||||||
|
|
||||||
if (Bot.HasMobileAuthenticator) {
|
if (Bot.HasMobileAuthenticator) {
|
||||||
HashSet<ParseTradeResult> mobileTradeResults = validTradeResults.Where(static result => result is { Result: ParseTradeResult.EResult.Accepted, Confirmed: false }).ToHashSet();
|
HashSet<ParseTradeResult> mobileTradeResults = results.Where(static result => result is { Result: ParseTradeResult.EResult.Accepted, Confirmed: false }).ToHashSet();
|
||||||
|
|
||||||
if (mobileTradeResults.Count > 0) {
|
if (mobileTradeResults.Count > 0) {
|
||||||
HashSet<ulong> mobileTradeOfferIDs = mobileTradeResults.Select(static tradeOffer => tradeOffer.TradeOfferID).ToHashSet();
|
HashSet<ulong> mobileTradeOfferIDs = mobileTradeResults.Select(static tradeOffer => tradeOffer.TradeOfferID).ToHashSet();
|
||||||
@@ -431,29 +429,16 @@ public sealed class Trading : IDisposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (validTradeResults.Count > 0) {
|
if (results.Count > 0) {
|
||||||
await PluginsCore.OnBotTradeOfferResults(Bot, validTradeResults).ConfigureAwait(false);
|
await PluginsCore.OnBotTradeOfferResults(Bot, results as IReadOnlyCollection<ParseTradeResult> ?? results.ToHashSet()).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return validTradeResults.Any(result => result is { Result: ParseTradeResult.EResult.Accepted, Confirmed: true } && (result.ItemsToReceive?.Any(receivedItem => Bot.BotConfig.LootableTypes.Contains(receivedItem.Type)) == true));
|
return results.Any(result => result is { Result: ParseTradeResult.EResult.Accepted, Confirmed: true } && (result.ItemsToReceive?.Any(receivedItem => Bot.BotConfig.LootableTypes.Contains(receivedItem.Type)) == true));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ParseTradeResult?> ParseTrade(TradeOffer tradeOffer) {
|
private async Task<ParseTradeResult> ParseTrade(TradeOffer tradeOffer) {
|
||||||
ArgumentNullException.ThrowIfNull(tradeOffer);
|
ArgumentNullException.ThrowIfNull(tradeOffer);
|
||||||
|
|
||||||
if (tradeOffer.State != ETradeOfferState.Active) {
|
|
||||||
Bot.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, tradeOffer.State));
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!HandledTradeOfferIDs.Add(tradeOffer.TradeOfferID)) {
|
|
||||||
// We've already seen this trade, this should not happen
|
|
||||||
Bot.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.IgnoringTrade, tradeOffer.TradeOfferID));
|
|
||||||
|
|
||||||
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.Ignored, false, tradeOffer.ItemsToGive, tradeOffer.ItemsToReceive);
|
|
||||||
}
|
|
||||||
|
|
||||||
ParseTradeResult.EResult result = await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false);
|
ParseTradeResult.EResult result = await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false);
|
||||||
bool tradeRequiresMobileConfirmation = false;
|
bool tradeRequiresMobileConfirmation = false;
|
||||||
|
|
||||||
@@ -481,6 +466,9 @@ public sealed class Trading : IDisposable {
|
|||||||
goto case ParseTradeResult.EResult.TryAgain;
|
goto case ParseTradeResult.EResult.TryAgain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We do not expect to see this trade offer again, so retry it if needed
|
||||||
|
HandledTradeOfferIDs.Remove(tradeOffer.TradeOfferID);
|
||||||
|
|
||||||
if (tradeOffer.ItemsToReceive.Sum(static item => item.Amount) > tradeOffer.ItemsToGive.Sum(static item => item.Amount)) {
|
if (tradeOffer.ItemsToReceive.Sum(static item => item.Amount) > tradeOffer.ItemsToGive.Sum(static item => item.Amount)) {
|
||||||
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, Strings.BotAcceptedDonationTrade, tradeOffer.TradeOfferID));
|
Bot.ArchiLogger.LogGenericTrace(string.Format(CultureInfo.CurrentCulture, Strings.BotAcceptedDonationTrade, tradeOffer.TradeOfferID));
|
||||||
}
|
}
|
||||||
@@ -498,20 +486,25 @@ public sealed class Trading : IDisposable {
|
|||||||
goto case ParseTradeResult.EResult.TryAgain;
|
goto case ParseTradeResult.EResult.TryAgain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We do not expect to see this trade offer again, so retry it if needed
|
||||||
|
HandledTradeOfferIDs.Remove(tradeOffer.TradeOfferID);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case ParseTradeResult.EResult.Ignored:
|
case ParseTradeResult.EResult.Ignored:
|
||||||
case ParseTradeResult.EResult.Rejected:
|
case ParseTradeResult.EResult.Rejected:
|
||||||
|
// We expect to see this trade offer in the future, so we keep it in HandledTradeOfferIDs if it wasn't removed as part of other result
|
||||||
Bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.IgnoringTrade, tradeOffer.TradeOfferID));
|
Bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.IgnoringTrade, tradeOffer.TradeOfferID));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case ParseTradeResult.EResult.TryAgain:
|
case ParseTradeResult.EResult.TryAgain:
|
||||||
|
// We expect to see this trade offer again and we intend to retry it
|
||||||
HandledTradeOfferIDs.Remove(tradeOffer.TradeOfferID);
|
HandledTradeOfferIDs.Remove(tradeOffer.TradeOfferID);
|
||||||
|
|
||||||
goto case ParseTradeResult.EResult.Ignored;
|
goto case ParseTradeResult.EResult.Ignored;
|
||||||
default:
|
default:
|
||||||
Bot.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(result), result));
|
Bot.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(result), result));
|
||||||
|
|
||||||
return null;
|
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.Ignored, false, tradeOffer.ItemsToGive, tradeOffer.ItemsToReceive);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ParseTradeResult(tradeOffer.TradeOfferID, result, tradeRequiresMobileConfirmation, tradeOffer.ItemsToGive, tradeOffer.ItemsToReceive);
|
return new ParseTradeResult(tradeOffer.TradeOfferID, result, tradeRequiresMobileConfirmation, tradeOffer.ItemsToGive, tradeOffer.ItemsToReceive);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>5.4.7.1</Version>
|
<Version>5.4.7.2</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="AngleSharp.XPath" Version="2.0.2" />
|
<PackageVersion Include="AngleSharp.XPath" Version="2.0.3" />
|
||||||
<PackageVersion Include="ConfigureAwaitChecker.Analyzer" Version="5.0.0.1" />
|
<PackageVersion Include="ConfigureAwaitChecker.Analyzer" Version="5.0.0.1" />
|
||||||
<PackageVersion Include="CryptSharpStandard" Version="1.0.0" />
|
<PackageVersion Include="CryptSharpStandard" Version="1.0.0" />
|
||||||
<PackageVersion Include="Humanizer" Version="2.14.1" />
|
<PackageVersion Include="Humanizer" Version="2.14.1" />
|
||||||
<PackageVersion Include="JetBrains.Annotations" Version="2022.3.1" />
|
<PackageVersion Include="JetBrains.Annotations" Version="2022.3.1" />
|
||||||
<PackageVersion Include="Markdig.Signed" Version="0.31.0" />
|
<PackageVersion Include="Markdig.Signed" Version="0.31.0" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.6.1" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
|
||||||
<PackageVersion Include="MSTest.TestAdapter" Version="3.0.4" />
|
<PackageVersion Include="MSTest.TestAdapter" Version="3.0.4" />
|
||||||
<PackageVersion Include="MSTest.TestFramework" Version="3.0.4" />
|
<PackageVersion Include="MSTest.TestFramework" Version="3.0.4" />
|
||||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
|||||||
2
wiki
2
wiki
Submodule wiki updated: 1906cebbef...6683f64884
Reference in New Issue
Block a user