From b12e97a0a78b215a78f8c312285044b6651c3331 Mon Sep 17 00:00:00 2001 From: Kwoth Date: Mon, 18 Jul 2022 04:44:27 +0200 Subject: [PATCH] Added uncommited files. Fixed nullref in update loop when users gain voice xp --- src/Nadeko.Common/Nadeko.Common.csproj | 2 +- .../NadekoBot.Coordinator.csproj | 2 +- .../Command/CommandAttributesGenerator.cs | 672 +++++++++--------- src/NadekoBot.Tests/BotStringsTests.cs | 2 +- .../NadekoBot.VotesApi.csproj | 2 +- src/NadekoBot/Modules/Xp/XpService.cs | 20 +- .../Modules/Xp/_Common/UserCacheItem.cs | 8 +- 7 files changed, 358 insertions(+), 350 deletions(-) diff --git a/src/Nadeko.Common/Nadeko.Common.csproj b/src/Nadeko.Common/Nadeko.Common.csproj index 1a829c83e..205c08172 100644 --- a/src/Nadeko.Common/Nadeko.Common.csproj +++ b/src/Nadeko.Common/Nadeko.Common.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/NadekoBot.Coordinator/NadekoBot.Coordinator.csproj b/src/NadekoBot.Coordinator/NadekoBot.Coordinator.csproj index bbd01309d..6489f7819 100644 --- a/src/NadekoBot.Coordinator/NadekoBot.Coordinator.csproj +++ b/src/NadekoBot.Coordinator/NadekoBot.Coordinator.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/NadekoBot.Generators/Command/CommandAttributesGenerator.cs b/src/NadekoBot.Generators/Command/CommandAttributesGenerator.cs index 2410ec9b7..ca18bb71d 100644 --- a/src/NadekoBot.Generators/Command/CommandAttributesGenerator.cs +++ b/src/NadekoBot.Generators/Command/CommandAttributesGenerator.cs @@ -1,336 +1,336 @@ -#nullable enable -using System; -using System.CodeDom.Compiler; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Text; - -namespace NadekoBot.Generators.Command; - -[Generator] -public class CommandAttributesGenerator : IIncrementalGenerator -{ - public const string ATTRIBUTE = @"// - -namespace NadekoBot.Common; - -[System.AttributeUsage(System.AttributeTargets.Method)] -public class CmdAttribute : System.Attribute -{ - -}"; - - public class MethodModel - { - public string? Namespace { get; } - public IReadOnlyCollection Classes { get; } - public string ReturnType { get; } - public string MethodName { get; } - public IEnumerable Params { get; } - - public MethodModel(string? ns, IReadOnlyCollection classes, string returnType, string methodName, IEnumerable @params) - { - Namespace = ns; - Classes = classes; - ReturnType = returnType; - MethodName = methodName; - Params = @params; - } - } - - public class FileModel - { - public string? Namespace { get; } - public IReadOnlyCollection ClassHierarchy { get; } - public IReadOnlyCollection Methods { get; } - - public FileModel(string? ns, IReadOnlyCollection classHierarchy, IReadOnlyCollection methods) - { - Namespace = ns; - ClassHierarchy = classHierarchy; - Methods = methods; - } - } - - public void Initialize(IncrementalGeneratorInitializationContext context) - { -// #if DEBUG -// if (!Debugger.IsAttached) -// Debugger.Launch(); -// // SpinWait.SpinUntil(() => Debugger.IsAttached); -// #endif - context.RegisterPostInitializationOutput(static ctx => ctx.AddSource( - "CmdAttribute.g.cs", - SourceText.From(ATTRIBUTE, Encoding.UTF8))); - - var methods = context.SyntaxProvider - .CreateSyntaxProvider( - static (node, _) => node is MethodDeclarationSyntax { AttributeLists.Count: > 0 }, - static (ctx, cancel) => Transform(ctx, cancel)) - .Where(static m => m is not null) - .Where(static m => m?.ChildTokens().Any(static x => x.IsKind(SyntaxKind.PublicKeyword)) ?? false); - - var compilationMethods = context.CompilationProvider.Combine(methods.Collect()); - - context.RegisterSourceOutput(compilationMethods, - static (ctx, tuple) => RegisterAction(in ctx, tuple.Left, in tuple.Right)); - } - - private static void RegisterAction(in SourceProductionContext ctx, - Compilation comp, - in ImmutableArray methods) - { - if (methods is { IsDefaultOrEmpty: true }) - return; - - var models = GetModels(comp, methods, ctx.CancellationToken); - - foreach (var model in models) - { - var name = $"{model.Namespace}.{string.Join(".", model.ClassHierarchy)}.g.cs"; - try - { - var source = GetSourceText(model); - ctx.AddSource(name, SourceText.From(source, Encoding.UTF8)); - } - catch (Exception ex) - { - Console.WriteLine($"Error writing source file {name}\n" + ex); - } - } - } - - private static string GetSourceText(FileModel model) - { - using var sw = new StringWriter(); - using var tw = new IndentedTextWriter(sw); - - tw.WriteLine("// "); - tw.WriteLine("#pragma warning disable CS1066"); - - if (model.Namespace is not null) - { - tw.WriteLine($"namespace {model.Namespace};"); - tw.WriteLine(); - } - - foreach (var className in model.ClassHierarchy) - { - tw.WriteLine($"public partial class {className}"); - tw.WriteLine("{"); - tw.Indent ++; - } - - foreach (var method in model.Methods) - { - tw.WriteLine("[NadekoCommand]"); - tw.WriteLine("[NadekoDescription]"); - tw.WriteLine("[Aliases]"); - tw.WriteLine($"public partial {method.ReturnType} {method.MethodName}({string.Join(", ", method.Params)});"); - } - - foreach (var _ in model.ClassHierarchy) - { - tw.Indent --; - tw.WriteLine("}"); - } - - tw.Flush(); - return sw.ToString(); - } - - private static IReadOnlyCollection GetModels(Compilation compilation, - in ImmutableArray inputMethods, - CancellationToken cancel) - { - var models = new List(); - - var methods = inputMethods - .Where(static x => x is not null) - .Distinct(); - - var methodModels = methods - .Select(x => MethodDeclarationToMethodModel(compilation, x!)) - .Where(static x => x is not null) - .Cast(); - - var groups = methodModels - .GroupBy(static x => $"{x.Namespace}.{string.Join(".", x.Classes)}"); - - foreach (var group in groups) - { - if (cancel.IsCancellationRequested) - return new Collection(); - - if (group is null) - continue; - - var elems = group.ToList(); - if (elems.Count is 0) - continue; - - var model = new FileModel( - methods: elems, - ns: elems[0].Namespace, - classHierarchy: elems![0].Classes - ); - - models.Add(model); - } - - - return models; - } - - private static MethodModel? MethodDeclarationToMethodModel(Compilation comp, MethodDeclarationSyntax decl) - { - // SpinWait.SpinUntil(static () => Debugger.IsAttached); - - SemanticModel semanticModel; - try - { - semanticModel = comp.GetSemanticModel(decl.SyntaxTree); - } - catch - { - // for some reason this method can throw "Not part of this compilation" argument exception - return null; - } - - var methodModel = new MethodModel( - @params: decl.ParameterList.Parameters - .Where(p => p.Type is not null) - .Select(p => - { - var prefix = p.Modifiers.Any(static x => x.IsKind(SyntaxKind.ParamsKeyword)) - ? "params " - : string.Empty; - - var type = semanticModel - .GetTypeInfo(p.Type!) - .Type - ?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - - - var name = p.Identifier.Text; - - var suffix = string.Empty; - if (p.Default is not null) - { - if (p.Default.Value is LiteralExpressionSyntax) - { - suffix = " = " + p.Default.Value; - } - else if (p.Default.Value is MemberAccessExpressionSyntax maes) - { - var maesSemModel = comp.GetSemanticModel(maes.SyntaxTree); - var sym = maesSemModel.GetSymbolInfo(maes.Name); - if (sym.Symbol is null) - { - suffix = " = " + p.Default.Value; - } - else - { - suffix = " = " + sym.Symbol.ToDisplayString(); - } - } - } - - return $"{prefix}{type} {name}{suffix}"; - }) - .ToList(), - methodName: decl.Identifier.Text, - returnType: decl.ReturnType.ToString(), - ns: GetNamespace(decl), - classes: GetClasses(decl) - ); - - return methodModel; - } - - //https://github.com/andrewlock/NetEscapades.EnumGenerators/blob/main/src/NetEscapades.EnumGenerators/EnumGenerator.cs - static string? GetNamespace(MethodDeclarationSyntax declarationSyntax) - { - // determine the namespace the class is declared in, if any - string? nameSpace = null; - var parentOfInterest = declarationSyntax.Parent; - while (parentOfInterest is not null) - { - parentOfInterest = parentOfInterest.Parent; - - if (parentOfInterest is BaseNamespaceDeclarationSyntax ns) - { - nameSpace = ns.Name.ToString(); - while (true) - { - if (ns.Parent is not NamespaceDeclarationSyntax parent) - { - break; - } - - ns = parent; - nameSpace = $"{ns.Name}.{nameSpace}"; - } - - return nameSpace; - } - - } - - return nameSpace; - } - - static IReadOnlyCollection GetClasses(MethodDeclarationSyntax declarationSyntax) - { - // determine the namespace the class is declared in, if any - var classes = new LinkedList(); - var parentOfInterest = declarationSyntax.Parent; - while (parentOfInterest is not null) - { - if (parentOfInterest is ClassDeclarationSyntax cds) - { - classes.AddFirst(cds.Identifier.ToString()); - } - - parentOfInterest = parentOfInterest.Parent; - } - - Debug.WriteLine($"Method {declarationSyntax.Identifier.Text} has {classes.Count} classes"); - - return classes; - } - - private static MethodDeclarationSyntax? Transform(GeneratorSyntaxContext ctx, CancellationToken cancel) - { - var methodDecl = ctx.Node as MethodDeclarationSyntax; - if (methodDecl is null) - return default; - - foreach (var attListSyntax in methodDecl.AttributeLists) - { - foreach (var attSyntax in attListSyntax.Attributes) - { - if (cancel.IsCancellationRequested) - return default; - - var symbol = ctx.SemanticModel.GetSymbolInfo(attSyntax).Symbol; - if (symbol is not IMethodSymbol attSymbol) - continue; - - if (attSymbol.ContainingType.ToDisplayString() == "NadekoBot.Common.CmdAttribute") - return methodDecl; - } - } - - return default; - } -} \ No newline at end of file +// #nullable enable +// using System; +// using System.CodeDom.Compiler; +// using System.Collections.Generic; +// using System.Collections.Immutable; +// using System.Collections.ObjectModel; +// using System.Diagnostics; +// using System.IO; +// using System.Linq; +// using System.Text; +// using System.Threading; +// using Microsoft.CodeAnalysis; +// using Microsoft.CodeAnalysis.CSharp; +// using Microsoft.CodeAnalysis.CSharp.Syntax; +// using Microsoft.CodeAnalysis.Text; +// +// namespace NadekoBot.Generators.Command; +// +// [Generator] +// public class CommandAttributesGenerator : IIncrementalGenerator +// { +// public const string ATTRIBUTE = @"// +// +// namespace NadekoBot.Common; +// +// [System.AttributeUsage(System.AttributeTargets.Method)] +// public class CmdAttribute : System.Attribute +// { +// +// }"; +// +// public class MethodModel +// { +// public string? Namespace { get; } +// public IReadOnlyCollection Classes { get; } +// public string ReturnType { get; } +// public string MethodName { get; } +// public IEnumerable Params { get; } +// +// public MethodModel(string? ns, IReadOnlyCollection classes, string returnType, string methodName, IEnumerable @params) +// { +// Namespace = ns; +// Classes = classes; +// ReturnType = returnType; +// MethodName = methodName; +// Params = @params; +// } +// } +// +// public class FileModel +// { +// public string? Namespace { get; } +// public IReadOnlyCollection ClassHierarchy { get; } +// public IReadOnlyCollection Methods { get; } +// +// public FileModel(string? ns, IReadOnlyCollection classHierarchy, IReadOnlyCollection methods) +// { +// Namespace = ns; +// ClassHierarchy = classHierarchy; +// Methods = methods; +// } +// } +// +// public void Initialize(IncrementalGeneratorInitializationContext context) +// { +// // #if DEBUG +// // if (!Debugger.IsAttached) +// // Debugger.Launch(); +// // // SpinWait.SpinUntil(() => Debugger.IsAttached); +// // #endif +// context.RegisterPostInitializationOutput(static ctx => ctx.AddSource( +// "CmdAttribute.g.cs", +// SourceText.From(ATTRIBUTE, Encoding.UTF8))); +// +// var methods = context.SyntaxProvider +// .CreateSyntaxProvider( +// static (node, _) => node is MethodDeclarationSyntax { AttributeLists.Count: > 0 }, +// static (ctx, cancel) => Transform(ctx, cancel)) +// .Where(static m => m is not null) +// .Where(static m => m?.ChildTokens().Any(static x => x.IsKind(SyntaxKind.PublicKeyword)) ?? false); +// +// var compilationMethods = context.CompilationProvider.Combine(methods.Collect()); +// +// context.RegisterSourceOutput(compilationMethods, +// static (ctx, tuple) => RegisterAction(in ctx, tuple.Left, in tuple.Right)); +// } +// +// private static void RegisterAction(in SourceProductionContext ctx, +// Compilation comp, +// in ImmutableArray methods) +// { +// if (methods is { IsDefaultOrEmpty: true }) +// return; +// +// var models = GetModels(comp, methods, ctx.CancellationToken); +// +// foreach (var model in models) +// { +// var name = $"{model.Namespace}.{string.Join(".", model.ClassHierarchy)}.g.cs"; +// try +// { +// var source = GetSourceText(model); +// ctx.AddSource(name, SourceText.From(source, Encoding.UTF8)); +// } +// catch (Exception ex) +// { +// Console.WriteLine($"Error writing source file {name}\n" + ex); +// } +// } +// } +// +// private static string GetSourceText(FileModel model) +// { +// using var sw = new StringWriter(); +// using var tw = new IndentedTextWriter(sw); +// +// tw.WriteLine("// "); +// tw.WriteLine("#pragma warning disable CS1066"); +// +// if (model.Namespace is not null) +// { +// tw.WriteLine($"namespace {model.Namespace};"); +// tw.WriteLine(); +// } +// +// foreach (var className in model.ClassHierarchy) +// { +// tw.WriteLine($"public partial class {className}"); +// tw.WriteLine("{"); +// tw.Indent ++; +// } +// +// foreach (var method in model.Methods) +// { +// tw.WriteLine("[NadekoCommand]"); +// tw.WriteLine("[NadekoDescription]"); +// tw.WriteLine("[Aliases]"); +// tw.WriteLine($"public partial {method.ReturnType} {method.MethodName}({string.Join(", ", method.Params)});"); +// } +// +// foreach (var _ in model.ClassHierarchy) +// { +// tw.Indent --; +// tw.WriteLine("}"); +// } +// +// tw.Flush(); +// return sw.ToString(); +// } +// +// private static IReadOnlyCollection GetModels(Compilation compilation, +// in ImmutableArray inputMethods, +// CancellationToken cancel) +// { +// var models = new List(); +// +// var methods = inputMethods +// .Where(static x => x is not null) +// .Distinct(); +// +// var methodModels = methods +// .Select(x => MethodDeclarationToMethodModel(compilation, x!)) +// .Where(static x => x is not null) +// .Cast(); +// +// var groups = methodModels +// .GroupBy(static x => $"{x.Namespace}.{string.Join(".", x.Classes)}"); +// +// foreach (var group in groups) +// { +// if (cancel.IsCancellationRequested) +// return new Collection(); +// +// if (group is null) +// continue; +// +// var elems = group.ToList(); +// if (elems.Count is 0) +// continue; +// +// var model = new FileModel( +// methods: elems, +// ns: elems[0].Namespace, +// classHierarchy: elems![0].Classes +// ); +// +// models.Add(model); +// } +// +// +// return models; +// } +// +// private static MethodModel? MethodDeclarationToMethodModel(Compilation comp, MethodDeclarationSyntax decl) +// { +// // SpinWait.SpinUntil(static () => Debugger.IsAttached); +// +// SemanticModel semanticModel; +// try +// { +// semanticModel = comp.GetSemanticModel(decl.SyntaxTree); +// } +// catch +// { +// // for some reason this method can throw "Not part of this compilation" argument exception +// return null; +// } +// +// var methodModel = new MethodModel( +// @params: decl.ParameterList.Parameters +// .Where(p => p.Type is not null) +// .Select(p => +// { +// var prefix = p.Modifiers.Any(static x => x.IsKind(SyntaxKind.ParamsKeyword)) +// ? "params " +// : string.Empty; +// +// var type = semanticModel +// .GetTypeInfo(p.Type!) +// .Type +// ?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); +// +// +// var name = p.Identifier.Text; +// +// var suffix = string.Empty; +// if (p.Default is not null) +// { +// if (p.Default.Value is LiteralExpressionSyntax) +// { +// suffix = " = " + p.Default.Value; +// } +// else if (p.Default.Value is MemberAccessExpressionSyntax maes) +// { +// var maesSemModel = comp.GetSemanticModel(maes.SyntaxTree); +// var sym = maesSemModel.GetSymbolInfo(maes.Name); +// if (sym.Symbol is null) +// { +// suffix = " = " + p.Default.Value; +// } +// else +// { +// suffix = " = " + sym.Symbol.ToDisplayString(); +// } +// } +// } +// +// return $"{prefix}{type} {name}{suffix}"; +// }) +// .ToList(), +// methodName: decl.Identifier.Text, +// returnType: decl.ReturnType.ToString(), +// ns: GetNamespace(decl), +// classes: GetClasses(decl) +// ); +// +// return methodModel; +// } +// +// //https://github.com/andrewlock/NetEscapades.EnumGenerators/blob/main/src/NetEscapades.EnumGenerators/EnumGenerator.cs +// static string? GetNamespace(MethodDeclarationSyntax declarationSyntax) +// { +// // determine the namespace the class is declared in, if any +// string? nameSpace = null; +// var parentOfInterest = declarationSyntax.Parent; +// while (parentOfInterest is not null) +// { +// parentOfInterest = parentOfInterest.Parent; +// +// if (parentOfInterest is BaseNamespaceDeclarationSyntax ns) +// { +// nameSpace = ns.Name.ToString(); +// while (true) +// { +// if (ns.Parent is not NamespaceDeclarationSyntax parent) +// { +// break; +// } +// +// ns = parent; +// nameSpace = $"{ns.Name}.{nameSpace}"; +// } +// +// return nameSpace; +// } +// +// } +// +// return nameSpace; +// } +// +// static IReadOnlyCollection GetClasses(MethodDeclarationSyntax declarationSyntax) +// { +// // determine the namespace the class is declared in, if any +// var classes = new LinkedList(); +// var parentOfInterest = declarationSyntax.Parent; +// while (parentOfInterest is not null) +// { +// if (parentOfInterest is ClassDeclarationSyntax cds) +// { +// classes.AddFirst(cds.Identifier.ToString()); +// } +// +// parentOfInterest = parentOfInterest.Parent; +// } +// +// Debug.WriteLine($"Method {declarationSyntax.Identifier.Text} has {classes.Count} classes"); +// +// return classes; +// } +// +// private static MethodDeclarationSyntax? Transform(GeneratorSyntaxContext ctx, CancellationToken cancel) +// { +// var methodDecl = ctx.Node as MethodDeclarationSyntax; +// if (methodDecl is null) +// return default; +// +// foreach (var attListSyntax in methodDecl.AttributeLists) +// { +// foreach (var attSyntax in attListSyntax.Attributes) +// { +// if (cancel.IsCancellationRequested) +// return default; +// +// var symbol = ctx.SemanticModel.GetSymbolInfo(attSyntax).Symbol; +// if (symbol is not IMethodSymbol attSymbol) +// continue; +// +// if (attSymbol.ContainingType.ToDisplayString() == "NadekoBot.Common.CmdAttribute") +// return methodDecl; +// } +// } +// +// return default; +// } +// } \ No newline at end of file diff --git a/src/NadekoBot.Tests/BotStringsTests.cs b/src/NadekoBot.Tests/BotStringsTests.cs index 31b4cbc34..b9b39cab6 100644 --- a/src/NadekoBot.Tests/BotStringsTests.cs +++ b/src/NadekoBot.Tests/BotStringsTests.cs @@ -47,7 +47,7 @@ namespace NadekoBot.Tests || !(type.GetCustomAttribute(true) is null)) // or a submodule .SelectMany(x => x.GetMethods() .Where(mi => mi.CustomAttributes - .Any(ca => ca.AttributeType == typeof(NadekoCommandAttribute)))) + .Any(ca => ca.AttributeType == typeof(CmdAttribute)))) .Select(x => x.Name.ToLowerInvariant()) .ToArray(); diff --git a/src/NadekoBot.VotesApi/NadekoBot.VotesApi.csproj b/src/NadekoBot.VotesApi/NadekoBot.VotesApi.csproj index 1ce2cc51f..61b68b3aa 100644 --- a/src/NadekoBot.VotesApi/NadekoBot.VotesApi.csproj +++ b/src/NadekoBot.VotesApi/NadekoBot.VotesApi.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/NadekoBot/Modules/Xp/XpService.cs b/src/NadekoBot/Modules/Xp/XpService.cs index 696776f50..f196d402a 100644 --- a/src/NadekoBot/Modules/Xp/XpService.cs +++ b/src/NadekoBot/Modules/Xp/XpService.cs @@ -1,4 +1,3 @@ -#nullable disable warnings using LinqToDB; using Microsoft.EntityFrameworkCore; using NadekoBot.Common.ModuleBehaviors; @@ -342,7 +341,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand } else // channel { - await ch.SendConfirmAsync(_eb, + await ch?.SendConfirmAsync(_eb, _strings.GetText(strs.level_up_channel(user.Mention, Format.Bold(newLevel.ToString())), guild.Id)); @@ -494,7 +493,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand { Level = level, RoleId = roleId, - Remove = remove + Remove = remove, }); } @@ -654,6 +653,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand Guild = channel.Guild, User = user, XpAmount = actualXp, + Channel = channel }); } } @@ -862,7 +862,15 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand public Task<(Stream Image, IImageFormat Format)> GenerateXpImageAsync(FullUserStats stats) => Task.Run(async () => { - using var img = Image.Load(await GetXpBackgroundAsync(stats.User.UserId), out var imageFormat); + var bgBytes = await GetXpBackgroundAsync(stats.User.UserId); + + if (bgBytes is null) + { + Log.Warning("Xp background image could not be loaded"); + throw new ArgumentNullException(nameof(bgBytes)); + } + + using var img = Image.Load(bgBytes, out var imageFormat); if (template.User.Name.Show) { var fontSize = (int)(template.User.Name.FontSize * 0.9); @@ -1113,7 +1121,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand return output; }); - private async Task GetXpBackgroundAsync(ulong userId) + private async Task GetXpBackgroundAsync(ulong _) { var img = await _images.GetXpBackgroundImageAsync(); return img; @@ -1123,7 +1131,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand private async Task DrawFrame(Image img, ulong userId) { var patron = await _ps.GetPatronAsync(userId); - Image frame = null; + Image? frame = null; if (patron.Tier == PatronTier.V) frame = Image.Load(File.OpenRead("data/images/frame_silver.png")); else if (patron.Tier >= PatronTier.X || _creds.IsOwner(userId)) diff --git a/src/NadekoBot/Modules/Xp/_Common/UserCacheItem.cs b/src/NadekoBot/Modules/Xp/_Common/UserCacheItem.cs index a5e18f373..90cbdfd8d 100644 --- a/src/NadekoBot/Modules/Xp/_Common/UserCacheItem.cs +++ b/src/NadekoBot/Modules/Xp/_Common/UserCacheItem.cs @@ -1,4 +1,4 @@ -#nullable disable +#nullable disable warnings using Cloneable; namespace NadekoBot.Modules.Xp.Services; @@ -6,8 +6,8 @@ namespace NadekoBot.Modules.Xp.Services; [Cloneable] public sealed partial class UserXpGainData : ICloneable { - public IGuildUser User { get; set; } - public IGuild Guild { get; set; } - public IMessageChannel Channel { get; set; } + public IGuildUser User { get; init; } + public IGuild Guild { get; init; } + public IMessageChannel Channel { get; init; } public int XpAmount { get; set; } } \ No newline at end of file