add: Added .honeypot command

fix: Fixed .betdraw not respecting max bet limit
This commit is contained in:
Kwoth
2024-06-27 04:27:04 +00:00
parent 49d557caec
commit ef471c32bb
20 changed files with 10898 additions and 31 deletions

View File

@@ -2,6 +2,19 @@
Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
## [5.1.1]
### Added
- Added `.honeypot` command, which automatically softbans (ban and immediate unban) any user who posts in that channel.
- Useful to auto softban bots who spam every channel upon joining
- Users who run commands or expressions won't be softbanned.
- Users who have ban member permissions are also excluded.
### Fixed
- Fixed `.betdraw` not respecting maxbet
## [5.1.0] - 25.06.2024
### Added
@@ -10,9 +23,9 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except da
- You can send natural language questions, queries or execute commands. For example "@Nadeko how's the weather in paris" and it will return `.we Paris` and run it for you.
- In case the bot can't execute a command using your query, It will fall back to your chatter bot, in case you have it enabled in data/games.yml. (Cleverbot or chatgpt)
- (It's far from perfect so please don't ask the bot to do dangerous things like banning or pruning)
- Requires Patreon subscription, after which you'll be able to run it on global @Nadeko bot. If you're selfhosting, you also will need to acquire the api key from <https://dashy.nadeko.bot/api> (coming soon(ish)...)
- Requires Patreon subscription, after which you'll be able to run it on global @Nadeko bot.
- Selfhosters: If you're selfhosting, you also will need to acquire the api key from <https://dashy.nadeko.bot/me> after pledging on patreon and put it in nadekoAiToken in creds.yml
- Added support for `gpt-4o` in `data/games.yml`
- Added nadekoAiToken to `creds.yml`
### Changed

View File

@@ -0,0 +1,11 @@
using System.ComponentModel.DataAnnotations;
namespace NadekoBot.Db.Models;
public class HoneypotChannel
{
[Key]
public ulong GuildId { get; set; }
public ulong ChannelId { get; set; }
}

View File

@@ -59,6 +59,7 @@ public abstract class NadekoContext : DbContext
public DbSet<TodoModel> Todos { get; set; }
public DbSet<ArchivedTodoListModel> TodosArchive { get; set; }
public DbSet<HoneypotChannel> HoneyPotChannels { get; set; }
// todo add guild colors
// public DbSet<GuildColors> GuildColors { get; set; }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,36 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.Mysql
{
/// <inheritdoc />
public partial class honeypot : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "honeypotchannels",
columns: table => new
{
guildid = table.Column<ulong>(type: "bigint unsigned", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
channelid = table.Column<ulong>(type: "bigint unsigned", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_honeypotchannels", x => x.guildid);
})
.Annotation("MySql:CharSet", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "honeypotchannels");
}
}
}

View File

@@ -1388,6 +1388,25 @@ namespace NadekoBot.Migrations.Mysql
b.ToTable("guildconfigs", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.HoneypotChannel", b =>
{
b.Property<ulong>("GuildId")
.ValueGeneratedOnAdd()
.HasColumnType("bigint unsigned")
.HasColumnName("guildid");
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<ulong>("GuildId"));
b.Property<ulong>("ChannelId")
.HasColumnType("bigint unsigned")
.HasColumnName("channelid");
b.HasKey("GuildId")
.HasName("pk_honeypotchannels");
b.ToTable("honeypotchannels", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.IgnoredLogItem", b =>
{
b.Property<int>("Id")

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,33 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations.PostgreSql
{
/// <inheritdoc />
public partial class honeypot : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "honeypotchannels",
columns: table => new
{
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_honeypotchannels", x => x.guildid);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "honeypotchannels");
}
}
}

View File

@@ -1387,6 +1387,23 @@ namespace NadekoBot.Migrations.PostgreSql
b.ToTable("guildconfigs", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.HoneypotChannel", b =>
{
b.Property<decimal>("GuildId")
.ValueGeneratedOnAdd()
.HasColumnType("numeric(20,0)")
.HasColumnName("guildid");
b.Property<decimal>("ChannelId")
.HasColumnType("numeric(20,0)")
.HasColumnName("channelid");
b.HasKey("GuildId")
.HasName("pk_honeypotchannels");
b.ToTable("honeypotchannels", (string)null);
});
modelBuilder.Entity("NadekoBot.Db.Models.IgnoredLogItem", b =>
{
b.Property<int>("Id")

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,34 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations
{
/// <inheritdoc />
public partial class honeypot : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "HoneyPotChannels",
columns: table => new
{
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_HoneyPotChannels", x => x.GuildId);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "HoneyPotChannels");
}
}
}

View File

@@ -1033,6 +1033,20 @@ namespace NadekoBot.Migrations
b.ToTable("GuildConfigs");
});
modelBuilder.Entity("NadekoBot.Db.Models.HoneypotChannel", b =>
{
b.Property<ulong>("GuildId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<ulong>("ChannelId")
.HasColumnType("INTEGER");
b.HasKey("GuildId");
b.ToTable("HoneyPotChannels");
});
modelBuilder.Entity("NadekoBot.Db.Models.IgnoredLogItem", b =>
{
b.Property<int>("Id")

View File

@@ -0,0 +1,94 @@
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Db.Models;
using System.Threading.Channels;
namespace NadekoBot.Modules.Administration.Honeypot;
public sealed class HoneyPotService : IHoneyPotService, IReadyExecutor, IExecNoCommand, INService
{
private readonly DbService _db;
private readonly CommandHandler _handler;
private ConcurrentHashSet<ulong> _channels = new();
private Channel<SocketGuildUser> _punishments = Channel.CreateBounded<SocketGuildUser>(
new BoundedChannelOptions(100)
{
FullMode = BoundedChannelFullMode.DropOldest,
SingleReader = true,
SingleWriter = false,
});
public HoneyPotService(DbService db, CommandHandler handler)
{
_db = db;
_handler = handler;
}
public async Task<bool> ToggleHoneypotChannel(ulong guildId, ulong channelId)
{
await using var uow = _db.GetDbContext();
var deleted = await uow.HoneyPotChannels
.Where(x => x.GuildId == guildId)
.DeleteWithOutputAsync();
if (deleted.Length > 0)
{
_channels.TryRemove(deleted[0].ChannelId);
return false;
}
await uow.HoneyPotChannels
.ToLinqToDBTable()
.InsertAsync(() => new HoneypotChannel
{
GuildId = guildId,
ChannelId = channelId
});
_channels.Add(channelId);
return true;
}
public async Task OnReadyAsync()
{
await using var uow = _db.GetDbContext();
var channels = await uow.HoneyPotChannels
.Select(x => x.ChannelId)
.ToListAsyncLinqToDB();
_channels = new(channels);
while (await _punishments.Reader.WaitToReadAsync())
{
while (_punishments.Reader.TryRead(out var user))
{
try
{
Log.Information("Honeypot caught user {User} [{UserId}]", user, user.Id);
await user.BanAsync();
}
catch (Exception e)
{
Log.Warning(e, "Failed banning {User} due to {Error}", user, e.Message);
}
await Task.Delay(1000);
}
}
}
public async Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg)
{
if (_channels.Contains(msg.Channel.Id) && msg.Author is SocketGuildUser sgu)
{
if (!sgu.GuildPermissions.BanMembers)
await _punishments.Writer.WriteAsync(sgu);
}
}
}

View File

@@ -0,0 +1,29 @@
using NadekoBot.Modules.Administration.Honeypot;
namespace NadekoBot.Modules.Administration;
public partial class Administration
{
[Group]
public partial class HoneypotCommands : NadekoModule
{
private readonly IHoneyPotService _service;
public HoneypotCommands(IHoneyPotService service)
=> _service = service;
[Cmd]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.Administrator)]
[RequireBotPermission(GuildPermission.BanMembers)]
public async Task Honeypot()
{
var enabled = await _service.ToggleHoneypotChannel(ctx.Guild.Id, ctx.Channel.Id);
if (enabled)
await Response().Confirm(strs.honeypot_on).SendAsync();
else
await Response().Confirm(strs.honeypot_off).SendAsync();
}
}
}

View File

@@ -0,0 +1,6 @@
namespace NadekoBot.Modules.Administration.Honeypot;
public interface IHoneyPotService
{
public Task<bool> ToggleHoneypotChannel(ulong guildId, ulong channelId);
}

View File

@@ -17,7 +17,8 @@ public partial class Gambling
private static readonly ConcurrentDictionary<IGuild, Deck> _allDecks = new();
private readonly IImageCache _images;
public DrawCommands(IImageCache images, GamblingConfigService gcs) : base(gcs)
public DrawCommands(IImageCache images, GamblingConfigService gcs)
: base(gcs)
=> _images = images;
private async Task InternalDraw(int count, ulong? guildId = null)
@@ -56,7 +57,7 @@ public partial class Gambling
i.Dispose();
var eb = _sender.CreateEmbed()
.WithOkColor();
.WithOkColor();
var toSend = string.Empty;
if (cardObjects.Count == 5)
@@ -136,18 +137,28 @@ public partial class Gambling
[Cmd]
[RequireContext(ContextType.Guild)]
public Task BetDraw([OverrideTypeReader(typeof(BalanceTypeReader))] long amount, InputValueGuess val, InputColorGuess? col = null)
public Task BetDraw(
[OverrideTypeReader(typeof(BalanceTypeReader))]
long amount,
InputValueGuess val,
InputColorGuess? col = null)
=> BetDrawInternal(amount, val, col);
[Cmd]
[RequireContext(ContextType.Guild)]
public Task BetDraw([OverrideTypeReader(typeof(BalanceTypeReader))] long amount, InputColorGuess col, InputValueGuess? val = null)
public Task BetDraw(
[OverrideTypeReader(typeof(BalanceTypeReader))]
long amount,
InputColorGuess col,
InputValueGuess? val = null)
=> BetDrawInternal(amount, val, col);
public async Task BetDrawInternal(long amount, InputValueGuess? val, InputColorGuess? col)
{
if (amount <= 0)
if (!await CheckBetMandatory(amount))
{
return;
}
var res = await _service.BetDrawAsync(ctx.User.Id,
amount,
@@ -161,13 +172,13 @@ public partial class Gambling
}
var eb = _sender.CreateEmbed()
.WithOkColor()
.WithAuthor(ctx.User)
.WithDescription(result.Card.GetEmoji())
.AddField(GetText(strs.guess), GetGuessInfo(val, col), true)
.AddField(GetText(strs.card), GetCardInfo(result.Card), true)
.AddField(GetText(strs.won), N((long)result.Won), false)
.WithImageUrl("attachment://card.png");
.WithOkColor()
.WithAuthor(ctx.User)
.WithDescription(result.Card.GetEmoji())
.AddField(GetText(strs.guess), GetGuessInfo(val, col), true)
.AddField(GetText(strs.card), GetCardInfo(result.Card), true)
.AddField(GetText(strs.won), N((long)result.Won), false)
.WithImageUrl("attachment://card.png");
using var img = await GetCardImageAsync(result.Card);
await using var imgStream = await img.ToStreamAsync();
@@ -192,6 +203,7 @@ public partial class Gambling
return $"{val} / {col}";
}
private string GetCardInfo(RegularCard card)
{
var val = (int)card.Value switch

View File

@@ -12,8 +12,8 @@ public partial class ResponseBuilder
private readonly DiscordSocketClient _client;
private int currentPage;
private NadekoButtonInteractionHandler left;
private NadekoButtonInteractionHandler right;
private NadekoButtonInteractionHandler? left;
private NadekoButtonInteractionHandler? right;
private NadekoInteractionBase? extra;
public PaginationSender(
@@ -120,8 +120,8 @@ public partial class ResponseBuilder
if (_paginationBuilder.AddPaginatedFooter)
toSend.AddPaginatedFooter(currentPage, lastPage);
left.SetCompleted();
right.SetCompleted();
left?.SetCompleted();
right?.SetCompleted();
extra?.SetCompleted();
(left, extra, right) = (await GetInteractions());

View File

@@ -1401,3 +1401,5 @@ cleanupguilddata:
- cleanupguilddata
prompt:
- prompt
honeypot:
- honeypot

View File

@@ -4523,4 +4523,12 @@ prompt:
params:
- query:
desc: "The message to send to the bot."
honeypot:
desc: |-
Toggles honeypot on the current channel.
Anyone sending a message in this channel will be soft banned. (Banned and then unbanned)
This is useful for automatically getting rid of spam bots.
ex:
- ''
params:
- {}

View File

@@ -1101,5 +1101,7 @@
"todo_archived_list": "Archived Todo List",
"search_results": "Search results",
"queue_search_results": "Type the number of the search result to queue up that track.",
"overloads": "Overloads"
"overloads": "Overloads",
"honeypot_on": "Honeypot enabled on this channel." ,
"honeypot_off": "Honeypot disabled."
}