mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-11 01:38:27 -04:00
Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1716c69132 | ||
|
14bfcb54dc | ||
|
9f445c0866 | ||
|
3343fd2f6e | ||
|
9103dd9fdb | ||
|
1a8c9a6cba | ||
|
9d2f251923 | ||
|
3744dd287c | ||
|
f65ba100af | ||
|
cc52605c90 | ||
|
3d3dc532dc | ||
|
6c58a6a72d | ||
|
cefd81d810 | ||
|
34c96c697a | ||
|
1cc5e0e1d8 | ||
|
deaedce6c7 | ||
|
91e4d9dffc | ||
|
a826f4245f | ||
|
780eec62b3 |
@@ -113,6 +113,7 @@ docker-build:
|
||||
# Use the official docker image.
|
||||
image: docker:latest
|
||||
stage: build
|
||||
allow_failure: true
|
||||
services:
|
||||
- docker:dind
|
||||
before_script:
|
||||
|
20
CHANGELOG.md
20
CHANGELOG.md
@@ -3,6 +3,26 @@
|
||||
|
||||
Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
|
||||
|
||||
## [4.2.6] - 22.06.2022
|
||||
|
||||
### Fixed
|
||||
|
||||
- Patron system should now properly by disabled on selfhosts by default.
|
||||
|
||||
## [4.2.5] - 18.06.2022
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `.crypto`, you will still need coinmarketcapApiKey in `creds.yml` in order to make it run consistently as the key is shared
|
||||
|
||||
## [4.2.3] - 17.06.2022
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `.timely` nullref bug and made it nicer
|
||||
- Fixed `.streamrole` not updating in real time!
|
||||
- Disabling specific Global Expressions should now work with `.sc` (and other permission commands)
|
||||
|
||||
## [4.2.2] - 15.06.2022
|
||||
|
||||
### Fixed
|
||||
|
@@ -3,19 +3,19 @@
|
||||
### Important
|
||||
|
||||
- For modifying **global** expressions, the ones which will work across all the servers your bot is connected to, you **must** be a Bot Owner.
|
||||
You must also use the commands for adding, deleting and listing these reactions in a direct message with the bot.
|
||||
You must also use the commands for adding, deleting and listing these expressions in a direct message with the bot.
|
||||
- For modifying **local** expressions, the ones which will only work on the server that they are added on, it is required to have the **Administrator** permission.
|
||||
You must also use the commands for adding, deleting and listing these reactions in the server you want the expressions to work on.
|
||||
You must also use the commands for adding, deleting and listing these expressions in the server you want the expressions to work on.
|
||||
|
||||
### Commands and Their Use
|
||||
|
||||
| Command Name | Description | Example |
|
||||
| :----------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------- |
|
||||
| `.exadd` | Add an expression with a trigger and a response. Running this command in a server requries the Administrator permission. Running this command in DM is Bot Owner only, and adds a new global expression. | `.exadd "hello" Hi there, %user%!` |
|
||||
| `exl` | Lists a page of global or server expression(15 reactions / expressions per page). Running this command in a DM will list the global expression, while running it in a server will list that server's expression. | `.exl 1` |
|
||||
| `.exa` | Add an expression with a trigger and a response. Running this command in a server requries the Administrator permission. Running this command in DM is Bot Owner only, and adds a new global expression. | `.exadd "hello" Hi there, %user%!` |
|
||||
| `exl` | Lists a page of global or server expression(15 expressions per page). Running this command in a DM will list the global expression, while running it in a server will list that server's expression. | `.exl 1` |
|
||||
| `.exd` | Deletes an expression based on the provided index. Running this command in a server requires the Administrator permission. Running this command in DM is Bot Owner only, and will delete a global expression. | `.exd 5` |
|
||||
|
||||
#### Now that we know the commands let's take a look at an example of adding a command with `.acr`,
|
||||
#### Now that we know the commands let's take a look at an example of adding a command with `.exa`,
|
||||
|
||||
`.exadd "Nice Weather" It sure is, %user%!`
|
||||
|
||||
@@ -35,7 +35,7 @@ Now, if that command was ran in a server, anyone on that server can make the bot
|
||||
If you want to disable a global expression which you do not like, and you do not want to remove it, or you are not the bot owner, you can do so by adding a new expression with the same trigger on your server, and set the response to `-`.
|
||||
|
||||
For example:
|
||||
`.acr /o/ -`
|
||||
`.exa /o/ -`
|
||||
|
||||
Now if you try to trigger `/o/`, it won't print anything even if there is a global expression with the same name.
|
||||
|
||||
|
@@ -17,7 +17,7 @@ It is recommended that you use **Ubuntu 20.04**, as there have been nearly no pr
|
||||
|
||||
##### Compatible operating systems:
|
||||
|
||||
- Ubuntu: 16.04, 18.04, 20.04, 21.04, 21.10
|
||||
- Ubuntu: 16.04, 18.04, 20.04, 21.04, 21.10 22.04
|
||||
- Mint: 19, 20
|
||||
- Debian: 9, 10
|
||||
- CentOS: 7
|
||||
@@ -63,9 +63,20 @@ Open Terminal (if you're on an installation with a window manager) and navigate
|
||||
4. Run the bot (type `3` and press enter)
|
||||
5. 🎉
|
||||
|
||||
## **⚠ IF YOU ARE FOLLOWING THE GUIDE ABOVE, IGNORE THIS SECTION ⚠**
|
||||
|
||||
## Linux Release
|
||||
|
||||
**⚠ IF YOU ARE FOLLOWING THE GUIDE ABOVE, IGNORE THIS SECTION ⚠**
|
||||
###### 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 Instructions
|
||||
|
||||
@@ -92,19 +103,6 @@ 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
|
||||
|
@@ -63,9 +63,9 @@ You can still install them manually:
|
||||
- [ffmpeg-32bit] | [ffmpeg-64bit] - Download the **appropriate version** for your system (32 bit if you're running a 32 bit OS, or 64 if you're running a 64bit OS). Unzip it, and move `ffmpeg.exe` to a path that's in your PATH environment variable. If you don't know what that is, then just move the `ffmpeg.exe` file to NadekoBot/system
|
||||
- [youtube-dl] - Click to download the file. Then put `youtube-dl.exe` in a path that's in your PATH environment variable. If you don't know what that is, then just move the `youtube-dl.exe` file to NadekoBot/system
|
||||
|
||||
### Windows From Source
|
||||
## **⚠ IF YOU ARE FOLLOWING THE GUIDE ABOVE, IGNORE THIS SECTION ⚠**
|
||||
|
||||
⚠ IF YOU ARE FOLLOWING THE GUIDE ABOVE, IGNORE THIS SECTION ⚠
|
||||
### Windows From Source
|
||||
|
||||
##### Prerequisites
|
||||
|
||||
|
@@ -77,8 +77,8 @@ Say you want to only enable NSFW commands for a specific role, just do the follo
|
||||
|
||||
If you don't want server or global Expressions, just block the module that controls their usage:
|
||||
|
||||
1. `.sm Expressions disable`
|
||||
- Disables the ActualCustomReactions module from being used
|
||||
1. `.sm ActualExpressions disable`
|
||||
- Disables the ActualExpression module from being used
|
||||
|
||||
**Note**: The `Expressions` module controls the usage of Expressions. The `Expressions` module controls commands related to Expressions (such as `.acr`, `.lcr`, `.crca`, etc).
|
||||
|
||||
|
@@ -92,4 +92,3 @@ nav:
|
||||
- medusa/snek-lifecycle.md
|
||||
- Contribution Guide: contribution-guide.md
|
||||
- Donate: donate.md
|
||||
- License: license.md
|
||||
|
62
src/NadekoBot/Common/QueueRunner.cs
Normal file
62
src/NadekoBot/Common/QueueRunner.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace NadekoBot.Common;
|
||||
|
||||
public sealed class QueueRunner
|
||||
{
|
||||
private readonly Channel<Func<Task>> _channel;
|
||||
private readonly int _delayMs;
|
||||
|
||||
public QueueRunner(int delayMs = 0, int maxCapacity = -1)
|
||||
{
|
||||
if (delayMs < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(delayMs));
|
||||
|
||||
_delayMs = delayMs;
|
||||
_channel = maxCapacity switch
|
||||
{
|
||||
0 or < -1 => throw new ArgumentOutOfRangeException(nameof(maxCapacity)),
|
||||
-1 => Channel.CreateUnbounded<Func<Task>>(new UnboundedChannelOptions()
|
||||
{
|
||||
SingleReader = true,
|
||||
SingleWriter = false,
|
||||
AllowSynchronousContinuations = true,
|
||||
}),
|
||||
_ => Channel.CreateBounded<Func<Task>>(new BoundedChannelOptions(maxCapacity)
|
||||
{
|
||||
Capacity = maxCapacity,
|
||||
FullMode = BoundedChannelFullMode.DropOldest,
|
||||
SingleReader = true,
|
||||
SingleWriter = false,
|
||||
AllowSynchronousContinuations = true
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
public async Task RunAsync(CancellationToken cancel = default)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var func = await _channel.Reader.ReadAsync(cancel);
|
||||
|
||||
try
|
||||
{
|
||||
await func();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Exception executing a staggered func: {ErrorMessage}", ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_delayMs != 0)
|
||||
{
|
||||
await Task.Delay(_delayMs, cancel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ValueTask Enqueue(Func<Task> action)
|
||||
=> _channel.Writer.WriteAsync(action);
|
||||
}
|
@@ -31,38 +31,38 @@ public sealed class CommandTypeReader : NadekoTypeReader<CommandInfo>
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CommandOrCrTypeReader : NadekoTypeReader<CommandOrCrInfo>
|
||||
public sealed class CommandOrExprTypeReader : NadekoTypeReader<CommandOrExprInfo>
|
||||
{
|
||||
private readonly CommandService _cmds;
|
||||
private readonly CommandHandler _commandHandler;
|
||||
private readonly NadekoExpressionsService _exprs;
|
||||
|
||||
public CommandOrCrTypeReader(CommandService cmds, NadekoExpressionsService exprs, CommandHandler commandHandler)
|
||||
public CommandOrExprTypeReader(CommandService cmds, NadekoExpressionsService exprs, CommandHandler commandHandler)
|
||||
{
|
||||
_cmds = cmds;
|
||||
_exprs = exprs;
|
||||
_commandHandler = commandHandler;
|
||||
}
|
||||
|
||||
public override async ValueTask<TypeReaderResult<CommandOrCrInfo>> ReadAsync(ICommandContext ctx, string input)
|
||||
public override async ValueTask<TypeReaderResult<CommandOrExprInfo>> ReadAsync(ICommandContext ctx, string input)
|
||||
{
|
||||
input = input.ToUpperInvariant();
|
||||
|
||||
if (_exprs.ExpressionExists(ctx.Guild?.Id, input))
|
||||
return TypeReaderResult.FromSuccess(new CommandOrCrInfo(input, CommandOrCrInfo.Type.Custom));
|
||||
if (_exprs.ExpressionExists(ctx.Guild?.Id, input) || _exprs.ExpressionExists(null, input))
|
||||
return TypeReaderResult.FromSuccess(new CommandOrExprInfo(input, CommandOrExprInfo.Type.Custom));
|
||||
|
||||
var cmd = await new CommandTypeReader(_commandHandler, _cmds).ReadAsync(ctx, input);
|
||||
if (cmd.IsSuccess)
|
||||
{
|
||||
return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Name,
|
||||
CommandOrCrInfo.Type.Normal));
|
||||
return TypeReaderResult.FromSuccess(new CommandOrExprInfo(((CommandInfo)cmd.Values.First().Value).Name,
|
||||
CommandOrExprInfo.Type.Normal));
|
||||
}
|
||||
|
||||
return TypeReaderResult.FromError<CommandOrCrInfo>(CommandError.ParseFailed, "No such command or cr found.");
|
||||
return TypeReaderResult.FromError<CommandOrExprInfo>(CommandError.ParseFailed, "No such command or expression found.");
|
||||
}
|
||||
}
|
||||
|
||||
public class CommandOrCrInfo
|
||||
public class CommandOrExprInfo
|
||||
{
|
||||
public enum Type
|
||||
{
|
||||
@@ -76,7 +76,7 @@ public class CommandOrCrInfo
|
||||
public bool IsCustom
|
||||
=> CmdType == Type.Custom;
|
||||
|
||||
public CommandOrCrInfo(string input, Type type)
|
||||
public CommandOrExprInfo(string input, Type type)
|
||||
{
|
||||
Name = input;
|
||||
CmdType = type;
|
||||
|
@@ -606,4 +606,4 @@ public class GreetService : INService, IReadyExecutor
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@ public partial class Administration
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
public async partial Task DiscordPermOverride(CommandOrCrInfo cmd, params GuildPerm[] perms)
|
||||
public async partial Task DiscordPermOverride(CommandOrExprInfo cmd, params GuildPerm[] perms)
|
||||
{
|
||||
if (perms is null || perms.Length == 0)
|
||||
{
|
||||
|
@@ -189,7 +189,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
|
||||
continue;
|
||||
}
|
||||
|
||||
// if CA is disabled, and CR has AllowTarget, then the
|
||||
// if CA is disabled, and expr has AllowTarget, then the
|
||||
// content has to start with the trigger followed by a space
|
||||
if (expr.AllowTarget
|
||||
&& content.StartsWith(trigger, StringComparison.OrdinalIgnoreCase)
|
||||
|
@@ -126,13 +126,15 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
|
||||
if (_cache.AddTimelyClaim(ctx.User.Id, period) is { } rem)
|
||||
{
|
||||
await ReplyErrorLocalizedAsync(strs.timely_already_claimed(rem.ToString(@"dd\d\ hh\h\ mm\m\ ss\s")));
|
||||
var now = DateTime.UtcNow;
|
||||
var relativeTag = TimestampTag.FromDateTime(now.Add(rem), TimestampTagStyles.Relative);
|
||||
await ReplyErrorLocalizedAsync(strs.timely_already_claimed(relativeTag));
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await _ps.TryGetFeatureLimitAsync(_timelyKey, ctx.User.Id, 0);
|
||||
|
||||
val = (int)(val * (1 + (result.Quota * 0.01f)));
|
||||
val = (int)(val * (1 + (result.Quota! * 0.01f)));
|
||||
|
||||
await _cs.AddAsync(ctx.User.Id, val, new("timely", "claim"));
|
||||
|
||||
|
@@ -145,7 +145,7 @@ public partial class Help : NadekoModule<HelpService>
|
||||
return "❓";
|
||||
case "administration":
|
||||
return "🛠️";
|
||||
case "customreactions":
|
||||
case "expressions":
|
||||
return "🗣️";
|
||||
case "searches":
|
||||
return "🔍";
|
||||
@@ -402,7 +402,7 @@ public partial class Help : NadekoModule<HelpService>
|
||||
ContentType = "application/json",
|
||||
ContentBody = uploadData,
|
||||
// either use a path provided in the argument or the default one for public nadeko, other/cmds.json
|
||||
Key = $"cmds/v4/{StatsService.BOT_VERSION}.json",
|
||||
Key = $"cmds/{StatsService.BOT_VERSION}.json",
|
||||
CannedACL = S3CannedACL.PublicRead
|
||||
});
|
||||
}
|
||||
@@ -414,7 +414,7 @@ public partial class Help : NadekoModule<HelpService>
|
||||
using var oldVersionObject = await dlClient.GetObjectAsync(new()
|
||||
{
|
||||
BucketName = "nadeko-pictures",
|
||||
Key = "cmds/v4/versions.json"
|
||||
Key = "cmds/versions.json"
|
||||
});
|
||||
|
||||
await using var ms = new MemoryStream();
|
||||
@@ -445,7 +445,7 @@ public partial class Help : NadekoModule<HelpService>
|
||||
ContentType = "application/json",
|
||||
ContentBody = versionListString,
|
||||
// either use a path provided in the argument or the default one for public nadeko, other/cmds.json
|
||||
Key = "cmds/v4/versions.json",
|
||||
Key = "cmds/versions.json",
|
||||
CannedACL = S3CannedACL.PublicRead
|
||||
});
|
||||
}
|
||||
|
@@ -79,7 +79,7 @@ public partial class Permissions
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[Priority(1)]
|
||||
public partial Task CmdCooldown(CommandOrCrInfo command, int secs)
|
||||
public partial Task CmdCooldown(CommandOrExprInfo command, int secs)
|
||||
=> CmdCooldownInternal(command.Name, secs);
|
||||
|
||||
[Cmd]
|
||||
|
@@ -60,7 +60,7 @@ public partial class Permissions
|
||||
|
||||
[Cmd]
|
||||
[OwnerOnly]
|
||||
public async partial Task GlobalCommand(CommandOrCrInfo cmd)
|
||||
public async partial Task GlobalCommand(CommandOrExprInfo cmd)
|
||||
{
|
||||
var commandName = cmd.Name.ToLowerInvariant();
|
||||
var added = _service.ToggleCommand(commandName);
|
||||
|
@@ -204,7 +204,7 @@ public partial class Permissions : NadekoModule<PermissionService>
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async partial Task SrvrCmd(CommandOrCrInfo command, PermissionAction action)
|
||||
public async partial Task SrvrCmd(CommandOrExprInfo command, PermissionAction action)
|
||||
{
|
||||
await _service.AddPermissions(ctx.Guild.Id,
|
||||
new Permissionv2
|
||||
@@ -245,7 +245,7 @@ public partial class Permissions : NadekoModule<PermissionService>
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async partial Task UsrCmd(CommandOrCrInfo command, PermissionAction action, [Leftover] IGuildUser user)
|
||||
public async partial Task UsrCmd(CommandOrExprInfo command, PermissionAction action, [Leftover] IGuildUser user)
|
||||
{
|
||||
await _service.AddPermissions(ctx.Guild.Id,
|
||||
new Permissionv2
|
||||
@@ -302,7 +302,7 @@ public partial class Permissions : NadekoModule<PermissionService>
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async partial Task RoleCmd(CommandOrCrInfo command, PermissionAction action, [Leftover] IRole role)
|
||||
public async partial Task RoleCmd(CommandOrExprInfo command, PermissionAction action, [Leftover] IRole role)
|
||||
{
|
||||
if (role == role.Guild.EveryoneRole)
|
||||
return;
|
||||
@@ -366,7 +366,7 @@ public partial class Permissions : NadekoModule<PermissionService>
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async partial Task ChnlCmd(CommandOrCrInfo command, PermissionAction action, [Leftover] ITextChannel chnl)
|
||||
public async partial Task ChnlCmd(CommandOrExprInfo command, PermissionAction action, [Leftover] ITextChannel chnl)
|
||||
{
|
||||
await _service.AddPermissions(ctx.Guild.Id,
|
||||
new Permissionv2
|
||||
|
@@ -16,11 +16,11 @@ public class CmcQuote
|
||||
[JsonPropertyName("volume_24h")]
|
||||
public double Volume24h { get; set; }
|
||||
|
||||
[JsonPropertyName("volume_change_24h")]
|
||||
public double VolumeChange24h { get; set; }
|
||||
|
||||
[JsonPropertyName("percent_change_1h")]
|
||||
public double PercentChange1h { get; set; }
|
||||
// [JsonPropertyName("volume_change_24h")]
|
||||
// public double VolumeChange24h { get; set; }
|
||||
//
|
||||
// [JsonPropertyName("percent_change_1h")]
|
||||
// public double PercentChange1h { get; set; }
|
||||
|
||||
[JsonPropertyName("percent_change_24h")]
|
||||
public double PercentChange24h { get; set; }
|
||||
@@ -33,12 +33,6 @@ public class CmcQuote
|
||||
|
||||
[JsonPropertyName("market_cap_dominance")]
|
||||
public double MarketCapDominance { get; set; }
|
||||
|
||||
[JsonPropertyName("fully_diluted_market_cap")]
|
||||
public double FullyDilutedMarketCap { get; set; }
|
||||
|
||||
[JsonPropertyName("last_updated")]
|
||||
public DateTime LastUpdated { get; set; }
|
||||
}
|
||||
|
||||
public class CmcResponseData
|
||||
@@ -58,9 +52,6 @@ public class CmcResponseData
|
||||
[JsonPropertyName("cmc_rank")]
|
||||
public int CmcRank { get; set; }
|
||||
|
||||
[JsonPropertyName("num_market_pairs")]
|
||||
public int NumMarketPairs { get; set; }
|
||||
|
||||
[JsonPropertyName("circulating_supply")]
|
||||
public double? CirculatingSupply { get; set; }
|
||||
|
||||
@@ -70,15 +61,6 @@ public class CmcResponseData
|
||||
[JsonPropertyName("max_supply")]
|
||||
public double? MaxSupply { get; set; }
|
||||
|
||||
[JsonPropertyName("last_updated")]
|
||||
public DateTime LastUpdated { get; set; }
|
||||
|
||||
[JsonPropertyName("date_added")]
|
||||
public DateTime DateAdded { get; set; }
|
||||
|
||||
[JsonPropertyName("tags")]
|
||||
public List<string> Tags { get; set; }
|
||||
|
||||
[JsonPropertyName("quote")]
|
||||
public Dictionary<string, CmcQuote> Quote { get; set; }
|
||||
}
|
@@ -7,7 +7,7 @@ namespace NadekoBot.Modules.Utility.Patronage;
|
||||
public partial class PatronConfigData : ICloneable<PatronConfigData>
|
||||
{
|
||||
[Comment("DO NOT CHANGE")]
|
||||
public int Version { get; set; } = 1;
|
||||
public int Version { get; set; } = 2;
|
||||
|
||||
[Comment("Whether the patronage feature is enabled")]
|
||||
public bool IsEnabled { get; set; }
|
||||
|
@@ -18,5 +18,19 @@ public class PatronageConfig : ConfigServiceBase<PatronConfigData>
|
||||
x => x.IsEnabled,
|
||||
bool.TryParse,
|
||||
ConfigPrinters.ToString);
|
||||
|
||||
Migrate();
|
||||
}
|
||||
|
||||
private void Migrate()
|
||||
{
|
||||
ModifyConfig(c =>
|
||||
{
|
||||
if (c.Version == 1)
|
||||
{
|
||||
c.Version = 2;
|
||||
c.IsEnabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@@ -3,6 +3,7 @@ using LinqToDB.EntityFrameworkCore;
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Db.Models;
|
||||
using OneOf;
|
||||
using OneOf.Types;
|
||||
using StackExchange.Redis;
|
||||
using CommandInfo = Discord.Commands.CommandInfo;
|
||||
|
||||
@@ -500,8 +501,8 @@ public sealed class PatronageService
|
||||
if (!confData.IsEnabled)
|
||||
return default;
|
||||
|
||||
if (_creds.IsOwner(userId))
|
||||
return default;
|
||||
// if (_creds.IsOwner(userId))
|
||||
// return default;
|
||||
|
||||
// get user tier
|
||||
var patron = await GetPatronAsync(userId);
|
||||
@@ -558,7 +559,9 @@ public sealed class PatronageService
|
||||
data.TryGetValue(QuotaPer.PerMonth, out var monthly) ? monthly : null
|
||||
);
|
||||
|
||||
return quotaCheckResult.Match(_ => default, x => x);
|
||||
return quotaCheckResult.Match<OneOf<Success, InsufficientTier, QuotaLimit>>(
|
||||
_ => new Success(),
|
||||
x => x);
|
||||
}
|
||||
|
||||
private bool TryGetTierDataOrLower<T>(
|
||||
@@ -697,7 +700,7 @@ public sealed class PatronageService
|
||||
return new()
|
||||
{
|
||||
Name = key.PrettyName,
|
||||
Quota = default,
|
||||
Quota = defaultValue,
|
||||
IsPatronLimit = false
|
||||
};
|
||||
|
||||
|
@@ -1,17 +1,20 @@
|
||||
#nullable disable
|
||||
using NadekoBot.Common.ModuleBehaviors;
|
||||
using NadekoBot.Db;
|
||||
using NadekoBot.Modules.Utility.Common;
|
||||
using NadekoBot.Modules.Utility.Common.Exceptions;
|
||||
using NadekoBot.Services.Database.Models;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
|
||||
namespace NadekoBot.Modules.Utility.Services;
|
||||
|
||||
public class StreamRoleService : INService
|
||||
public class StreamRoleService : IReadyExecutor, INService
|
||||
{
|
||||
private readonly DbService _db;
|
||||
private readonly DiscordSocketClient _client;
|
||||
private readonly ConcurrentDictionary<ulong, StreamRoleSettings> _guildSettings;
|
||||
private readonly QueueRunner _queueRunner;
|
||||
|
||||
public StreamRoleService(DiscordSocketClient client, DbService db, Bot bot)
|
||||
{
|
||||
@@ -22,33 +25,35 @@ public class StreamRoleService : INService
|
||||
.Where(x => x.Value is { Enabled: true })
|
||||
.ToConcurrent();
|
||||
|
||||
_client.GuildMemberUpdated += Client_GuildMemberUpdated;
|
||||
_client.PresenceUpdated += OnPresenceUpdate;
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await client.Guilds.Select(g => RescanUsers(g)).WhenAll();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
});
|
||||
_queueRunner = new QueueRunner();
|
||||
}
|
||||
|
||||
private Task Client_GuildMemberUpdated(Cacheable<SocketGuildUser, ulong> cacheable, SocketGuildUser after)
|
||||
private Task OnPresenceUpdate(SocketUser user, SocketPresence oldPresence, SocketPresence newPresence)
|
||||
{
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
//if user wasn't streaming or didn't have a game status at all
|
||||
if (_guildSettings.TryGetValue(after.Guild.Id, out var setting))
|
||||
await RescanUser(after, setting);
|
||||
if (oldPresence.Activities.Count != newPresence.Activities.Count)
|
||||
{
|
||||
var guildUsers = _client.Guilds
|
||||
.Select(x => x.GetUser(user.Id));
|
||||
|
||||
foreach (var guildUser in guildUsers)
|
||||
{
|
||||
if (_guildSettings.TryGetValue(guildUser.Guild.Id, out var s))
|
||||
await RescanUser(guildUser, s);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task OnReadyAsync()
|
||||
=> Task.WhenAll(_client.Guilds.Select(RescanUsers).WhenAll(), _queueRunner.RunAsync());
|
||||
|
||||
/// <summary>
|
||||
/// Adds or removes a user from a blacklist or a whitelist in the specified guild.
|
||||
/// </summary>
|
||||
@@ -135,7 +140,7 @@ public class StreamRoleService : INService
|
||||
|
||||
streamRoleSettings.Keyword = keyword;
|
||||
UpdateCache(guild.Id, streamRoleSettings);
|
||||
uow.SaveChanges();
|
||||
await uow.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await RescanUsers(guild);
|
||||
@@ -191,8 +196,7 @@ public class StreamRoleService : INService
|
||||
|
||||
foreach (var usr in await fromRole.GetMembersAsync())
|
||||
{
|
||||
if (usr is { } x)
|
||||
await RescanUser(x, setting, addRole);
|
||||
await RescanUser(usr, setting, addRole);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +220,10 @@ public class StreamRoleService : INService
|
||||
await RescanUsers(guild);
|
||||
}
|
||||
|
||||
private async Task RescanUser(IGuildUser user, StreamRoleSettings setting, IRole addRole = null)
|
||||
private async ValueTask RescanUser(IGuildUser user, StreamRoleSettings setting, IRole addRole = null)
|
||||
=> await _queueRunner.Enqueue(() => RescanUserInternal(user, setting, addRole));
|
||||
|
||||
private async Task RescanUserInternal(IGuildUser user, StreamRoleSettings setting, IRole addRole = null)
|
||||
{
|
||||
if (user.IsBot)
|
||||
return;
|
||||
@@ -232,58 +239,77 @@ public class StreamRoleService : INService
|
||||
&& setting.Blacklist.All(x => x.UserId != user.Id)
|
||||
&& user.RoleIds.Contains(setting.FromRoleId))
|
||||
{
|
||||
try
|
||||
await _queueRunner.Enqueue(async () =>
|
||||
{
|
||||
addRole ??= user.Guild.GetRole(setting.AddRoleId);
|
||||
if (addRole is null)
|
||||
try
|
||||
{
|
||||
addRole ??= user.Guild.GetRole(setting.AddRoleId);
|
||||
if (addRole is null)
|
||||
{
|
||||
await StopStreamRole(user.Guild);
|
||||
Log.Warning("Stream role in server {RoleId} no longer exists. Stopping", setting.AddRoleId);
|
||||
return;
|
||||
}
|
||||
|
||||
//check if he doesn't have addrole already, to avoid errors
|
||||
if (!user.RoleIds.Contains(addRole.Id))
|
||||
{
|
||||
await user.AddRoleAsync(addRole);
|
||||
Log.Information("Added stream role to user {User} in {Server} server",
|
||||
user.ToString(),
|
||||
user.Guild.ToString());
|
||||
}
|
||||
}
|
||||
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
await StopStreamRole(user.Guild);
|
||||
Log.Warning("Stream role in server {RoleId} no longer exists. Stopping", setting.AddRoleId);
|
||||
return;
|
||||
Log.Warning(ex, "Error adding stream role(s). Forcibly disabling stream role feature");
|
||||
throw new StreamRolePermissionException();
|
||||
}
|
||||
|
||||
//check if he doesn't have addrole already, to avoid errors
|
||||
if (!user.RoleIds.Contains(addRole.Id))
|
||||
catch (Exception ex)
|
||||
{
|
||||
await user.AddRoleAsync(addRole);
|
||||
Log.Information("Added stream role to user {User} in {Server} server",
|
||||
user.ToString(),
|
||||
user.Guild.ToString());
|
||||
Log.Warning(ex, "Failed adding stream role");
|
||||
}
|
||||
}
|
||||
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
await StopStreamRole(user.Guild);
|
||||
Log.Warning(ex, "Error adding stream role(s). Forcibly disabling stream role feature");
|
||||
throw new StreamRolePermissionException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "Failed adding stream role");
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
//check if user is in the addrole
|
||||
if (user.RoleIds.Contains(setting.AddRoleId))
|
||||
{
|
||||
try
|
||||
await _queueRunner.Enqueue(async () =>
|
||||
{
|
||||
addRole ??= user.Guild.GetRole(setting.AddRoleId);
|
||||
if (addRole is null)
|
||||
throw new StreamRoleNotFoundException();
|
||||
try
|
||||
{
|
||||
addRole ??= user.Guild.GetRole(setting.AddRoleId);
|
||||
if (addRole is null)
|
||||
{
|
||||
await StopStreamRole(user.Guild);
|
||||
Log.Warning(
|
||||
"Addrole doesn't exist in {GuildId} server. Forcibly disabling stream role feature",
|
||||
user.Guild.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
await user.RemoveRoleAsync(addRole);
|
||||
Log.Information("Removed stream role from the user {User} in {Server} server",
|
||||
user.ToString(),
|
||||
user.Guild.ToString());
|
||||
}
|
||||
catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
await StopStreamRole(user.Guild);
|
||||
Log.Warning(ex, "Error removing stream role(s). Forcibly disabling stream role feature");
|
||||
throw new StreamRolePermissionException();
|
||||
}
|
||||
// need to check again in case queuer is taking too long to execute
|
||||
if (user.RoleIds.Contains(setting.AddRoleId))
|
||||
{
|
||||
await user.RemoveRoleAsync(addRole);
|
||||
}
|
||||
|
||||
Log.Information("Removed stream role from the user {User} in {Server} server",
|
||||
user.ToString(),
|
||||
user.Guild.ToString());
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
if (ex.HttpCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
await StopStreamRole(user.Guild);
|
||||
Log.Warning(ex, "Error removing stream role(s). Forcibly disabling stream role feature");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -103,8 +103,10 @@ public sealed class BotCredsProvider : IBotCredsProvider
|
||||
if (string.IsNullOrWhiteSpace(_creds.RedisOptions))
|
||||
_creds.RedisOptions = "127.0.0.1,syncTimeout=3000";
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_creds.CoinmarketcapApiKey))
|
||||
_creds.CoinmarketcapApiKey = "e79ec505-0913-439d-ae07-069e296a6079";
|
||||
// replace the old generated key with the shared key
|
||||
if (string.IsNullOrWhiteSpace(_creds.CoinmarketcapApiKey)
|
||||
|| _creds.CoinmarketcapApiKey.StartsWith("e79ec505-0913"))
|
||||
_creds.CoinmarketcapApiKey = "3077537c-7dfb-4d97-9a60-56fc9a9f5035";
|
||||
|
||||
_creds.TotalShards = _totalShards ?? _creds.TotalShards;
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@ namespace NadekoBot.Services;
|
||||
|
||||
public sealed class StatsService : IStatsService, IReadyExecutor, INService
|
||||
{
|
||||
public const string BOT_VERSION = "4.2.2";
|
||||
public const string BOT_VERSION = "4.2.6";
|
||||
|
||||
public string Author
|
||||
=> "Kwoth#2452";
|
||||
|
@@ -221,7 +221,7 @@ public static class Extensions
|
||||
|
||||
public static void Lap(this Stopwatch sw, string checkpoint)
|
||||
{
|
||||
Log.Information("Checkpoint {CheckPoint}: {Time}", checkpoint, sw.Elapsed.TotalMilliseconds);
|
||||
Log.Information("Checkpoint {CheckPoint}: {Time}ms", checkpoint, sw.Elapsed.TotalMilliseconds);
|
||||
sw.Restart();
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
# DO NOT CHANGE
|
||||
version: 1
|
||||
version: 2
|
||||
# Whether the patronage feature is enabled
|
||||
isEnabled: true
|
||||
isEnabled: false
|
||||
# List of patron only features and relevant quota data
|
||||
quotas:
|
||||
# Dictionary of feature names with their respective limits. Set to null for unlimited
|
||||
|
@@ -878,7 +878,7 @@
|
||||
"autodc_enable": "I will disconnect from the voice channel when there are no more tracks to play.",
|
||||
"autodc_disable": "I will no longer disconnect from the voice channel when there are no more tracks to play.",
|
||||
"timely_none": "Bot owner didn't specify a timely reward.",
|
||||
"timely_already_claimed": "You've already claimed your timely reward. You can get it again in {0}.",
|
||||
"timely_already_claimed": "You've already claimed your timely reward. You can get it again {0}.",
|
||||
"timely": "You've claimed your {0}. You can claim again in {1}h",
|
||||
"timely_set": "Users will be able to claim {0} every {1}h",
|
||||
"timely_set_none": "Users will not be able to claim any timely currency.",
|
||||
|
Reference in New Issue
Block a user