5.1.1
This commit is contained in:
commit
151c3d5a6a
36 changed files with 18322 additions and 7524 deletions
19
CHANGELOG.md
19
CHANGELOG.md
|
@ -2,15 +2,24 @@
|
||||||
|
|
||||||
Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
|
Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
|
||||||
|
|
||||||
|
## [5.1.1] - 29.06.2024
|
||||||
|
|
||||||
|
### 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
|
||||||
|
- Fixed `'xpshop` pagination for real this time?
|
||||||
|
|
||||||
## [5.1.0] - 28.06.2024
|
## [5.1.0] - 28.06.2024
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added `'prompt` command, Ellie Ai Assistant
|
|
||||||
- You can send natural language questions, queries or execute commands. For example "@Ellie 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 @Ellie bot. If you're selfhosting, you also will need to acquire the api key from <https://dashy.elliebot.net/api> (coming soon(ish)...)
|
|
||||||
- Added support for `gpt-4o` in `data/games.yml`
|
- Added support for `gpt-4o` in `data/games.yml`
|
||||||
- Added EllieAiToken to `creds.yml`
|
- Added EllieAiToken to `creds.yml`
|
||||||
|
|
||||||
|
|
|
@ -93,7 +93,7 @@ public sealed class Bot : IBot
|
||||||
private void AddServices()
|
private void AddServices()
|
||||||
{
|
{
|
||||||
var startingGuildIdList = GetCurrentGuildIds();
|
var startingGuildIdList = GetCurrentGuildIds();
|
||||||
var sw = Stopwatch.StartNew();
|
var startTime = Stopwatch.GetTimestamp();
|
||||||
var bot = Client.CurrentUser;
|
var bot = Client.CurrentUser;
|
||||||
|
|
||||||
using (var uow = _db.GetDbContext())
|
using (var uow = _db.GetDbContext())
|
||||||
|
@ -161,8 +161,7 @@ public sealed class Bot : IBot
|
||||||
LoadTypeReaders(a);
|
LoadTypeReaders(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
sw.Stop();
|
Log.Information("All services loaded in {ServiceLoadTime:F2}s", Stopwatch.GetElapsedTime(startTime).TotalSeconds);
|
||||||
Log.Information("All services loaded in {ServiceLoadTime:F2}s", sw.Elapsed.TotalSeconds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadTypeReaders(Assembly assembly)
|
private void LoadTypeReaders(Assembly assembly)
|
||||||
|
@ -259,7 +258,7 @@ public sealed class Bot : IBot
|
||||||
if (ShardId == 0)
|
if (ShardId == 0)
|
||||||
await _db.SetupAsync();
|
await _db.SetupAsync();
|
||||||
|
|
||||||
var sw = Stopwatch.StartNew();
|
var startTime = Stopwatch.GetTimestamp();
|
||||||
|
|
||||||
await LoginAsync(_creds.Token);
|
await LoginAsync(_creds.Token);
|
||||||
|
|
||||||
|
@ -274,8 +273,7 @@ public sealed class Bot : IBot
|
||||||
Helpers.ReadErrorAndExit(9);
|
Helpers.ReadErrorAndExit(9);
|
||||||
}
|
}
|
||||||
|
|
||||||
sw.Stop();
|
Log.Information("Shard {ShardId} connected in {Elapsed:F2}s", Client.ShardId, Stopwatch.GetElapsedTime(startTime).TotalSeconds);
|
||||||
Log.Information("Shard {ShardId} connected in {Elapsed:F2}s", Client.ShardId, sw.Elapsed.TotalSeconds);
|
|
||||||
var commandHandler = Services.GetRequiredService<CommandHandler>();
|
var commandHandler = Services.GetRequiredService<CommandHandler>();
|
||||||
|
|
||||||
// start handling messages received in commandhandler
|
// start handling messages received in commandhandler
|
||||||
|
|
|
@ -59,6 +59,7 @@ public abstract class EllieContext : DbContext
|
||||||
|
|
||||||
public DbSet<TodoModel> Todos { get; set; }
|
public DbSet<TodoModel> Todos { get; set; }
|
||||||
public DbSet<ArchivedTodoListModel> TodosArchive { get; set; }
|
public DbSet<ArchivedTodoListModel> TodosArchive { get; set; }
|
||||||
|
public DbSet<HoneypotChannel> HoneyPotChannels { get; set; }
|
||||||
|
|
||||||
// todo add guild colors
|
// todo add guild colors
|
||||||
// public DbSet<GuildColors> GuildColors { get; set; }
|
// public DbSet<GuildColors> GuildColors { get; set; }
|
||||||
|
|
11
src/EllieBot/Db/Models/HoneypotChannel.cs
Normal file
11
src/EllieBot/Db/Models/HoneypotChannel.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace EllieBot.Db.Models;
|
||||||
|
|
||||||
|
public class HoneypotChannel
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public ulong GuildId { get; set; }
|
||||||
|
|
||||||
|
public ulong ChannelId { get; set; }
|
||||||
|
}
|
|
@ -4,7 +4,7 @@
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>true</ImplicitUsings>
|
<ImplicitUsings>true</ImplicitUsings>
|
||||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||||
<Version>5.1.0</Version>
|
<Version>5.1.1</Version>
|
||||||
|
|
||||||
<!-- Output/build -->
|
<!-- Output/build -->
|
||||||
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
||||||
|
@ -27,79 +27,79 @@
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<Publish>True</Publish>
|
<Publish>True</Publish>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="CodeHollow.FeedReader" Version="1.2.6"/>
|
<PackageReference Include="CodeHollow.FeedReader" Version="1.2.6" />
|
||||||
<PackageReference Include="CommandLineParser" Version="2.9.1"/>
|
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||||
<PackageReference Include="Discord.Net" Version="3.204.0"/>
|
<PackageReference Include="Discord.Net" Version="3.204.0" />
|
||||||
<PackageReference Include="CoreCLR-NCalc" Version="3.1.246"/>
|
<PackageReference Include="CoreCLR-NCalc" Version="3.1.246" />
|
||||||
<PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.41.1.138"/>
|
<PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.41.1.138" />
|
||||||
<PackageReference Include="Google.Apis.YouTube.v3" Version="1.68.0.3414"/>
|
<PackageReference Include="Google.Apis.YouTube.v3" Version="1.68.0.3414" />
|
||||||
<PackageReference Include="Google.Apis.Customsearch.v1" Version="1.49.0.2084"/>
|
<PackageReference Include="Google.Apis.Customsearch.v1" Version="1.49.0.2084" />
|
||||||
<!-- <PackageReference Include="Grpc.AspNetCore" Version="2.62.0" />-->
|
<!-- <PackageReference Include="Grpc.AspNetCore" Version="2.62.0" />-->
|
||||||
<PackageReference Include="Google.Protobuf" Version="3.26.1"/>
|
<PackageReference Include="Google.Protobuf" Version="3.26.1" />
|
||||||
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.62.0"/>
|
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.62.0" />
|
||||||
<PackageReference Include="Grpc.Tools" Version="2.63.0">
|
<PackageReference Include="Grpc.Tools" Version="2.63.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.5.0"/>
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.5.0" />
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0"/>
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0"/>
|
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0"/>
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
|
||||||
|
|
||||||
<PackageReference Include="MorseCode.ITask" Version="2.0.3"/>
|
<PackageReference Include="MorseCode.ITask" Version="2.0.3" />
|
||||||
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.1.0"/>
|
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.1.0" />
|
||||||
|
|
||||||
<!-- DI -->
|
<!-- DI -->
|
||||||
<!-- <PackageReference Include="Ninject" Version="3.3.6"/>-->
|
<!-- <PackageReference Include="Ninject" Version="3.3.6"/>-->
|
||||||
<!-- <PackageReference Include="Ninject.Extensions.Conventions" Version="3.3.0"/>-->
|
<!-- <PackageReference Include="Ninject.Extensions.Conventions" Version="3.3.0"/>-->
|
||||||
<!-- <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />-->
|
<!-- <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />-->
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1"/>
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
|
||||||
<PackageReference Include="DryIoc.dll" Version="5.4.3"/>
|
<PackageReference Include="DryIoc.dll" Version="5.4.3" />
|
||||||
<!-- <PackageReference Include="Scrutor" Version="4.2.0" />-->
|
<!-- <PackageReference Include="Scrutor" Version="4.2.0" />-->
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0"/>
|
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0"/>
|
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0"/>
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2"/>
|
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="NonBlocking" Version="2.1.2"/>
|
<PackageReference Include="NonBlocking" Version="2.1.2" />
|
||||||
<PackageReference Include="OneOf" Version="3.0.263"/>
|
<PackageReference Include="OneOf" Version="3.0.263" />
|
||||||
<PackageReference Include="OneOf.SourceGenerator" Version="3.0.263"/>
|
<PackageReference Include="OneOf.SourceGenerator" Version="3.0.263" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1"/>
|
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
||||||
<PackageReference Include="Serilog.Sinks.Seq" Version="7.0.1"/>
|
<PackageReference Include="Serilog.Sinks.Seq" Version="7.0.1" />
|
||||||
|
|
||||||
<PackageReference Include="SixLabors.Fonts" Version="1.0.0-beta17"/>
|
<PackageReference Include="SixLabors.Fonts" Version="1.0.0-beta17" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.8"/>
|
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.8" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta14"/>
|
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta14" />
|
||||||
<PackageReference Include="SixLabors.Shapes" Version="1.0.0-beta0009"/>
|
<PackageReference Include="SixLabors.Shapes" Version="1.0.0-beta0009" />
|
||||||
<PackageReference Include="StackExchange.Redis" Version="2.7.33"/>
|
<PackageReference Include="StackExchange.Redis" Version="2.8.0" />
|
||||||
<PackageReference Include="YamlDotNet" Version="15.1.4"/>
|
<PackageReference Include="YamlDotNet" Version="15.1.4" />
|
||||||
<PackageReference Include="SharpToken" Version="2.0.3" />
|
<PackageReference Include="SharpToken" Version="2.0.3" />
|
||||||
|
|
||||||
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0"/>
|
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
|
||||||
|
|
||||||
|
|
||||||
<!-- Db-related packages -->
|
<!-- Db-related packages -->
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.4"/>
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.4" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.4">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.4">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|
||||||
<PackageReference Include="linq2db.EntityFrameworkCore" Version="8.1.0"/>
|
<PackageReference Include="linq2db.EntityFrameworkCore" Version="8.1.0" />
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.4"/>
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.4" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4"/>
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
|
||||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2"/>
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
|
||||||
|
|
||||||
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3"/>
|
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3" />
|
||||||
|
|
||||||
<!-- Used by stream notifications -->
|
<!-- Used by stream notifications -->
|
||||||
<PackageReference Include="TwitchLib.Api" Version="3.4.1"/>
|
<PackageReference Include="TwitchLib.Api" Version="3.4.1" />
|
||||||
|
|
||||||
<!-- sqlselectcsv and stock -->
|
<!-- sqlselectcsv and stock -->
|
||||||
<PackageReference Include="CsvHelper" Version="32.0.3"/>
|
<PackageReference Include="CsvHelper" Version="32.0.3" />
|
||||||
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
3803
src/EllieBot/Migrations/Mysql/20240627033532_honeypot.Designer.cs
generated
Normal file
3803
src/EllieBot/Migrations/Mysql/20240627033532_honeypot.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
36
src/EllieBot/Migrations/Mysql/20240627033532_honeypot.cs
Normal file
36
src/EllieBot/Migrations/Mysql/20240627033532_honeypot.cs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace EllieBot.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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
3798
src/EllieBot/Migrations/PostgreSql/20240627033522_honeypot.Designer.cs
generated
Normal file
3798
src/EllieBot/Migrations/PostgreSql/20240627033522_honeypot.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,33 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace EllieBot.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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
2935
src/EllieBot/Migrations/Sqlite/20240627033508_honeypot.Designer.cs
generated
Normal file
2935
src/EllieBot/Migrations/Sqlite/20240627033508_honeypot.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
34
src/EllieBot/Migrations/Sqlite/20240627033508_honeypot.cs
Normal file
34
src/EllieBot/Migrations/Sqlite/20240627033508_honeypot.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace EllieBot.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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,94 @@
|
||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
|
using EllieBot.Db.Models;
|
||||||
|
using System.Threading.Channels;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Administration.Honeypot;
|
||||||
|
|
||||||
|
public sealed class HoneyPotService : IHoneyPotService, IReadyExecutor, IExecNoCommand, IEService
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
using EllieBot.Modules.Administration.Honeypot;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Administration;
|
||||||
|
|
||||||
|
public partial class Administration
|
||||||
|
{
|
||||||
|
[Group]
|
||||||
|
public partial class HoneypotCommands : EllieModule
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace EllieBot.Modules.Administration.Honeypot;
|
||||||
|
|
||||||
|
public interface IHoneyPotService
|
||||||
|
{
|
||||||
|
public Task<bool> ToggleHoneypotChannel(ulong guildId, ulong channelId);
|
||||||
|
}
|
|
@ -17,7 +17,8 @@ public partial class Gambling
|
||||||
private static readonly ConcurrentDictionary<IGuild, Deck> _allDecks = new();
|
private static readonly ConcurrentDictionary<IGuild, Deck> _allDecks = new();
|
||||||
private readonly IImageCache _images;
|
private readonly IImageCache _images;
|
||||||
|
|
||||||
public DrawCommands(IImageCache images, GamblingConfigService gcs) : base(gcs)
|
public DrawCommands(IImageCache images, GamblingConfigService gcs)
|
||||||
|
: base(gcs)
|
||||||
=> _images = images;
|
=> _images = images;
|
||||||
|
|
||||||
private async Task InternalDraw(int count, ulong? guildId = null)
|
private async Task InternalDraw(int count, ulong? guildId = null)
|
||||||
|
@ -56,8 +57,8 @@ public partial class Gambling
|
||||||
i.Dispose();
|
i.Dispose();
|
||||||
|
|
||||||
var eb = _sender.CreateEmbed()
|
var eb = _sender.CreateEmbed()
|
||||||
.WithOkColor();
|
.WithOkColor();
|
||||||
|
|
||||||
var toSend = string.Empty;
|
var toSend = string.Empty;
|
||||||
if (cardObjects.Count == 5)
|
if (cardObjects.Count == 5)
|
||||||
eb.AddField(GetText(strs.hand_value), Deck.GetHandValue(cardObjects), true);
|
eb.AddField(GetText(strs.hand_value), Deck.GetHandValue(cardObjects), true);
|
||||||
|
@ -71,7 +72,7 @@ public partial class Gambling
|
||||||
|
|
||||||
if (count > 1)
|
if (count > 1)
|
||||||
eb.AddField(GetText(strs.cards), count.ToString(), true);
|
eb.AddField(GetText(strs.cards), count.ToString(), true);
|
||||||
|
|
||||||
await using var imageStream = await img.ToStreamAsync();
|
await using var imageStream = await img.ToStreamAsync();
|
||||||
await ctx.Channel.SendFileAsync(imageStream,
|
await ctx.Channel.SendFileAsync(imageStream,
|
||||||
imgName,
|
imgName,
|
||||||
|
@ -84,7 +85,7 @@ public partial class Gambling
|
||||||
var cardBytes = await File.ReadAllBytesAsync($"data/images/cards/{cardName}.jpg");
|
var cardBytes = await File.ReadAllBytesAsync($"data/images/cards/{cardName}.jpg");
|
||||||
return Image.Load<Rgba32>(cardBytes);
|
return Image.Load<Rgba32>(cardBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Image<Rgba32>> GetCardImageAsync(Deck.Card currentCard)
|
private async Task<Image<Rgba32>> GetCardImageAsync(Deck.Card currentCard)
|
||||||
{
|
{
|
||||||
var cardName = currentCard.ToString().ToLowerInvariant().Replace(' ', '_');
|
var cardName = currentCard.ToString().ToLowerInvariant().Replace(' ', '_');
|
||||||
|
@ -98,7 +99,7 @@ public partial class Gambling
|
||||||
{
|
{
|
||||||
if (num < 1)
|
if (num < 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (num > 10)
|
if (num > 10)
|
||||||
num = 10;
|
num = 10;
|
||||||
|
|
||||||
|
@ -110,7 +111,7 @@ public partial class Gambling
|
||||||
{
|
{
|
||||||
if (num < 1)
|
if (num < 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (num > 10)
|
if (num > 10)
|
||||||
num = 10;
|
num = 10;
|
||||||
|
|
||||||
|
@ -136,19 +137,29 @@ public partial class Gambling
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[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);
|
=> BetDrawInternal(amount, val, col);
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[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);
|
=> BetDrawInternal(amount, val, col);
|
||||||
|
|
||||||
public async Task BetDrawInternal(long amount, InputValueGuess? val, InputColorGuess? col)
|
public async Task BetDrawInternal(long amount, InputValueGuess? val, InputColorGuess? col)
|
||||||
{
|
{
|
||||||
if (amount <= 0)
|
if (!await CheckBetMandatory(amount))
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var res = await _service.BetDrawAsync(ctx.User.Id,
|
var res = await _service.BetDrawAsync(ctx.User.Id,
|
||||||
amount,
|
amount,
|
||||||
(byte?)val,
|
(byte?)val,
|
||||||
|
@ -161,13 +172,13 @@ public partial class Gambling
|
||||||
}
|
}
|
||||||
|
|
||||||
var eb = _sender.CreateEmbed()
|
var eb = _sender.CreateEmbed()
|
||||||
.WithOkColor()
|
.WithOkColor()
|
||||||
.WithAuthor(ctx.User)
|
.WithAuthor(ctx.User)
|
||||||
.WithDescription(result.Card.GetEmoji())
|
.WithDescription(result.Card.GetEmoji())
|
||||||
.AddField(GetText(strs.guess), GetGuessInfo(val, col), true)
|
.AddField(GetText(strs.guess), GetGuessInfo(val, col), true)
|
||||||
.AddField(GetText(strs.card), GetCardInfo(result.Card), true)
|
.AddField(GetText(strs.card), GetCardInfo(result.Card), true)
|
||||||
.AddField(GetText(strs.won), N((long)result.Won), false)
|
.AddField(GetText(strs.won), N((long)result.Won), false)
|
||||||
.WithImageUrl("attachment://card.png");
|
.WithImageUrl("attachment://card.png");
|
||||||
|
|
||||||
using var img = await GetCardImageAsync(result.Card);
|
using var img = await GetCardImageAsync(result.Card);
|
||||||
await using var imgStream = await img.ToStreamAsync();
|
await using var imgStream = await img.ToStreamAsync();
|
||||||
|
@ -189,9 +200,10 @@ public partial class Gambling
|
||||||
InputColorGuess.Black => "B ⚫",
|
InputColorGuess.Black => "B ⚫",
|
||||||
_ => "❓"
|
_ => "❓"
|
||||||
};
|
};
|
||||||
|
|
||||||
return $"{val} / {col}";
|
return $"{val} / {col}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetCardInfo(RegularCard card)
|
private string GetCardInfo(RegularCard card)
|
||||||
{
|
{
|
||||||
var val = (int)card.Value switch
|
var val = (int)card.Value switch
|
||||||
|
@ -208,7 +220,7 @@ public partial class Gambling
|
||||||
RegularSuit.Diamonds or RegularSuit.Hearts => "R 🔴",
|
RegularSuit.Diamonds or RegularSuit.Hearts => "R 🔴",
|
||||||
_ => "B ⚫"
|
_ => "B ⚫"
|
||||||
};
|
};
|
||||||
|
|
||||||
return $"{val} / {col}";
|
return $"{val} / {col}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -165,7 +165,7 @@ public class ChatterBotService : IExecOnMessage
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Log.Warning("Error in chatterbot: {Error}", error);
|
Log.Warning("Error in chatterbot: {Error}", error.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Information("""
|
Log.Information("""
|
||||||
|
|
|
@ -103,6 +103,8 @@ public class OfficialGptSession : IChatterBotSession
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = JsonConvert.DeserializeObject<OpenAiCompletionResponse>(dataString);
|
var response = JsonConvert.DeserializeObject<OpenAiCompletionResponse>(dataString);
|
||||||
|
|
||||||
|
Log.Information("Received response: {response}", dataString);
|
||||||
var res = response?.Choices?[0];
|
var res = response?.Choices?[0];
|
||||||
var message = res?.Message?.Content;
|
var message = res?.Message?.Content;
|
||||||
|
|
||||||
|
|
|
@ -32,20 +32,21 @@ public sealed class SearxSearchService : SearchServiceBase, IEService
|
||||||
var instanceUrl = GetRandomInstance();
|
var instanceUrl = GetRandomInstance();
|
||||||
|
|
||||||
Log.Information("Using {Instance} instance for web search...", instanceUrl);
|
Log.Information("Using {Instance} instance for web search...", instanceUrl);
|
||||||
var sw = Stopwatch.StartNew();
|
var startTime = Stopwatch.GetTimestamp();
|
||||||
|
|
||||||
using var http = _http.CreateClient();
|
using var http = _http.CreateClient();
|
||||||
await using var res = await http.GetStreamAsync($"{instanceUrl}"
|
await using var res = await http.GetStreamAsync($"{instanceUrl}"
|
||||||
+ $"?q={Uri.EscapeDataString(query)}"
|
+ $"?q={Uri.EscapeDataString(query)}"
|
||||||
+ $"&format=json"
|
+ $"&format=json"
|
||||||
+ $"&strict=2");
|
+ $"&strict=2");
|
||||||
|
|
||||||
sw.Stop();
|
var elapsed = Stopwatch.GetElapsedTime(startTime);
|
||||||
var dat = await JsonSerializer.DeserializeAsync<SearxSearchResult>(res);
|
var dat = await JsonSerializer.DeserializeAsync<SearxSearchResult>(res);
|
||||||
|
|
||||||
if (dat is null)
|
if (dat is null)
|
||||||
return new SearxSearchResult();
|
return new SearxSearchResult();
|
||||||
|
|
||||||
dat.SearchTime = sw.Elapsed.TotalSeconds.ToString("N2", CultureInfo.InvariantCulture);
|
dat.SearchTime = elapsed.TotalSeconds.ToString("N2", CultureInfo.InvariantCulture);
|
||||||
return dat;
|
return dat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +57,7 @@ public sealed class SearxSearchService : SearchServiceBase, IEService
|
||||||
var instanceUrl = GetRandomInstance();
|
var instanceUrl = GetRandomInstance();
|
||||||
|
|
||||||
Log.Information("Using {Instance} instance for img search...", instanceUrl);
|
Log.Information("Using {Instance} instance for img search...", instanceUrl);
|
||||||
var sw = Stopwatch.StartNew();
|
var startTime = Stopwatch.GetTimestamp();
|
||||||
using var http = _http.CreateClient();
|
using var http = _http.CreateClient();
|
||||||
await using var res = await http.GetStreamAsync($"{instanceUrl}"
|
await using var res = await http.GetStreamAsync($"{instanceUrl}"
|
||||||
+ $"?q={Uri.EscapeDataString(query)}"
|
+ $"?q={Uri.EscapeDataString(query)}"
|
||||||
|
@ -64,13 +65,13 @@ public sealed class SearxSearchService : SearchServiceBase, IEService
|
||||||
+ $"&category_images=on"
|
+ $"&category_images=on"
|
||||||
+ $"&strict=2");
|
+ $"&strict=2");
|
||||||
|
|
||||||
sw.Stop();
|
var elapsed = Stopwatch.GetElapsedTime(startTime);
|
||||||
var dat = await JsonSerializer.DeserializeAsync<SearxImageSearchResult>(res);
|
var dat = await JsonSerializer.DeserializeAsync<SearxImageSearchResult>(res);
|
||||||
|
|
||||||
if (dat is null)
|
if (dat is null)
|
||||||
return new SearxImageSearchResult();
|
return new SearxImageSearchResult();
|
||||||
|
|
||||||
dat.SearchTime = sw.Elapsed.TotalSeconds.ToString("N2", CultureInfo.InvariantCulture);
|
dat.SearchTime = elapsed.TotalSeconds.ToString("N2", CultureInfo.InvariantCulture);
|
||||||
return dat;
|
return dat;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,9 +8,9 @@ public sealed class CommandPromptResultModel
|
||||||
public required string Name { get; set; }
|
public required string Name { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("arguments")]
|
[JsonPropertyName("arguments")]
|
||||||
public required Dictionary<string, string> Arguments { get; set; }
|
public Dictionary<string, string> Arguments { get; set; } = new();
|
||||||
|
|
||||||
[JsonPropertyName("remaining")]
|
[JsonPropertyName("remaining")]
|
||||||
[JsonConverter(typeof(NumberToStringConverter))]
|
[JsonConverter(typeof(NumberToStringConverter))]
|
||||||
public required string Remaining { get; set; }
|
public string Remaining { get; set; } = string.Empty;
|
||||||
}
|
}
|
|
@ -480,7 +480,8 @@ public partial class Xp : EllieModule<XpService>
|
||||||
ctx.User.Id,
|
ctx.User.Id,
|
||||||
button,
|
button,
|
||||||
OnShopUse,
|
OnShopUse,
|
||||||
(key, itemType));
|
(key, itemType),
|
||||||
|
clearAfter: false);
|
||||||
|
|
||||||
return inter;
|
return inter;
|
||||||
}
|
}
|
||||||
|
@ -494,7 +495,9 @@ public partial class Xp : EllieModule<XpService>
|
||||||
ctx.User.Id,
|
ctx.User.Id,
|
||||||
button,
|
button,
|
||||||
OnShopBuy,
|
OnShopBuy,
|
||||||
(key, itemType));
|
(key, itemType),
|
||||||
|
singleUse: true,
|
||||||
|
clearAfter: false);
|
||||||
|
|
||||||
return inter;
|
return inter;
|
||||||
}
|
}
|
||||||
|
@ -577,6 +580,10 @@ public partial class Xp : EllieModule<XpService>
|
||||||
{
|
{
|
||||||
await Response().Error(strs.not_enough(_gss.GetCurrencySign())).SendAsync();
|
await Response().Error(strs.not_enough(_gss.GetCurrencySign())).SendAsync();
|
||||||
}
|
}
|
||||||
|
else if (result == BuyResult.Success)
|
||||||
|
{
|
||||||
|
await _service.UseShopItemAsync(ctx.User.Id, type, key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetNotifLocationString(XpNotificationLocation loc)
|
private string GetNotifLocationString(XpNotificationLocation loc)
|
||||||
|
|
|
@ -133,52 +133,8 @@ public sealed class BotCredsProvider : IBotCredsProvider
|
||||||
File.WriteAllText(CREDS_FILE_NAME, ymlData);
|
File.WriteAllText(CREDS_FILE_NAME, ymlData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string OldCredsJsonPath
|
|
||||||
=> Path.Combine(Directory.GetCurrentDirectory(), "credentials.json");
|
|
||||||
|
|
||||||
private string OldCredsJsonBackupPath
|
|
||||||
=> Path.Combine(Directory.GetCurrentDirectory(), "credentials.json.bak");
|
|
||||||
|
|
||||||
private void MigrateCredentials()
|
private void MigrateCredentials()
|
||||||
{
|
{
|
||||||
if (File.Exists(OldCredsJsonPath))
|
|
||||||
{
|
|
||||||
Log.Information("Migrating old creds...");
|
|
||||||
var jsonCredentialsFileText = File.ReadAllText(OldCredsJsonPath);
|
|
||||||
var oldCreds = JsonConvert.DeserializeObject<OldCreds>(jsonCredentialsFileText);
|
|
||||||
|
|
||||||
if (oldCreds is null)
|
|
||||||
{
|
|
||||||
Log.Error("Error while reading old credentials file. Make sure that the file is formatted correctly");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var creds = new Creds
|
|
||||||
{
|
|
||||||
Version = 1,
|
|
||||||
Token = oldCreds.Token,
|
|
||||||
OwnerIds = oldCreds.OwnerIds.Distinct().ToHashSet(),
|
|
||||||
GoogleApiKey = oldCreds.GoogleApiKey,
|
|
||||||
RapidApiKey = oldCreds.MashapeKey,
|
|
||||||
OsuApiKey = oldCreds.OsuApiKey,
|
|
||||||
CleverbotApiKey = oldCreds.CleverbotApiKey,
|
|
||||||
TotalShards = oldCreds.TotalShards <= 1 ? 1 : oldCreds.TotalShards,
|
|
||||||
Patreon = new Creds.PatreonSettings(oldCreds.PatreonAccessToken, null, null, oldCreds.PatreonCampaignId),
|
|
||||||
Votes = new Creds.VotesSettings(oldCreds.VotesUrl, oldCreds.VotesToken, string.Empty, string.Empty),
|
|
||||||
BotListToken = oldCreds.BotListToken,
|
|
||||||
RedisOptions = oldCreds.RedisOptions,
|
|
||||||
LocationIqApiKey = oldCreds.LocationIqApiKey,
|
|
||||||
TimezoneDbApiKey = oldCreds.TimezoneDbApiKey,
|
|
||||||
CoinmarketcapApiKey = oldCreds.CoinmarketcapApiKey
|
|
||||||
};
|
|
||||||
|
|
||||||
File.Move(OldCredsJsonPath, OldCredsJsonBackupPath, true);
|
|
||||||
File.WriteAllText(CredsPath, Yaml.Serializer.Serialize(creds));
|
|
||||||
|
|
||||||
Log.Warning(
|
|
||||||
"Data from credentials.json has been moved to creds.yml\nPlease inspect your creds.yml for correctness");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (File.Exists(CREDS_FILE_NAME))
|
if (File.Exists(CREDS_FILE_NAME))
|
||||||
{
|
{
|
||||||
var creds = Yaml.Deserializer.Deserialize<Creds>(File.ReadAllText(CREDS_FILE_NAME));
|
var creds = Yaml.Deserializer.Deserialize<Creds>(File.ReadAllText(CREDS_FILE_NAME));
|
||||||
|
@ -192,9 +148,9 @@ public sealed class BotCredsProvider : IBotCredsProvider
|
||||||
File.WriteAllText(CREDS_FILE_NAME, Yaml.Serializer.Serialize(creds));
|
File.WriteAllText(CREDS_FILE_NAME, Yaml.Serializer.Serialize(creds));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (creds.Version <= 7)
|
if (creds.Version <= 8)
|
||||||
{
|
{
|
||||||
creds.Version = 8;
|
creds.Version = 9;
|
||||||
File.WriteAllText(CREDS_FILE_NAME, Yaml.Serializer.Serialize(creds));
|
File.WriteAllText(CREDS_FILE_NAME, Yaml.Serializer.Serialize(creds));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ public sealed class Creds : IBotCredentials
|
||||||
|
|
||||||
[Comment("""
|
[Comment("""
|
||||||
Pledge 5$ or more on https://patreon.com/elliebot and connect your discord account to Patreon.
|
Pledge 5$ or more on https://patreon.com/elliebot and connect your discord account to Patreon.
|
||||||
Go to https://dashy.elliebot.net and login with your discord account
|
Go to https://dashy.elliebot.net/me and login with your discord account
|
||||||
Go to the Keys page and click "Generate New Key" and copy it here
|
Go to the Keys page and click "Generate New Key" and copy it here
|
||||||
You and anyone else with the permission to run `.prompt` command will be able to use natural language to run bot's commands.
|
You and anyone else with the permission to run `.prompt` command will be able to use natural language to run bot's commands.
|
||||||
For example '@Bot how's the weather in Paris' will return the current weather in Paris as if you were to run `.weather Paris` command
|
For example '@Bot how's the weather in Paris' will return the current weather in Paris as if you were to run `.weather Paris` command
|
||||||
|
@ -156,7 +156,7 @@ public sealed class Creds : IBotCredentials
|
||||||
|
|
||||||
public Creds()
|
public Creds()
|
||||||
{
|
{
|
||||||
Version = 7;
|
Version = 9;
|
||||||
Token = string.Empty;
|
Token = string.Empty;
|
||||||
UsePrivilegedIntents = true;
|
UsePrivilegedIntents = true;
|
||||||
OwnerIds = new List<ulong>();
|
OwnerIds = new List<ulong>();
|
||||||
|
|
|
@ -12,6 +12,7 @@ public abstract class EllieInteractionBase
|
||||||
private IUserMessage message = null!;
|
private IUserMessage message = null!;
|
||||||
private readonly string _customId;
|
private readonly string _customId;
|
||||||
private readonly bool _singleUse;
|
private readonly bool _singleUse;
|
||||||
|
private readonly bool _clearAfter;
|
||||||
|
|
||||||
public EllieInteractionBase(
|
public EllieInteractionBase(
|
||||||
DiscordSocketClient client,
|
DiscordSocketClient client,
|
||||||
|
@ -19,13 +20,16 @@ public abstract class EllieInteractionBase
|
||||||
string customId,
|
string customId,
|
||||||
Func<SocketMessageComponent, Task> onAction,
|
Func<SocketMessageComponent, Task> onAction,
|
||||||
bool onlyAuthor,
|
bool onlyAuthor,
|
||||||
bool singleUse = true)
|
bool singleUse = true,
|
||||||
|
bool clearAfter = true)
|
||||||
{
|
{
|
||||||
_authorId = authorId;
|
_authorId = authorId;
|
||||||
_customId = customId;
|
_customId = customId;
|
||||||
_onAction = onAction;
|
_onAction = onAction;
|
||||||
_onlyAuthor = onlyAuthor;
|
_onlyAuthor = onlyAuthor;
|
||||||
_singleUse = singleUse;
|
_singleUse = singleUse;
|
||||||
|
_clearAfter = clearAfter;
|
||||||
|
|
||||||
_interactionCompletedSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
_interactionCompletedSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
Client = client;
|
Client = client;
|
||||||
|
@ -36,13 +40,11 @@ public abstract class EllieInteractionBase
|
||||||
message = msg;
|
message = msg;
|
||||||
|
|
||||||
Client.InteractionCreated += OnInteraction;
|
Client.InteractionCreated += OnInteraction;
|
||||||
if (_singleUse)
|
await Task.WhenAny(Task.Delay(30_000), _interactionCompletedSource.Task);
|
||||||
await Task.WhenAny(Task.Delay(30_000), _interactionCompletedSource.Task);
|
|
||||||
else
|
|
||||||
await Task.Delay(30_000);
|
|
||||||
Client.InteractionCreated -= OnInteraction;
|
Client.InteractionCreated -= OnInteraction;
|
||||||
|
|
||||||
await msg.ModifyAsync(m => m.Components = new ComponentBuilder().Build());
|
if (_clearAfter)
|
||||||
|
await msg.ModifyAsync(m => m.Components = new ComponentBuilder().Build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task OnInteraction(SocketInteraction arg)
|
private Task OnInteraction(SocketInteraction arg)
|
||||||
|
@ -59,11 +61,15 @@ public abstract class EllieInteractionBase
|
||||||
if (smc.Data.CustomId != _customId)
|
if (smc.Data.CustomId != _customId)
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
if (_interactionCompletedSource.Task.IsCompleted)
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_interactionCompletedSource.TrySetResult(true);
|
if (_singleUse)
|
||||||
|
_interactionCompletedSource.TrySetResult(true);
|
||||||
await ExecuteOnActionAsync(smc);
|
await ExecuteOnActionAsync(smc);
|
||||||
|
|
||||||
if (!smc.HasResponded)
|
if (!smc.HasResponded)
|
||||||
|
|
|
@ -13,25 +13,29 @@ public class EllieInteractionService : IEllieInteractionService, IEService
|
||||||
ulong userId,
|
ulong userId,
|
||||||
ButtonBuilder button,
|
ButtonBuilder button,
|
||||||
Func<SocketMessageComponent, Task> onTrigger,
|
Func<SocketMessageComponent, Task> onTrigger,
|
||||||
bool singleUse = true)
|
bool singleUse = true,
|
||||||
|
bool clearAfter = true)
|
||||||
=> new EllieButtonInteractionHandler(_client,
|
=> new EllieButtonInteractionHandler(_client,
|
||||||
userId,
|
userId,
|
||||||
button,
|
button,
|
||||||
onTrigger,
|
onTrigger,
|
||||||
onlyAuthor: true,
|
onlyAuthor: true,
|
||||||
singleUse: singleUse);
|
singleUse: singleUse,
|
||||||
|
clearAfter: clearAfter);
|
||||||
|
|
||||||
public EllieInteractionBase Create<T>(
|
public EllieInteractionBase Create<T>(
|
||||||
ulong userId,
|
ulong userId,
|
||||||
ButtonBuilder button,
|
ButtonBuilder button,
|
||||||
Func<SocketMessageComponent, T, Task> onTrigger,
|
Func<SocketMessageComponent, T, Task> onTrigger,
|
||||||
in T state,
|
in T state,
|
||||||
bool singleUse = true)
|
bool singleUse = true,
|
||||||
|
bool clearAfter = true)
|
||||||
=> Create(userId,
|
=> Create(userId,
|
||||||
button,
|
button,
|
||||||
((Func<T, Func<SocketMessageComponent, Task>>)((data)
|
((Func<T, Func<SocketMessageComponent, Task>>)((data)
|
||||||
=> smc => onTrigger(smc, data)))(state),
|
=> smc => onTrigger(smc, data)))(state),
|
||||||
singleUse);
|
singleUse,
|
||||||
|
clearAfter);
|
||||||
|
|
||||||
public EllieInteractionBase Create(
|
public EllieInteractionBase Create(
|
||||||
ulong userId,
|
ulong userId,
|
||||||
|
|
|
@ -6,14 +6,16 @@ public interface IEllieInteractionService
|
||||||
ulong userId,
|
ulong userId,
|
||||||
ButtonBuilder button,
|
ButtonBuilder button,
|
||||||
Func<SocketMessageComponent, Task> onTrigger,
|
Func<SocketMessageComponent, Task> onTrigger,
|
||||||
bool singleUse = true);
|
bool singleUse = true,
|
||||||
|
bool clearAfter = true);
|
||||||
|
|
||||||
public EllieInteractionBase Create<T>(
|
public EllieInteractionBase Create<T>(
|
||||||
ulong userId,
|
ulong userId,
|
||||||
ButtonBuilder button,
|
ButtonBuilder button,
|
||||||
Func<SocketMessageComponent, T, Task> onTrigger,
|
Func<SocketMessageComponent, T, Task> onTrigger,
|
||||||
in T state,
|
in T state,
|
||||||
bool singleUse = true);
|
bool singleUse = true,
|
||||||
|
bool clearAfter = true);
|
||||||
|
|
||||||
EllieInteractionBase Create(
|
EllieInteractionBase Create(
|
||||||
ulong userId,
|
ulong userId,
|
||||||
|
|
|
@ -8,7 +8,8 @@ public sealed class EllieButtonInteractionHandler : EllieInteractionBase
|
||||||
ButtonBuilder button,
|
ButtonBuilder button,
|
||||||
Func<SocketMessageComponent, Task> onAction,
|
Func<SocketMessageComponent, Task> onAction,
|
||||||
bool onlyAuthor,
|
bool onlyAuthor,
|
||||||
bool singleUse = true)
|
bool singleUse = true,
|
||||||
|
bool clearAfter = true)
|
||||||
: base(client, authorId, button.CustomId, onAction, onlyAuthor, singleUse)
|
: base(client, authorId, button.CustomId, onAction, onlyAuthor, singleUse)
|
||||||
{
|
{
|
||||||
Button = button;
|
Button = button;
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
#nullable disable
|
|
||||||
namespace EllieBot.Common;
|
|
||||||
|
|
||||||
public class OldCreds
|
|
||||||
{
|
|
||||||
public string Token { get; set; } = string.Empty;
|
|
||||||
public ulong[] OwnerIds { get; set; } = new ulong[1];
|
|
||||||
public string LoLApiKey { get; set; } = string.Empty;
|
|
||||||
public string GoogleApiKey { get; set; } = string.Empty;
|
|
||||||
public string MashapeKey { get; set; } = string.Empty;
|
|
||||||
public string OsuApiKey { get; set; } = string.Empty;
|
|
||||||
public string CleverbotApiKey { get; set; } = string.Empty;
|
|
||||||
public string CarbonKey { get; set; } = string.Empty;
|
|
||||||
public int TotalShards { get; set; } = 1;
|
|
||||||
public string PatreonAccessToken { get; set; } = string.Empty;
|
|
||||||
public string PatreonCampaignId { get; set; } = "334038";
|
|
||||||
public RestartConfig RestartCommand { get; set; }
|
|
||||||
|
|
||||||
public string ShardRunCommand { get; set; } = string.Empty;
|
|
||||||
public string ShardRunArguments { get; set; } = string.Empty;
|
|
||||||
public int? ShardRunPort { get; set; }
|
|
||||||
public string MiningProxyUrl { get; set; } = string.Empty;
|
|
||||||
public string MiningProxyCreds { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public string BotListToken { get; set; } = string.Empty;
|
|
||||||
public string TwitchClientId { get; set; } = string.Empty;
|
|
||||||
public string VotesToken { get; set; } = string.Empty;
|
|
||||||
public string VotesUrl { get; set; } = string.Empty;
|
|
||||||
public string RedisOptions { get; set; } = string.Empty;
|
|
||||||
public string LocationIqApiKey { get; set; } = string.Empty;
|
|
||||||
public string TimezoneDbApiKey { get; set; } = string.Empty;
|
|
||||||
public string CoinmarketcapApiKey { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public class RestartConfig
|
|
||||||
{
|
|
||||||
public string Cmd { get; set; }
|
|
||||||
public string Args { get; set; }
|
|
||||||
|
|
||||||
public RestartConfig(string cmd, string args)
|
|
||||||
{
|
|
||||||
Cmd = cmd;
|
|
||||||
Args = args;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,8 +12,8 @@ public partial class ResponseBuilder
|
||||||
private readonly DiscordSocketClient _client;
|
private readonly DiscordSocketClient _client;
|
||||||
private int currentPage;
|
private int currentPage;
|
||||||
|
|
||||||
private EllieButtonInteractionHandler left;
|
private EllieButtonInteractionHandler? left;
|
||||||
private EllieButtonInteractionHandler right;
|
private EllieButtonInteractionHandler? right;
|
||||||
private EllieInteractionBase? extra;
|
private EllieInteractionBase? extra;
|
||||||
|
|
||||||
public PaginationSender(
|
public PaginationSender(
|
||||||
|
@ -71,7 +71,8 @@ public partial class ResponseBuilder
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
singleUse: false);
|
singleUse: false,
|
||||||
|
clearAfter: false);
|
||||||
|
|
||||||
if (_paginationBuilder.InteractionFunc is not null)
|
if (_paginationBuilder.InteractionFunc is not null)
|
||||||
{
|
{
|
||||||
|
@ -106,7 +107,8 @@ public partial class ResponseBuilder
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
singleUse: false);
|
singleUse: false,
|
||||||
|
clearAfter: false);
|
||||||
|
|
||||||
return (leftBtnInter, maybeInter, rightBtnInter);
|
return (leftBtnInter, maybeInter, rightBtnInter);
|
||||||
}
|
}
|
||||||
|
@ -120,8 +122,8 @@ public partial class ResponseBuilder
|
||||||
if (_paginationBuilder.AddPaginatedFooter)
|
if (_paginationBuilder.AddPaginatedFooter)
|
||||||
toSend.AddPaginatedFooter(currentPage, lastPage);
|
toSend.AddPaginatedFooter(currentPage, lastPage);
|
||||||
|
|
||||||
left.SetCompleted();
|
left?.SetCompleted();
|
||||||
right.SetCompleted();
|
right?.SetCompleted();
|
||||||
extra?.SetCompleted();
|
extra?.SetCompleted();
|
||||||
(left, extra, right) = (await GetInteractions());
|
(left, extra, right) = (await GetInteractions());
|
||||||
|
|
||||||
|
|
|
@ -229,10 +229,4 @@ public static class Extensions
|
||||||
public static IEnumerable<IRole> GetRoles(this IGuildUser user)
|
public static IEnumerable<IRole> GetRoles(this IGuildUser user)
|
||||||
=> user.RoleIds.Select(r => user.Guild.GetRole(r)).Where(r => r is not null);
|
=> user.RoleIds.Select(r => user.Guild.GetRole(r)).Where(r => r is not null);
|
||||||
|
|
||||||
// todo remove
|
|
||||||
public static void Lap(this Stopwatch sw, string checkpoint)
|
|
||||||
{
|
|
||||||
Log.Information("Checkpoint {CheckPoint}: {Time}ms", checkpoint, sw.Elapsed.TotalMilliseconds);
|
|
||||||
sw.Restart();
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -13,6 +13,12 @@ usePrivilegedIntents: true
|
||||||
# note: If you are planning to have more than one shard, then you must change botCache to 'redis'.
|
# note: If you are planning to have more than one shard, then you must change botCache to 'redis'.
|
||||||
# Also, in that case you should be using EllieBot.Coordinator to start the bot, and it will correctly override this value.
|
# Also, in that case you should be using EllieBot.Coordinator to start the bot, and it will correctly override this value.
|
||||||
totalShards: 1
|
totalShards: 1
|
||||||
|
# Pledge 5$ or more on https://patreon.com/elliebot and connect your discord account to Patreon.
|
||||||
|
# Go to https://dashy.elliebot.net/me and login with your discord account
|
||||||
|
# Go to the Keys page and click "Generate New Key" and copy it here
|
||||||
|
# You and anyone else with the permission to run `.prompt` command will be able to use natural language to run bot's commands.
|
||||||
|
# For example '@Bot how's the weather in Paris' will return the current weather in Paris as if you were to run `.weather Paris` command.
|
||||||
|
ellieAiToken:
|
||||||
# Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it.
|
# Login to https://console.cloud.google.com, create a new project, go to APIs & Services -> Library -> YouTube Data API and enable it.
|
||||||
# Then, go to APIs and Services -> Credentials and click Create credentials -> API key.
|
# Then, go to APIs and Services -> Credentials and click Create credentials -> API key.
|
||||||
# Used only for Youtube Data Api (at the moment).
|
# Used only for Youtube Data Api (at the moment).
|
||||||
|
@ -24,9 +30,9 @@ googleApiKey: ""
|
||||||
# Copy the 'Search Engine ID' to the SearchId field
|
# Copy the 'Search Engine ID' to the SearchId field
|
||||||
#
|
#
|
||||||
# Do all steps again but enable image search for the ImageSearchId
|
# Do all steps again but enable image search for the ImageSearchId
|
||||||
google:
|
google:
|
||||||
searchId:
|
searchId:
|
||||||
imageSearchId:
|
imageSearchId:
|
||||||
# Settings for voting system for discordbots. Meant for use on global Ellie.
|
# Settings for voting system for discordbots. Meant for use on global Ellie.
|
||||||
votes:
|
votes:
|
||||||
# top.gg votes service url
|
# top.gg votes service url
|
||||||
|
@ -46,7 +52,7 @@ votes:
|
||||||
# Patreon auto reward system settings.
|
# Patreon auto reward system settings.
|
||||||
# go to https://www.patreon.com/portal -> my clients -> create client
|
# go to https://www.patreon.com/portal -> my clients -> create client
|
||||||
patreon:
|
patreon:
|
||||||
clientId:
|
clientId:
|
||||||
accessToken: ""
|
accessToken: ""
|
||||||
refreshToken: ""
|
refreshToken: ""
|
||||||
clientSecret: ""
|
clientSecret: ""
|
||||||
|
@ -56,7 +62,7 @@ patreon:
|
||||||
botListToken: ""
|
botListToken: ""
|
||||||
# Official cleverbot api key.
|
# Official cleverbot api key.
|
||||||
cleverbotApiKey: ""
|
cleverbotApiKey: ""
|
||||||
# Official GPT-3 api key.
|
# OpenAi api key.
|
||||||
gpt3ApiKey: ""
|
gpt3ApiKey: ""
|
||||||
# Which cache implementation should bot use.
|
# Which cache implementation should bot use.
|
||||||
# 'memory' - Cache will be in memory of the bot's process itself. Only use this on bots with a single shard. When the bot is restarted the cache is reset.
|
# 'memory' - Cache will be in memory of the bot's process itself. Only use this on bots with a single shard. When the bot is restarted the cache is reset.
|
||||||
|
@ -80,25 +86,25 @@ db:
|
||||||
# Change only if you've changed the coordinator address or port.
|
# Change only if you've changed the coordinator address or port.
|
||||||
coordinatorUrl: http://localhost:3442
|
coordinatorUrl: http://localhost:3442
|
||||||
# Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)
|
# Api key obtained on https://rapidapi.com (go to MyApps -> Add New App -> Enter Name -> Application key)
|
||||||
rapidApiKey:
|
rapidApiKey:
|
||||||
# https://locationiq.com api key (register and you will receive the token in the email).
|
# https://locationiq.com api key (register and you will receive the token in the email).
|
||||||
# Used only for .time command.
|
# Used only for .time command.
|
||||||
locationIqApiKey:
|
locationIqApiKey:
|
||||||
# https://timezonedb.com api key (register and you will receive the token in the email).
|
# https://timezonedb.com api key (register and you will receive the token in the email).
|
||||||
# Used only for .time command
|
# Used only for .time command
|
||||||
timezoneDbApiKey:
|
timezoneDbApiKey:
|
||||||
# https://pro.coinmarketcap.com/account/ api key. There is a free plan for personal use.
|
# https://pro.coinmarketcap.com/account/ api key. There is a free plan for personal use.
|
||||||
# Used for cryptocurrency related commands.
|
# Used for cryptocurrency related commands.
|
||||||
coinmarketcapApiKey:
|
coinmarketcapApiKey:
|
||||||
# Api key used for Osu related commands. Obtain this key at https://osu.ppy.sh/p/api
|
# Api key used for Osu related commands. Obtain this key at https://osu.ppy.sh/p/api
|
||||||
osuApiKey:
|
osuApiKey:
|
||||||
# Optional Trovo client id.
|
# Optional Trovo client id.
|
||||||
# You should use this if Trovo stream notifications stopped working or you're getting ratelimit errors.
|
# You should use this if Trovo stream notifications stopped working or you're getting ratelimit errors.
|
||||||
trovoClientId:
|
trovoClientId:
|
||||||
# Obtain by creating an application at https://dev.twitch.tv/console/apps
|
# Obtain by creating an application at https://dev.twitch.tv/console/apps
|
||||||
twitchClientId:
|
twitchClientId:
|
||||||
# Obtain by creating an application at https://dev.twitch.tv/console/apps
|
# Obtain by creating an application at https://dev.twitch.tv/console/apps
|
||||||
twitchClientSecret:
|
twitchClientSecret:
|
||||||
# Command and args which will be used to restart the bot.
|
# Command and args which will be used to restart the bot.
|
||||||
# Only used if bot is executed directly (NOT through the coordinator)
|
# Only used if bot is executed directly (NOT through the coordinator)
|
||||||
# placeholders:
|
# placeholders:
|
||||||
|
@ -111,5 +117,5 @@ twitchClientSecret:
|
||||||
# cmd: EllieBot.exe
|
# cmd: EllieBot.exe
|
||||||
# args: "{0}"
|
# args: "{0}"
|
||||||
restartCommand:
|
restartCommand:
|
||||||
cmd:
|
cmd:
|
||||||
args:
|
args:
|
||||||
|
|
|
@ -1400,4 +1400,6 @@ stickyroles:
|
||||||
cleanupguilddata:
|
cleanupguilddata:
|
||||||
- cleanupguilddata
|
- cleanupguilddata
|
||||||
prompt:
|
prompt:
|
||||||
- prompt
|
- prompt
|
||||||
|
honeypot:
|
||||||
|
- honeypot
|
|
@ -4522,4 +4522,13 @@ prompt:
|
||||||
- What's the weather like today?
|
- What's the weather like today?
|
||||||
params:
|
params:
|
||||||
- query:
|
- query:
|
||||||
desc: "The message to send to the bot."
|
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:
|
||||||
|
- {}
|
|
@ -619,7 +619,7 @@
|
||||||
"quote_deleted": "Quote #{0} deleted.",
|
"quote_deleted": "Quote #{0} deleted.",
|
||||||
"quote_edited": "Quote Edited",
|
"quote_edited": "Quote Edited",
|
||||||
"region": "Region",
|
"region": "Region",
|
||||||
"remind2": "I will remind {0} to {1} {2} ({3})`",
|
"remind2": "I will remind {0} to {1} {2} ({3})",
|
||||||
"remind_timely": "I will remind you about your timely reward {0}",
|
"remind_timely": "I will remind you about your timely reward {0}",
|
||||||
"remind_invalid": "Not a valid remind format. Remind must have a target, timer and a reason. Check the command list.",
|
"remind_invalid": "Not a valid remind format. Remind must have a target, timer and a reason. Check the command list.",
|
||||||
"remind_too_long": "Remind time has exceeded maximum.",
|
"remind_too_long": "Remind time has exceeded maximum.",
|
||||||
|
@ -1101,5 +1101,7 @@
|
||||||
"todo_archived_list": "Archived Todo List",
|
"todo_archived_list": "Archived Todo List",
|
||||||
"search_results": "Search results",
|
"search_results": "Search results",
|
||||||
"queue_search_results": "Type the number of the search result to queue up that track.",
|
"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."
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue