mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-24 10:16:49 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e550cc0f43 | ||
|
|
5894226e93 | ||
|
|
d50fa8eeb7 | ||
|
|
6efd333684 | ||
|
|
05701b60c1 | ||
|
|
f8e7e55a00 | ||
|
|
20663f0226 | ||
|
|
ad175ba2ac | ||
|
|
bdf90a5e51 | ||
|
|
49618534ce | ||
|
|
f2bb2a6bee | ||
|
|
ba4f3aea7b | ||
|
|
b30068103b |
2
.github/workflows/code-quality.yml
vendored
2
.github/workflows/code-quality.yml
vendored
@@ -40,6 +40,6 @@ jobs:
|
||||
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
|
||||
|
||||
- name: Report Qodana results to GitHub
|
||||
uses: github/codeql-action/upload-sarif@v3.24.10
|
||||
uses: github/codeql-action/upload-sarif@v3.25.0
|
||||
with:
|
||||
sarif_file: ${{ runner.temp }}/qodana/results/qodana.sarif.json
|
||||
|
||||
11
.github/workflows/publish.yml
vendored
11
.github/workflows/publish.yml
vendored
@@ -466,12 +466,11 @@ jobs:
|
||||
name: windows-latest_ASF-win-x64
|
||||
path: out
|
||||
|
||||
# TODO: Enable me when documentation is ready and plugin is stable for usage
|
||||
# - name: Download ArchiSteamFarm.OfficialPlugins.Monitoring artifact
|
||||
# uses: actions/download-artifact@v4.1.4
|
||||
# with:
|
||||
# name: ArchiSteamFarm.OfficialPlugins.Monitoring
|
||||
# path: out
|
||||
- name: Download ArchiSteamFarm.OfficialPlugins.Monitoring artifact
|
||||
uses: actions/download-artifact@v4.1.4
|
||||
with:
|
||||
name: ArchiSteamFarm.OfficialPlugins.Monitoring
|
||||
path: out
|
||||
|
||||
- name: Import GPG key for signing
|
||||
uses: crazy-max/ghaction-import-gpg@v6.1.0
|
||||
|
||||
2
ASF-ui
2
ASF-ui
Submodule ASF-ui updated: d3082d36c1...58779f52ce
@@ -64,6 +64,18 @@
|
||||
<value>{0} sets ont été matché pendant ce round.</value>
|
||||
<comment>{0} will be replaced by number of sets traded</comment>
|
||||
</data>
|
||||
<data name="ListingAnnouncing" xml:space="preserve">
|
||||
<value>Annonce de {0} ({1}) avec l'inventaire réalisé à partir de {2} au total sur la liste...</value>
|
||||
<comment>{0} will be replaced by steam ID (number), {1} will be replaced by user's nickname, {2} will be replaced with number of items in the inventory</comment>
|
||||
</data>
|
||||
<data name="MatchingFound" xml:space="preserve">
|
||||
<value>Correspondance totale de {0} éléments avec le bot {1} ({2}), envoi de l'offre d'échange...</value>
|
||||
<comment>{0} will be replaced by number of items matched, {1} will be replaced by steam ID (number), {2} will be replaced by user's nickname</comment>
|
||||
</data>
|
||||
<data name="TradeOfferFailed" xml:space="preserve">
|
||||
<value>Impossible d'envoyer une offre d'échange au bot {0} ({1}), en cours ...</value>
|
||||
<comment>{0} will be replaced by steam ID (number), {1} will be replaced by user's nickname'</comment>
|
||||
</data>
|
||||
<data name="ActivelyMatchingSomeConfirmationsFailed" xml:space="preserve">
|
||||
<value>Certaines confirmations ont échoué, environ {0} sur les transactions {1} ont été envoyées avec succès.</value>
|
||||
<comment>{0} will be replaced by amount of the trade offers that succeeded (number), {1} will be replaced by amount of the trade offers that were supposed to be sent in total (number)</comment>
|
||||
|
||||
@@ -18,4 +18,12 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ArchiSteamFarm\ArchiSteamFarm.csproj" ExcludeAssets="all" Private="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="overlay\all\**\*.*">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace ArchiSteamFarm.OfficialPlugins.Monitoring;
|
||||
|
||||
[Export(typeof(IPlugin))]
|
||||
[SuppressMessage("ReSharper", "MemberCanBeFileLocal")]
|
||||
internal sealed class MonitoringPlugin : OfficialPlugin, IWebServiceProvider, IGitHubPluginUpdates, IDisposable {
|
||||
internal sealed class MonitoringPlugin : OfficialPlugin, IDisposable, IGitHubPluginUpdates, IWebInterface, IWebServiceProvider {
|
||||
private const string MeterName = SharedInfo.AssemblyName;
|
||||
|
||||
private const string MetricNamePrefix = "asf";
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -377,6 +377,9 @@ internal static class ArchiKestrel {
|
||||
}
|
||||
);
|
||||
|
||||
// Add support for optional healtchecks
|
||||
services.AddHealthChecks();
|
||||
|
||||
// We need MVC for /Api, but we're going to use only a small subset of all available features
|
||||
IMvcBuilder mvc = services.AddControllers();
|
||||
|
||||
|
||||
58
ArchiSteamFarm/IPC/Controllers/Api/HealthCheckController.cs
Normal file
58
ArchiSteamFarm/IPC/Controllers/Api/HealthCheckController.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// |
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// |
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.IPC.Responses;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
|
||||
namespace ArchiSteamFarm.IPC.Controllers.Api;
|
||||
|
||||
[ApiController]
|
||||
[Produces("application/json")]
|
||||
[Route("/HealthCheck")]
|
||||
public sealed class HealthCheckController : ControllerBase {
|
||||
private readonly HealthCheckService HealthCheckService;
|
||||
|
||||
public HealthCheckController(HealthCheckService healthCheckService) {
|
||||
ArgumentNullException.ThrowIfNull(healthCheckService);
|
||||
|
||||
HealthCheckService = healthCheckService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(HealthCheckResponse), (int) HttpStatusCode.OK)]
|
||||
[ProducesResponseType(typeof(HealthCheckResponse), (int) HttpStatusCode.ServiceUnavailable)]
|
||||
public async Task<IActionResult> Get() {
|
||||
CancellationToken cancellationToken = HttpContext.RequestAborted;
|
||||
|
||||
HealthReport report = await HealthCheckService.CheckHealthAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
HealthCheckResponse response = new(report);
|
||||
|
||||
return response.Status == HealthStatus.Healthy ? Ok(response) : StatusCode((int) HttpStatusCode.ServiceUnavailable, response);
|
||||
}
|
||||
}
|
||||
46
ArchiSteamFarm/IPC/Responses/HealthCheckResponse.cs
Normal file
46
ArchiSteamFarm/IPC/Responses/HealthCheckResponse.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// |
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// |
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
|
||||
namespace ArchiSteamFarm.IPC.Responses;
|
||||
|
||||
public sealed class HealthCheckResponse {
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
public string StatusText => Status.ToString();
|
||||
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
[Required]
|
||||
public HealthStatus Status { get; private init; }
|
||||
|
||||
internal HealthCheckResponse(HealthReport report) {
|
||||
ArgumentNullException.ThrowIfNull(report);
|
||||
|
||||
Status = report.Status;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,10 @@ StackTrace :
|
||||
{2}</value>
|
||||
<comment>{0} will be replaced by function name, {1} will be replaced by exception message, {2} will be replaced by entire stack trace. Please note that this string should include newlines for formatting.</comment>
|
||||
</data>
|
||||
|
||||
<data name="ErrorExitingWithNonZeroErrorCode" xml:space="preserve">
|
||||
<value>Erreur : Sortie avec un code d'erreur non nul {0}!</value>
|
||||
<comment>{0} will be replaced by error code (number)</comment>
|
||||
</data>
|
||||
<data name="ErrorFailingRequest" xml:space="preserve">
|
||||
<value>Échec de la requête : {0}</value>
|
||||
<comment>{0} will be replaced by URL of the request</comment>
|
||||
@@ -703,7 +706,10 @@ Durée de fonctionnement : {1}</value>
|
||||
<value>Votre clé de chiffrement est trop courte. Nous vous recommandons d'en utiliser une longue d'au moins {0} octets (caractères).</value>
|
||||
<comment>{0} will be replaced by the number of bytes (characters) recommended</comment>
|
||||
</data>
|
||||
|
||||
<data name="WarningDefaultCryptKeyUsedForHashing" xml:space="preserve">
|
||||
<value>Vous utilisez le paramètre {0} de la propriété {1} , mais vous n'avez pas fourni de --cryptkey personnalisé. Vous devriez fournir une --cryptkey personnalisée pour plus de sécurité.</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>Vous utilisez le paramètre {0} de la propriété {1} , mais vous n'avez pas fourni de --cryptkey personnalisé. Cela casse complètement la protection, car ASF est forcé d’utiliser sa propre clé (connue). Vous devez fournir une --cryptkey personnalisée pour utiliser les avantages de sécurité offerts par ce paramètre.</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>
|
||||
@@ -723,7 +729,9 @@ Durée de fonctionnement : {1}</value>
|
||||
<data name="ChecksumMissing" xml:space="preserve">
|
||||
<value>Le serveur distant n'a pas d'informations sur la version vers laquelle nous mettons à jour. Il est possible que la version ai été publiée récemment. Refus de procéder à la mise à jour pour plus de sécurité.</value>
|
||||
</data>
|
||||
|
||||
<data name="ChecksumTimeout" xml:space="preserve">
|
||||
<value>Impossible de récupérer la somme de contrôle du binaire téléchargé - refusant de procéder à la procédure de mise à jour en ce moment comme mesure de sécurité supplémentaire.</value>
|
||||
</data>
|
||||
<data name="ChecksumWrong" xml:space="preserve">
|
||||
<value>Le serveur distant a répondu avec une somme de contrôle différente, ce qui pourrait indiquer un téléchargement corrompu ou une attaque de type MITM. Refus de procéder à la mise à jour !</value>
|
||||
</data>
|
||||
@@ -746,20 +754,65 @@ Durée de fonctionnement : {1}</value>
|
||||
<value>ASF ne peut pas jouer l'application {0} car elle a une restriction relative à la région pour le pays {1} qui dure jusqu'à {2}.</value>
|
||||
<comment>{0} will be replaced by app ID (number), {1} will be replaced by short country code (string, such as "PL"), {2} will be replaced by human-readable date (string).</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="WarningUnsupportedOfficialPlugins" xml:space="preserve">
|
||||
<value>Vous essayez d'exécuter le plugin officiel {0} en ne correspondant pas à la version ASF: {1} (attendu {2}). Cela suggère que vous fassiez quelque chose d'horriblement mauvais, que ce soit pour réparer votre configuration ou pour fournir --ignore-unsupported-environment argument si vous savez vraiment ce que vous faites.</value>
|
||||
<comment>{0} will be replaced by plugin name, {1} will be replaced by plugin's version number, {2} will be replaced by ASF's version number.</comment>
|
||||
</data>
|
||||
<data name="ErrorTooManyCrashes" xml:space="preserve">
|
||||
<value>Votre ASF a planté trop de fois récemment, et en raison de cela l'initialisation du processus a été désactivée. Recherchez, corrigez votre configuration, puis supprimez ASF. rash file from your config directory, or supply --ignore-unsupported-environment argument si vous savez vraiment ce que vous faites.</value>
|
||||
</data>
|
||||
<data name="IdlingGameNotPossiblePrivate" xml:space="preserve">
|
||||
<value>L'agriculture {0} ({1}) est désactivée, car ce jeu est actuellement marqué comme privé. Si vous avez l'intention d'ASF de cultiver ce jeu, alors envisagez de modifier ses paramètres de confidentialité.</value>
|
||||
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
|
||||
</data>
|
||||
<data name="WarningSkipping" xml:space="preserve">
|
||||
<value>Ignorer : {0}...</value>
|
||||
<comment>{0} will be replaced by text value (string) of entry being skipped.</comment>
|
||||
</data>
|
||||
<data name="PluginUpdatesChecking" xml:space="preserve">
|
||||
<value>Vérification des mises à jour du plugin...</value>
|
||||
</data>
|
||||
<data name="PluginUpdateChecking" xml:space="preserve">
|
||||
<value>Vérification de la mise à jour du plugin {0}...</value>
|
||||
<comment>{0} will be replaced by plugin name (string).</comment>
|
||||
</data>
|
||||
<data name="PluginUpdateNotFound" xml:space="preserve">
|
||||
<value>Aucune mise à jour disponible pour le plugin {0} : {1} † {2}.</value>
|
||||
<comment>{0} will be replaced by plugin name (string), {1} will be replaced by current plugin's version, {2} will be replaced by remote plugin's version.</comment>
|
||||
</data>
|
||||
<data name="PluginUpdateNewVersionAvailable" xml:space="preserve">
|
||||
<value>La nouvelle version du plugin {0} est disponible! Pensez à vous mettre à jour !</value>
|
||||
<comment>{0} will be replaced by plugin name (string).</comment>
|
||||
</data>
|
||||
<data name="PluginUpdateFound" xml:space="preserve">
|
||||
<value>Mise à jour du plugin {0} trouvée de la version {1} à {2}...</value>
|
||||
<comment>{0} will be replaced by plugin name (string), {1} will be replaced by current plugin's version, {2} will be replaced by remote plugin's version.</comment>
|
||||
</data>
|
||||
<data name="PluginUpdateNoAssetFound" xml:space="preserve">
|
||||
<value>Aucune ressource disponible pour la mise à jour du plugin {0} de la version {1} vers {2}, cela signifie généralement que la mise à jour sera disponible plus tard.</value>
|
||||
<comment>{0} will be replaced by plugin name (string), {1} will be replaced by current plugin's version, {2} will be replaced by remote plugin's version.</comment>
|
||||
</data>
|
||||
<data name="PluginUpdateConflictingAssetsFound" xml:space="preserve">
|
||||
<value>Aucune ressource n'a pu être déterminée pour la mise à jour du plugin {0} de la version {1} à {2}. Cela peut se produire si la version n'est pas encore terminée - si cela continue de se produire, vous devriez en informer le créateur du plugin.</value>
|
||||
<comment>{0} will be replaced by plugin name (string), {1} will be replaced by current plugin's version, {2} will be replaced by remote plugin's version.</comment>
|
||||
</data>
|
||||
<data name="PluginUpdateInProgress" xml:space="preserve">
|
||||
<value>Mise à jour du plugin {0}...</value>
|
||||
<comment>{0} will be replaced by plugin name (string).</comment>
|
||||
</data>
|
||||
<data name="PluginUpdateFinished" xml:space="preserve">
|
||||
<value>La mise à jour du plugin {0} a réussi, les modifications seront chargées au prochain lancement d'ASF.</value>
|
||||
<comment>{0} will be replaced by plugin name (string).</comment>
|
||||
</data>
|
||||
<data name="PluginUpdateEnabled" xml:space="preserve">
|
||||
<value>Le plugin {0}/{1} a été enregistré et activé pour les mises à jour automatiques.</value>
|
||||
<comment>{0} will be replaced by plugin name (string), {1} will be replaced by plugin assembly name (string).</comment>
|
||||
</data>
|
||||
<data name="PluginUpdateDisabled" xml:space="preserve">
|
||||
<value>Le plugin {0} ({1}) a été désactivé à partir des mises à jour automatiques, malgré la prise en charge de cette fonctionnalité.</value>
|
||||
<comment>{0} will be replaced by plugin name (string), {1} will be replaced by plugin assembly name (string).</comment>
|
||||
</data>
|
||||
<data name="CustomPluginUpdatesEnabled" xml:space="preserve">
|
||||
<value>Les plugins personnalisés ont été enregistrés pour les mises à jour automatiques. L'équipe ASF voudrait vous rappeler que, pour votre propre sécurité, vous ne devriez activer les mises à jour automatiques que des groupes de confiance. Si vous n'aviez pas l'intention de le faire, vous pouvez désactiver les mises à jour de plugin dans la configuration globale d'ASF.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -770,10 +770,10 @@ Tempo de execução: {1}</value>
|
||||
<comment>{0} will be replaced by text value (string) of entry being skipped.</comment>
|
||||
</data>
|
||||
<data name="PluginUpdatesChecking" xml:space="preserve">
|
||||
<value>Procurando por atualizações dos plugins...</value>
|
||||
<value>Procurando por atualizações de plugins...</value>
|
||||
</data>
|
||||
<data name="PluginUpdateChecking" xml:space="preserve">
|
||||
<value>Verificando atualizações para o plugin {0}...</value>
|
||||
<value>Verificando atualização para o plugin {0}...</value>
|
||||
<comment>{0} will be replaced by plugin name (string).</comment>
|
||||
</data>
|
||||
<data name="PluginUpdateNotFound" xml:space="preserve">
|
||||
@@ -793,7 +793,7 @@ Tempo de execução: {1}</value>
|
||||
<comment>{0} will be replaced by plugin name (string), {1} will be replaced by current plugin's version, {2} will be replaced by remote plugin's version.</comment>
|
||||
</data>
|
||||
<data name="PluginUpdateConflictingAssetsFound" xml:space="preserve">
|
||||
<value>Nenhum recurso pôde ser determinado para a atualização do plugin {0} da versão {1} para {2}. Isso pode acontecer se a atualização ainda não estiver concluída - se isso continuar acontecendo, você deve notificar o criador do plugin sobre isso.</value>
|
||||
<value>Nenhum recurso pôde ser determinado para a atualização do plugin {0} da versão {1} para {2}. Isso pode acontecer caso a atualização ainda não estiver concluída - se isso continuar acontecendo, você deve notificar o criador do plugin sobre isso.</value>
|
||||
<comment>{0} will be replaced by plugin name (string), {1} will be replaced by current plugin's version, {2} will be replaced by remote plugin's version.</comment>
|
||||
</data>
|
||||
<data name="PluginUpdateInProgress" xml:space="preserve">
|
||||
@@ -801,7 +801,7 @@ Tempo de execução: {1}</value>
|
||||
<comment>{0} will be replaced by plugin name (string).</comment>
|
||||
</data>
|
||||
<data name="PluginUpdateFinished" xml:space="preserve">
|
||||
<value>A atualização do plugin {0} foi bem-sucedida. As alterações serão carregadas na próxima inicialização do ASF.</value>
|
||||
<value>A atualização do plugin {0} foi bem-sucedida, as alterações serão carregadas na próxima inicialização do ASF.</value>
|
||||
<comment>{0} will be replaced by plugin name (string).</comment>
|
||||
</data>
|
||||
<data name="PluginUpdateEnabled" xml:space="preserve">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>6.0.2.2</Version>
|
||||
<Version>6.0.2.3</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
2
wiki
2
wiki
Submodule wiki updated: 2cee675f72...6543dd7284
Reference in New Issue
Block a user