logger
This commit is contained in:
@@ -8,7 +8,7 @@ using Core.Utils;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
[Injectable(InjectableTypeOverride = typeof(OnUpdate), TypePriority = OnUpdateOrder.DialogCallbacks)]
|
||||
[Injectable(InjectableTypeOverride = typeof(OnUpdate), TypePriority = OnUpdateOrder.DialogueCallbacks)]
|
||||
[Injectable(InjectableTypeOverride = typeof(DialogueCallbacks))]
|
||||
public class DialogueCallbacks : OnUpdate
|
||||
{
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
using Core.Annotations;
|
||||
using Core.DI;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Servers;
|
||||
using Core.Services;
|
||||
using Core.Utils;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Callbacks;
|
||||
|
||||
[Injectable(InjectableTypeOverride = typeof(OnLoad), TypePriority = OnLoadOrder.ModCallbacks)]
|
||||
public class ModCallbacks : OnLoad
|
||||
{
|
||||
protected ILogger _logger;
|
||||
protected HttpResponseUtil _httpResponseUtil;
|
||||
protected HttpFileUtil _httpFileUtil;
|
||||
// protected PostSptModLoader _postSptModLoader; TODO: needs to be implemented
|
||||
protected LocalisationService _localisationService;
|
||||
protected ConfigServer _configServer;
|
||||
|
||||
protected HttpConfig _httpConfig;
|
||||
|
||||
public ModCallbacks
|
||||
(
|
||||
ILogger logger,
|
||||
HttpResponseUtil httpResponseUtil,
|
||||
HttpFileUtil httpFileUtil,
|
||||
LocalisationService localisationService,
|
||||
ConfigServer configServer
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpResponseUtil = httpResponseUtil;
|
||||
_httpFileUtil = httpFileUtil;
|
||||
_localisationService = localisationService;
|
||||
_configServer = configServer;
|
||||
_httpConfig = configServer.GetConfig<HttpConfig>(ConfigTypes.HTTP);
|
||||
}
|
||||
|
||||
public async Task OnLoad()
|
||||
{
|
||||
// if (ProgramStatics.MODS) {
|
||||
// await this.postSptModLoader.load();
|
||||
// } TODO: needs to be implemented
|
||||
return;
|
||||
}
|
||||
|
||||
public string GetRoute()
|
||||
{
|
||||
return "spt-mods";
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,10 @@ public class ClientLogController
|
||||
public void ClientLog(ClientLogRequest logRequest)
|
||||
{
|
||||
var message = $"[{logRequest.Source}] {logRequest.Message}";
|
||||
/* TODO: what do we do with this?
|
||||
var color = logRequest.Color ?? LogTextColor.White;
|
||||
var backgroundColor = logRequest.BackgroundColor ?? LogBackgroundColor.Default;
|
||||
|
||||
*/
|
||||
|
||||
// Allow supporting either string or enum levels
|
||||
// Required due to the C# modules serializing enums as their name
|
||||
@@ -40,13 +41,11 @@ public class ClientLogController
|
||||
this._logger.Warning(message);
|
||||
break;
|
||||
case LogLevel.SUCCESS:
|
||||
this._logger.Success(message);
|
||||
break;
|
||||
case LogLevel.INFO:
|
||||
this._logger.Info(message);
|
||||
break;
|
||||
case LogLevel.CUSTOM:
|
||||
this._logger.Log(message, color.ToString(), backgroundColor.ToString());
|
||||
this._logger.Info(message/* TODO: , color.ToString(), backgroundColor.ToString()*/);
|
||||
break;
|
||||
case LogLevel.DEBUG:
|
||||
this._logger.Debug(message);
|
||||
|
||||
@@ -135,7 +135,7 @@ public class GameController
|
||||
|
||||
// flag as migrated
|
||||
fullProfile.SptData.Migrations.Add("39x", _timeUtil.GetTimeStamp());
|
||||
_logger.Success($"Migration of 3.9.x profile: {fullProfile.ProfileInfo.Username} completed successfully");
|
||||
_logger.Info($"Migration of 3.9.x profile: {fullProfile.ProfileInfo.Username} completed successfully");
|
||||
}
|
||||
|
||||
// with our method of converting type from array for this prop, we *might* not need this?
|
||||
|
||||
+10
-10
@@ -3,14 +3,14 @@ namespace Core.DI;
|
||||
public static class OnLoadOrder
|
||||
{
|
||||
public const int Database = 0;
|
||||
public const int PostDBModLoader = 1;
|
||||
public const int HandbookCallbacks = 2;
|
||||
public const int HttpCallbacks = 3;
|
||||
public const int PresetCallbacks = 4;
|
||||
public const int SaveCallbacks = 5;
|
||||
public const int TraderCallbacks = 6;
|
||||
public const int RagfairPriceService = 7;
|
||||
public const int RagfairCallbacks = 8;
|
||||
public const int ModCallbacks = 9;
|
||||
public const int GameCallbacks = 10;
|
||||
public const int GameCallbacks = 100;
|
||||
public const int PostDBModLoader = 200;
|
||||
public const int HandbookCallbacks = 300;
|
||||
public const int HttpCallbacks = 400;
|
||||
public const int SaveCallbacks = 500;
|
||||
public const int TraderCallbacks = 600;
|
||||
public const int PostSptModLoader = 700;
|
||||
public const int PresetCallbacks = 800;
|
||||
public const int RagfairPriceService = 900;
|
||||
public const int RagfairCallbacks = 1000;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ namespace Core.DI;
|
||||
|
||||
public static class OnUpdateOrder
|
||||
{
|
||||
public const int DialogCallbacks = 0;
|
||||
public const int HideoutCallbacks = 1;
|
||||
public const int TraderCallbacks = 2;
|
||||
public const int RagfairCallbacks = 3;
|
||||
public const int InsuranceCallbacks = 4;
|
||||
public const int SaveCallbacks = 5;
|
||||
public const int DialogueCallbacks = 0;
|
||||
public const int HideoutCallbacks = 100;
|
||||
public const int TraderCallbacks = 200;
|
||||
public const int RagfairCallbacks = 300;
|
||||
public const int InsuranceCallbacks = 400;
|
||||
public const int SaveCallbacks = 500;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
using Core.Annotations;
|
||||
using Core.DI;
|
||||
using Core.Models.External;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Loaders;
|
||||
|
||||
[Injectable(InjectableTypeOverride = typeof(OnLoad), TypePriority = OnLoadOrder.PostDBModLoader)]
|
||||
public class PostDBModLoader : OnLoad
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IEnumerable<IPostDBLoadMod> _postDbLoadMods;
|
||||
|
||||
public PostDBModLoader(
|
||||
ILogger logger,
|
||||
IEnumerable<IPostDBLoadMod> postDbLoadMods
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_postDbLoadMods = postDbLoadMods;
|
||||
}
|
||||
|
||||
public async Task OnLoad()
|
||||
{
|
||||
_logger.Info("Loading PostDBLoadMod...");
|
||||
foreach (var postDbLoadMod in _postDbLoadMods)
|
||||
{
|
||||
postDbLoadMod.PostDBLoad();
|
||||
}
|
||||
_logger.Info("Finished loading PostDBLoadMod...");
|
||||
}
|
||||
|
||||
public string GetRoute()
|
||||
{
|
||||
return "spt-post-db-mods";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Core.Annotations;
|
||||
using Core.DI;
|
||||
using Core.Models.External;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Loaders;
|
||||
|
||||
[Injectable(InjectableTypeOverride = typeof(OnLoad), TypePriority = OnLoadOrder.PostSptModLoader)]
|
||||
public class PostSptModLoader : OnLoad
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IEnumerable<IPostSptLoadMod> _postSptLoadMods;
|
||||
|
||||
public PostSptModLoader(
|
||||
ILogger logger,
|
||||
IEnumerable<IPostSptLoadMod> postSptLoadMods
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_postSptLoadMods = postSptLoadMods;
|
||||
}
|
||||
|
||||
public async Task OnLoad()
|
||||
{
|
||||
// if (ProgramStatics.MODS) {
|
||||
// await this.postSptModLoader.load();
|
||||
// } TODO: needs to be implemented
|
||||
_logger.Info("Loading PostSptMods...");
|
||||
foreach (var postSptLoadMod in _postSptLoadMods)
|
||||
{
|
||||
postSptLoadMod.PostSptLoad();
|
||||
}
|
||||
_logger.Info("Finished loading PostSptMods...");
|
||||
}
|
||||
|
||||
public string GetRoute()
|
||||
{
|
||||
return "spt-post-spt-mods";
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,12 @@
|
||||
|
||||
public enum LogBackgroundColor
|
||||
{
|
||||
Default,
|
||||
Black,
|
||||
Red,
|
||||
Green,
|
||||
Yellow,
|
||||
Blue,
|
||||
Magenta,
|
||||
Cyan,
|
||||
White
|
||||
}
|
||||
Black = 40,
|
||||
Red = 41,
|
||||
Green = 42,
|
||||
Yellow = 43,
|
||||
Blue = 44,
|
||||
Magenta = 45,
|
||||
Cyan = 46,
|
||||
White = 47
|
||||
}
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
|
||||
public enum LogTextColor
|
||||
{
|
||||
Black,
|
||||
Red,
|
||||
Green,
|
||||
Yellow,
|
||||
Blue,
|
||||
Magenta,
|
||||
Cyan,
|
||||
White,
|
||||
Gray
|
||||
}
|
||||
Black = 30,
|
||||
Red = 31,
|
||||
Green = 32,
|
||||
Yellow = 33,
|
||||
Blue = 34,
|
||||
Magenta = 35,
|
||||
Cyan = 36,
|
||||
White = 37
|
||||
}
|
||||
|
||||
@@ -4,12 +4,13 @@ namespace Core.Models.Utils;
|
||||
|
||||
public interface ILogger
|
||||
{
|
||||
void WriteToLogFile(string data);
|
||||
void Log(string data, string color, string? backgroundColor = null);
|
||||
void LogWithColor(string data, LogTextColor textColor, LogBackgroundColor? backgroundColor = null);
|
||||
// TODO: Removing these 4 methods for now, revisit in the future
|
||||
// void WriteToLogFile(string data);
|
||||
// void Log(string data, LogTextColor? color, string? backgroundColor = null);
|
||||
// void LogWithColor(string data, LogTextColor textColor, LogBackgroundColor? backgroundColor = null);
|
||||
// void Success(string data);
|
||||
void Error(string data);
|
||||
void Warning(string data);
|
||||
void Success(string data);
|
||||
void Info(string data);
|
||||
void Debug(string data, bool? onlyShowInConsole = null);
|
||||
}
|
||||
void Debug(string data);
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ public class SptHttpListener : IHttpListener
|
||||
}
|
||||
|
||||
if (!requestIsCompressed) {
|
||||
_logger.Debug(value, true);
|
||||
_logger.Debug(value);
|
||||
}
|
||||
|
||||
var response = GetResponse(sessionId, req, value);
|
||||
|
||||
@@ -106,7 +106,7 @@ public class SaveServer
|
||||
totalTime += SaveProfile(sessionID.Key);
|
||||
}
|
||||
|
||||
_logger.Debug($"Saved {profiles.Count} profiles, took: {totalTime}ms", false);
|
||||
_logger.Debug($"Saved {profiles.Count} profiles, took: {totalTime}ms");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,7 +7,7 @@ public class PostDbLoadService
|
||||
{
|
||||
public void PerformPostDbLoadActions()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// TODO:
|
||||
}
|
||||
|
||||
protected void AdjustMinReserveRaiderSpawnChance()
|
||||
|
||||
@@ -21,7 +21,8 @@ public class JsonUtil
|
||||
new EftEnumConverter<SptAirdropTypeEnum>(),
|
||||
new EftEnumConverter<GiftSenderType>(),
|
||||
new EftEnumConverter<SeasonalEventType>(),
|
||||
new EftEnumConverter<ProfileChangeEventType>()
|
||||
new EftEnumConverter<ProfileChangeEventType>(),
|
||||
new EftEnumConverter<QuestStatusEnum>()
|
||||
}
|
||||
};
|
||||
private static readonly JsonSerializerOptions jsonSerializerOptionsIndented = new(jsonSerializerOptionsNoIndent)
|
||||
|
||||
@@ -4,24 +4,9 @@ using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Core.Utils.Logging;
|
||||
|
||||
[Injectable(InjectionType.Singleton)]
|
||||
// [Injectable(InjectionType.Singleton)]
|
||||
public class SimpleTextLogger : ILogger
|
||||
{
|
||||
// TODO: for now we simplify the logger into this barebones console writer
|
||||
public void WriteToLogFile(string data)
|
||||
{
|
||||
Console.WriteLine(data);
|
||||
}
|
||||
|
||||
public void Log(string data, string color, string? backgroundColor = null)
|
||||
{
|
||||
Console.WriteLine(data);
|
||||
}
|
||||
|
||||
public void LogWithColor(string data, LogTextColor textColor, LogBackgroundColor? backgroundColor = null)
|
||||
{
|
||||
Console.WriteLine(data);
|
||||
}
|
||||
|
||||
public void Error(string data)
|
||||
{
|
||||
@@ -32,19 +17,14 @@ public class SimpleTextLogger : ILogger
|
||||
{
|
||||
Console.WriteLine(data);
|
||||
}
|
||||
|
||||
public void Success(string data)
|
||||
{
|
||||
Console.WriteLine(data);
|
||||
}
|
||||
|
||||
|
||||
public void Info(string data)
|
||||
{
|
||||
Console.WriteLine(data);
|
||||
}
|
||||
|
||||
public void Debug(string data, bool? onlyShowInConsole = null)
|
||||
public void Debug(string data)
|
||||
{
|
||||
Console.WriteLine(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ public class Watermark {
|
||||
|
||||
// Log watermark to screen
|
||||
foreach (var text in result) {
|
||||
_logger.LogWithColor(text, LogTextColor.Yellow);
|
||||
_logger.Warning(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
using Serilog.Events;
|
||||
using Serilog.Formatting;
|
||||
|
||||
namespace Server.Logger;
|
||||
|
||||
public abstract class AbstractFormatter : ITextFormatter
|
||||
{
|
||||
protected abstract string ProcessText(string text);
|
||||
|
||||
public void Format(LogEvent logEvent, TextWriter output)
|
||||
{
|
||||
var newLine = Environment.NewLine;
|
||||
var timestamp = logEvent.Timestamp.ToString("HH:mm:ss");
|
||||
var logLevel = logEvent.Level.ToString().ToUpper().Substring(0, 4);
|
||||
var message = logEvent.RenderMessage();
|
||||
var exception = logEvent.Exception != null ? $"{newLine}{logEvent.Exception}{newLine}{logEvent.Exception.StackTrace}" : "";
|
||||
var logMessage = ProcessText($"[{timestamp} {logLevel}] {message}{exception}");
|
||||
output.WriteLine(logMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Server.Logger;
|
||||
|
||||
public class ConsoleFormatter : AbstractFormatter
|
||||
{
|
||||
protected override string ProcessText(string text)
|
||||
{
|
||||
return text;
|
||||
}
|
||||
|
||||
public static ConsoleFormatter Default { get; } = new ConsoleFormatter();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Server.Logger;
|
||||
|
||||
public class FileFormatter : AbstractFormatter
|
||||
{
|
||||
protected override string ProcessText(string message)
|
||||
{
|
||||
foreach (Match match in Regex.Matches(message, @"\x1b\[((\d.+?)?\d)m"))
|
||||
{
|
||||
message = message.Replace(match.Value, "");
|
||||
}
|
||||
return message.Replace("\"", "");
|
||||
}
|
||||
|
||||
public static FileFormatter Default { get; } = new FileFormatter();
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using Core.Annotations;
|
||||
using ILogger = Core.Models.Utils.ILogger;
|
||||
|
||||
namespace Server.Logger;
|
||||
|
||||
[Injectable]
|
||||
public class WebApplicationLogger : ILogger
|
||||
{
|
||||
private Microsoft.Extensions.Logging.ILogger _logger;
|
||||
public WebApplicationLogger(ILoggerProvider provider)
|
||||
{
|
||||
_logger = provider.CreateLogger("SptLogger");
|
||||
}
|
||||
|
||||
public void Error(string data)
|
||||
{
|
||||
_logger.LogError(data);
|
||||
}
|
||||
|
||||
public void Warning(string data)
|
||||
{
|
||||
_logger.LogWarning(data);
|
||||
}
|
||||
|
||||
public void Info(string data)
|
||||
{
|
||||
_logger.LogInformation(data);
|
||||
}
|
||||
|
||||
public void Debug(string data)
|
||||
{
|
||||
_logger.LogDebug(data);
|
||||
}
|
||||
}
|
||||
+29
-4
@@ -3,9 +3,11 @@ using System.Security.Cryptography;
|
||||
using Core.Annotations;
|
||||
using Core.Context;
|
||||
using Core.Models.Enums;
|
||||
using Core.Models.External;
|
||||
using Core.Models.Spt.Config;
|
||||
using Core.Servers;
|
||||
using Core.Utils;
|
||||
using Serilog;
|
||||
|
||||
namespace Server;
|
||||
|
||||
@@ -17,21 +19,33 @@ public static class Program
|
||||
HarmonyBootstrapper.LoadAllPatches(assemblies);
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Configuration.AddJsonFile("appsettings.json", true, true);
|
||||
|
||||
CreateAndRegisterLogger(builder);
|
||||
|
||||
RegisterSptComponents(builder.Services);
|
||||
RegisterModOverrideComponents(builder.Services, assemblies);
|
||||
|
||||
try
|
||||
{
|
||||
var serviceProvider = builder.Services.BuildServiceProvider();
|
||||
var watermark = serviceProvider.GetService<Watermark>();
|
||||
// Initialize Watermak
|
||||
watermark.Initialize();
|
||||
// TODO: var preSptModLoader = serviceProvider.GetService<PreSptModLoader>();
|
||||
var app = serviceProvider.GetService<App>();
|
||||
|
||||
// Initialize PreSptMods
|
||||
var preSptLoadMods = serviceProvider.GetServices<IPreSptLoadMod>();
|
||||
foreach (var preSptLoadMod in preSptLoadMods)
|
||||
{
|
||||
preSptLoadMod.PreSptLoad();
|
||||
}
|
||||
var appContext = serviceProvider.GetService<ApplicationContext>();
|
||||
// These are the mod assemblies we are gonna use on the PreSpt, PostDb and PostSpt loaders
|
||||
// Add the Loaded Mod Assemblies for later
|
||||
appContext.AddValue(ContextVariableType.LOADED_MOD_ASSEMBLIES, assemblies);
|
||||
// This is the builder that will get use by the HttpServer to start up the web application
|
||||
appContext.AddValue(ContextVariableType.APP_BUILDER, builder);
|
||||
|
||||
// Get the Built app and run it
|
||||
var app = serviceProvider.GetService<App>();
|
||||
app.Run().Wait();
|
||||
|
||||
var httpConfig = serviceProvider.GetService<ConfigServer>().GetConfig<HttpConfig>(ConfigTypes.HTTP);
|
||||
@@ -45,6 +59,15 @@ public static class Program
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateAndRegisterLogger(WebApplicationBuilder builder)
|
||||
{
|
||||
builder.Logging.ClearProviders();
|
||||
var logger = new LoggerConfiguration()
|
||||
.ReadFrom.Configuration(builder.Configuration)
|
||||
.CreateLogger();
|
||||
builder.Logging.AddSerilog(logger);
|
||||
}
|
||||
|
||||
private static void RegisterModOverrideComponents(IServiceCollection builderServices, List<Assembly> assemblies)
|
||||
{
|
||||
// We get all the services from this assembly first, since mods will override them later
|
||||
@@ -101,6 +124,8 @@ public static class Program
|
||||
private static void RegisterSptComponents(IServiceCollection builderServices)
|
||||
{
|
||||
// We get all the services from this assembly first, since mods will override them later
|
||||
RegisterComponents(builderServices, typeof(Program).Assembly.GetTypes()
|
||||
.Where(type => Attribute.IsDefined(type, typeof(Injectable))));
|
||||
RegisterComponents(builderServices, typeof(App).Assembly.GetTypes()
|
||||
.Where(type => Attribute.IsDefined(type, typeof(Injectable))));
|
||||
}
|
||||
|
||||
@@ -18,12 +18,18 @@
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0"/>
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="2.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Assets\**">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<None Remove="appsettings.json" />
|
||||
<Content Include="appsettings.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
}
|
||||
},
|
||||
"Serilog": {
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "Async",
|
||||
"Args": {
|
||||
"configure": [
|
||||
{
|
||||
"Name": "Console",
|
||||
"Args": {
|
||||
"formatter": "Server.Logger.ConsoleFormatter::Default, Server"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "File",
|
||||
"Args": {
|
||||
"formatter": "Server.Logger.FileFormatter::Default, Server",
|
||||
"path": "./user/logs/log.txt",
|
||||
"fileSizeLimitBytes": "20971520",
|
||||
"rollOnFileSizeLimit": true,
|
||||
"rollingInterval": "Day"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user