mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 09:18:27 -04:00
Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
44478e0f47 | ||
|
c73c2da6a4 | ||
|
5ed005211e | ||
|
d80cbb4647 | ||
|
9a96ef76ba | ||
|
5b5bc278ff | ||
|
5cb95cf94d | ||
|
f132aa2624 | ||
|
3b6b3bcf07 | ||
|
78d97db224 | ||
|
35ddd150ba | ||
|
39ae070c9d | ||
|
24a9a02cc3 | ||
|
0f68abcac9 | ||
|
908c61633d | ||
|
054fc30672 | ||
|
11ffdd84a3 | ||
|
5d2d74b92a | ||
|
18400dc53a | ||
|
29d94640af | ||
|
f6a53b96c7 | ||
|
1aa95a5dd0 | ||
|
fcfeb152c9 |
60
CHANGELOG.md
60
CHANGELOG.md
@@ -1,9 +1,65 @@
|
||||
|
||||
# Changelog
|
||||
|
||||
Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
|
||||
|
||||
## Unreleased
|
||||
|
||||
### [4.1.4] - 06-05-2022
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `.yun`
|
||||
|
||||
## [4.1.3] - 06.05.2022
|
||||
|
||||
### Added
|
||||
|
||||
- Added support for embed arrays in commands such as .say, .greet, .bye, etc...
|
||||
- Website to create them is live at eb.nadeko.bot (old one is moved to oldeb.nadeko.bot)
|
||||
- Embed arrays don't have a plainText property (it's renamed to 'content')
|
||||
- Embed arrays use color hex values instead of an integer
|
||||
- Old embed format will still work
|
||||
- There shouldn't be any breaking changes
|
||||
- Added `.stondel` command which, when toggled, will make the bot delete online stream messages on the server when the stream goes offline
|
||||
- Added a simple bank system.
|
||||
- Users can deposit, withdraw and check the balance of their currency in the bank.
|
||||
- Users can't check other user's bank balances.
|
||||
- Added a button on a .$ command which, when clicked, sends you a message with your bank balance that only you can see.
|
||||
- Added `.h <command group>`
|
||||
- Using this command will list all commands in the specified group
|
||||
- Atm only .bank is a proper group (`.h bank`)
|
||||
- Added "Bank Accounts" entry to `.economy`
|
||||
|
||||
### Changed
|
||||
|
||||
- Reaction roles rewritten completely
|
||||
- Supports multiple exclusivity groups per message
|
||||
- Supports level requirements
|
||||
- However they can only be added one by one
|
||||
- Use the following commands for more information
|
||||
- `.h .reroa`
|
||||
- `.h .reroli`
|
||||
- `.h .rerot`
|
||||
- `.h .rerorm`
|
||||
- `.h .rerodela`
|
||||
- Pagination is now using buttons instead of reactions
|
||||
- Bot will now support much higher XP values for global and server levels
|
||||
- [dev] Small change and generation perf improvement for the localized response strings
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `.deletexp` command
|
||||
- `.give` command should send DMs again
|
||||
- `.modules` commanad now has a medusa module description
|
||||
|
||||
## [4.1.2] - 16.04.2022
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed an issue with missing `.dll` files in release versions
|
||||
|
||||
## [4.1.0] - 16.04.2022
|
||||
|
||||
### Added
|
||||
@@ -30,14 +86,14 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
|
||||
|
||||
## [4.0.6] - 21.03.2022
|
||||
|
||||
### Fixes
|
||||
### Fixed
|
||||
|
||||
- Fixed voice presence logging
|
||||
- Fixed .clubaccept, .clubban, .clubkick and .clubunban commands
|
||||
|
||||
## [4.0.5] - 21.03.2022
|
||||
|
||||
### Fixes
|
||||
### Fixed
|
||||
|
||||
- Fixed several bugs in the currency code
|
||||
- Fixed some potential memory leaks
|
||||
|
@@ -92,6 +92,19 @@ Open Terminal (if you're on an installation with a window manager) and navigate
|
||||
|
||||
##### Release Update Instructions
|
||||
|
||||
###### Prerequisites
|
||||
|
||||
1. Nadeko requires redis to function
|
||||
- ubuntu installation command: `sudo apt-get install redis-server`
|
||||
2. Playing music requires `ffmpeg`, `libopus`, `libsodium` and `youtube-dl` (which in turn requires python3)
|
||||
- ubuntu installation command: `sudo apt-get install ffmpeg libopus0 opus-tools libopus-dev libsodium-dev -y`
|
||||
3. Make sure your python is version 3+ with `python --version`
|
||||
- if it's not, you can install python 3 and make it the default with: `sudo apt-get install python3.8 python-is-python3`
|
||||
|
||||
*You can use nadeko bash script [prerequisites installer](https://gitlab.com/Kwoth/nadeko-bash-installer/-/blob/v4/n-prereq.sh) as a reference*
|
||||
|
||||
###### Installation
|
||||
|
||||
1. Stop the bot
|
||||
2. Download the latest release from <https://gitlab.com/Kwoth/nadekobot/-/releases>
|
||||
- Look for the file called "x.x.x-linux-x64-build.tar" (where `X.X.X` is a version, for example 3.0.4) and download it
|
||||
|
@@ -97,13 +97,12 @@ public class CmdAttribute : System.Attribute
|
||||
var name = $"{model.Namespace}.{string.Join(".", model.ClassHierarchy)}.g.cs";
|
||||
try
|
||||
{
|
||||
Debug.WriteLine($"Writing {name}");
|
||||
var source = GetSourceText(model);
|
||||
ctx.AddSource(name, SourceText.From(source, Encoding.UTF8));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error writing source file {name}\n" + ex);
|
||||
Console.WriteLine($"Error writing source file {name}\n" + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -62,6 +62,7 @@ namespace NadekoBot.Generators
|
||||
sw.WriteLine("{");
|
||||
sw.Indent++;
|
||||
|
||||
var typedParamStrings = new List<string>(10);
|
||||
foreach (var field in fields)
|
||||
{
|
||||
var matches = Regex.Matches(field.Value, @"{(?<num>\d)[}:]");
|
||||
@@ -71,20 +72,30 @@ namespace NadekoBot.Generators
|
||||
max = Math.Max(max, int.Parse(match.Groups["num"].Value) + 1);
|
||||
}
|
||||
|
||||
List<string> typedParamStrings = new List<string>();
|
||||
var paramStrings = string.Empty;
|
||||
typedParamStrings.Clear();
|
||||
var typeParams = new string[max];
|
||||
var passedParamString = string.Empty;
|
||||
for (var i = 0; i < max; i++)
|
||||
{
|
||||
typedParamStrings.Add($"object p{i}");
|
||||
paramStrings += $", p{i}";
|
||||
typedParamStrings.Add($"in T{i} p{i}");
|
||||
passedParamString += $", p{i}";
|
||||
typeParams[i] = $"T{i}";
|
||||
}
|
||||
|
||||
|
||||
var sig = string.Empty;
|
||||
if(max > 0)
|
||||
var typeParamStr = string.Empty;
|
||||
if (max > 0)
|
||||
{
|
||||
sig = $"({string.Join(", ", typedParamStrings)})";
|
||||
|
||||
sw.WriteLine($"public static LocStr {field.Name}{sig} => new LocStr(\"{field.Name}\"{paramStrings});");
|
||||
typeParamStr = $"<{string.Join(", ", typeParams)}>";
|
||||
}
|
||||
|
||||
sw.WriteLine("public static LocStr {0}{1}{2} => new LocStr(\"{3}\"{4});",
|
||||
field.Name,
|
||||
typeParamStr,
|
||||
sig,
|
||||
field.Name,
|
||||
passedParamString);
|
||||
}
|
||||
|
||||
sw.Indent--;
|
||||
|
@@ -67,12 +67,13 @@ public sealed class Bot
|
||||
? GatewayIntents.All
|
||||
: GatewayIntents.AllUnprivileged,
|
||||
LogGatewayIntentWarnings = false,
|
||||
FormatUsersInBidirectionalUnicode = false,
|
||||
});
|
||||
|
||||
_commandService = new(new()
|
||||
{
|
||||
CaseSensitiveCommands = false,
|
||||
DefaultRunMode = RunMode.Sync
|
||||
DefaultRunMode = RunMode.Sync,
|
||||
});
|
||||
|
||||
// _interactionService = new(Client.Rest);
|
||||
|
27
src/NadekoBot/Common/Interaction/NadekoActionInteraction.cs
Normal file
27
src/NadekoBot/Common/Interaction/NadekoActionInteraction.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace NadekoBot;
|
||||
|
||||
public sealed class NadekoActionInteraction : NadekoOwnInteraction
|
||||
{
|
||||
private readonly NadekoInteractionData _data;
|
||||
private readonly Func<SocketMessageComponent, Task> _action;
|
||||
|
||||
public NadekoActionInteraction(
|
||||
DiscordSocketClient client,
|
||||
ulong authorId,
|
||||
NadekoInteractionData data,
|
||||
Func<SocketMessageComponent, Task> action
|
||||
)
|
||||
: base(client, authorId)
|
||||
{
|
||||
_data = data;
|
||||
_action = action;
|
||||
}
|
||||
|
||||
public override string Name
|
||||
=> _data.CustomId;
|
||||
public override IEmote Emote
|
||||
=> _data.Emote;
|
||||
|
||||
public override Task ExecuteOnActionAsync(SocketMessageComponent smc)
|
||||
=> _action(smc);
|
||||
}
|
77
src/NadekoBot/Common/Interaction/NadekoInteraction.cs
Normal file
77
src/NadekoBot/Common/Interaction/NadekoInteraction.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
namespace NadekoBot;
|
||||
|
||||
public abstract class NadekoInteraction
|
||||
{
|
||||
// improvements:
|
||||
// - state in OnAction
|
||||
// - configurable delay
|
||||
// -
|
||||
public abstract string Name { get; }
|
||||
public abstract IEmote Emote { get; }
|
||||
|
||||
protected readonly DiscordSocketClient _client;
|
||||
|
||||
protected readonly TaskCompletionSource<bool> _interactionCompletedSource;
|
||||
|
||||
protected IUserMessage message = null!;
|
||||
|
||||
protected NadekoInteraction(DiscordSocketClient client)
|
||||
{
|
||||
_client = client;
|
||||
_interactionCompletedSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
}
|
||||
|
||||
public async Task RunAsync(IUserMessage msg)
|
||||
{
|
||||
message = msg;
|
||||
|
||||
_client.InteractionCreated += OnInteraction;
|
||||
await Task.WhenAny(Task.Delay(10_000), _interactionCompletedSource.Task);
|
||||
_client.InteractionCreated -= OnInteraction;
|
||||
|
||||
await msg.ModifyAsync(m => m.Components = new ComponentBuilder().Build());
|
||||
}
|
||||
|
||||
protected abstract ValueTask<bool> Validate(SocketMessageComponent smc);
|
||||
private async Task OnInteraction(SocketInteraction arg)
|
||||
{
|
||||
if (arg is not SocketMessageComponent smc)
|
||||
return;
|
||||
|
||||
if (smc.Message.Id != message.Id)
|
||||
return;
|
||||
|
||||
if (smc.Data.CustomId != Name)
|
||||
return;
|
||||
|
||||
if (!await Validate(smc))
|
||||
{
|
||||
await smc.DeferAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await ExecuteOnActionAsync(smc);
|
||||
|
||||
// this should only be a thing on single-response buttons
|
||||
_interactionCompletedSource.TrySetResult(true);
|
||||
|
||||
if (!smc.HasResponded)
|
||||
{
|
||||
await smc.DeferAsync();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public MessageComponent CreateComponent()
|
||||
{
|
||||
var comp = new ComponentBuilder()
|
||||
.WithButton(new ButtonBuilder(style: ButtonStyle.Secondary, emote: Emote, customId: Name));
|
||||
|
||||
return comp.Build();
|
||||
}
|
||||
|
||||
public abstract Task ExecuteOnActionAsync(SocketMessageComponent smc);
|
||||
}
|
41
src/NadekoBot/Common/Interaction/NadekoInteractionBuilder.cs
Normal file
41
src/NadekoBot/Common/Interaction/NadekoInteractionBuilder.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
namespace NadekoBot;
|
||||
|
||||
/// <summary>
|
||||
/// Builder class for NadekoInteractions
|
||||
/// </summary>
|
||||
public class NadekoInteractionBuilder
|
||||
{
|
||||
private NadekoInteractionData? iData;
|
||||
private Func<SocketMessageComponent, Task>? action;
|
||||
// private bool isOwn;
|
||||
|
||||
public NadekoInteractionBuilder WithData<T>(in T data)
|
||||
where T : NadekoInteractionData
|
||||
{
|
||||
iData = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
// public NadekoOwnInteractionBuiler WithIsOwn(bool isOwn = true)
|
||||
// {
|
||||
// this.isOwn = isOwn;
|
||||
// return this;
|
||||
// }
|
||||
|
||||
public NadekoInteractionBuilder WithAction(in Func<SocketMessageComponent, Task> fn)
|
||||
{
|
||||
this.action = fn;
|
||||
return this;
|
||||
}
|
||||
|
||||
public NadekoActionInteraction Build(DiscordSocketClient client, ulong userId)
|
||||
{
|
||||
if (iData is null)
|
||||
throw new InvalidOperationException("You have to specify the data before building the interaction");
|
||||
|
||||
if (action is null)
|
||||
throw new InvalidOperationException("You have to specify the action before building the interaction");
|
||||
|
||||
return new(client, userId, iData, action);
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
namespace NadekoBot;
|
||||
|
||||
/// <summary>
|
||||
/// Represents essential interacation data
|
||||
/// </summary>
|
||||
/// <param name="Emote">Emote which will show on a button</param>
|
||||
/// <param name="CustomId">Custom interaction id</param>
|
||||
public record NadekoInteractionData(IEmote Emote, string CustomId);
|
15
src/NadekoBot/Common/Interaction/NadekoOwnInteraction.cs
Normal file
15
src/NadekoBot/Common/Interaction/NadekoOwnInteraction.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace NadekoBot;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction which only the author can use
|
||||
/// </summary>
|
||||
public abstract class NadekoOwnInteraction : NadekoInteraction
|
||||
{
|
||||
protected readonly ulong _authorId;
|
||||
|
||||
protected NadekoOwnInteraction(DiscordSocketClient client, ulong authorId) : base(client)
|
||||
=> _authorId = authorId;
|
||||
|
||||
protected override ValueTask<bool> Validate(SocketMessageComponent smc)
|
||||
=> new(smc.User.Id == _authorId);
|
||||
}
|
16
src/NadekoBot/Common/Linq2DbExpressions.cs
Normal file
16
src/NadekoBot/Common/Linq2DbExpressions.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
#nullable disable
|
||||
using LinqToDB;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace NadekoBot.Common;
|
||||
|
||||
public static class Linq2DbExpressions
|
||||
{
|
||||
[ExpressionMethod(nameof(GuildOnShardExpression))]
|
||||
public static bool GuildOnShard(ulong guildId, int totalShards, int shardId)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
private static Expression<Func<ulong, int, int, bool>> GuildOnShardExpression()
|
||||
=> (guildId, totalShards, shardId)
|
||||
=> guildId / 4194304 % (ulong)totalShards == (ulong)shardId;
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
using Cloneable;
|
||||
#nullable enable
|
||||
using Cloneable;
|
||||
using NadekoBot.Common.Yml;
|
||||
|
||||
namespace Nadeko.Medusa;
|
||||
@@ -10,7 +11,7 @@ public sealed partial class MedusaConfig : ICloneable<MedusaConfig>
|
||||
public int Version { get; set; } = 1;
|
||||
|
||||
[Comment("List of medusae automatically loaded at startup")]
|
||||
public List<string> Loaded { get; set; }
|
||||
public List<string>? Loaded { get; set; }
|
||||
|
||||
public MedusaConfig()
|
||||
{
|
||||
|
@@ -18,7 +18,7 @@ public sealed class MedusaConfigService : ConfigServiceBase<MedusaConfig>, IMedu
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<string> GetLoadedMedusae()
|
||||
=> Data.Loaded.ToList();
|
||||
=> Data.Loaded?.ToList() ?? new List<string>();
|
||||
|
||||
public void AddLoadedMedusa(string name)
|
||||
{
|
||||
@@ -26,6 +26,9 @@ public sealed class MedusaConfigService : ConfigServiceBase<MedusaConfig>, IMedu
|
||||
|
||||
ModifyConfig(conf =>
|
||||
{
|
||||
if (conf.Loaded is null)
|
||||
conf.Loaded = new();
|
||||
|
||||
if(!conf.Loaded.Contains(name))
|
||||
conf.Loaded.Add(name);
|
||||
});
|
||||
@@ -37,6 +40,9 @@ public sealed class MedusaConfigService : ConfigServiceBase<MedusaConfig>, IMedu
|
||||
|
||||
ModifyConfig(conf =>
|
||||
{
|
||||
if (conf.Loaded is null)
|
||||
conf.Loaded = new();
|
||||
|
||||
conf.Loaded.Remove(name);
|
||||
});
|
||||
}
|
||||
|
@@ -191,14 +191,12 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
|
||||
await _lock.WaitAsync();
|
||||
try
|
||||
{
|
||||
var success = LoadAssemblyInternal(safeName,
|
||||
out var ctx,
|
||||
out var snekData,
|
||||
out var services,
|
||||
out var strings,
|
||||
out var typeReaders);
|
||||
|
||||
if (success)
|
||||
if (LoadAssemblyInternal(safeName,
|
||||
out var ctx,
|
||||
out var snekData,
|
||||
out var services,
|
||||
out var strings,
|
||||
out var typeReaders))
|
||||
{
|
||||
var moduleInfos = new List<ModuleInfo>();
|
||||
|
||||
@@ -769,7 +767,7 @@ public sealed class MedusaLoaderService : IMedusaLoaderService, IReadyExecutor,
|
||||
var paramName = pi.Name ?? "unnamed";
|
||||
var isContext = paramCounter == 0 && pi.ParameterType.IsAssignableTo(typeof(AnyContext));
|
||||
|
||||
var leftoverAttribute = pi.GetCustomAttribute<Nadeko.Snake.leftoverAttribute>(true);
|
||||
var leftoverAttribute = pi.GetCustomAttribute<leftoverAttribute>(true);
|
||||
var hasDefaultValue = pi.HasDefaultValue;
|
||||
var isLeftover = leftoverAttribute != null;
|
||||
var isParams = pi.GetCustomAttribute<ParamArrayAttribute>() is not null;
|
||||
|
@@ -1,5 +1,6 @@
|
||||
#nullable disable
|
||||
using System.Globalization;
|
||||
using MessageType = NadekoBot.Extensions.MessageType;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
@@ -29,20 +30,15 @@ public abstract class NadekoModule : ModuleBase
|
||||
|
||||
protected string GetText(in LocStr data)
|
||||
=> Strings.GetText(data, Culture);
|
||||
|
||||
public Task<IUserMessage> SendErrorAsync(string error)
|
||||
=> ctx.Channel.SendErrorAsync(_eb, error);
|
||||
|
||||
|
||||
public Task<IUserMessage> SendErrorAsync(
|
||||
string title,
|
||||
string error,
|
||||
string url = null,
|
||||
string footer = null)
|
||||
string footer = null,
|
||||
NadekoInteraction inter = null)
|
||||
=> ctx.Channel.SendErrorAsync(_eb, title, error, url, footer);
|
||||
|
||||
public Task<IUserMessage> SendConfirmAsync(string text)
|
||||
=> ctx.Channel.SendConfirmAsync(_eb, text);
|
||||
|
||||
|
||||
public Task<IUserMessage> SendConfirmAsync(
|
||||
string title,
|
||||
string text,
|
||||
@@ -50,25 +46,33 @@ public abstract class NadekoModule : ModuleBase
|
||||
string footer = null)
|
||||
=> ctx.Channel.SendConfirmAsync(_eb, title, text, url, footer);
|
||||
|
||||
public Task<IUserMessage> SendPendingAsync(string text)
|
||||
=> ctx.Channel.SendPendingAsync(_eb, text);
|
||||
//
|
||||
public Task<IUserMessage> SendErrorAsync(string text, NadekoInteraction inter = null)
|
||||
=> ctx.Channel.SendAsync(_eb, text, MessageType.Error, inter);
|
||||
public Task<IUserMessage> SendConfirmAsync(string text, NadekoInteraction inter = null)
|
||||
=> ctx.Channel.SendAsync(_eb, text, MessageType.Ok, inter);
|
||||
public Task<IUserMessage> SendPendingAsync(string text, NadekoInteraction inter = null)
|
||||
=> ctx.Channel.SendAsync(_eb, text, MessageType.Pending, inter);
|
||||
|
||||
public Task<IUserMessage> ErrorLocalizedAsync(LocStr str)
|
||||
=> SendErrorAsync(GetText(str));
|
||||
|
||||
// localized normal
|
||||
public Task<IUserMessage> ErrorLocalizedAsync(LocStr str, NadekoInteraction inter = null)
|
||||
=> SendErrorAsync(GetText(str), inter);
|
||||
|
||||
public Task<IUserMessage> PendingLocalizedAsync(LocStr str)
|
||||
=> SendPendingAsync(GetText(str));
|
||||
public Task<IUserMessage> PendingLocalizedAsync(LocStr str, NadekoInteraction inter = null)
|
||||
=> SendPendingAsync(GetText(str), inter);
|
||||
|
||||
public Task<IUserMessage> ConfirmLocalizedAsync(LocStr str)
|
||||
=> SendConfirmAsync(GetText(str));
|
||||
public Task<IUserMessage> ConfirmLocalizedAsync(LocStr str, NadekoInteraction inter = null)
|
||||
=> SendConfirmAsync(GetText(str), inter);
|
||||
|
||||
public Task<IUserMessage> ReplyErrorLocalizedAsync(LocStr str)
|
||||
// localized replies
|
||||
public Task<IUserMessage> ReplyErrorLocalizedAsync(LocStr str, NadekoInteraction inter = null)
|
||||
=> SendErrorAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
||||
|
||||
public Task<IUserMessage> ReplyPendingLocalizedAsync(LocStr str)
|
||||
public Task<IUserMessage> ReplyPendingLocalizedAsync(LocStr str, NadekoInteraction inter = null)
|
||||
=> SendPendingAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
||||
|
||||
public Task<IUserMessage> ReplyConfirmLocalizedAsync(LocStr str)
|
||||
public Task<IUserMessage> ReplyConfirmLocalizedAsync(LocStr str, NadekoInteraction inter = null)
|
||||
=> SendConfirmAsync($"{Format.Bold(ctx.User.ToString())} {GetText(str)}");
|
||||
|
||||
public async Task<bool> PromptUserConfirmAsync(IEmbedBuilder embed)
|
||||
|
@@ -34,61 +34,59 @@ public class Replacer
|
||||
public SmartText Replace(SmartText data)
|
||||
=> data switch
|
||||
{
|
||||
SmartEmbedText embedData => Replace(embedData),
|
||||
SmartEmbedText embedData => Replace(embedData) with
|
||||
{
|
||||
PlainText = Replace(embedData.PlainText),
|
||||
Color = embedData.Color
|
||||
},
|
||||
SmartPlainText plain => Replace(plain),
|
||||
SmartEmbedTextArray arr => Replace(arr),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(data), "Unsupported argument type")
|
||||
};
|
||||
|
||||
public SmartPlainText Replace(SmartPlainText plainText)
|
||||
=> Replace(plainText.Text);
|
||||
|
||||
public SmartEmbedText Replace(SmartEmbedText embedData)
|
||||
{
|
||||
var newEmbedData = new SmartEmbedText
|
||||
private SmartEmbedTextArray Replace(SmartEmbedTextArray embedArr)
|
||||
=> new()
|
||||
{
|
||||
Embeds = embedArr.Embeds.Map(e => Replace(e) with
|
||||
{
|
||||
Color = e.Color
|
||||
}),
|
||||
Content = Replace(embedArr.Content)
|
||||
};
|
||||
|
||||
private SmartPlainText Replace(SmartPlainText plain)
|
||||
=> Replace(plain.Text);
|
||||
|
||||
private T Replace<T>(T embedData) where T: SmartEmbedTextBase, new()
|
||||
{
|
||||
var newEmbedData = new T
|
||||
{
|
||||
PlainText = Replace(embedData.PlainText),
|
||||
Description = Replace(embedData.Description),
|
||||
Title = Replace(embedData.Title),
|
||||
Thumbnail = Replace(embedData.Thumbnail),
|
||||
Image = Replace(embedData.Image),
|
||||
Url = Replace(embedData.Url)
|
||||
};
|
||||
if (embedData.Author is not null)
|
||||
{
|
||||
newEmbedData.Author = new()
|
||||
{
|
||||
Name = Replace(embedData.Author.Name),
|
||||
IconUrl = Replace(embedData.Author.IconUrl)
|
||||
};
|
||||
}
|
||||
|
||||
if (embedData.Fields is not null)
|
||||
{
|
||||
var fields = new List<SmartTextEmbedField>();
|
||||
foreach (var f in embedData.Fields)
|
||||
{
|
||||
var newF = new SmartTextEmbedField
|
||||
Url = Replace(embedData.Url),
|
||||
Author = embedData.Author is null
|
||||
? null
|
||||
: new()
|
||||
{
|
||||
Name = Replace(f.Name),
|
||||
Value = Replace(f.Value),
|
||||
Inline = f.Inline
|
||||
};
|
||||
fields.Add(newF);
|
||||
}
|
||||
|
||||
newEmbedData.Fields = fields.ToArray();
|
||||
}
|
||||
|
||||
if (embedData.Footer is not null)
|
||||
{
|
||||
newEmbedData.Footer = new()
|
||||
Name = Replace(embedData.Author.Name),
|
||||
IconUrl = Replace(embedData.Author.IconUrl)
|
||||
},
|
||||
Fields = embedData.Fields?.Map(f => new SmartTextEmbedField
|
||||
{
|
||||
Text = Replace(embedData.Footer.Text),
|
||||
IconUrl = Replace(embedData.Footer.IconUrl)
|
||||
};
|
||||
}
|
||||
|
||||
newEmbedData.Color = embedData.Color;
|
||||
Name = Replace(f.Name),
|
||||
Value = Replace(f.Value),
|
||||
Inline = f.Inline
|
||||
}),
|
||||
Footer = embedData.Footer is null
|
||||
? null
|
||||
: new()
|
||||
{
|
||||
Text = Replace(embedData.Footer.Text),
|
||||
IconUrl = Replace(embedData.Footer.IconUrl)
|
||||
}
|
||||
};
|
||||
|
||||
return newEmbedData;
|
||||
}
|
||||
|
@@ -1,20 +1,67 @@
|
||||
#nullable disable
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
||||
#nullable disable
|
||||
namespace NadekoBot;
|
||||
|
||||
public sealed record SmartEmbedText : SmartText
|
||||
public sealed record SmartEmbedArrayElementText : SmartEmbedTextBase
|
||||
{
|
||||
public string PlainText { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string Thumbnail { get; set; }
|
||||
public string Image { get; set; }
|
||||
public string Color { get; init; } = string.Empty;
|
||||
|
||||
public SmartTextEmbedAuthor Author { get; set; }
|
||||
public SmartTextEmbedFooter Footer { get; set; }
|
||||
public SmartTextEmbedField[] Fields { get; set; }
|
||||
public SmartEmbedArrayElementText() : base()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public SmartEmbedArrayElementText(IEmbed eb) : base(eb)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public uint Color { get; set; } = 7458112;
|
||||
protected override EmbedBuilder GetEmbedInternal()
|
||||
{
|
||||
var embed = base.GetEmbedInternal();
|
||||
if (Rgba32.TryParseHex(Color, out var color))
|
||||
return embed.WithColor(color.ToDiscordColor());
|
||||
|
||||
return embed;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record SmartEmbedText : SmartEmbedTextBase
|
||||
{
|
||||
public string PlainText { get; init; }
|
||||
|
||||
public uint Color { get; init; } = 7458112;
|
||||
|
||||
public SmartEmbedText()
|
||||
{
|
||||
}
|
||||
|
||||
private SmartEmbedText(IEmbed eb, string plainText = null)
|
||||
: base(eb)
|
||||
=> (PlainText, Color) = (plainText, eb.Color?.RawValue ?? 0);
|
||||
|
||||
public static SmartEmbedText FromEmbed(IEmbed eb, string plainText = null)
|
||||
=> new(eb, plainText);
|
||||
|
||||
protected override EmbedBuilder GetEmbedInternal()
|
||||
{
|
||||
var embed = base.GetEmbedInternal();
|
||||
return embed.WithColor(Color);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract record SmartEmbedTextBase : SmartText
|
||||
{
|
||||
public string Title { get; init; }
|
||||
public string Description { get; init; }
|
||||
public string Url { get; init; }
|
||||
public string Thumbnail { get; init; }
|
||||
public string Image { get; init; }
|
||||
|
||||
public SmartTextEmbedAuthor Author { get; init; }
|
||||
public SmartTextEmbedFooter Footer { get; init; }
|
||||
public SmartTextEmbedField[] Fields { get; init; }
|
||||
|
||||
public bool IsValid
|
||||
=> !string.IsNullOrWhiteSpace(Title)
|
||||
@@ -26,36 +73,37 @@ public sealed record SmartEmbedText : SmartText
|
||||
&& (!string.IsNullOrWhiteSpace(Footer.Text) || !string.IsNullOrWhiteSpace(Footer.IconUrl)))
|
||||
|| Fields is { Length: > 0 };
|
||||
|
||||
public static SmartEmbedText FromEmbed(IEmbed eb, string plainText = null)
|
||||
protected SmartEmbedTextBase()
|
||||
{
|
||||
var set = new SmartEmbedText
|
||||
{
|
||||
PlainText = plainText,
|
||||
Title = eb.Title,
|
||||
Description = eb.Description,
|
||||
Url = eb.Url,
|
||||
Thumbnail = eb.Thumbnail?.Url,
|
||||
Image = eb.Image?.Url,
|
||||
Author = eb.Author is { } ea
|
||||
? new()
|
||||
{
|
||||
Name = ea.Name,
|
||||
Url = ea.Url,
|
||||
IconUrl = ea.IconUrl
|
||||
}
|
||||
: null,
|
||||
Footer = eb.Footer is { } ef
|
||||
? new()
|
||||
{
|
||||
Text = ef.Text,
|
||||
IconUrl = ef.IconUrl
|
||||
}
|
||||
: null
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected SmartEmbedTextBase(IEmbed eb)
|
||||
{
|
||||
Title = eb.Title;
|
||||
Description = eb.Description;
|
||||
Url = eb.Url;
|
||||
Thumbnail = eb.Thumbnail?.Url;
|
||||
Image = eb.Image?.Url;
|
||||
Author = eb.Author is { } ea
|
||||
? new()
|
||||
{
|
||||
Name = ea.Name,
|
||||
Url = ea.Url,
|
||||
IconUrl = ea.IconUrl
|
||||
}
|
||||
: null;
|
||||
Footer = eb.Footer is { } ef
|
||||
? new()
|
||||
{
|
||||
Text = ef.Text,
|
||||
IconUrl = ef.IconUrl
|
||||
}
|
||||
: null;
|
||||
|
||||
if (eb.Fields.Length > 0)
|
||||
{
|
||||
set.Fields = eb.Fields.Select(field
|
||||
Fields = eb.Fields.Select(field
|
||||
=> new SmartTextEmbedField
|
||||
{
|
||||
Inline = field.Inline,
|
||||
@@ -64,14 +112,14 @@ public sealed record SmartEmbedText : SmartText
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
set.Color = eb.Color?.RawValue ?? 0;
|
||||
return set;
|
||||
}
|
||||
|
||||
public EmbedBuilder GetEmbed()
|
||||
=> GetEmbedInternal();
|
||||
|
||||
protected virtual EmbedBuilder GetEmbedInternal()
|
||||
{
|
||||
var embed = new EmbedBuilder().WithColor(Color);
|
||||
var embed = new EmbedBuilder();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Title))
|
||||
embed.WithTitle(Title);
|
||||
|
28
src/NadekoBot/Common/SmartText/SmartEmbedTextArray.cs
Normal file
28
src/NadekoBot/Common/SmartText/SmartEmbedTextArray.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot;
|
||||
|
||||
public sealed record SmartEmbedTextArray : SmartText
|
||||
{
|
||||
public string Content { get; set; }
|
||||
public SmartEmbedArrayElementText[] Embeds { get; set; }
|
||||
|
||||
public bool IsValid
|
||||
=> Embeds?.All(x => x.IsValid) ?? false;
|
||||
|
||||
public EmbedBuilder[] GetEmbedBuilders()
|
||||
{
|
||||
if (Embeds is null)
|
||||
return Array.Empty<EmbedBuilder>();
|
||||
|
||||
return Embeds.Map(em => em.GetEmbed());
|
||||
}
|
||||
|
||||
public void NormalizeFields()
|
||||
{
|
||||
if (Embeds is null)
|
||||
return;
|
||||
|
||||
foreach(var eb in Embeds)
|
||||
eb.NormalizeFields();
|
||||
}
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
#nullable disable
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace NadekoBot;
|
||||
|
||||
@@ -11,6 +12,9 @@ public abstract record SmartText
|
||||
public bool IsPlainText
|
||||
=> this is SmartPlainText;
|
||||
|
||||
public bool IsEmbedArray
|
||||
=> this is SmartEmbedTextArray;
|
||||
|
||||
public static SmartText operator +(SmartText text, string input)
|
||||
=> text switch
|
||||
{
|
||||
@@ -19,6 +23,10 @@ public abstract record SmartText
|
||||
PlainText = set.PlainText + input
|
||||
},
|
||||
SmartPlainText spt => new SmartPlainText(spt.Text + input),
|
||||
SmartEmbedTextArray arr => arr with
|
||||
{
|
||||
Content = arr.Content + input
|
||||
},
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(text))
|
||||
};
|
||||
|
||||
@@ -30,27 +38,46 @@ public abstract record SmartText
|
||||
PlainText = input + set.PlainText
|
||||
},
|
||||
SmartPlainText spt => new SmartPlainText(input + spt.Text),
|
||||
SmartEmbedTextArray arr => arr with
|
||||
{
|
||||
Content = input + arr.Content
|
||||
},
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(text))
|
||||
};
|
||||
|
||||
[CanBeNull]
|
||||
public static SmartText CreateFrom(string input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input) || !input.TrimStart().StartsWith("{"))
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
return new SmartPlainText(input);
|
||||
|
||||
try
|
||||
{
|
||||
var smartEmbedText = JsonConvert.DeserializeObject<SmartEmbedText>(input);
|
||||
var doc = JObject.Parse(input);
|
||||
var root = doc.Root;
|
||||
if (root.Type == JTokenType.Object)
|
||||
{
|
||||
if (((JObject)root).TryGetValue("embeds", out _))
|
||||
{
|
||||
var arr = root.ToObject<SmartEmbedTextArray>();
|
||||
|
||||
if (smartEmbedText is null)
|
||||
throw new FormatException();
|
||||
if (arr is null)
|
||||
return new SmartPlainText(input);
|
||||
|
||||
smartEmbedText.NormalizeFields();
|
||||
arr!.NormalizeFields();
|
||||
return arr;
|
||||
}
|
||||
|
||||
if (!smartEmbedText.IsValid)
|
||||
return new SmartPlainText(input);
|
||||
var obj = root.ToObject<SmartEmbedText>();
|
||||
|
||||
return smartEmbedText;
|
||||
if (obj is null)
|
||||
return new SmartPlainText(input);
|
||||
|
||||
obj.NormalizeFields();
|
||||
return obj;
|
||||
}
|
||||
|
||||
return new SmartPlainText(input);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
@@ -1,95 +0,0 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Common;
|
||||
|
||||
public sealed class ReactionEventWrapper : IDisposable
|
||||
{
|
||||
public event Action<SocketReaction> OnReactionAdded = delegate { };
|
||||
public event Action<SocketReaction> OnReactionRemoved = delegate { };
|
||||
public event Action OnReactionsCleared = delegate { };
|
||||
|
||||
public IUserMessage Message { get; }
|
||||
private readonly DiscordSocketClient _client;
|
||||
|
||||
private bool disposing;
|
||||
|
||||
public ReactionEventWrapper(DiscordSocketClient client, IUserMessage msg)
|
||||
{
|
||||
Message = msg ?? throw new ArgumentNullException(nameof(msg));
|
||||
_client = client;
|
||||
|
||||
_client.ReactionAdded += Discord_ReactionAdded;
|
||||
_client.ReactionRemoved += Discord_ReactionRemoved;
|
||||
_client.ReactionsCleared += Discord_ReactionsCleared;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposing)
|
||||
return;
|
||||
disposing = true;
|
||||
UnsubAll();
|
||||
}
|
||||
|
||||
private Task Discord_ReactionsCleared(Cacheable<IUserMessage, ulong> msg, Cacheable<IMessageChannel, ulong> channel)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (msg.Id == Message.Id)
|
||||
OnReactionsCleared?.Invoke();
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task Discord_ReactionRemoved(
|
||||
Cacheable<IUserMessage, ulong> msg,
|
||||
Cacheable<IMessageChannel, ulong> cacheable,
|
||||
SocketReaction reaction)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (msg.Id == Message.Id)
|
||||
OnReactionRemoved?.Invoke(reaction);
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task Discord_ReactionAdded(
|
||||
Cacheable<IUserMessage, ulong> msg,
|
||||
Cacheable<IMessageChannel, ulong> cacheable,
|
||||
SocketReaction reaction)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (msg.Id == Message.Id)
|
||||
OnReactionAdded?.Invoke(reaction);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void UnsubAll()
|
||||
{
|
||||
_client.ReactionAdded -= Discord_ReactionAdded;
|
||||
_client.ReactionRemoved -= Discord_ReactionRemoved;
|
||||
_client.ReactionsCleared -= Discord_ReactionsCleared;
|
||||
OnReactionAdded = null;
|
||||
OnReactionRemoved = null;
|
||||
OnReactionsCleared = null;
|
||||
}
|
||||
}
|
@@ -50,9 +50,7 @@ public static class GuildConfigExtensions
|
||||
.Include(gc => gc.StreamRole)
|
||||
.Include(gc => gc.XpSettings)
|
||||
.ThenInclude(x => x.ExclusionList)
|
||||
.Include(gc => gc.DelMsgOnCmdChannels)
|
||||
.Include(gc => gc.ReactionRoleMessages)
|
||||
.ThenInclude(x => x.ReactionRoles);
|
||||
.Include(gc => gc.DelMsgOnCmdChannels);
|
||||
|
||||
public static IEnumerable<GuildConfig> GetAllGuildConfigs(
|
||||
this DbSet<GuildConfig> configs,
|
||||
|
9
src/NadekoBot/Db/Models/BankUser.cs
Normal file
9
src/NadekoBot/Db/Models/BankUser.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Db.Models;
|
||||
|
||||
public class BankUser : DbEntity
|
||||
{
|
||||
public ulong UserId { get; set; }
|
||||
public long Balance { get; set; }
|
||||
}
|
@@ -14,7 +14,7 @@ public class DiscordUser : DbEntity
|
||||
public ClubInfo Club { get; set; }
|
||||
public bool IsClubAdmin { get; set; }
|
||||
|
||||
public int TotalXp { get; set; }
|
||||
public long TotalXp { get; set; }
|
||||
public DateTime LastLevelUp { get; set; } = DateTime.UtcNow;
|
||||
public DateTime LastXpGain { get; set; } = DateTime.MinValue;
|
||||
public XpNotificationLocation NotifyOnLevelUp { get; set; }
|
||||
|
@@ -90,8 +90,8 @@ public class GuildConfig : DbEntity
|
||||
|
||||
public XpSettings XpSettings { get; set; }
|
||||
public List<FeedSub> FeedSubs { get; set; } = new();
|
||||
public IndexedCollection<ReactionRoleMessage> ReactionRoleMessages { get; set; } = new();
|
||||
public bool NotifyStreamOffline { get; set; }
|
||||
public bool DeleteStreamOnlineMessage { get; set; }
|
||||
public List<GroupName> SelfAssignableRoleGroupNames { get; set; }
|
||||
public int WarnExpireHours { get; set; }
|
||||
public WarnExpireAction WarnExpireAction { get; set; } = WarnExpireAction.Clear;
|
||||
|
@@ -1,22 +1,18 @@
|
||||
#nullable disable
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace NadekoBot.Services.Database.Models;
|
||||
|
||||
public class ReactionRoleMessage : DbEntity, IIndexed
|
||||
public class ReactionRoleV2 : DbEntity
|
||||
{
|
||||
public int Index { get; set; }
|
||||
|
||||
public int GuildConfigId { get; set; }
|
||||
public GuildConfig GuildConfig { get; set; }
|
||||
|
||||
public ulong GuildId { get; set; }
|
||||
public ulong ChannelId { get; set; }
|
||||
|
||||
public ulong MessageId { get; set; }
|
||||
|
||||
public List<ReactionRole> ReactionRoles { get; set; }
|
||||
public bool Exclusive { get; set; }
|
||||
}
|
||||
|
||||
public class ReactionRole : DbEntity
|
||||
{
|
||||
public string EmoteName { get; set; }
|
||||
|
||||
[MaxLength(100)]
|
||||
public string Emote { get; set; }
|
||||
public ulong RoleId { get; set; }
|
||||
public int Group { get; set; }
|
||||
public int LevelReq { get; set; }
|
||||
}
|
@@ -5,8 +5,8 @@ public class UserXpStats : DbEntity
|
||||
{
|
||||
public ulong UserId { get; set; }
|
||||
public ulong GuildId { get; set; }
|
||||
public int Xp { get; set; }
|
||||
public int AwardedXp { get; set; }
|
||||
public long Xp { get; set; }
|
||||
public long AwardedXp { get; set; }
|
||||
public XpNotificationLocation NotifyOnLevelUp { get; set; }
|
||||
public DateTime LastLevelUp { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
@@ -51,6 +51,10 @@ public abstract class NadekoContext : DbContext
|
||||
public DbSet<AutoTranslateUser> AutoTranslateUsers { get; set; }
|
||||
|
||||
public DbSet<Permissionv2> Permissions { get; set; }
|
||||
|
||||
public DbSet<BankUser> BankUsers { get; set; }
|
||||
|
||||
public DbSet<ReactionRoleV2> ReactionRoles { get; set; }
|
||||
|
||||
#region Mandatory Provider-Specific Values
|
||||
|
||||
@@ -359,11 +363,18 @@ public abstract class NadekoContext : DbContext
|
||||
|
||||
#region Reaction roles
|
||||
|
||||
modelBuilder.Entity<ReactionRoleMessage>(rrm => rrm
|
||||
.HasMany(x => x.ReactionRoles)
|
||||
.WithOne()
|
||||
.OnDelete(DeleteBehavior.Cascade));
|
||||
modelBuilder.Entity<ReactionRoleV2>(rr2 =>
|
||||
{
|
||||
rr2.HasIndex(x => x.GuildId)
|
||||
.IsUnique(false);
|
||||
|
||||
rr2.HasIndex(x => new
|
||||
{
|
||||
x.MessageId,
|
||||
x.Emote
|
||||
}).IsUnique();
|
||||
});
|
||||
|
||||
#endregion
|
||||
|
||||
#region LogSettings
|
||||
@@ -402,6 +413,13 @@ public abstract class NadekoContext : DbContext
|
||||
x.ChannelId,
|
||||
x.UserId
|
||||
}));
|
||||
|
||||
#region BANK
|
||||
|
||||
modelBuilder.Entity<BankUser>(bu => bu.HasIndex(x => x.UserId).IsUnique());
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
16
src/NadekoBot/Migrations/MigrationQueries.cs
Normal file
16
src/NadekoBot/Migrations/MigrationQueries.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace NadekoBot.Migrations;
|
||||
|
||||
public static class MigrationQueries
|
||||
{
|
||||
public static void MigrateRero(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(
|
||||
@"insert or ignore into reactionroles(guildid, channelid, messageid, emote, roleid, 'group', levelreq, dateadded)
|
||||
select guildid, channelid, messageid, emotename, roleid, exclusive, 0, reactionrolemessage.dateadded
|
||||
from reactionrole
|
||||
left join reactionrolemessage on reactionrolemessage.id = reactionrole.reactionrolemessageid
|
||||
left join guildconfigs on reactionrolemessage.guildconfigid = guildconfigs.id;");
|
||||
}
|
||||
}
|
3429
src/NadekoBot/Migrations/MySql/20220428044612_stondel.Designer.cs
generated
Normal file
3429
src/NadekoBot/Migrations/MySql/20220428044612_stondel.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
src/NadekoBot/Migrations/MySql/20220428044612_stondel.cs
Normal file
26
src/NadekoBot/Migrations/MySql/20220428044612_stondel.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.Mysql
|
||||
{
|
||||
public partial class stondel : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "deletestreamonlinemessage",
|
||||
table: "guildconfigs",
|
||||
type: "tinyint(1)",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "deletestreamonlinemessage",
|
||||
table: "guildconfigs");
|
||||
}
|
||||
}
|
||||
}
|
3458
src/NadekoBot/Migrations/MySql/20220429044757_bank.Designer.cs
generated
Normal file
3458
src/NadekoBot/Migrations/MySql/20220429044757_bank.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
42
src/NadekoBot/Migrations/MySql/20220429044757_bank.cs
Normal file
42
src/NadekoBot/Migrations/MySql/20220429044757_bank.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.Mysql
|
||||
{
|
||||
public partial class bank : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "bankusers",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
userid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
|
||||
balance = table.Column<long>(type: "bigint", nullable: false),
|
||||
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_bankusers", x => x.id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_bankusers_userid",
|
||||
table: "bankusers",
|
||||
column: "userid",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "bankusers");
|
||||
}
|
||||
}
|
||||
}
|
3411
src/NadekoBot/Migrations/MySql/20220504162509_new-rero.Designer.cs
generated
Normal file
3411
src/NadekoBot/Migrations/MySql/20220504162509_new-rero.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
121
src/NadekoBot/Migrations/MySql/20220504162509_new-rero.cs
Normal file
121
src/NadekoBot/Migrations/MySql/20220504162509_new-rero.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.Mysql
|
||||
{
|
||||
public partial class newrero : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "reactionroles",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
guildid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
|
||||
channelid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
|
||||
messageid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
|
||||
emote = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
roleid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
|
||||
group = table.Column<int>(type: "int", nullable: false),
|
||||
levelreq = table.Column<int>(type: "int", nullable: false),
|
||||
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_reactionroles", x => x.id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_reactionroles_guildid",
|
||||
table: "reactionroles",
|
||||
column: "guildid");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_reactionroles_messageid_emote",
|
||||
table: "reactionroles",
|
||||
columns: new[] { "messageid", "emote" },
|
||||
unique: true);
|
||||
|
||||
MigrationQueries.MigrateRero(migrationBuilder);
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "reactionrole");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "reactionrolemessage");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "reactionroles");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "reactionrolemessage",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
guildconfigid = table.Column<int>(type: "int", nullable: false),
|
||||
channelid = table.Column<ulong>(type: "bigint unsigned", nullable: false),
|
||||
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
exclusive = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
index = table.Column<int>(type: "int", nullable: false),
|
||||
messageid = table.Column<ulong>(type: "bigint unsigned", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_reactionrolemessage", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_reactionrolemessage_guildconfigs_guildconfigid",
|
||||
column: x => x.guildconfigid,
|
||||
principalTable: "guildconfigs",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "reactionrole",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
dateadded = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
emotename = table.Column<string>(type: "longtext", nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
reactionrolemessageid = table.Column<int>(type: "int", nullable: true),
|
||||
roleid = table.Column<ulong>(type: "bigint unsigned", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_reactionrole", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_reactionrole_reactionrolemessage_reactionrolemessageid",
|
||||
column: x => x.reactionrolemessageid,
|
||||
principalTable: "reactionrolemessage",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_reactionrole_reactionrolemessageid",
|
||||
table: "reactionrole",
|
||||
column: "reactionrolemessageid");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_reactionrolemessage_guildconfigid",
|
||||
table: "reactionrolemessage",
|
||||
column: "guildconfigid");
|
||||
}
|
||||
}
|
||||
}
|
@@ -16,9 +16,38 @@ namespace NadekoBot.Migrations.Mysql
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "6.0.3")
|
||||
.HasAnnotation("ProductVersion", "6.0.4")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<long>("Balance")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("balance");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("dateadded");
|
||||
|
||||
b.Property<ulong>("UserId")
|
||||
.HasColumnType("bigint unsigned")
|
||||
.HasColumnName("userid");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_bankusers");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_bankusers_userid");
|
||||
|
||||
b.ToTable("bankusers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.ClubApplicants", b =>
|
||||
{
|
||||
b.Property<int>("ClubId")
|
||||
@@ -1032,6 +1061,10 @@ namespace NadekoBot.Migrations.Mysql
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasColumnName("deletemessageoncommand");
|
||||
|
||||
b.Property<bool>("DeleteStreamOnlineMessage")
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasColumnName("deletestreamonlinemessage");
|
||||
|
||||
b.Property<string>("DmGreetMessageText")
|
||||
.HasColumnType("longtext")
|
||||
.HasColumnName("dmgreetmessagetext");
|
||||
@@ -1780,39 +1813,7 @@ namespace NadekoBot.Migrations.Mysql
|
||||
b.ToTable("quotes", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRole", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("dateadded");
|
||||
|
||||
b.Property<string>("EmoteName")
|
||||
.HasColumnType("longtext")
|
||||
.HasColumnName("emotename");
|
||||
|
||||
b.Property<int?>("ReactionRoleMessageId")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("reactionrolemessageid");
|
||||
|
||||
b.Property<ulong>("RoleId")
|
||||
.HasColumnType("bigint unsigned")
|
||||
.HasColumnName("roleid");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_reactionrole");
|
||||
|
||||
b.HasIndex("ReactionRoleMessageId")
|
||||
.HasDatabaseName("ix_reactionrole_reactionrolemessageid");
|
||||
|
||||
b.ToTable("reactionrole", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleMessage", b =>
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleV2", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -1827,29 +1828,42 @@ namespace NadekoBot.Migrations.Mysql
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("dateadded");
|
||||
|
||||
b.Property<bool>("Exclusive")
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasColumnName("exclusive");
|
||||
b.Property<string>("Emote")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)")
|
||||
.HasColumnName("emote");
|
||||
|
||||
b.Property<int>("GuildConfigId")
|
||||
b.Property<int>("Group")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("guildconfigid");
|
||||
.HasColumnName("group");
|
||||
|
||||
b.Property<int>("Index")
|
||||
b.Property<ulong>("GuildId")
|
||||
.HasColumnType("bigint unsigned")
|
||||
.HasColumnName("guildid");
|
||||
|
||||
b.Property<int>("LevelReq")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("index");
|
||||
.HasColumnName("levelreq");
|
||||
|
||||
b.Property<ulong>("MessageId")
|
||||
.HasColumnType("bigint unsigned")
|
||||
.HasColumnName("messageid");
|
||||
|
||||
b.Property<ulong>("RoleId")
|
||||
.HasColumnType("bigint unsigned")
|
||||
.HasColumnName("roleid");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_reactionrolemessage");
|
||||
.HasName("pk_reactionroles");
|
||||
|
||||
b.HasIndex("GuildConfigId")
|
||||
.HasDatabaseName("ix_reactionrolemessage_guildconfigid");
|
||||
b.HasIndex("GuildId")
|
||||
.HasDatabaseName("ix_reactionroles_guildid");
|
||||
|
||||
b.ToTable("reactionrolemessage", (string)null);
|
||||
b.HasIndex("MessageId", "Emote")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_reactionroles_messageid_emote");
|
||||
|
||||
b.ToTable("reactionroles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b =>
|
||||
@@ -3075,27 +3089,6 @@ namespace NadekoBot.Migrations.Mysql
|
||||
.HasConstraintName("fk_pollvote_poll_pollid");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRole", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.ReactionRoleMessage", null)
|
||||
.WithMany("ReactionRoles")
|
||||
.HasForeignKey("ReactionRoleMessageId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.HasConstraintName("fk_reactionrole_reactionrolemessage_reactionrolemessageid");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleMessage", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig")
|
||||
.WithMany("ReactionRoleMessages")
|
||||
.HasForeignKey("GuildConfigId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_reactionrolemessage_guildconfigs_guildconfigid");
|
||||
|
||||
b.Navigation("GuildConfig");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null)
|
||||
@@ -3345,8 +3338,6 @@ namespace NadekoBot.Migrations.Mysql
|
||||
|
||||
b.Navigation("Permissions");
|
||||
|
||||
b.Navigation("ReactionRoleMessages");
|
||||
|
||||
b.Navigation("SelfAssignableRoleGroupNames");
|
||||
|
||||
b.Navigation("ShopEntries");
|
||||
@@ -3387,11 +3378,6 @@ namespace NadekoBot.Migrations.Mysql
|
||||
b.Navigation("Votes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleMessage", b =>
|
||||
{
|
||||
b.Navigation("ReactionRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b =>
|
||||
{
|
||||
b.Navigation("Items");
|
||||
|
3569
src/NadekoBot/Migrations/Postgresql/20220428044547_stondel.Designer.cs
generated
Normal file
3569
src/NadekoBot/Migrations/Postgresql/20220428044547_stondel.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.PostgreSql
|
||||
{
|
||||
public partial class stondel : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "deletestreamonlinemessage",
|
||||
table: "guildconfigs",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "deletestreamonlinemessage",
|
||||
table: "guildconfigs");
|
||||
}
|
||||
}
|
||||
}
|
3600
src/NadekoBot/Migrations/Postgresql/20220429044808_bank.Designer.cs
generated
Normal file
3600
src/NadekoBot/Migrations/Postgresql/20220429044808_bank.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
41
src/NadekoBot/Migrations/Postgresql/20220429044808_bank.cs
Normal file
41
src/NadekoBot/Migrations/Postgresql/20220429044808_bank.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.PostgreSql
|
||||
{
|
||||
public partial class bank : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "bankusers",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
userid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
balance = table.Column<long>(type: "bigint", nullable: false),
|
||||
dateadded = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_bankusers", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_bankusers_userid",
|
||||
table: "bankusers",
|
||||
column: "userid",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "bankusers");
|
||||
}
|
||||
}
|
||||
}
|
3551
src/NadekoBot/Migrations/Postgresql/20220504162457_new-rero.Designer.cs
generated
Normal file
3551
src/NadekoBot/Migrations/Postgresql/20220504162457_new-rero.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
115
src/NadekoBot/Migrations/Postgresql/20220504162457_new-rero.cs
Normal file
115
src/NadekoBot/Migrations/Postgresql/20220504162457_new-rero.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations.PostgreSql
|
||||
{
|
||||
public partial class newrero : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "reactionroles",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
messageid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
emote = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
|
||||
roleid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
group = table.Column<int>(type: "integer", nullable: false),
|
||||
levelreq = table.Column<int>(type: "integer", nullable: false),
|
||||
dateadded = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_reactionroles", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_reactionroles_guildid",
|
||||
table: "reactionroles",
|
||||
column: "guildid");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_reactionroles_messageid_emote",
|
||||
table: "reactionroles",
|
||||
columns: new[] { "messageid", "emote" },
|
||||
unique: true);
|
||||
|
||||
MigrationQueries.MigrateRero(migrationBuilder);
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "reactionrole");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "reactionrolemessage");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "reactionroles");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "reactionrolemessage",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
guildconfigid = table.Column<int>(type: "integer", nullable: false),
|
||||
channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||
dateadded = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
exclusive = table.Column<bool>(type: "boolean", nullable: false),
|
||||
index = table.Column<int>(type: "integer", nullable: false),
|
||||
messageid = table.Column<decimal>(type: "numeric(20,0)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_reactionrolemessage", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_reactionrolemessage_guildconfigs_guildconfigid",
|
||||
column: x => x.guildconfigid,
|
||||
principalTable: "guildconfigs",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "reactionrole",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
dateadded = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
emotename = table.Column<string>(type: "text", nullable: true),
|
||||
reactionrolemessageid = table.Column<int>(type: "integer", nullable: true),
|
||||
roleid = table.Column<decimal>(type: "numeric(20,0)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_reactionrole", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_reactionrole_reactionrolemessage_reactionrolemessageid",
|
||||
column: x => x.reactionrolemessageid,
|
||||
principalTable: "reactionrolemessage",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_reactionrole_reactionrolemessageid",
|
||||
table: "reactionrole",
|
||||
column: "reactionrolemessageid");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_reactionrolemessage_guildconfigid",
|
||||
table: "reactionrolemessage",
|
||||
column: "guildconfigid");
|
||||
}
|
||||
}
|
||||
}
|
@@ -17,11 +17,42 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "6.0.3")
|
||||
.HasAnnotation("ProductVersion", "6.0.4")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<long>("Balance")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("balance");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("dateadded");
|
||||
|
||||
b.Property<decimal>("UserId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("userid");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_bankusers");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_bankusers_userid");
|
||||
|
||||
b.ToTable("bankusers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.ClubApplicants", b =>
|
||||
{
|
||||
b.Property<int>("ClubId")
|
||||
@@ -1086,6 +1117,10 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("deletemessageoncommand");
|
||||
|
||||
b.Property<bool>("DeleteStreamOnlineMessage")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("deletestreamonlinemessage");
|
||||
|
||||
b.Property<string>("DmGreetMessageText")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("dmgreetmessagetext");
|
||||
@@ -1866,41 +1901,7 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
b.ToTable("quotes", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRole", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("dateadded");
|
||||
|
||||
b.Property<string>("EmoteName")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("emotename");
|
||||
|
||||
b.Property<int?>("ReactionRoleMessageId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("reactionrolemessageid");
|
||||
|
||||
b.Property<decimal>("RoleId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("roleid");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_reactionrole");
|
||||
|
||||
b.HasIndex("ReactionRoleMessageId")
|
||||
.HasDatabaseName("ix_reactionrole_reactionrolemessageid");
|
||||
|
||||
b.ToTable("reactionrole", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleMessage", b =>
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleV2", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -1917,29 +1918,42 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("dateadded");
|
||||
|
||||
b.Property<bool>("Exclusive")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("exclusive");
|
||||
b.Property<string>("Emote")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasColumnName("emote");
|
||||
|
||||
b.Property<int>("GuildConfigId")
|
||||
b.Property<int>("Group")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("guildconfigid");
|
||||
.HasColumnName("group");
|
||||
|
||||
b.Property<int>("Index")
|
||||
b.Property<decimal>("GuildId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("guildid");
|
||||
|
||||
b.Property<int>("LevelReq")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("index");
|
||||
.HasColumnName("levelreq");
|
||||
|
||||
b.Property<decimal>("MessageId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("messageid");
|
||||
|
||||
b.Property<decimal>("RoleId")
|
||||
.HasColumnType("numeric(20,0)")
|
||||
.HasColumnName("roleid");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_reactionrolemessage");
|
||||
.HasName("pk_reactionroles");
|
||||
|
||||
b.HasIndex("GuildConfigId")
|
||||
.HasDatabaseName("ix_reactionrolemessage_guildconfigid");
|
||||
b.HasIndex("GuildId")
|
||||
.HasDatabaseName("ix_reactionroles_guildid");
|
||||
|
||||
b.ToTable("reactionrolemessage", (string)null);
|
||||
b.HasIndex("MessageId", "Emote")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_reactionroles_messageid_emote");
|
||||
|
||||
b.ToTable("reactionroles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b =>
|
||||
@@ -3215,27 +3229,6 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
.HasConstraintName("fk_pollvote_poll_pollid");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRole", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.ReactionRoleMessage", null)
|
||||
.WithMany("ReactionRoles")
|
||||
.HasForeignKey("ReactionRoleMessageId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.HasConstraintName("fk_reactionrole_reactionrolemessage_reactionrolemessageid");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleMessage", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig")
|
||||
.WithMany("ReactionRoleMessages")
|
||||
.HasForeignKey("GuildConfigId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_reactionrolemessage_guildconfigs_guildconfigid");
|
||||
|
||||
b.Navigation("GuildConfig");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null)
|
||||
@@ -3485,8 +3478,6 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
|
||||
b.Navigation("Permissions");
|
||||
|
||||
b.Navigation("ReactionRoleMessages");
|
||||
|
||||
b.Navigation("SelfAssignableRoleGroupNames");
|
||||
|
||||
b.Navigation("ShopEntries");
|
||||
@@ -3527,11 +3518,6 @@ namespace NadekoBot.Migrations.PostgreSql
|
||||
b.Navigation("Votes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleMessage", b =>
|
||||
{
|
||||
b.Navigation("ReactionRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b =>
|
||||
{
|
||||
b.Navigation("Items");
|
||||
|
2759
src/NadekoBot/Migrations/Sqlite/20220427200557_stondel.Designer.cs
generated
Normal file
2759
src/NadekoBot/Migrations/Sqlite/20220427200557_stondel.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
src/NadekoBot/Migrations/Sqlite/20220427200557_stondel.cs
Normal file
26
src/NadekoBot/Migrations/Sqlite/20220427200557_stondel.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
public partial class stondel : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "DeleteStreamOnlineMessage",
|
||||
table: "GuildConfigs",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DeleteStreamOnlineMessage",
|
||||
table: "GuildConfigs");
|
||||
}
|
||||
}
|
||||
}
|
2782
src/NadekoBot/Migrations/Sqlite/20220428051304_bank.Designer.cs
generated
Normal file
2782
src/NadekoBot/Migrations/Sqlite/20220428051304_bank.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
src/NadekoBot/Migrations/Sqlite/20220428051304_bank.cs
Normal file
40
src/NadekoBot/Migrations/Sqlite/20220428051304_bank.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
public partial class bank : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "BankUsers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
UserId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
Balance = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_BankUsers", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BankUsers_UserId",
|
||||
table: "BankUsers",
|
||||
column: "UserId",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "BankUsers");
|
||||
}
|
||||
}
|
||||
}
|
2741
src/NadekoBot/Migrations/Sqlite/20220503234243_new-rero.Designer.cs
generated
Normal file
2741
src/NadekoBot/Migrations/Sqlite/20220503234243_new-rero.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
114
src/NadekoBot/Migrations/Sqlite/20220503234243_new-rero.cs
Normal file
114
src/NadekoBot/Migrations/Sqlite/20220503234243_new-rero.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NadekoBot.Migrations
|
||||
{
|
||||
public partial class newrero : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ReactionRoles",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
MessageId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
Emote = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
||||
RoleId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
Group = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
LevelReq = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ReactionRoles", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReactionRoles_GuildId",
|
||||
table: "ReactionRoles",
|
||||
column: "GuildId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReactionRoles_MessageId_Emote",
|
||||
table: "ReactionRoles",
|
||||
columns: new[] { "MessageId", "Emote" },
|
||||
unique: true);
|
||||
|
||||
MigrationQueries.MigrateRero(migrationBuilder);
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ReactionRole");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ReactionRoleMessage");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ReactionRoles");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ReactionRoleMessage",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
GuildConfigId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||
Exclusive = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
Index = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
MessageId = table.Column<ulong>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ReactionRoleMessage", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ReactionRoleMessage_GuildConfigs_GuildConfigId",
|
||||
column: x => x.GuildConfigId,
|
||||
principalTable: "GuildConfigs",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ReactionRole",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||
EmoteName = table.Column<string>(type: "TEXT", nullable: true),
|
||||
ReactionRoleMessageId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||
RoleId = table.Column<ulong>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ReactionRole", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ReactionRole_ReactionRoleMessage_ReactionRoleMessageId",
|
||||
column: x => x.ReactionRoleMessageId,
|
||||
principalTable: "ReactionRoleMessage",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReactionRole_ReactionRoleMessageId",
|
||||
table: "ReactionRole",
|
||||
column: "ReactionRoleMessageId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReactionRoleMessage_GuildConfigId",
|
||||
table: "ReactionRoleMessage",
|
||||
column: "GuildConfigId");
|
||||
}
|
||||
}
|
||||
}
|
@@ -17,6 +17,29 @@ namespace NadekoBot.Migrations
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "6.0.3");
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.BankUser", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("Balance")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<ulong>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("BankUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Db.Models.ClubApplicants", b =>
|
||||
{
|
||||
b.Property<int>("ClubId")
|
||||
@@ -809,6 +832,9 @@ namespace NadekoBot.Migrations
|
||||
b.Property<bool>("DeleteMessageOnCommand")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("DeleteStreamOnlineMessage")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("DmGreetMessageText")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@@ -1389,32 +1415,7 @@ namespace NadekoBot.Migrations
|
||||
b.ToTable("Quotes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRole", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("EmoteName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("ReactionRoleMessageId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("RoleId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ReactionRoleMessageId");
|
||||
|
||||
b.ToTable("ReactionRole");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleMessage", b =>
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleV2", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -1426,23 +1427,33 @@ namespace NadekoBot.Migrations
|
||||
b.Property<DateTime?>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Exclusive")
|
||||
b.Property<string>("Emote")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Group")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("GuildConfigId")
|
||||
b.Property<ulong>("GuildId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Index")
|
||||
b.Property<int>("LevelReq")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("MessageId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<ulong>("RoleId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GuildConfigId");
|
||||
b.HasIndex("GuildId");
|
||||
|
||||
b.ToTable("ReactionRoleMessage");
|
||||
b.HasIndex("MessageId", "Emote")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ReactionRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b =>
|
||||
@@ -2430,25 +2441,6 @@ namespace NadekoBot.Migrations
|
||||
.HasForeignKey("PollId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRole", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.ReactionRoleMessage", null)
|
||||
.WithMany("ReactionRoles")
|
||||
.HasForeignKey("ReactionRoleMessageId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleMessage", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig")
|
||||
.WithMany("ReactionRoleMessages")
|
||||
.HasForeignKey("GuildConfigId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("GuildConfig");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b =>
|
||||
{
|
||||
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", null)
|
||||
@@ -2676,8 +2668,6 @@ namespace NadekoBot.Migrations
|
||||
|
||||
b.Navigation("Permissions");
|
||||
|
||||
b.Navigation("ReactionRoleMessages");
|
||||
|
||||
b.Navigation("SelfAssignableRoleGroupNames");
|
||||
|
||||
b.Navigation("ShopEntries");
|
||||
@@ -2718,11 +2708,6 @@ namespace NadekoBot.Migrations
|
||||
b.Navigation("Votes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ReactionRoleMessage", b =>
|
||||
{
|
||||
b.Navigation("ReactionRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.ShopEntry", b =>
|
||||
{
|
||||
b.Navigation("Items");
|
||||
|
@@ -19,7 +19,7 @@ public class DangerousCommandsService : INService
|
||||
await using var ctx = _db.GetDbContext();
|
||||
await ctx.DiscordUser.UpdateAsync(_ => new DiscordUser()
|
||||
{
|
||||
Club = null,
|
||||
ClubId = null,
|
||||
// IsClubAdmin = false,
|
||||
TotalXp = 0
|
||||
});
|
||||
|
@@ -256,15 +256,54 @@ public class GreetService : INService, IReadyExecutor
|
||||
{
|
||||
text = new SmartEmbedText()
|
||||
{
|
||||
PlainText = pt.Text
|
||||
Description = pt.Text
|
||||
};
|
||||
}
|
||||
|
||||
((SmartEmbedText)text).Footer = new()
|
||||
else if (text is SmartEmbedText set)
|
||||
{
|
||||
Text = $"This message was sent from {user.Guild} server.",
|
||||
IconUrl = user.Guild.IconUrl
|
||||
};
|
||||
text = set with
|
||||
{
|
||||
Footer = CreateFooterSource(user)
|
||||
};
|
||||
}
|
||||
else if (text is SmartEmbedTextArray seta)
|
||||
{
|
||||
// if the greet dm message is a text array
|
||||
var ebElem = seta.Embeds.LastOrDefault();
|
||||
if (ebElem is null)
|
||||
{
|
||||
// if there are no embeds, add an embed with the footer
|
||||
text = seta with
|
||||
{
|
||||
Embeds = new[]
|
||||
{
|
||||
new SmartEmbedArrayElementText()
|
||||
{
|
||||
Footer = CreateFooterSource(user)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// if the maximum amount of embeds is reached, edit the last embed
|
||||
if (seta.Embeds.Length >= 10)
|
||||
{
|
||||
seta.Embeds[^1] = seta.Embeds[^1] with
|
||||
{
|
||||
Footer = CreateFooterSource(user)
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// if there is less than 10 embeds, add an embed with footer only
|
||||
seta.Embeds = seta.Embeds.Append(new SmartEmbedArrayElementText()
|
||||
{
|
||||
Footer = CreateFooterSource(user)
|
||||
}).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await user.SendAsync(text);
|
||||
}
|
||||
@@ -276,6 +315,13 @@ public class GreetService : INService, IReadyExecutor
|
||||
return true;
|
||||
}
|
||||
|
||||
private static SmartTextEmbedFooter CreateFooterSource(IGuildUser user)
|
||||
=> new()
|
||||
{
|
||||
Text = $"This message was sent from {user.Guild} server.",
|
||||
IconUrl = user.Guild.IconUrl
|
||||
};
|
||||
|
||||
private Task OnUserJoined(IGuildUser user)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
|
@@ -0,0 +1,52 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using System.Collections;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
|
||||
public interface IReactionRoleService
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a single reaction role
|
||||
/// </summary>
|
||||
/// <param name="guildId"></param>
|
||||
/// <param name="msg"></param>
|
||||
/// <param name="channel"></param>
|
||||
/// <param name="emote"></param>
|
||||
/// <param name="role"></param>
|
||||
/// <param name="group"></param>
|
||||
/// <param name="levelReq"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> AddReactionRole(
|
||||
ulong guildId,
|
||||
IMessage msg,
|
||||
ITextChannel channel,
|
||||
string emote,
|
||||
IRole role,
|
||||
int group = 0,
|
||||
int levelReq = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Get all reaction roles on the specified server
|
||||
/// </summary>
|
||||
/// <param name="guildId"></param>
|
||||
/// <returns></returns>
|
||||
Task<IReadOnlyCollection<ReactionRoleV2>> GetReactionRolesAsync(ulong guildId);
|
||||
|
||||
/// <summary>
|
||||
/// Remove reaction roles on the specified message
|
||||
/// </summary>
|
||||
/// <param name="guildId"></param>
|
||||
/// <param name="messageId"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> RemoveReactionRoles(ulong guildId, ulong messageId);
|
||||
|
||||
/// <summary>
|
||||
/// Remove all reaction roles in the specified server
|
||||
/// </summary>
|
||||
/// <param name="guildId"></param>
|
||||
/// <returns></returns>
|
||||
Task<int> RemoveAllReactionRoles(ulong guildId);
|
||||
|
||||
Task<IReadOnlyCollection<IEmote>> TransferReactionRolesAsync(ulong guildId, ulong fromMessageId, ulong toMessageId);
|
||||
}
|
@@ -0,0 +1,166 @@
|
||||
using NadekoBot.Modules.Administration.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Administration;
|
||||
|
||||
public partial class Administration
|
||||
{
|
||||
public partial class ReactionRoleCommands : NadekoModule
|
||||
{
|
||||
private readonly IReactionRoleService _rero;
|
||||
|
||||
public ReactionRoleCommands(IReactionRoleService rero)
|
||||
{
|
||||
_rero = rero;
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NoPublicBot]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
public async partial Task ReactionRoleAdd(
|
||||
ulong messageId,
|
||||
string emoteStr,
|
||||
IRole role,
|
||||
int group = 0,
|
||||
int levelReq = 0)
|
||||
{
|
||||
if (group < 0)
|
||||
return;
|
||||
|
||||
if (levelReq < 0)
|
||||
return;
|
||||
|
||||
var msg = await ctx.Channel.GetMessageAsync(messageId);
|
||||
if (msg is null)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_found);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.User.Id != ctx.Guild.OwnerId && ((IGuildUser)ctx.User).GetRoles().Max(x => x.Position) <= role.Position)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.hierarchy);
|
||||
return;
|
||||
}
|
||||
|
||||
var emote = emoteStr.ToIEmote();
|
||||
await msg.AddReactionAsync(emote);
|
||||
var succ = await _rero.AddReactionRole(ctx.Guild.Id,
|
||||
msg,
|
||||
(ITextChannel)ctx.Channel,
|
||||
emoteStr,
|
||||
role,
|
||||
group,
|
||||
levelReq);
|
||||
|
||||
if (succ)
|
||||
{
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ctx.ErrorAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NoPublicBot]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
public async partial Task ReactionRolesList()
|
||||
{
|
||||
var reros = await _rero.GetReactionRolesAsync(ctx.Guild.Id);
|
||||
|
||||
var embed = _eb.Create(ctx)
|
||||
.WithOkColor();
|
||||
|
||||
var content = string.Empty;
|
||||
foreach (var g in reros.GroupBy(x => x.MessageId).OrderBy(x => x.Key))
|
||||
{
|
||||
var messageId = g.Key;
|
||||
content +=
|
||||
$"[{messageId}](https://discord.com/channels/{ctx.Guild.Id}/{g.First().ChannelId}/{g.Key})\n";
|
||||
|
||||
var groupGroups = g.GroupBy(x => x.Group);
|
||||
|
||||
foreach (var ggs in groupGroups)
|
||||
{
|
||||
content += $"`< {(g.Key == 0 ? ("Not Exclusive (Group 0)") : ($"Group {ggs.Key}"))} >`\n";
|
||||
|
||||
foreach (var rero in ggs)
|
||||
{
|
||||
content += $"\t{rero.Emote} -> {(ctx.Guild.GetRole(rero.RoleId)?.Mention ?? "<missing role>")}";
|
||||
if (rero.LevelReq > 0)
|
||||
content += $" (lvl {rero.LevelReq}+)";
|
||||
content += '\n';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
embed.WithDescription(string.IsNullOrWhiteSpace(content)
|
||||
? "There are no reaction roles on this server"
|
||||
: content);
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NoPublicBot]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
public async partial Task ReactionRolesRemove(ulong messageId)
|
||||
{
|
||||
var succ = await _rero.RemoveReactionRoles(ctx.Guild.Id, messageId);
|
||||
if (succ)
|
||||
await ctx.OkAsync();
|
||||
else
|
||||
await ctx.ErrorAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NoPublicBot]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
public async partial Task ReactionRolesDeleteAll()
|
||||
{
|
||||
await _rero.RemoveAllReactionRoles(ctx.Guild.Id);
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NoPublicBot]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[Ratelimit(60)]
|
||||
public async partial Task ReactionRolesTransfer(ulong fromMessageId, ulong toMessageId)
|
||||
{
|
||||
var msg = await ctx.Channel.GetMessageAsync(toMessageId);
|
||||
|
||||
if (msg is null)
|
||||
{
|
||||
await ctx.ErrorAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
var reactions = await _rero.TransferReactionRolesAsync(ctx.Guild.Id, fromMessageId, toMessageId);
|
||||
|
||||
if (reactions.Count == 0)
|
||||
{
|
||||
await ctx.ErrorAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var r in reactions)
|
||||
{
|
||||
await msg.AddReactionAsync(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,356 @@
|
||||
#nullable disable
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Modules.Xp.Extensions;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
|
||||
public sealed class ReactionRolesService : IReadyExecutor, INService, IReactionRoleService
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly IBotCredentials _creds;
|
||||
|
||||
private ConcurrentDictionary<ulong, List<ReactionRoleV2>> _cache;
|
||||
private readonly object _cacheLock = new();
|
||||
private readonly SemaphoreSlim _assignementLock = new(1, 1);
|
||||
|
||||
public ReactionRolesService(DiscordSocketClient client, DbService db, IBotCredentials creds)
|
||||
{
|
||||
_db = db;
|
||||
_client = client;
|
||||
_creds = creds;
|
||||
_cache = new();
|
||||
}
|
||||
|
||||
public async Task OnReadyAsync()
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
var reros = await uow.GetTable<ReactionRoleV2>()
|
||||
.Where(x => Linq2DbExpressions.GuildOnShard(x.GuildId, _creds.TotalShards, _client.ShardId))
|
||||
.ToListAsyncLinqToDB();
|
||||
|
||||
foreach (var group in reros.GroupBy(x => x.MessageId))
|
||||
{
|
||||
_cache[group.Key] = group.ToList();
|
||||
}
|
||||
|
||||
_client.ReactionAdded += ClientOnReactionAdded;
|
||||
_client.ReactionRemoved += ClientOnReactionRemoved;
|
||||
}
|
||||
|
||||
private async Task<(IGuildUser, IRole)> GetUserAndRoleAsync(
|
||||
SocketReaction r,
|
||||
ReactionRoleV2 rero)
|
||||
{
|
||||
var guild = _client.GetGuild(rero.GuildId);
|
||||
var role = guild?.GetRole(rero.RoleId);
|
||||
|
||||
if (role is null)
|
||||
return default;
|
||||
|
||||
var user = guild.GetUser(r.UserId) as IGuildUser
|
||||
?? await _client.Rest.GetGuildUserAsync(guild.Id, r.UserId);
|
||||
|
||||
if (user is null)
|
||||
return default;
|
||||
|
||||
return (user, role);
|
||||
}
|
||||
|
||||
private Task ClientOnReactionRemoved(
|
||||
Cacheable<IUserMessage, ulong> msg,
|
||||
Cacheable<IMessageChannel, ulong> ch,
|
||||
SocketReaction r)
|
||||
{
|
||||
if (!_cache.TryGetValue(msg.Id, out var reros))
|
||||
return Task.CompletedTask;
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
var rero = reros.FirstOrDefault(x => x.Emote == r.Emote.Name || x.Emote == r.Emote.ToString());
|
||||
if (rero is null)
|
||||
return;
|
||||
|
||||
var (user, role) = await GetUserAndRoleAsync(r, rero);
|
||||
|
||||
if (user.IsBot)
|
||||
return;
|
||||
|
||||
await _assignementLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (user.RoleIds.Contains(role.Id))
|
||||
{
|
||||
await user.RemoveRoleAsync(role.Id);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_assignementLock.Release();
|
||||
}
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task ClientOnReactionAdded(
|
||||
Cacheable<IUserMessage, ulong> msg,
|
||||
Cacheable<IMessageChannel, ulong> ch,
|
||||
SocketReaction r)
|
||||
{
|
||||
if (!_cache.TryGetValue(msg.Id, out var reros))
|
||||
return Task.CompletedTask;
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
var rero = reros.FirstOrDefault(x => x.Emote == r.Emote.Name || x.Emote == r.Emote.ToString());
|
||||
if (rero is null)
|
||||
return;
|
||||
|
||||
var (user, role) = await GetUserAndRoleAsync(r, rero);
|
||||
|
||||
if (user.IsBot)
|
||||
return;
|
||||
|
||||
await _assignementLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (!user.RoleIds.Contains(role.Id))
|
||||
{
|
||||
// first check if there is a level requirement
|
||||
// and if there is, make sure user satisfies it
|
||||
if (rero.LevelReq > 0)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
var levelData = await ctx.GetTable<UserXpStats>()
|
||||
.GetLevelDataFor(user.GuildId, user.Id);
|
||||
|
||||
if (levelData.Level < rero.LevelReq)
|
||||
return;
|
||||
}
|
||||
|
||||
// remove all other roles from the same group from the user
|
||||
// execept in group 0, which is a special, non-exclusive group
|
||||
if (rero.Group != 0)
|
||||
{
|
||||
var exclusive = reros
|
||||
.Where(x => x.Group == rero.Group && x.RoleId != role.Id)
|
||||
.Select(x => x.RoleId)
|
||||
.Distinct();
|
||||
|
||||
|
||||
try { await user.RemoveRolesAsync(exclusive); }
|
||||
catch { }
|
||||
|
||||
// remove user's previous reaction
|
||||
try
|
||||
{
|
||||
var m = await msg.GetOrDownloadAsync();
|
||||
if (m is not null)
|
||||
{
|
||||
var reactToRemove = m.Reactions
|
||||
.FirstOrDefault(x => x.Key.ToString() != r.Emote.ToString())
|
||||
.Key;
|
||||
|
||||
if (reactToRemove is not null)
|
||||
{
|
||||
await m.RemoveReactionAsync(reactToRemove, user);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
await user.AddRoleAsync(role.Id);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_assignementLock.Release();
|
||||
}
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a single reaction role
|
||||
/// </summary>
|
||||
/// <param name="guildId"></param>
|
||||
/// <param name="msg"></param>
|
||||
/// <param name="channel"></param>
|
||||
/// <param name="emote"></param>
|
||||
/// <param name="role"></param>
|
||||
/// <param name="group"></param>
|
||||
/// <param name="levelReq"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> AddReactionRole(
|
||||
ulong guildId,
|
||||
IMessage msg,
|
||||
ITextChannel channel,
|
||||
string emote,
|
||||
IRole role,
|
||||
int group = 0,
|
||||
int levelReq = 0)
|
||||
{
|
||||
if (group < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(group));
|
||||
|
||||
if (levelReq < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(group));
|
||||
|
||||
await using var ctx = _db.GetDbContext();
|
||||
var activeReactionRoles = await ctx.GetTable<ReactionRoleV2>()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.CountAsync();
|
||||
|
||||
if (activeReactionRoles >= 50)
|
||||
return false;
|
||||
|
||||
var changed = await ctx.GetTable<ReactionRoleV2>()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
GuildId = guildId,
|
||||
ChannelId = channel.Id,
|
||||
|
||||
MessageId = msg.Id,
|
||||
Emote = emote,
|
||||
|
||||
RoleId = role.Id,
|
||||
Group = group,
|
||||
LevelReq = levelReq
|
||||
},
|
||||
(old) => new()
|
||||
{
|
||||
RoleId = role.Id,
|
||||
Group = group,
|
||||
LevelReq = levelReq
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
MessageId = msg.Id,
|
||||
Emote = emote,
|
||||
});
|
||||
|
||||
if (changed == 0)
|
||||
return false;
|
||||
|
||||
var obj = new ReactionRoleV2()
|
||||
{
|
||||
GuildId = guildId,
|
||||
MessageId = msg.Id,
|
||||
Emote = emote,
|
||||
RoleId = role.Id,
|
||||
Group = group,
|
||||
LevelReq = levelReq
|
||||
};
|
||||
|
||||
lock (_cacheLock)
|
||||
{
|
||||
_cache.AddOrUpdate(msg.Id,
|
||||
_ => new()
|
||||
{
|
||||
obj
|
||||
},
|
||||
(_, list) =>
|
||||
{
|
||||
list.RemoveAll(x => x.Emote == emote);
|
||||
list.Add(obj);
|
||||
return list;
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all reaction roles on the specified server
|
||||
/// </summary>
|
||||
/// <param name="guildId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IReadOnlyCollection<ReactionRoleV2>> GetReactionRolesAsync(ulong guildId)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
return await ctx.GetTable<ReactionRoleV2>()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove reaction roles on the specified message
|
||||
/// </summary>
|
||||
/// <param name="guildId"></param>
|
||||
/// <param name="messageId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> RemoveReactionRoles(ulong guildId, ulong messageId)
|
||||
{
|
||||
// guildid is used for quick index lookup
|
||||
await using var ctx = _db.GetDbContext();
|
||||
var changed = await ctx.GetTable<ReactionRoleV2>()
|
||||
.Where(x => x.GuildId == guildId && x.MessageId == messageId)
|
||||
.DeleteAsync();
|
||||
|
||||
_cache.TryRemove(messageId, out _);
|
||||
|
||||
if (changed == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all reaction roles in the specified server
|
||||
/// </summary>
|
||||
/// <param name="guildId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<int> RemoveAllReactionRoles(ulong guildId)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
var output = await ctx.GetTable<ReactionRoleV2>()
|
||||
.Where(x => x.GuildId == guildId)
|
||||
.DeleteWithOutputAsync(x => x.MessageId);
|
||||
|
||||
lock (_cacheLock)
|
||||
{
|
||||
foreach (var o in output)
|
||||
{
|
||||
_cache.TryRemove(o, out _);
|
||||
}
|
||||
}
|
||||
|
||||
return output.Length;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<IEmote>> TransferReactionRolesAsync(ulong guildId, ulong fromMessageId, ulong toMessageId)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
var updated = ctx.GetTable<ReactionRoleV2>()
|
||||
.Where(x => x.GuildId == guildId && x.MessageId == fromMessageId)
|
||||
.UpdateWithOutput(old => new()
|
||||
{
|
||||
MessageId = toMessageId
|
||||
},
|
||||
(old, neu) => neu);
|
||||
lock (_cacheLock)
|
||||
{
|
||||
if (_cache.TryRemove(fromMessageId, out var data))
|
||||
{
|
||||
if (_cache.TryGetValue(toMessageId, out var newData))
|
||||
{
|
||||
newData.AddRange(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
_cache[toMessageId] = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updated.Select(x => x.Emote.ToIEmote()).ToList();
|
||||
}
|
||||
}
|
@@ -7,172 +7,18 @@ using Color = SixLabors.ImageSharp.Color;
|
||||
|
||||
namespace NadekoBot.Modules.Administration;
|
||||
|
||||
|
||||
public partial class Administration
|
||||
{
|
||||
public partial class RoleCommands : NadekoModule<RoleCommandsService>
|
||||
public partial class RoleCommands : NadekoModule
|
||||
{
|
||||
public enum Exclude { Excl }
|
||||
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
public RoleCommands(IServiceProvider services)
|
||||
=> _services = services;
|
||||
|
||||
public async Task InternalReactionRoles(bool exclusive, ulong? messageId, params string[] input)
|
||||
{
|
||||
var target = messageId is { } msgId
|
||||
? await ctx.Channel.GetMessageAsync(msgId)
|
||||
: (await ctx.Channel.GetMessagesAsync(2).FlattenAsync()).Skip(1).FirstOrDefault();
|
||||
|
||||
if (input.Length % 2 != 0 || target is null)
|
||||
return;
|
||||
|
||||
var all = await input.Chunk(2)
|
||||
.Select(async x =>
|
||||
{
|
||||
var inputRoleStr = x.First();
|
||||
var roleReader = new RoleTypeReader<SocketRole>();
|
||||
var roleResult = await roleReader.ReadAsync(ctx, inputRoleStr, _services);
|
||||
if (!roleResult.IsSuccess)
|
||||
{
|
||||
Log.Warning("Role {Role} not found", inputRoleStr);
|
||||
return null;
|
||||
}
|
||||
|
||||
var role = (IRole)roleResult.BestMatch;
|
||||
if (role.Position
|
||||
> ((IGuildUser)ctx.User).GetRoles()
|
||||
.Select(r => r.Position)
|
||||
.Max()
|
||||
&& ctx.User.Id != ctx.Guild.OwnerId)
|
||||
return null;
|
||||
var emote = x.Last().ToIEmote();
|
||||
return new
|
||||
{
|
||||
role,
|
||||
emote
|
||||
};
|
||||
})
|
||||
.Where(x => x is not null)
|
||||
.WhenAll();
|
||||
|
||||
if (!all.Any())
|
||||
return;
|
||||
|
||||
foreach (var x in all)
|
||||
{
|
||||
try
|
||||
{
|
||||
await target.AddReactionAsync(x.emote,
|
||||
new()
|
||||
{
|
||||
RetryMode = RetryMode.Retry502 | RetryMode.RetryRatelimit
|
||||
});
|
||||
}
|
||||
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.BadRequest)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.reaction_cant_access(Format.Code(x.emote.ToString())));
|
||||
return;
|
||||
}
|
||||
|
||||
await Task.Delay(500);
|
||||
}
|
||||
|
||||
if (_service.Add(ctx.Guild.Id,
|
||||
new()
|
||||
{
|
||||
Exclusive = exclusive,
|
||||
MessageId = target.Id,
|
||||
ChannelId = target.Channel.Id,
|
||||
ReactionRoles = all.Select(x =>
|
||||
{
|
||||
return new ReactionRole
|
||||
{
|
||||
EmoteName = x.emote.ToString(),
|
||||
RoleId = x.role.Id
|
||||
};
|
||||
})
|
||||
.ToList()
|
||||
}))
|
||||
await ctx.OkAsync();
|
||||
else
|
||||
await ReplyErrorLocalizedAsync(strs.reaction_roles_full);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NoPublicBot]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[Priority(0)]
|
||||
public partial Task ReactionRoles(ulong messageId, params string[] input)
|
||||
=> InternalReactionRoles(false, messageId, input);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NoPublicBot]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[Priority(1)]
|
||||
public partial Task ReactionRoles(ulong messageId, Exclude _, params string[] input)
|
||||
=> InternalReactionRoles(true, messageId, input);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NoPublicBot]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[Priority(0)]
|
||||
public partial Task ReactionRoles(params string[] input)
|
||||
=> InternalReactionRoles(false, null, input);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NoPublicBot]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
[Priority(1)]
|
||||
public partial Task ReactionRoles(Exclude _, params string[] input)
|
||||
=> InternalReactionRoles(true, null, input);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NoPublicBot]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
public async partial Task ReactionRolesList()
|
||||
{
|
||||
var embed = _eb.Create().WithOkColor();
|
||||
if (!_service.Get(ctx.Guild.Id, out var rrs) || !rrs.Any())
|
||||
embed.WithDescription(GetText(strs.no_reaction_roles));
|
||||
else
|
||||
{
|
||||
var g = (SocketGuild)ctx.Guild;
|
||||
foreach (var rr in rrs)
|
||||
{
|
||||
var ch = g.GetTextChannel(rr.ChannelId);
|
||||
IUserMessage msg = null;
|
||||
if (ch is not null)
|
||||
msg = await ch.GetMessageAsync(rr.MessageId) as IUserMessage;
|
||||
var content = msg?.Content.TrimTo(30) ?? "DELETED!";
|
||||
embed.AddField($"**{rr.Index + 1}.** {ch?.Name ?? "DELETED!"}",
|
||||
GetText(strs.reaction_roles_message(rr.ReactionRoles?.Count ?? 0, content)));
|
||||
}
|
||||
}
|
||||
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[NoPublicBot]
|
||||
[UserPerm(GuildPerm.ManageRoles)]
|
||||
public async partial Task ReactionRolesRemove(int index)
|
||||
{
|
||||
if (index < 1 || !_service.Get(ctx.Guild.Id, out var rrs) || !rrs.Any() || rrs.Count < index)
|
||||
return;
|
||||
index--;
|
||||
_service.Remove(ctx.Guild.Id, index);
|
||||
await ReplyConfirmLocalizedAsync(strs.reaction_role_removed(index + 1));
|
||||
_services = services;
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
|
@@ -1,253 +0,0 @@
|
||||
#nullable disable
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.Collections;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Administration.Services;
|
||||
|
||||
public class RoleCommandsService : INService
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly ConcurrentDictionary<ulong, IndexedCollection<ReactionRoleMessage>> _models;
|
||||
|
||||
/// <summary>
|
||||
/// Contains the (Message ID, User ID) of reaction roles that are currently being processed.
|
||||
/// </summary>
|
||||
private readonly ConcurrentHashSet<(ulong, ulong)> _reacting = new();
|
||||
|
||||
public RoleCommandsService(DiscordSocketClient client, DbService db, Bot bot)
|
||||
{
|
||||
_db = db;
|
||||
_client = client;
|
||||
#if !GLOBAL_NADEKO
|
||||
_models = bot.AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.ReactionRoleMessages).ToConcurrent();
|
||||
|
||||
_client.ReactionAdded += _client_ReactionAdded;
|
||||
_client.ReactionRemoved += _client_ReactionRemoved;
|
||||
#endif
|
||||
}
|
||||
|
||||
private Task _client_ReactionAdded(
|
||||
Cacheable<IUserMessage, ulong> msg,
|
||||
Cacheable<IMessageChannel, ulong> chan,
|
||||
SocketReaction reaction)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
if (!reaction.User.IsSpecified
|
||||
|| reaction.User.Value.IsBot
|
||||
|| reaction.User.Value is not SocketGuildUser gusr
|
||||
|| chan.Value is not SocketGuildChannel gch
|
||||
|| !_models.TryGetValue(gch.Guild.Id, out var confs))
|
||||
return;
|
||||
|
||||
var conf = confs.FirstOrDefault(x => x.MessageId == msg.Id);
|
||||
|
||||
if (conf is null)
|
||||
return;
|
||||
|
||||
// compare emote names for backwards compatibility :facepalm:
|
||||
var reactionRole = conf.ReactionRoles.FirstOrDefault(x
|
||||
=> x.EmoteName == reaction.Emote.Name || x.EmoteName == reaction.Emote.ToString());
|
||||
|
||||
if (reactionRole is not null)
|
||||
{
|
||||
if (!conf.Exclusive)
|
||||
{
|
||||
await AddReactionRoleAsync(gusr, reactionRole);
|
||||
return;
|
||||
}
|
||||
|
||||
// If same (message, user) are being processed in an exclusive rero, quit
|
||||
if (!_reacting.Add((msg.Id, reaction.UserId)))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var removeExclusiveTask = RemoveExclusiveReactionRoleAsync(msg,
|
||||
gusr,
|
||||
reaction,
|
||||
conf,
|
||||
reactionRole,
|
||||
CancellationToken.None);
|
||||
var addRoleTask = AddReactionRoleAsync(gusr, reactionRole);
|
||||
|
||||
await Task.WhenAll(removeExclusiveTask, addRoleTask);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Free (message/user) for another exclusive rero
|
||||
_reacting.TryRemove((msg.Id, reaction.UserId));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var dl = await msg.GetOrDownloadAsync();
|
||||
await dl.RemoveReactionAsync(reaction.Emote,
|
||||
dl.Author,
|
||||
new()
|
||||
{
|
||||
RetryMode = RetryMode.RetryRatelimit | RetryMode.Retry502
|
||||
});
|
||||
Log.Warning("User {Author} is adding unrelated reactions to the reaction roles message", dl.Author);
|
||||
}
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task _client_ReactionRemoved(
|
||||
Cacheable<IUserMessage, ulong> msg,
|
||||
Cacheable<IMessageChannel, ulong> chan,
|
||||
SocketReaction reaction)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!reaction.User.IsSpecified
|
||||
|| reaction.User.Value.IsBot
|
||||
|| reaction.User.Value is not SocketGuildUser gusr)
|
||||
return;
|
||||
|
||||
if (chan.Value is not SocketGuildChannel gch)
|
||||
return;
|
||||
|
||||
if (!_models.TryGetValue(gch.Guild.Id, out var confs))
|
||||
return;
|
||||
|
||||
var conf = confs.FirstOrDefault(x => x.MessageId == msg.Id);
|
||||
|
||||
if (conf is null)
|
||||
return;
|
||||
|
||||
var reactionRole = conf.ReactionRoles.FirstOrDefault(x
|
||||
=> x.EmoteName == reaction.Emote.Name || x.EmoteName == reaction.Emote.ToString());
|
||||
|
||||
if (reactionRole is not null)
|
||||
{
|
||||
var role = gusr.Guild.GetRole(reactionRole.RoleId);
|
||||
if (role is null)
|
||||
return;
|
||||
await gusr.RemoveRoleAsync(role);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public bool Get(ulong id, out IndexedCollection<ReactionRoleMessage> rrs)
|
||||
=> _models.TryGetValue(id, out rrs);
|
||||
|
||||
public bool Add(ulong id, ReactionRoleMessage rrm)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var table = uow.GetTable<ReactionRoleMessage>();
|
||||
table.Delete(x => x.MessageId == rrm.MessageId);
|
||||
|
||||
var gc = uow.GuildConfigsForId(id,
|
||||
set => set.Include(x => x.ReactionRoleMessages).ThenInclude(x => x.ReactionRoles));
|
||||
|
||||
if (gc.ReactionRoleMessages.Count >= 10)
|
||||
return false;
|
||||
|
||||
gc.ReactionRoleMessages.Add(rrm);
|
||||
uow.SaveChanges();
|
||||
|
||||
_models.AddOrUpdate(id, gc.ReactionRoleMessages, delegate { return gc.ReactionRoleMessages; });
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Remove(ulong id, int index)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigsForId(id,
|
||||
set => set.Include(x => x.ReactionRoleMessages).ThenInclude(x => x.ReactionRoles));
|
||||
uow.Set<ReactionRole>().RemoveRange(gc.ReactionRoleMessages[index].ReactionRoles);
|
||||
gc.ReactionRoleMessages.RemoveAt(index);
|
||||
_models.AddOrUpdate(id, gc.ReactionRoleMessages, delegate { return gc.ReactionRoleMessages; });
|
||||
uow.SaveChanges();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a reaction role to the specified user.
|
||||
/// </summary>
|
||||
/// <param name="user">A Discord guild user.</param>
|
||||
/// <param name="dbRero">The database settings of this reaction role.</param>
|
||||
private Task AddReactionRoleAsync(SocketGuildUser user, ReactionRole dbRero)
|
||||
{
|
||||
var toAdd = user.Guild.GetRole(dbRero.RoleId);
|
||||
|
||||
return toAdd is not null && !user.Roles.Contains(toAdd) ? user.AddRoleAsync(toAdd) : Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the exclusive reaction roles and reactions from the specified user.
|
||||
/// </summary>
|
||||
/// <param name="reactionMessage">The Discord message that contains the reaction roles.</param>
|
||||
/// <param name="user">A Discord guild user.</param>
|
||||
/// <param name="reaction">The Discord reaction of the user.</param>
|
||||
/// <param name="dbReroMsg">The database entry of the reaction role message.</param>
|
||||
/// <param name="dbRero">The database settings of this reaction role.</param>
|
||||
/// <param name="cToken">A cancellation token to cancel the operation.</param>
|
||||
/// <exception cref="OperationCanceledException">Occurs when the operation is cancelled before it began.</exception>
|
||||
/// <exception cref="TaskCanceledException">Occurs when the operation is cancelled while it's still executing.</exception>
|
||||
private Task RemoveExclusiveReactionRoleAsync(
|
||||
Cacheable<IUserMessage, ulong> reactionMessage,
|
||||
SocketGuildUser user,
|
||||
SocketReaction reaction,
|
||||
ReactionRoleMessage dbReroMsg,
|
||||
ReactionRole dbRero,
|
||||
CancellationToken cToken = default)
|
||||
{
|
||||
cToken.ThrowIfCancellationRequested();
|
||||
|
||||
var roleIds = dbReroMsg.ReactionRoles.Select(x => x.RoleId)
|
||||
.Where(x => x != dbRero.RoleId)
|
||||
.Select(x => user.Guild.GetRole(x))
|
||||
.Where(x => x is not null);
|
||||
|
||||
var removeReactionsTask = RemoveOldReactionsAsync(reactionMessage, user, reaction, cToken);
|
||||
|
||||
var removeRolesTask = user.RemoveRolesAsync(roleIds);
|
||||
|
||||
return Task.WhenAll(removeReactionsTask, removeRolesTask);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes old reactions from an exclusive reaction role.
|
||||
/// </summary>
|
||||
/// <param name="reactionMessage">The Discord message that contains the reaction roles.</param>
|
||||
/// <param name="user">A Discord guild user.</param>
|
||||
/// <param name="reaction">The Discord reaction of the user.</param>
|
||||
/// <param name="cToken">A cancellation token to cancel the operation.</param>
|
||||
/// <exception cref="OperationCanceledException">Occurs when the operation is cancelled before it began.</exception>
|
||||
/// <exception cref="TaskCanceledException">Occurs when the operation is cancelled while it's still executing.</exception>
|
||||
private async Task RemoveOldReactionsAsync(
|
||||
Cacheable<IUserMessage, ulong> reactionMessage,
|
||||
SocketGuildUser user,
|
||||
SocketReaction reaction,
|
||||
CancellationToken cToken = default)
|
||||
{
|
||||
cToken.ThrowIfCancellationRequested();
|
||||
|
||||
//if the role is exclusive,
|
||||
// remove all other reactions user added to the message
|
||||
var dl = await reactionMessage.GetOrDownloadAsync();
|
||||
foreach (var r in dl.Reactions)
|
||||
{
|
||||
if (r.Key.Name == reaction.Emote.Name)
|
||||
continue;
|
||||
try { await dl.RemoveReactionAsync(r.Key, user); }
|
||||
catch { }
|
||||
|
||||
await Task.Delay(100, cToken);
|
||||
}
|
||||
}
|
||||
}
|
@@ -530,7 +530,7 @@ public class UserPunishService : INService, IReadyExecutor
|
||||
return default;
|
||||
// if template is an embed, send that embed with replacements
|
||||
// otherwise, treat template as a regular string with replacements
|
||||
else if (!SmartText.CreateFrom(template).IsEmbed)
|
||||
else if (SmartText.CreateFrom(template) is not { IsEmbed: true } or { IsEmbedArray: true })
|
||||
{
|
||||
template = JsonConvert.SerializeObject(new
|
||||
{
|
||||
|
72
src/NadekoBot/Modules/Gambling/Bank/BankCommands.cs
Normal file
72
src/NadekoBot/Modules/Gambling/Bank/BankCommands.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using NadekoBot.Modules.Gambling.Bank;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
public partial class Gambling
|
||||
{
|
||||
[Name("Bank")]
|
||||
[Group("bank")]
|
||||
public partial class BankCommands : GamblingModule<IBankService>
|
||||
{
|
||||
private readonly IBankService _bank;
|
||||
|
||||
public BankCommands(GamblingConfigService gcs, IBankService bank) : base(gcs)
|
||||
{
|
||||
_bank = bank;
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async partial Task BankDeposit(ShmartNumber amount)
|
||||
{
|
||||
if (amount <= 0)
|
||||
return;
|
||||
|
||||
if (await _bank.DepositAsync(ctx.User.Id, amount))
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.bank_deposited(N(amount)));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async partial Task BankWithdraw(ShmartNumber amount)
|
||||
{
|
||||
if (amount <= 0)
|
||||
return;
|
||||
|
||||
if (await _bank.WithdrawAsync(ctx.User.Id, amount))
|
||||
{
|
||||
await ReplyConfirmLocalizedAsync(strs.bank_withdrew(N(amount)));
|
||||
}
|
||||
else
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.bank_withdraw_insuff(CurrencySign));
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async partial Task BankBalance()
|
||||
{
|
||||
var bal = await _bank.GetBalanceAsync(ctx.User.Id);
|
||||
|
||||
var eb = _eb.Create(ctx)
|
||||
.WithOkColor()
|
||||
.WithDescription(GetText(strs.bank_balance(N(bal))));
|
||||
|
||||
try
|
||||
{
|
||||
await ctx.User.EmbedAsync(eb);
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.unable_to_dm_user);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
77
src/NadekoBot/Modules/Gambling/Bank/BankService.cs
Normal file
77
src/NadekoBot/Modules/Gambling/Bank/BankService.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
|
||||
namespace NadekoBot.Modules.Gambling.Bank;
|
||||
|
||||
public sealed class BankService : IBankService, INService
|
||||
{
|
||||
private readonly ICurrencyService _cur;
|
||||
private readonly DbService _db;
|
||||
|
||||
public BankService(ICurrencyService cur, DbService db)
|
||||
{
|
||||
_cur = cur;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public async Task<bool> DepositAsync(ulong userId, long amount)
|
||||
{
|
||||
if (amount <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
||||
|
||||
if (!await _cur.RemoveAsync(userId, amount, new("bank", "deposit")))
|
||||
return false;
|
||||
|
||||
await using var ctx = _db.GetDbContext();
|
||||
await ctx.BankUsers
|
||||
.ToLinqToDBTable()
|
||||
.InsertOrUpdateAsync(() => new()
|
||||
{
|
||||
UserId = userId,
|
||||
Balance = amount
|
||||
},
|
||||
(old) => new()
|
||||
{
|
||||
Balance = old.Balance + amount
|
||||
},
|
||||
() => new()
|
||||
{
|
||||
UserId = userId
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> WithdrawAsync(ulong userId, long amount)
|
||||
{
|
||||
if (amount <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(amount));
|
||||
|
||||
await using var ctx = _db.GetDbContext();
|
||||
var rows = await ctx.BankUsers
|
||||
.ToLinqToDBTable()
|
||||
.Where(x => x.UserId == userId && x.Balance >= amount)
|
||||
.UpdateAsync((old) => new()
|
||||
{
|
||||
Balance = old.Balance - amount
|
||||
});
|
||||
|
||||
if (rows > 0)
|
||||
{
|
||||
await _cur.AddAsync(userId, amount, new("bank", "withdraw"));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<long> GetBalanceAsync(ulong userId)
|
||||
{
|
||||
await using var ctx = _db.GetDbContext();
|
||||
return (await ctx.BankUsers
|
||||
.ToLinqToDBTable()
|
||||
.FirstOrDefaultAsync(x => x.UserId == userId))
|
||||
?.Balance
|
||||
?? 0;
|
||||
}
|
||||
}
|
8
src/NadekoBot/Modules/Gambling/Bank/IBankService.cs
Normal file
8
src/NadekoBot/Modules/Gambling/Bank/IBankService.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace NadekoBot.Modules.Gambling.Bank;
|
||||
|
||||
public interface IBankService
|
||||
{
|
||||
Task<bool> DepositAsync(ulong userId, long amount);
|
||||
Task<bool> WithdrawAsync(ulong userId, long amount);
|
||||
Task<long> GetBalanceAsync(ulong userId);
|
||||
}
|
17
src/NadekoBot/Modules/Gambling/CashInteraction.cs
Normal file
17
src/NadekoBot/Modules/Gambling/CashInteraction.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
#nullable disable
|
||||
namespace NadekoBot.Modules.Gambling;
|
||||
|
||||
public class CashInteraction
|
||||
{
|
||||
public static NadekoInteractionData Data =
|
||||
new NadekoInteractionData(new Emoji("🏦"), "cash:bank_show_balance");
|
||||
|
||||
public static NadekoInteraction CreateInstance(
|
||||
DiscordSocketClient client,
|
||||
ulong userId,
|
||||
Func<SocketMessageComponent, Task> action)
|
||||
=> new NadekoInteractionBuilder()
|
||||
.WithData(Data)
|
||||
.WithAction(action)
|
||||
.Build(client, userId);
|
||||
}
|
@@ -3,6 +3,7 @@ using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Db.Models;
|
||||
using NadekoBot.Modules.Gambling.Bank;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Modules.Gambling.Services;
|
||||
using NadekoBot.Services.Currency;
|
||||
@@ -40,6 +41,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
private readonly NumberFormatInfo _enUsCulture;
|
||||
private readonly DownloadTracker _tracker;
|
||||
private readonly GamblingConfigService _configService;
|
||||
private readonly IBankService _bank;
|
||||
|
||||
private IUserMessage rdMsg;
|
||||
|
||||
@@ -49,13 +51,16 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
IDataCache cache,
|
||||
DiscordSocketClient client,
|
||||
DownloadTracker tracker,
|
||||
GamblingConfigService configService)
|
||||
GamblingConfigService configService,
|
||||
IBankService bank)
|
||||
: base(configService)
|
||||
{
|
||||
_db = db;
|
||||
_cs = currency;
|
||||
_cache = cache;
|
||||
_client = client;
|
||||
_bank = bank;
|
||||
|
||||
_enUsCulture = new CultureInfo("en-US", false).NumberFormat;
|
||||
_enUsCulture.NumberDecimalDigits = 0;
|
||||
_enUsCulture.NumberGroupSeparator = " ";
|
||||
@@ -72,7 +77,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
[Cmd]
|
||||
public async partial Task Economy()
|
||||
{
|
||||
var ec = _service.GetEconomy();
|
||||
var ec = await _service.GetEconomyAsync();
|
||||
decimal onePercent = 0;
|
||||
|
||||
// This stops the top 1% from owning more than 100% of the money
|
||||
@@ -84,13 +89,13 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
// [21:03] Bob Page: Kinda remids me of US economy
|
||||
var embed = _eb.Create()
|
||||
.WithTitle(GetText(strs.economy_state))
|
||||
.AddField(GetText(strs.currency_owned),
|
||||
N(ec.Cash - ec.Bot))
|
||||
.AddField(GetText(strs.currency_owned), N(ec.Cash - ec.Bot))
|
||||
.AddField(GetText(strs.currency_one_percent), (onePercent * 100).ToString("F2") + "%")
|
||||
.AddField(GetText(strs.currency_planted), N(ec.Planted))
|
||||
.AddField(GetText(strs.owned_waifus_total), N(ec.Waifus))
|
||||
.AddField(GetText(strs.bot_currency), N(ec.Bot))
|
||||
.AddField(GetText(strs.total), N(ec.Cash + ec.Planted + ec.Waifus))
|
||||
.AddField(GetText(strs.bank_accounts), N(ec.Bank))
|
||||
.AddField(GetText(strs.total), N(ec.Cash + ec.Planted + ec.Waifus + ec.Bank))
|
||||
.WithOkColor();
|
||||
|
||||
// ec.Cash already contains ec.Bot as it's the total of all values in the CurrencyAmount column of the DiscordUser table
|
||||
@@ -232,7 +237,6 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
var kwumId = new kwum(tr.Id).ToString();
|
||||
var date = $"#{Format.Code(kwumId)} `〖{GetFormattedCurtrDate(tr)}〗`";
|
||||
|
||||
|
||||
sb.AppendLine($"\\{change} {date} {Format.Bold(N(tr.Amount))}");
|
||||
var transactionString = GetHumanReadableTransaction(tr.Type, tr.Extra, tr.OtherId);
|
||||
if (transactionString is not null)
|
||||
@@ -260,8 +264,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
int intId = id;
|
||||
await using var uow = _db.GetDbContext();
|
||||
|
||||
var tr = await uow.CurrencyTransactions
|
||||
.ToLinqToDBTable()
|
||||
var tr = await uow.CurrencyTransactions.ToLinqToDBTable()
|
||||
.Where(x => x.Id == intId && x.UserId == ctx.User.Id)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
@@ -271,8 +274,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
return;
|
||||
}
|
||||
|
||||
var eb = _eb.Create(ctx)
|
||||
.WithOkColor();
|
||||
var eb = _eb.Create(ctx).WithOkColor();
|
||||
|
||||
eb.WithAuthor(ctx.User);
|
||||
eb.WithTitle(GetText(strs.transaction));
|
||||
@@ -291,7 +293,6 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
eb.AddField("Note", tr.Note);
|
||||
}
|
||||
|
||||
|
||||
eb.WithFooter(GetFormattedCurtrDate(tr));
|
||||
|
||||
await ctx.Channel.EmbedAsync(eb);
|
||||
@@ -311,7 +312,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
(_, _, ulong userId) => $"{type.Titleize()} - {subType.Titleize()} | [{userId}]",
|
||||
_ => $"{type.Titleize()} - {subType.Titleize()}"
|
||||
};
|
||||
|
||||
|
||||
[Cmd]
|
||||
[Priority(0)]
|
||||
public async partial Task Cash(ulong userId)
|
||||
@@ -320,13 +321,36 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
await ReplyConfirmLocalizedAsync(strs.has(Format.Code(userId.ToString()), cur));
|
||||
}
|
||||
|
||||
private async Task BankAction(SocketMessageComponent smc)
|
||||
{
|
||||
var balance = await _bank.GetBalanceAsync(ctx.User.Id);
|
||||
|
||||
await N(balance)
|
||||
.Pipe(strs.bank_balance)
|
||||
.Pipe(GetText)
|
||||
.Pipe(text => smc.RespondConfirmAsync(_eb, text, ephemeral: true));
|
||||
}
|
||||
|
||||
private NadekoInteraction CreateCashInteraction()
|
||||
=> CashInteraction.CreateInstance(_client, ctx.User.Id, BankAction);
|
||||
|
||||
[Cmd]
|
||||
[Priority(1)]
|
||||
public async partial Task Cash([Leftover] IUser user = null)
|
||||
{
|
||||
user ??= ctx.User;
|
||||
var cur = await GetBalanceStringAsync(user.Id);
|
||||
await ConfirmLocalizedAsync(strs.has(Format.Bold(user.ToString()), cur));
|
||||
|
||||
var inter = user == ctx.User
|
||||
? CreateCashInteraction()
|
||||
: null;
|
||||
|
||||
await ConfirmLocalizedAsync(
|
||||
user.ToString()
|
||||
.Pipe(Format.Bold)
|
||||
.With(cur)
|
||||
.Pipe(strs.has),
|
||||
inter);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
@@ -339,7 +363,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await _cs.TransferAsync(ctx.User.Id, receiver.Id, amount, ctx.User.ToString(), msg))
|
||||
if (!await _cs.TransferAsync(_eb, ctx.User, receiver, amount, msg))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
return;
|
||||
@@ -386,10 +410,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
return;
|
||||
}
|
||||
|
||||
await _cs.AddAsync(usr.Id,
|
||||
amount,
|
||||
new("award", ctx.User.ToString()!, msg, ctx.User.Id)
|
||||
);
|
||||
await _cs.AddAsync(usr.Id, amount, new("award", ctx.User.ToString()!, msg, ctx.User.Id));
|
||||
await ReplyConfirmLocalizedAsync(strs.awarded(N(amount), $"<@{usrId}>"));
|
||||
}
|
||||
|
||||
@@ -403,10 +424,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
|
||||
await _cs.AddBulkAsync(users.Select(x => x.Id).ToList(),
|
||||
amount,
|
||||
new("award",
|
||||
ctx.User.ToString()!,
|
||||
role.Name,
|
||||
ctx.User.Id));
|
||||
new("award", ctx.User.ToString()!, role.Name, ctx.User.Id));
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.mass_award(N(amount),
|
||||
Format.Bold(users.Count.ToString()),
|
||||
@@ -423,10 +441,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
|
||||
await _cs.RemoveBulkAsync(users.Select(x => x.Id).ToList(),
|
||||
amount,
|
||||
new("take",
|
||||
ctx.User.ToString()!,
|
||||
null,
|
||||
ctx.User.Id));
|
||||
new("take", ctx.User.ToString()!, null, ctx.User.Id));
|
||||
|
||||
await ReplyConfirmLocalizedAsync(strs.mass_take(N(amount),
|
||||
Format.Bold(users.Count.ToString()),
|
||||
@@ -444,10 +459,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
return;
|
||||
}
|
||||
|
||||
var extra = new TxData("take",
|
||||
ctx.User.ToString()!,
|
||||
null,
|
||||
ctx.User.Id);
|
||||
var extra = new TxData("take", ctx.User.ToString()!, null, ctx.User.Id);
|
||||
|
||||
if (await _cs.RemoveAsync(user.Id, amount, extra))
|
||||
{
|
||||
@@ -459,7 +471,6 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public async partial Task Take(long amount, [Leftover] ulong usrId)
|
||||
@@ -469,10 +480,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
return;
|
||||
}
|
||||
|
||||
var extra = new TxData("take",
|
||||
ctx.User.ToString()!,
|
||||
null,
|
||||
ctx.User.Id);
|
||||
var extra = new TxData("take", ctx.User.ToString()!, null, ctx.User.Id);
|
||||
|
||||
if (await _cs.RemoveAsync(usrId, amount, extra))
|
||||
{
|
||||
@@ -560,10 +568,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
}
|
||||
else
|
||||
{
|
||||
await rdMsg.ModifyAsync(x =>
|
||||
{
|
||||
x.Embed = embed.Build();
|
||||
});
|
||||
await rdMsg.ModifyAsync(x => { x.Embed = embed.Build(); });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -613,7 +618,6 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
|
||||
var result = br.Roll();
|
||||
|
||||
|
||||
var str = Format.Bold(ctx.User.ToString()) + Format.Code(GetText(strs.roll(result.Roll)));
|
||||
if (result.Multiplier > 0)
|
||||
{
|
||||
@@ -742,9 +746,7 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
|
||||
if (amount > 0)
|
||||
{
|
||||
if (!await _cs.RemoveAsync(ctx.User.Id,
|
||||
amount,
|
||||
new("rps", "bet", "")))
|
||||
if (!await _cs.RemoveAsync(ctx.User.Id, amount, new("rps", "bet", "")))
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.not_enough(CurrencySign));
|
||||
return;
|
||||
|
@@ -1,8 +1,11 @@
|
||||
#nullable disable
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Db.Models;
|
||||
using NadekoBot.Migrations;
|
||||
using NadekoBot.Modules.Gambling.Common;
|
||||
using NadekoBot.Modules.Gambling.Common.Connect4;
|
||||
using NadekoBot.Modules.Gambling.Common.Slot;
|
||||
@@ -158,7 +161,7 @@ public class GamblingService : INService, IReadyExecutor
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
public EconomyResult GetEconomy()
|
||||
public async Task<EconomyResult> GetEconomyAsync()
|
||||
{
|
||||
if (_cache.TryGetEconomy(out var data))
|
||||
{
|
||||
@@ -173,6 +176,7 @@ public class GamblingService : INService, IReadyExecutor
|
||||
decimal onePercent;
|
||||
decimal planted;
|
||||
decimal waifus;
|
||||
decimal bank;
|
||||
long bot;
|
||||
|
||||
using (var uow = _db.GetDbContext())
|
||||
@@ -182,6 +186,8 @@ public class GamblingService : INService, IReadyExecutor
|
||||
planted = uow.PlantedCurrency.AsQueryable().Sum(x => x.Amount);
|
||||
waifus = uow.WaifuInfo.GetTotalValue();
|
||||
bot = uow.DiscordUser.GetUserCurrency(_client.CurrentUser.Id);
|
||||
bank = await uow.GetTable<BankUser>()
|
||||
.SumAsyncLinqToDB(x => x.Balance);
|
||||
}
|
||||
|
||||
var result = new EconomyResult
|
||||
@@ -190,7 +196,8 @@ public class GamblingService : INService, IReadyExecutor
|
||||
Planted = planted,
|
||||
Bot = bot,
|
||||
Waifus = waifus,
|
||||
OnePercent = onePercent
|
||||
OnePercent = onePercent,
|
||||
Bank = bank
|
||||
};
|
||||
|
||||
_cache.SetEconomy(JsonConvert.SerializeObject(result));
|
||||
@@ -207,6 +214,7 @@ public class GamblingService : INService, IReadyExecutor
|
||||
public decimal Planted { get; set; }
|
||||
public decimal Waifus { get; set; }
|
||||
public decimal OnePercent { get; set; }
|
||||
public decimal Bank { get; set; }
|
||||
public long Bot { get; set; }
|
||||
}
|
||||
}
|
@@ -39,13 +39,19 @@ public abstract class GamblingModule<TService> : NadekoModule<TService>
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string N(long cur, IFormatProvider format)
|
||||
=> cur.ToString("C0", format);
|
||||
|
||||
public static string N(decimal cur, IFormatProvider format)
|
||||
=> cur.ToString("C0", format);
|
||||
|
||||
protected string N(long cur)
|
||||
=> cur.ToString("C0", GetFlowersCiInternal());
|
||||
=> N(cur, GetFlowersCiInternal());
|
||||
|
||||
protected string N(decimal cur)
|
||||
=> cur.ToString("C0", GetFlowersCiInternal());
|
||||
=> N(cur, GetFlowersCiInternal());
|
||||
|
||||
private IFormatProvider GetFlowersCiInternal()
|
||||
protected IFormatProvider GetFlowersCiInternal()
|
||||
{
|
||||
var flowersCi = (CultureInfo)Culture.Clone();
|
||||
flowersCi.NumberFormat.CurrencySymbol = CurrencySign;
|
||||
|
@@ -129,6 +129,8 @@ public partial class Help : NadekoModule<HelpService>
|
||||
return strs.module_description_permissions;
|
||||
case "xp":
|
||||
return strs.module_description_xp;
|
||||
case "medusa":
|
||||
return strs.module_description_medusa;
|
||||
default:
|
||||
return strs.module_description_missing;
|
||||
}
|
||||
@@ -264,6 +266,20 @@ public partial class Help : NadekoModule<HelpService>
|
||||
await ctx.Channel.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
private async Task Group(ModuleInfo group)
|
||||
{
|
||||
var eb = _eb.Create(ctx)
|
||||
.WithTitle(GetText(strs.cmd_group_commands(group.Name)))
|
||||
.WithOkColor();
|
||||
|
||||
foreach (var cmd in group.Commands)
|
||||
{
|
||||
eb.AddField(prefix + cmd.Aliases.First(), cmd.RealSummary(_strings, _medusae, Culture, prefix));
|
||||
}
|
||||
|
||||
await ctx.Channel.EmbedAsync(eb);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[Priority(0)]
|
||||
public async partial Task H([Leftover] string fail)
|
||||
@@ -276,6 +292,20 @@ public partial class Help : NadekoModule<HelpService>
|
||||
return;
|
||||
}
|
||||
|
||||
if (fail.StartsWith(prefix))
|
||||
fail = fail.Substring(prefix.Length);
|
||||
|
||||
var group = _cmds.Modules
|
||||
.SelectMany(x => x.Submodules)
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.Group))
|
||||
.FirstOrDefault(x => x.Group.Equals(fail, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
if (group is not null)
|
||||
{
|
||||
await Group(group);
|
||||
return;
|
||||
}
|
||||
|
||||
await ReplyErrorLocalizedAsync(strs.command_not_found);
|
||||
}
|
||||
|
||||
|
@@ -81,8 +81,7 @@ public class HelpService : IExecNoCommand, INService
|
||||
em.AddField(GetText(strs.requires, guild), string.Join("\n", reqs));
|
||||
|
||||
em.AddField(_strings.GetText(strs.usage),
|
||||
string.Join("\n",
|
||||
Array.ConvertAll(com.RealRemarksArr(_strings,_medusae, culture, prefix), arg => Format.Code(arg))))
|
||||
string.Join("\n", com.RealRemarksArr(_strings,_medusae, culture, prefix).Map(arg => Format.Code(arg))))
|
||||
.WithFooter(GetText(strs.module(com.Module.GetTopLevelModule().Name), guild))
|
||||
.WithOkColor();
|
||||
|
||||
|
@@ -53,7 +53,7 @@ public class FeedsService : INService
|
||||
if (kvp.Value.Count == 0)
|
||||
continue;
|
||||
|
||||
var rssUrl = kvp.Key;
|
||||
var rssUrl = kvp.Value.First().Url;
|
||||
try
|
||||
{
|
||||
var feed = await FeedReader.ReadAsync(rssUrl);
|
||||
@@ -143,8 +143,9 @@ public class FeedsService : INService
|
||||
allSendTasks.Add(feedSendTasks.WhenAll());
|
||||
}
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning("An error occured while getting rss stream: {Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -121,6 +121,18 @@ public partial class Searches
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_off_disabled);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
public async partial Task StreamOnlineDelete()
|
||||
{
|
||||
var newValue = _service.ToggleStreamOnlineDelete(ctx.Guild.Id);
|
||||
if (newValue)
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_online_delete_enabled);
|
||||
else
|
||||
await ReplyConfirmLocalizedAsync(strs.stream_online_delete_disabled);
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
|
@@ -1,4 +1,6 @@
|
||||
#nullable disable
|
||||
using LinqToDB;
|
||||
using LinqToDB.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Db;
|
||||
@@ -24,6 +26,7 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
|
||||
|
||||
private readonly Dictionary<StreamDataKey, Dictionary<ulong, HashSet<FollowedStream>>> _shardTrackedStreams;
|
||||
private readonly ConcurrentHashSet<ulong> _offlineNotificationServers;
|
||||
private readonly ConcurrentHashSet<ulong> _deleteOnOfflineServers;
|
||||
|
||||
private readonly IPubSub _pubSub;
|
||||
private readonly IEmbedBuilderService _eb;
|
||||
@@ -33,6 +36,7 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
|
||||
|
||||
private readonly TypedKey<FollowStreamPubData> _streamFollowKey;
|
||||
private readonly TypedKey<FollowStreamPubData> _streamUnfollowKey;
|
||||
private readonly ConnectionMultiplexer _redis;
|
||||
|
||||
public StreamNotificationService(
|
||||
DbService db,
|
||||
@@ -50,6 +54,7 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
|
||||
_strings = strings;
|
||||
_pubSub = pubSub;
|
||||
_eb = eb;
|
||||
_redis = redis;
|
||||
_streamTracker = new(httpFactory, creds, redis, creds.GetCreds().RedisKey(), client.ShardId == 0);
|
||||
|
||||
_streamsOnlineKey = new("streams.online");
|
||||
@@ -71,6 +76,11 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
|
||||
.Where(gc => gc.NotifyStreamOffline)
|
||||
.Select(x => x.GuildId)
|
||||
.ToList());
|
||||
|
||||
_deleteOnOfflineServers = new(guildConfigs
|
||||
.Where(gc => gc.DeleteStreamOnlineMessage)
|
||||
.Select(x => x.GuildId)
|
||||
.ToList());
|
||||
|
||||
var followedStreams = guildConfigs.SelectMany(x => x.FollowedStreams).ToList();
|
||||
|
||||
@@ -243,6 +253,44 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
|
||||
.WhenAll();
|
||||
}
|
||||
}
|
||||
|
||||
if (_client.ShardId == 0)
|
||||
{
|
||||
foreach (var stream in offlineStreams)
|
||||
{
|
||||
await DeleteOnlineMessages(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteOnlineMessages(StreamData stream)
|
||||
{
|
||||
var db = _redis.GetDatabase();
|
||||
var data = await db.ListRangeAsync($"streams_online_del:{stream.CreateKey()}");
|
||||
await db.KeyDeleteAsync($"streams_online_del:{stream.CreateKey()}");
|
||||
|
||||
foreach (string pair in data)
|
||||
{
|
||||
var pairArr = pair.Split(',');
|
||||
if (pairArr.Length != 2)
|
||||
continue;
|
||||
|
||||
if (!ulong.TryParse(pairArr[0], out var chId) || !ulong.TryParse(pairArr[1], out var msgId))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
var textChannel = await _client.GetChannelAsync(chId) as ITextChannel;
|
||||
if (textChannel is null)
|
||||
continue;
|
||||
|
||||
await textChannel.DeleteMessageAsync(msgId);
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask HandleStreamsOnline(List<StreamData> onlineStreams)
|
||||
@@ -252,13 +300,13 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
|
||||
var key = stream.CreateKey();
|
||||
if (_shardTrackedStreams.TryGetValue(key, out var fss))
|
||||
{
|
||||
await fss.SelectMany(x => x.Value)
|
||||
.Select(fs =>
|
||||
var messages = await fss.SelectMany(x => x.Value)
|
||||
.Select(async fs =>
|
||||
{
|
||||
var textChannel = _client.GetGuild(fs.GuildId)?.GetTextChannel(fs.ChannelId);
|
||||
|
||||
if (textChannel is null)
|
||||
return Task.CompletedTask;
|
||||
return default;
|
||||
|
||||
var rep = new ReplacementBuilder().WithOverride("%user%", () => fs.Username)
|
||||
.WithOverride("%platform%", () => fs.Type.ToString())
|
||||
@@ -266,9 +314,38 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
|
||||
|
||||
var message = string.IsNullOrWhiteSpace(fs.Message) ? "" : rep.Replace(fs.Message);
|
||||
|
||||
return textChannel.EmbedAsync(GetEmbed(fs.GuildId, stream), message);
|
||||
var msg = await textChannel.EmbedAsync(GetEmbed(fs.GuildId, stream), message);
|
||||
|
||||
// only cache the ids of channel/message pairs
|
||||
if(_deleteOnOfflineServers.Contains(fs.GuildId))
|
||||
return (textChannel.Id, msg.Id);
|
||||
else
|
||||
return default;
|
||||
})
|
||||
.WhenAll();
|
||||
|
||||
|
||||
// push online stream messages to redis
|
||||
// when streams go offline, any server which
|
||||
// has the online stream message deletion feature
|
||||
// enabled will have the online messages deleted
|
||||
try
|
||||
{
|
||||
var pairs = messages
|
||||
.Where(x => x != default)
|
||||
.Select(x => (RedisValue)$"{x.Item1},{x.Item2}")
|
||||
.ToArray();
|
||||
|
||||
if (pairs.Length > 0)
|
||||
{
|
||||
var db = _redis.GetDatabase();
|
||||
await db.ListRightPushAsync($"streams_online_del:{key}", pairs);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -484,6 +561,21 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
|
||||
|
||||
return newValue;
|
||||
}
|
||||
|
||||
public bool ToggleStreamOnlineDelete(ulong guildId)
|
||||
{
|
||||
using var uow = _db.GetDbContext();
|
||||
var gc = uow.GuildConfigsForId(guildId, set => set);
|
||||
var newValue = gc.DeleteStreamOnlineMessage = !gc.DeleteStreamOnlineMessage;
|
||||
uow.SaveChanges();
|
||||
|
||||
if (newValue)
|
||||
_deleteOnOfflineServers.Add(guildId);
|
||||
else
|
||||
_deleteOnOfflineServers.TryRemove(guildId);
|
||||
|
||||
return newValue;
|
||||
}
|
||||
|
||||
public Task<StreamData> GetStreamDataAsync(string url)
|
||||
=> _streamTracker.GetStreamDataByUrlAsync(url);
|
||||
|
@@ -84,13 +84,12 @@ public class PatreonRewardsService : INService, IReadyExecutor
|
||||
try
|
||||
{
|
||||
using var http = _httpFactory.CreateClient();
|
||||
using var content = new StringContent(string.Empty);
|
||||
using var res = await http.PostAsync("https://www.patreon.com/api/oauth2/token"
|
||||
+ "?grant_type=refresh_token"
|
||||
+ $"&refresh_token={creds.Patreon.RefreshToken}"
|
||||
+ $"&client_id={creds.Patreon.ClientId}"
|
||||
+ $"&client_secret={creds.Patreon.ClientSecret}",
|
||||
content);
|
||||
null);
|
||||
|
||||
res.EnsureSuccessStatusCode();
|
||||
|
||||
@@ -149,7 +148,7 @@ public class PatreonRewardsService : INService, IReadyExecutor
|
||||
if (!success)
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
LastUpdate = DateTime.UtcNow;
|
||||
try
|
||||
{
|
||||
|
@@ -76,6 +76,7 @@ public class RemindService : INService, IReadyExecutor
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
// todo move isonshard to a method
|
||||
private async Task<List<Reminder>> GetRemindersBeforeAsync(DateTime now)
|
||||
{
|
||||
await using var uow = _db.GetDbContext();
|
||||
|
@@ -48,7 +48,7 @@ public partial class Utility : NadekoModule
|
||||
_tracker = tracker;
|
||||
_httpFactory = httpFactory;
|
||||
}
|
||||
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.ManageMessages)]
|
||||
|
@@ -240,7 +240,7 @@ public partial class Xp
|
||||
[Cmd]
|
||||
[Priority(1)]
|
||||
public partial Task ClubAccept(IUser user)
|
||||
=> ClubAccept($"{user.Username}#{user.Discriminator}");
|
||||
=> ClubAccept(user.ToString());
|
||||
|
||||
[Cmd]
|
||||
[Priority(0)]
|
||||
@@ -282,7 +282,7 @@ public partial class Xp
|
||||
[Cmd]
|
||||
[Priority(1)]
|
||||
public partial Task ClubBan([Leftover] IUser user)
|
||||
=> ClubBan($"{user.Username}#{user.Discriminator}");
|
||||
=> ClubBan(user.ToString());
|
||||
|
||||
[Cmd]
|
||||
[Priority(0)]
|
||||
@@ -300,7 +300,7 @@ public partial class Xp
|
||||
[Cmd]
|
||||
[Priority(1)]
|
||||
public partial Task ClubUnBan([Leftover] IUser user)
|
||||
=> ClubUnBan($"{user.Username}#{user.Discriminator}");
|
||||
=> ClubUnBan(user.ToString());
|
||||
|
||||
[Cmd]
|
||||
[Priority(0)]
|
||||
|
@@ -17,7 +17,6 @@ using Image = SixLabors.ImageSharp.Image;
|
||||
|
||||
namespace NadekoBot.Modules.Xp.Services;
|
||||
|
||||
// todo improve xp with linqtodb
|
||||
public class XpService : INService, IReadyExecutor, IExecNoCommand
|
||||
{
|
||||
public const int XP_REQUIRED_LVL_1 = 36;
|
||||
@@ -133,7 +132,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
||||
try
|
||||
{
|
||||
var toNotify =
|
||||
new List<(IGuild Guild, IMessageChannel MessageChannel, IUser User, int Level,
|
||||
new List<(IGuild Guild, IMessageChannel MessageChannel, IUser User, long Level,
|
||||
XpNotificationLocation NotifyType, NotifOf NotifOf)>();
|
||||
var roleRewards = new Dictionary<ulong, List<XpRoleReward>>();
|
||||
var curRewards = new Dictionary<ulong, List<XpCurrencyReward>>();
|
||||
@@ -640,7 +639,7 @@ public class XpService : INService, IReadyExecutor, IExecNoCommand
|
||||
{
|
||||
DiscordUser du;
|
||||
UserXpStats stats;
|
||||
int totalXp;
|
||||
long totalXp;
|
||||
int globalRank;
|
||||
int guildRank;
|
||||
await using (var uow = _db.GetDbContext())
|
||||
|
@@ -1,29 +1,15 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Modules.Xp.Services;
|
||||
using LinqToDB;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
|
||||
namespace NadekoBot.Modules.Xp.Extensions;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
public static (int Level, int LevelXp, int LevelRequiredXp) GetLevelData(this UserXpStats stats)
|
||||
{
|
||||
var baseXp = XpService.XP_REQUIRED_LVL_1;
|
||||
|
||||
var required = baseXp;
|
||||
var totalXp = 0;
|
||||
var lvl = 1;
|
||||
while (true)
|
||||
{
|
||||
required = (int)(baseXp + (baseXp / 4.0 * (lvl - 1)));
|
||||
|
||||
if (required + totalXp > stats.Xp)
|
||||
break;
|
||||
|
||||
totalXp += required;
|
||||
lvl++;
|
||||
}
|
||||
|
||||
return (lvl - 1, stats.Xp - totalXp, required);
|
||||
}
|
||||
public static async Task<LevelStats> GetLevelDataFor(this ITable<UserXpStats> userXp, ulong guildId, ulong userId)
|
||||
=> await userXp
|
||||
.Where(x => x.GuildId == guildId && x.UserId == userId)
|
||||
.FirstOrDefaultAsync() is UserXpStats uxs
|
||||
? new(uxs.Xp + uxs.AwardedXp)
|
||||
: new(0);
|
||||
}
|
@@ -5,12 +5,12 @@ namespace NadekoBot.Modules.Xp;
|
||||
|
||||
public class LevelStats
|
||||
{
|
||||
public int Level { get; }
|
||||
public int LevelXp { get; }
|
||||
public int RequiredXp { get; }
|
||||
public int TotalXp { get; }
|
||||
public long Level { get; }
|
||||
public long LevelXp { get; }
|
||||
public long RequiredXp { get; }
|
||||
public long TotalXp { get; }
|
||||
|
||||
public LevelStats(int xp)
|
||||
public LevelStats(long xp)
|
||||
{
|
||||
if (xp < 0)
|
||||
xp = 0;
|
||||
|
@@ -66,20 +66,19 @@
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2021.3.0" />
|
||||
|
||||
<!-- Db-related packages -->
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.3">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="linq2db.EntityFrameworkCore" Version="6.6.1" />
|
||||
<PackageReference Include="linq2db.EntityFrameworkCore" Version="6.7.1" />
|
||||
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.3" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.4" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.4" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.1" />
|
||||
|
||||
<!-- Remove this when static abstract interface members support is released -->
|
||||
<PackageReference Include="System.Runtime.Experimental" Version="6.0.0" />
|
||||
<!-- <PackageReference Include="System.Runtime.Experimental" Version="6.0.2" />-->
|
||||
|
||||
<!-- Used by .crypto command -->
|
||||
<PackageReference Include="YahooFinanceApi" Version="2.1.2" />
|
||||
|
@@ -13,17 +13,26 @@ public static class CurrencyServiceExtensions
|
||||
// todo transfer should be a transaction
|
||||
public static async Task<bool> TransferAsync(
|
||||
this ICurrencyService cs,
|
||||
ulong fromId,
|
||||
ulong toId,
|
||||
IEmbedBuilderService ebs,
|
||||
IUser from,
|
||||
IUser to,
|
||||
long amount,
|
||||
string fromName,
|
||||
string note)
|
||||
string? note)
|
||||
{
|
||||
var fromWallet = await cs.GetWalletAsync(fromId);
|
||||
var toWallet = await cs.GetWalletAsync(toId);
|
||||
var fromWallet = await cs.GetWalletAsync(from.Id);
|
||||
var toWallet = await cs.GetWalletAsync(to.Id);
|
||||
|
||||
var extra = new TxData("gift", fromName, note, fromId);
|
||||
var extra = new TxData("gift", from.ToString()!, note, from.Id);
|
||||
|
||||
return await fromWallet.Transfer(amount, toWallet, extra);
|
||||
if (await fromWallet.Transfer(amount, toWallet, extra))
|
||||
{
|
||||
await to.SendConfirmAsync(ebs,
|
||||
string.IsNullOrWhiteSpace(note)
|
||||
? $"Gift from {from}"
|
||||
: $"Gift from {from}: {note}");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -53,9 +53,8 @@ public sealed class BotCredsProvider : IBotCredsProvider
|
||||
_config = new ConfigurationBuilder().AddYamlFile(CredsPath, false, true)
|
||||
.AddEnvironmentVariables("NadekoBot_")
|
||||
.Build();
|
||||
#if !GLOBAL_NADEKO
|
||||
|
||||
_changeToken = ChangeToken.OnChange(() => _config.GetReloadToken(), Reload);
|
||||
#endif
|
||||
Reload();
|
||||
}
|
||||
|
||||
|
@@ -7,7 +7,7 @@ namespace NadekoBot.Services;
|
||||
|
||||
public sealed class StatsService : IStatsService, IReadyExecutor, INService
|
||||
{
|
||||
public const string BOT_VERSION = "4.1.1";
|
||||
public const string BOT_VERSION = "4.1.4";
|
||||
|
||||
public string Author
|
||||
=> "Kwoth#2452";
|
||||
|
17
src/NadekoBot/Services/strings/IBotStringsExtensions.cs
Normal file
17
src/NadekoBot/Services/strings/IBotStringsExtensions.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
#nullable disable
|
||||
using System.Globalization;
|
||||
|
||||
namespace NadekoBot.Services;
|
||||
|
||||
public static class BotStringsExtensions
|
||||
{
|
||||
// this one is for pipe fun, see PipeExtensions.cs
|
||||
public static string GetText(this IBotStrings strings, in LocStr str, in ulong guildId)
|
||||
=> strings.GetText(str.Key, guildId, str.Params);
|
||||
|
||||
public static string GetText(this IBotStrings strings, in LocStr str, ulong? guildId = null)
|
||||
=> strings.GetText(str.Key, guildId, str.Params);
|
||||
|
||||
public static string GetText(this IBotStrings strings, in LocStr str, CultureInfo culture)
|
||||
=> strings.GetText(str.Key, culture, str.Params);
|
||||
}
|
@@ -46,7 +46,7 @@ public class RedisBotStringsProvider : IBotStringsProvider
|
||||
if (descStr == default)
|
||||
return null;
|
||||
|
||||
var args = Array.ConvertAll(argsStr.Split('&'), HttpUtility.UrlDecode);
|
||||
var args = argsStr.Split('&').Map(HttpUtility.UrlDecode);
|
||||
return new()
|
||||
{
|
||||
Args = args,
|
||||
@@ -68,7 +68,7 @@ public class RedisBotStringsProvider : IBotStringsProvider
|
||||
{
|
||||
var hashFields = localeStrings
|
||||
.Select(x => new HashEntry($"{x.Key}::args",
|
||||
string.Join('&', Array.ConvertAll(x.Value.Args, HttpUtility.UrlEncode))))
|
||||
string.Join('&', x.Value.Args.Map(HttpUtility.UrlEncode))))
|
||||
.Concat(localeStrings.Select(x => new HashEntry($"{x.Key}::desc", x.Value.Desc)))
|
||||
.ToArray();
|
||||
|
||||
|
@@ -23,6 +23,11 @@ public static class Extensions
|
||||
x.Embed = set.GetEmbed().Build();
|
||||
x.Content = set.PlainText?.SanitizeMentions() ?? "";
|
||||
}),
|
||||
SmartEmbedTextArray set => msg.ModifyAsync(x =>
|
||||
{
|
||||
x.Embeds = set.GetEmbedBuilders().Map(eb => eb.Build());
|
||||
x.Content = set.Content?.SanitizeMentions() ?? "";
|
||||
}),
|
||||
SmartPlainText spt => msg.ModifyAsync(x =>
|
||||
{
|
||||
x.Content = spt.Text.SanitizeMentions();
|
||||
@@ -116,8 +121,7 @@ public static class Extensions
|
||||
args = strings.GetCommandStrings(cmd.Summary, culture).Args;
|
||||
}
|
||||
|
||||
return Array.ConvertAll(args,
|
||||
arg => GetFullUsage(cmd.Name, arg, prefix));
|
||||
return args.Map(arg => GetFullUsage(cmd.Aliases.First(), arg, prefix));
|
||||
}
|
||||
|
||||
private static string GetFullUsage(string commandName, string args, string prefix)
|
||||
@@ -139,27 +143,6 @@ public static class Extensions
|
||||
public static IEmbedBuilder WithErrorColor(this IEmbedBuilder eb)
|
||||
=> eb.WithColor(EmbedColor.Error);
|
||||
|
||||
public static ReactionEventWrapper OnReaction(
|
||||
this IUserMessage msg,
|
||||
DiscordSocketClient client,
|
||||
Func<SocketReaction, Task> reactionAdded,
|
||||
Func<SocketReaction, Task>? reactionRemoved = null)
|
||||
{
|
||||
if (reactionRemoved is null)
|
||||
reactionRemoved = _ => Task.CompletedTask;
|
||||
|
||||
var wrap = new ReactionEventWrapper(client, msg);
|
||||
wrap.OnReactionAdded += r =>
|
||||
{
|
||||
_ = Task.Run(() => reactionAdded(r));
|
||||
};
|
||||
wrap.OnReactionRemoved += r =>
|
||||
{
|
||||
_ = Task.Run(() => reactionRemoved(r));
|
||||
};
|
||||
return wrap;
|
||||
}
|
||||
|
||||
public static HttpClient AddFakeHeaders(this HttpClient http)
|
||||
{
|
||||
AddFakeHeaders(http.DefaultRequestHeaders);
|
||||
@@ -231,10 +214,4 @@ public static class Extensions
|
||||
=> msg.Content.Headers.ContentLength is long length
|
||||
? length
|
||||
: long.MaxValue;
|
||||
|
||||
public static string GetText(this IBotStrings strings, in LocStr str, ulong? guildId = null)
|
||||
=> strings.GetText(str.Key, guildId, str.Params);
|
||||
|
||||
public static string GetText(this IBotStrings strings, in LocStr str, CultureInfo culture)
|
||||
=> strings.GetText(str.Key, culture, str.Params);
|
||||
}
|
@@ -2,48 +2,116 @@ namespace NadekoBot.Extensions;
|
||||
|
||||
public static class MessageChannelExtensions
|
||||
{
|
||||
private static readonly IEmote _arrowLeft = new Emoji("⬅");
|
||||
private static readonly IEmote _arrowRight = new Emoji("➡");
|
||||
// main overload that all other send methods reduce to
|
||||
public static Task<IUserMessage> SendAsync(
|
||||
this IMessageChannel channel,
|
||||
string? plainText,
|
||||
Embed? embed = null,
|
||||
IReadOnlyCollection<Embed>? embeds = null,
|
||||
bool sanitizeAll = false,
|
||||
MessageComponent? components = null)
|
||||
{
|
||||
plainText = sanitizeAll
|
||||
? plainText?.SanitizeAllMentions() ?? ""
|
||||
: plainText?.SanitizeMentions() ?? "";
|
||||
|
||||
public static Task<IUserMessage> EmbedAsync(this IMessageChannel ch, IEmbedBuilder embed, string msg = "")
|
||||
=> ch.SendMessageAsync(msg,
|
||||
embed: embed.Build(),
|
||||
return channel.SendMessageAsync(plainText,
|
||||
embed: embed,
|
||||
embeds: embeds is null
|
||||
? null
|
||||
: embeds as Embed[] ?? embeds.ToArray(),
|
||||
components: components,
|
||||
options: new()
|
||||
{
|
||||
RetryMode = RetryMode.AlwaysRetry
|
||||
});
|
||||
}
|
||||
|
||||
public static async Task<IUserMessage> SendAsync(
|
||||
this IMessageChannel channel,
|
||||
string? plainText,
|
||||
NadekoInteraction? inter,
|
||||
Embed? embed = null,
|
||||
IReadOnlyCollection<Embed>? embeds = null,
|
||||
bool sanitizeAll = false)
|
||||
{
|
||||
var msg = await channel.SendAsync(plainText,
|
||||
embed,
|
||||
embeds,
|
||||
sanitizeAll,
|
||||
inter?.CreateComponent());
|
||||
|
||||
if (inter is not null)
|
||||
await inter.RunAsync(msg);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
public static Task<IUserMessage> SendAsync(
|
||||
this IMessageChannel channel,
|
||||
string? plainText,
|
||||
Embed? embed,
|
||||
SmartText text,
|
||||
bool sanitizeAll = false)
|
||||
{
|
||||
plainText = sanitizeAll ? plainText?.SanitizeAllMentions() ?? "" : plainText?.SanitizeMentions() ?? "";
|
||||
|
||||
return channel.SendMessageAsync(plainText, embed: embed);
|
||||
}
|
||||
|
||||
public static Task<IUserMessage> SendAsync(this IMessageChannel channel, SmartText text, bool sanitizeAll = false)
|
||||
=> text switch
|
||||
{
|
||||
SmartEmbedText set => channel.SendAsync(set.PlainText, set.GetEmbed().Build(), sanitizeAll),
|
||||
SmartPlainText st => channel.SendAsync(st.Text, null, sanitizeAll),
|
||||
SmartEmbedText set => channel.SendAsync(set.PlainText,
|
||||
set.GetEmbed().Build(),
|
||||
sanitizeAll: sanitizeAll),
|
||||
SmartPlainText st => channel.SendAsync(st.Text,
|
||||
default(Embed),
|
||||
sanitizeAll: sanitizeAll),
|
||||
SmartEmbedTextArray arr => channel.SendAsync(arr.Content,
|
||||
embeds: arr.GetEmbedBuilders().Map(e => e.Build())),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(text))
|
||||
};
|
||||
|
||||
// this is a huge problem, because now i don't have
|
||||
// access to embed builder service
|
||||
// as this is an extension of the message channel
|
||||
public static Task<IUserMessage> SendErrorAsync(
|
||||
public static Task<IUserMessage> EmbedAsync(
|
||||
this IMessageChannel ch,
|
||||
IEmbedBuilder? embed,
|
||||
string plainText = "",
|
||||
IReadOnlyCollection<IEmbedBuilder>? embeds = null,
|
||||
NadekoInteraction? inter = null)
|
||||
=> ch.SendAsync(plainText,
|
||||
inter,
|
||||
embed: embed?.Build(),
|
||||
embeds: embeds?.Map(x => x.Build()));
|
||||
|
||||
public static Task<IUserMessage> SendAsync(
|
||||
this IMessageChannel ch,
|
||||
IEmbedBuilderService eb,
|
||||
string text,
|
||||
MessageType type,
|
||||
NadekoInteraction? inter = null)
|
||||
{
|
||||
var builder = eb.Create().WithDescription(text);
|
||||
|
||||
builder = (type switch
|
||||
{
|
||||
MessageType.Error => builder.WithErrorColor(),
|
||||
MessageType.Ok => builder.WithOkColor(),
|
||||
MessageType.Pending => builder.WithPendingColor(),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type))
|
||||
});
|
||||
|
||||
return ch.EmbedAsync(builder, inter: inter);
|
||||
}
|
||||
|
||||
// regular send overloads
|
||||
public static Task<IUserMessage> SendErrorAsync(this IMessageChannel ch, IEmbedBuilderService eb, string text)
|
||||
=> ch.SendAsync(eb, text, MessageType.Error);
|
||||
|
||||
public static Task<IUserMessage> SendConfirmAsync(this IMessageChannel ch, IEmbedBuilderService eb, string text)
|
||||
=> ch.SendAsync(eb, text, MessageType.Ok);
|
||||
|
||||
public static Task<IUserMessage> SendAsync(
|
||||
this IMessageChannel ch,
|
||||
IEmbedBuilderService eb,
|
||||
MessageType type,
|
||||
string title,
|
||||
string error,
|
||||
string text,
|
||||
string? url = null,
|
||||
string? footer = null)
|
||||
{
|
||||
var embed = eb.Create().WithErrorColor().WithDescription(error).WithTitle(title);
|
||||
var embed = eb.Create().WithDescription(text).WithTitle(title);
|
||||
|
||||
if (url is not null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
|
||||
embed.WithUrl(url);
|
||||
@@ -51,15 +119,19 @@ public static class MessageChannelExtensions
|
||||
if (!string.IsNullOrWhiteSpace(footer))
|
||||
embed.WithFooter(footer);
|
||||
|
||||
return ch.SendMessageAsync("", embed: embed.Build());
|
||||
embed = type switch
|
||||
{
|
||||
MessageType.Error => embed.WithErrorColor(),
|
||||
MessageType.Ok => embed.WithOkColor(),
|
||||
MessageType.Pending => embed.WithPendingColor(),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type))
|
||||
};
|
||||
|
||||
return ch.EmbedAsync(embed);
|
||||
}
|
||||
|
||||
public static Task<IUserMessage> SendErrorAsync(this IMessageChannel ch, IEmbedBuilderService eb, string error)
|
||||
=> ch.SendMessageAsync("", embed: eb.Create().WithErrorColor().WithDescription(error).Build());
|
||||
|
||||
public static Task<IUserMessage> SendPendingAsync(this IMessageChannel ch, IEmbedBuilderService eb, string message)
|
||||
=> ch.SendMessageAsync("", embed: eb.Create().WithPendingColor().WithDescription(message).Build());
|
||||
|
||||
// embed title and optional footer overloads
|
||||
|
||||
public static Task<IUserMessage> SendConfirmAsync(
|
||||
this IMessageChannel ch,
|
||||
IEmbedBuilderService eb,
|
||||
@@ -67,21 +139,19 @@ public static class MessageChannelExtensions
|
||||
string text,
|
||||
string? url = null,
|
||||
string? footer = null)
|
||||
{
|
||||
var embed = eb.Create().WithOkColor().WithDescription(text).WithTitle(title);
|
||||
|
||||
if (url is not null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
|
||||
embed.WithUrl(url);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(footer))
|
||||
embed.WithFooter(footer);
|
||||
|
||||
return ch.SendMessageAsync("", embed: embed.Build());
|
||||
}
|
||||
|
||||
public static Task<IUserMessage> SendConfirmAsync(this IMessageChannel ch, IEmbedBuilderService eb, string text)
|
||||
=> ch.SendMessageAsync("", embed: eb.Create().WithOkColor().WithDescription(text).Build());
|
||||
=> ch.SendAsync(eb, MessageType.Ok, title, text, url, footer);
|
||||
|
||||
public static Task<IUserMessage> SendErrorAsync(
|
||||
this IMessageChannel ch,
|
||||
IEmbedBuilderService eb,
|
||||
string title,
|
||||
string text,
|
||||
string? url = null,
|
||||
string? footer = null)
|
||||
=> ch.SendAsync(eb, MessageType.Error, title, text, url, footer);
|
||||
|
||||
// weird stuff
|
||||
|
||||
public static Task<IUserMessage> SendTableAsync<T>(
|
||||
this IMessageChannel ch,
|
||||
string seed,
|
||||
@@ -114,9 +184,12 @@ public static class MessageChannelExtensions
|
||||
itemsPerPage,
|
||||
addPaginatedFooter);
|
||||
|
||||
/// <summary>
|
||||
/// danny kamisama
|
||||
/// </summary>
|
||||
private const string BUTTON_LEFT = "BUTTON_LEFT";
|
||||
private const string BUTTON_RIGHT = "BUTTON_RIGHT";
|
||||
|
||||
private static readonly IEmote _arrowLeft = Emote.Parse("<:x:969658061805465651>");
|
||||
private static readonly IEmote _arrowRight = Emote.Parse("<:x:969658062220701746>");
|
||||
|
||||
public static async Task SendPaginatedConfirmAsync(
|
||||
this ICommandContext ctx,
|
||||
int currentPage,
|
||||
@@ -125,95 +198,116 @@ public static class MessageChannelExtensions
|
||||
int itemsPerPage,
|
||||
bool addPaginatedFooter = true)
|
||||
{
|
||||
var lastPage = (totalElements - 1) / itemsPerPage;
|
||||
|
||||
var embed = await pageFunc(currentPage);
|
||||
|
||||
var lastPage = (totalElements - 1) / itemsPerPage;
|
||||
|
||||
var canPaginate = true;
|
||||
if (ctx.Guild is SocketGuild sg && !sg.CurrentUser.GetPermissions((IGuildChannel)ctx.Channel).AddReactions)
|
||||
canPaginate = false;
|
||||
|
||||
if (!canPaginate)
|
||||
embed.WithFooter("⚠️ AddReaction permission required for pagination.");
|
||||
else if (addPaginatedFooter)
|
||||
if (addPaginatedFooter)
|
||||
embed.AddPaginatedFooter(currentPage, lastPage);
|
||||
|
||||
var msg = await ctx.Channel.EmbedAsync(embed);
|
||||
|
||||
if (lastPage == 0 || !canPaginate)
|
||||
return;
|
||||
|
||||
await msg.AddReactionAsync(_arrowLeft);
|
||||
await msg.AddReactionAsync(_arrowRight);
|
||||
|
||||
await Task.Delay(2000);
|
||||
|
||||
var lastPageChange = DateTime.MinValue;
|
||||
|
||||
async Task ChangePage(SocketReaction r)
|
||||
var component = new ComponentBuilder()
|
||||
.WithButton(new ButtonBuilder()
|
||||
.WithStyle(ButtonStyle.Primary)
|
||||
.WithCustomId(BUTTON_LEFT)
|
||||
.WithDisabled(lastPage == 0)
|
||||
.WithEmote(_arrowLeft))
|
||||
.WithButton(new ButtonBuilder()
|
||||
.WithStyle(ButtonStyle.Primary)
|
||||
.WithCustomId(BUTTON_RIGHT)
|
||||
.WithDisabled(lastPage == 0)
|
||||
.WithEmote(_arrowRight))
|
||||
.Build();
|
||||
|
||||
var msg = await ctx.Channel.SendAsync(null, embed: embed.Build(), components: component);
|
||||
|
||||
Task OnInteractionAsync(SocketInteraction si)
|
||||
{
|
||||
try
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
if (r.UserId != ctx.User.Id)
|
||||
if (si is not SocketMessageComponent smc)
|
||||
return;
|
||||
if (DateTime.UtcNow - lastPageChange < TimeSpan.FromSeconds(1))
|
||||
|
||||
if (smc.Message.Id != msg.Id)
|
||||
return;
|
||||
if (r.Emote.Name == _arrowLeft.Name)
|
||||
|
||||
if (smc.Data.CustomId != BUTTON_LEFT && smc.Data.CustomId != BUTTON_RIGHT)
|
||||
return;
|
||||
|
||||
await si.DeferAsync();
|
||||
if (smc.User.Id != ctx.User.Id)
|
||||
return;
|
||||
|
||||
if (smc.Data.CustomId == BUTTON_LEFT)
|
||||
{
|
||||
if (currentPage == 0)
|
||||
return;
|
||||
lastPageChange = DateTime.UtcNow;
|
||||
|
||||
var toSend = await pageFunc(--currentPage);
|
||||
if (addPaginatedFooter)
|
||||
toSend.AddPaginatedFooter(currentPage, lastPage);
|
||||
await msg.ModifyAsync(x => x.Embed = toSend.Build());
|
||||
|
||||
await smc.ModifyOriginalResponseAsync(x => x.Embed = toSend.Build());
|
||||
}
|
||||
else if (r.Emote.Name == _arrowRight.Name)
|
||||
else if (smc.Data.CustomId == BUTTON_RIGHT)
|
||||
{
|
||||
if (lastPage > currentPage)
|
||||
{
|
||||
lastPageChange = DateTime.UtcNow;
|
||||
var toSend = await pageFunc(++currentPage);
|
||||
if (addPaginatedFooter)
|
||||
toSend.AddPaginatedFooter(currentPage, lastPage);
|
||||
await msg.ModifyAsync(x => x.Embed = toSend.Build());
|
||||
|
||||
await smc.ModifyOriginalResponseAsync(x => x.Embed = toSend.Build());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//ignored
|
||||
}
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
using (msg.OnReaction((DiscordSocketClient)ctx.Client, ChangePage, ChangePage))
|
||||
{
|
||||
await Task.Delay(30000);
|
||||
}
|
||||
if (lastPage == 0)
|
||||
return;
|
||||
|
||||
try
|
||||
var client = (DiscordSocketClient)ctx.Client;
|
||||
|
||||
client.InteractionCreated += OnInteractionAsync;
|
||||
|
||||
await Task.Delay(30_000);
|
||||
|
||||
client.InteractionCreated -= OnInteractionAsync;
|
||||
|
||||
await msg.ModifyAsync(mp => mp.Components = new ComponentBuilder().Build());
|
||||
}
|
||||
|
||||
private static readonly Emoji _okEmoji = new Emoji("✅");
|
||||
private static readonly Emoji _warnEmoji = new Emoji("⚠️");
|
||||
private static readonly Emoji _errorEmoji = new Emoji("❌");
|
||||
|
||||
public static Task ReactAsync(this ICommandContext ctx, MessageType type)
|
||||
{
|
||||
var emoji = type switch
|
||||
{
|
||||
if (msg.Channel is ITextChannel && ((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages)
|
||||
await msg.RemoveAllReactionsAsync();
|
||||
else
|
||||
{
|
||||
await msg.Reactions.Where(x => x.Value.IsMe)
|
||||
.Select(x => msg.RemoveReactionAsync(x.Key, ctx.Client.CurrentUser))
|
||||
.WhenAll();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
MessageType.Error => _errorEmoji,
|
||||
MessageType.Pending => _warnEmoji,
|
||||
MessageType.Ok => _okEmoji,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type)),
|
||||
};
|
||||
|
||||
return ctx.Message.AddReactionAsync(emoji);
|
||||
}
|
||||
|
||||
public static Task OkAsync(this ICommandContext ctx)
|
||||
=> ctx.Message.AddReactionAsync(new Emoji("✅"));
|
||||
=> ctx.ReactAsync(MessageType.Ok);
|
||||
|
||||
public static Task ErrorAsync(this ICommandContext ctx)
|
||||
=> ctx.Message.AddReactionAsync(new Emoji("❌"));
|
||||
=> ctx.ReactAsync(MessageType.Error);
|
||||
|
||||
public static Task WarningAsync(this ICommandContext ctx)
|
||||
=> ctx.Message.AddReactionAsync(new Emoji("⚠️"));
|
||||
=> ctx.ReactAsync(MessageType.Pending);
|
||||
}
|
||||
|
||||
public enum MessageType
|
||||
{
|
||||
Ok,
|
||||
Pending,
|
||||
Error
|
||||
}
|
23
src/NadekoBot/_Extensions/PipeExtensions.cs
Normal file
23
src/NadekoBot/_Extensions/PipeExtensions.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace NadekoBot.Extensions;
|
||||
|
||||
|
||||
public delegate TOut PipeFunc<TIn, out TOut>(in TIn a);
|
||||
public delegate TOut PipeFunc<TIn1, TIn2, out TOut>(in TIn1 a, in TIn2 b);
|
||||
|
||||
public static class PipeExtensions
|
||||
{
|
||||
public static TOut Pipe<TIn, TOut>(this TIn a, Func<TIn, TOut> fn)
|
||||
=> fn(a);
|
||||
|
||||
public static TOut Pipe<TIn, TOut>(this TIn a, PipeFunc<TIn, TOut> fn)
|
||||
=> fn(a);
|
||||
|
||||
public static TOut Pipe<TIn1, TIn2, TOut>(this (TIn1, TIn2) a, PipeFunc<TIn1, TIn2, TOut> fn)
|
||||
=> fn(a.Item1, a.Item2);
|
||||
|
||||
public static (TIn, TExtra) With<TIn, TExtra>(this TIn a, TExtra b)
|
||||
=> (a, b);
|
||||
|
||||
public static async Task<TOut> Pipe<TIn, TOut>(this Task<TIn> a, Func<TIn, TOut> fn)
|
||||
=> fn(await a);
|
||||
}
|
@@ -0,0 +1,99 @@
|
||||
namespace NadekoBot.Extensions;
|
||||
|
||||
public static class SocketMessageComponentExtensions
|
||||
{
|
||||
public static Task RespondAsync(
|
||||
this SocketMessageComponent smc,
|
||||
string? plainText,
|
||||
Embed? embed = null,
|
||||
IReadOnlyCollection<Embed>? embeds = null,
|
||||
bool sanitizeAll = false,
|
||||
MessageComponent? components = null,
|
||||
bool ephemeral = true)
|
||||
{
|
||||
plainText = sanitizeAll
|
||||
? plainText?.SanitizeAllMentions() ?? ""
|
||||
: plainText?.SanitizeMentions() ?? "";
|
||||
|
||||
return smc.RespondAsync(plainText,
|
||||
embed: embed,
|
||||
embeds: embeds is null
|
||||
? null
|
||||
: embeds as Embed[] ?? embeds.ToArray(),
|
||||
components: components,
|
||||
ephemeral: ephemeral,
|
||||
options: new()
|
||||
{
|
||||
RetryMode = RetryMode.AlwaysRetry
|
||||
});
|
||||
}
|
||||
|
||||
public static Task RespondAsync(
|
||||
this SocketMessageComponent smc,
|
||||
SmartText text,
|
||||
bool sanitizeAll = false,
|
||||
bool ephemeral = true)
|
||||
=> text switch
|
||||
{
|
||||
SmartEmbedText set => smc.RespondAsync(set.PlainText,
|
||||
set.GetEmbed().Build(),
|
||||
sanitizeAll: sanitizeAll,
|
||||
ephemeral: ephemeral),
|
||||
SmartPlainText st => smc.RespondAsync(st.Text,
|
||||
default(Embed),
|
||||
sanitizeAll: sanitizeAll,
|
||||
ephemeral: ephemeral),
|
||||
SmartEmbedTextArray arr => smc.RespondAsync(arr.Content,
|
||||
embeds: arr.GetEmbedBuilders().Map(e => e.Build()),
|
||||
ephemeral: ephemeral),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(text))
|
||||
};
|
||||
|
||||
public static Task EmbedAsync(
|
||||
this SocketMessageComponent smc,
|
||||
IEmbedBuilder? embed,
|
||||
string plainText = "",
|
||||
IReadOnlyCollection<IEmbedBuilder>? embeds = null,
|
||||
NadekoInteraction? inter = null,
|
||||
bool ephemeral = false)
|
||||
=> smc.RespondAsync(plainText,
|
||||
embed: embed?.Build(),
|
||||
embeds: embeds?.Map(x => x.Build()));
|
||||
|
||||
public static Task RespondAsync(
|
||||
this SocketMessageComponent ch,
|
||||
IEmbedBuilderService eb,
|
||||
string text,
|
||||
MessageType type,
|
||||
bool ephemeral = false,
|
||||
NadekoInteraction? inter = null)
|
||||
{
|
||||
var builder = eb.Create().WithDescription(text);
|
||||
|
||||
builder = (type switch
|
||||
{
|
||||
MessageType.Error => builder.WithErrorColor(),
|
||||
MessageType.Ok => builder.WithOkColor(),
|
||||
MessageType.Pending => builder.WithPendingColor(),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type))
|
||||
});
|
||||
|
||||
return ch.EmbedAsync(builder, inter: inter, ephemeral: ephemeral);
|
||||
}
|
||||
|
||||
// embed title and optional footer overloads
|
||||
|
||||
public static Task RespondErrorAsync(
|
||||
this SocketMessageComponent smc,
|
||||
IEmbedBuilderService eb,
|
||||
string text,
|
||||
bool ephemeral = false)
|
||||
=> smc.RespondAsync(eb, text, MessageType.Error, ephemeral);
|
||||
|
||||
public static Task RespondConfirmAsync(
|
||||
this SocketMessageComponent smc,
|
||||
IEmbedBuilderService eb,
|
||||
string text,
|
||||
bool ephemeral = false)
|
||||
=> smc.RespondAsync(eb, text, MessageType.Ok, ephemeral);
|
||||
}
|
@@ -518,6 +518,9 @@ streamoffline:
|
||||
- streamoffline
|
||||
- sto
|
||||
- stoff
|
||||
streamonlinedelete:
|
||||
- streamonlinedelete
|
||||
- stondel
|
||||
streammessage:
|
||||
- streammsg
|
||||
- stm
|
||||
@@ -1161,15 +1164,21 @@ pathofexilecurrency:
|
||||
- poec
|
||||
rollduel:
|
||||
- rollduel
|
||||
reactionroles:
|
||||
- reactionroles
|
||||
- rero
|
||||
reactionroleadd:
|
||||
- reactionroleadd
|
||||
- reroa
|
||||
reactionroleslist:
|
||||
- reactionroleslist
|
||||
- reroli
|
||||
reactionrolesremove:
|
||||
- reactionrolesremove
|
||||
- rerorm
|
||||
reactionrolesdeleteall:
|
||||
- rerodeleteall
|
||||
- rerodela
|
||||
reactionrolestransfer:
|
||||
- rerotransfer
|
||||
- rerot
|
||||
blackjack:
|
||||
- blackjack
|
||||
- bj
|
||||
@@ -1282,4 +1291,16 @@ medusalist:
|
||||
- melist
|
||||
medusainfo:
|
||||
- medusainfo
|
||||
- meinfo
|
||||
- meinfo
|
||||
bankdeposit:
|
||||
- deposit
|
||||
- d
|
||||
- dep
|
||||
bankwithdraw:
|
||||
- withdraw
|
||||
- w
|
||||
- with
|
||||
bankbalance:
|
||||
- balance
|
||||
- b
|
||||
- bal
|
||||
|
@@ -2,4 +2,3 @@
|
||||
version: 1
|
||||
# List of medusae automatically loaded at startup
|
||||
loaded:
|
||||
- uwu
|
||||
|
@@ -912,6 +912,10 @@ streamoffline:
|
||||
desc: "Toggles whether the bot will also notify when added streams go offline."
|
||||
args:
|
||||
- ""
|
||||
streamonlinedelete:
|
||||
desc: "Toggles whether the bot will delete stream online message when the stream goes offline."
|
||||
args:
|
||||
- ""
|
||||
streammessage:
|
||||
desc: "Sets the message which will show when the stream on the specified index comes online. You can use %user% and %platform% placeholders."
|
||||
args:
|
||||
@@ -2064,21 +2068,33 @@ rollduel:
|
||||
args:
|
||||
- "50 @Someone"
|
||||
- "@Challenger"
|
||||
reactionroles:
|
||||
desc: "Specify role names and server emojis with which they're represented, the bot will then add those emojis to the previous message in the channel, and users will be able to get the roles by clicking on the emoji. You can set 'excl' as the parameter before the reactions and roles to make them exclusive. You can have up to 10 of these enabled on one server at a time. Optionally you can specify target message if you don't want it to be the previous one."
|
||||
reactionroleadd:
|
||||
desc: |-
|
||||
Specify a message id, emote and a role name to have the bot assign the specified role to the user who reacts to the specified message (in this channel) with the specified emoji.
|
||||
You can optionally specify an exclusivity group. Default is group 0 which is non-exclusive. Other groups are exclusive. Exclusive groups will let the user only have one of the roles specified in that group.
|
||||
You can optionally specify a level requirement after a group. Users who don't meet the level requirement will not receive the role.
|
||||
You can have up to 50 reaction roles per server in total.
|
||||
args:
|
||||
- "Gamer :SomeServerEmoji: Streamer :Other: Watcher :Other2:"
|
||||
- "excl Horde :Horde: Alliance :Alliance:"
|
||||
- "886382471732662332 excl Horde :Horde: Alliance :Alliance:"
|
||||
- "886382471732662332 Gamer :SomeServerEmoji: Streamer :Other: Watcher :Other2:"
|
||||
- 971276352684691466 😊 gamer
|
||||
- 971276352684691466 😢 emo 1
|
||||
- 971276352684691466 🤔 philosopher 5 20
|
||||
- 971276352684691466 👨 normie 5 20
|
||||
reactionroleslist:
|
||||
desc: "Lists all ReactionRole messages on this channel and their indexes."
|
||||
desc: "Lists all ReactionRole messages on this server with their message ids. Clicking/Tapping message ids will send you to that message."
|
||||
args:
|
||||
- ""
|
||||
reactionrolesremove:
|
||||
desc: "Removed a ReactionRole message on the specified index."
|
||||
desc: "Remove all reaction roles from message specified by the id"
|
||||
args:
|
||||
- "1"
|
||||
- "971276352684691466"
|
||||
reactionrolesdeleteall:
|
||||
desc: "Deletes all reaction roles on the server. This action is irreversible."
|
||||
args:
|
||||
- ""
|
||||
reactionrolestransfer:
|
||||
desc: "Transfers reaction roles from one message to another by specifying their ids. If the target message has reaction roles specified already, the reaction roles will be MERGED, not overwritten."
|
||||
args:
|
||||
- "971276352684691466 971427748448964628"
|
||||
blackjack:
|
||||
desc: "Start or join a blackjack game. You must specify the amount you're betting. Use `{0}hit`, `{0}stand` and `{0}double` commands to play. Game is played with 4 decks. Dealer hits on soft 17 and wins draws."
|
||||
args:
|
||||
@@ -2181,4 +2197,15 @@ medusalist:
|
||||
Read about the medusa system [here](https://nadekobot.readthedocs.io/en/latest/medusa/creating-a-medusa/)
|
||||
args:
|
||||
- ""
|
||||
|
||||
bankdeposit:
|
||||
desc: "Deposits the specified amount of currency into the bank for later use."
|
||||
args:
|
||||
- "50"
|
||||
bankwithdraw:
|
||||
desc: "Withdraws the specified amount of currency from the bank if available."
|
||||
args:
|
||||
- "49"
|
||||
bankbalance:
|
||||
desc: "Shows your current bank balance available for withdrawal."
|
||||
args:
|
||||
- ""
|
@@ -519,6 +519,8 @@
|
||||
"stream_no": "No such stream.",
|
||||
"stream_off_enabled": "Stream notifications will now show when a stream goes offline.",
|
||||
"stream_off_disabled": "Stream notifications will no longer show when a stream goes offline.",
|
||||
"stream_online_delete_enabled": "Online stream notifications will now be deleted when the stream goes offline.",
|
||||
"stream_online_delete_disabled": "Online stream notifications will no longer be deleted when the stream goes offline.",
|
||||
"stream_not_added": "Stream was not added. Either stream doesn't exist, that platform is unsupported, or you've reached the maximum number of streams allowed.",
|
||||
"stream_message_reset": "Announcement message for {0} stream has been reset.",
|
||||
"stream_message_set": "Announcement message when {0} stream goes online has been set.",
|
||||
@@ -937,6 +939,7 @@
|
||||
"owned_waifus_total": "Total value of owned waifus",
|
||||
"bot_currency": "Currency owned by the bot",
|
||||
"total": "Total",
|
||||
"bank_accounts": "Bank Accounts",
|
||||
"no_invites": "No invites on this page.",
|
||||
"invite_deleted": "Invite {0} has been deleted.",
|
||||
"group_name_added": "Group #{0} now has a name: {1}",
|
||||
@@ -958,6 +961,7 @@
|
||||
"module_description_permissions": "Setup perms for commands, filter words and set up command cooldowns",
|
||||
"module_description_searches": "Search for jokes, images of animals, anime and manga",
|
||||
"module_description_xp": "Gain xp based on chat activity, check users' xp cards",
|
||||
"module_description_medusa": "**Bot Owner only.** Load, unload and handle dynamic modules. Read more [here](https://nadekobot.readthedocs.io/en/latest/medusa/creating-a-medusa/)",
|
||||
"module_description_missing": "Description is missing for this module.",
|
||||
"obsolete_use": "⚠ Obsolete, use {0} instead.",
|
||||
"purge_user_confirm": "Are you sure that you want to purge {0} from the database?",
|
||||
@@ -990,5 +994,10 @@
|
||||
"medusa_unloaded": "Medusa {0} has been unloaded.",
|
||||
"medusa_empty": "Medusa wasn't loaded as it didn't contain any Sneks.",
|
||||
"medusa_already_loaded": "Medusa {0} is already loaded",
|
||||
"medusa_invalid_not_found": "Medusa with that name wasn't found or the file was invalid"
|
||||
"medusa_invalid_not_found": "Medusa with that name wasn't found or the file was invalid",
|
||||
"bank_balance": "You have {0} in your bank account.",
|
||||
"bank_deposited": "You deposited {0} to your bank account.",
|
||||
"bank_withdrew": "You withdrew {0} from your bank account.",
|
||||
"bank_withdraw_insuff": "You don't have sufficient {0} in your bank account.",
|
||||
"cmd_group_commands": "'{0}' command group"
|
||||
}
|
||||
|
Reference in New Issue
Block a user