Compare commits

...

17 Commits
5.1.0 ... 5.1.3

Author SHA1 Message Date
Kwoth
ca13684c0d docs: Updated CHANGELOG.
dev: upped version to 5.1.3
2024-07-06 09:05:34 +00:00
Kwoth
0ad6b741e7 add: chatterbot, (but not prompting) will also trigger if the user replies to a bot's message 2024-07-05 04:55:33 +00:00
Kwoth
4ce756d760 fix: if prompt ai responds with chat, it will prevent further further execution of tthat input to prevent the same query running multiple times 2024-07-05 04:48:00 +00:00
Kwoth
5f2813d3af fix: clean the bot username too from invalid characters for openai 2024-07-05 03:36:04 +00:00
Kwoth
1b7458529c fix: bot should now trim the invalid characters from openai message usernames 2024-07-05 03:33:04 +00:00
Kwoth
9c9c8d7490 fix: fixed pipeline 2024-06-29 08:43:50 +00:00
Kwoth
2700bfdce8 fix: Fixed .stickeradd, it now properly supports 300x300 image uploads. closes #434 2024-06-29 08:42:47 +00:00
Kwoth
5498c5ce3f add: Added .quran command, which will show the provided ayah in english and arabic, including recitation by Alafasy 2024-06-29 07:14:13 +00:00
Kwoth
6f444a8da0 fix: fixed pipeline, no functional change 2024-06-29 01:36:38 +00:00
Kwoth
454c14eee1 docs: Updated changelog 2024-06-29 01:25:48 +00:00
Kwoth
30aa8e8186 fix: fixed honeypot not unbanning and pruning 2024-06-29 01:24:14 +00:00
Kwoth
2ca141810c docs: updated pagination, upped version to 5.1.1 2024-06-28 05:17:40 +00:00
Kwoth
9da8e4f1c1 fix: Fixed pagination, for real this time, closes #435,
dev: removed some old creds migration code, incremented creds version
2024-06-27 19:48:39 +00:00
Kwoth
ef471c32bb add: Added .honeypot command
fix: Fixed .betdraw not respecting max bet limit
2024-06-27 04:50:55 +00:00
Kwoth
49d557caec fix: removed an extra in remind string 2024-06-25 16:01:59 +00:00
Kwoth
4366f908f3 dev: Using new stopwatch, some cleanup. No functional change 2024-06-25 07:09:16 +00:00
Kwoth
237e66495b fix: Potential fix for the invalid syntax redis issue, unless the issue is caused by the older version of redis on the server itself 2024-06-25 03:32:21 +00:00
41 changed files with 11214 additions and 239 deletions

View File

@@ -2,6 +2,42 @@
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.3] - 06.07.2024
### Added
- Added `.quran` command, which will show the provided ayah in english and arabic, including recitation by Alafasy
### Changed
- Replying to the bot's message in the channel where chatterbot is enabled will also trigger the ai response, as if you pinged the bot. This only works for chatterbot, but not for nadeko ai command prompts
### Fixed
- Fixed `.stickeradd` it now properly supports 300x300 image uploads.
- Bot should now trim the invalid characters from chatterbot usernames to avoid openai errors
- Fixed prompt triggering chatterbot responses twice
## [5.1.2] - 29.06.2024
### Fixed
- Fixed `.honeypot` not unbanning and not pruning messages
## [5.1.1] - 27.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] - 25.06.2024 ## [5.1.0] - 25.06.2024
### Added ### Added
@@ -10,9 +46,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. - 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) - 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) - (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 support for `gpt-4o` in `data/games.yml`
- Added nadekoAiToken to `creds.yml`
### Changed ### Changed

View File

@@ -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

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<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; }

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); 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 => modelBuilder.Entity("NadekoBot.Db.Models.IgnoredLogItem", b =>
{ {
b.Property<int>("Id") 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); 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 => modelBuilder.Entity("NadekoBot.Db.Models.IgnoredLogItem", b =>
{ {
b.Property<int>("Id") 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"); 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 => modelBuilder.Entity("NadekoBot.Db.Models.IgnoredLogItem", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")

View File

@@ -0,0 +1,95 @@
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(pruneDays: 1);
await user.Guild.RemoveBanAsync(user.Id);
}
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

@@ -0,0 +1,14 @@
// namespace NadekoBot.Modules.Administration;
//
// public partial class Administration
// {
// [Group]
// public partial class TicketCommands : NadekoModule
// {
// [Cmd]
// public async Task Ticket()
// {
//
// }
// }
// }

View File

@@ -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)
@@ -136,18 +137,28 @@ 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,
@@ -192,6 +203,7 @@ public partial class Gambling
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

View File

@@ -96,6 +96,8 @@ public class ChatterBotService : IExecOnMessage
message = msg.Content[normalMention.Length..].Trim(); message = msg.Content[normalMention.Length..].Trim();
else if (msg.Content.StartsWith(nickMention, StringComparison.InvariantCulture)) else if (msg.Content.StartsWith(nickMention, StringComparison.InvariantCulture))
message = msg.Content[nickMention.Length..].Trim(); message = msg.Content[nickMention.Length..].Trim();
else if (msg.ReferencedMessage?.Author.Id == nadekoId)
message = msg.Content;
else else
return null; return null;
@@ -165,7 +167,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("""

View File

@@ -3,10 +3,12 @@ using Newtonsoft.Json;
using OneOf.Types; using OneOf.Types;
using System.Net.Http.Json; using System.Net.Http.Json;
using SharpToken; using SharpToken;
using System.CodeDom;
using System.Text.RegularExpressions;
namespace NadekoBot.Modules.Games.Common.ChatterBot; namespace NadekoBot.Modules.Games.Common.ChatterBot;
public class OfficialGptSession : IChatterBotSession public partial class OfficialGptSession : IChatterBotSession
{ {
private string Uri private string Uri
=> $"https://api.openai.com/v1/chat/completions"; => $"https://api.openai.com/v1/chat/completions";
@@ -45,7 +47,7 @@ public class OfficialGptSession : IChatterBotSession
_maxHistory = chatHistory; _maxHistory = chatHistory;
_maxTokens = maxTokens; _maxTokens = maxTokens;
_minTokens = minTokens; _minTokens = minTokens;
_nadekoUsername = nadekoUsername; _nadekoUsername = UsernameCleaner().Replace(nadekoUsername, "");
_encoding = GptEncoding.GetEncodingForModel(_model); _encoding = GptEncoding.GetEncodingForModel(_model);
messages.Add(new() messages.Add(new()
{ {
@@ -55,14 +57,21 @@ public class OfficialGptSession : IChatterBotSession
}); });
} }
[GeneratedRegex("[^a-zA-Z0-9_-]")]
private static partial Regex UsernameCleaner();
public async Task<OneOf.OneOf<ThinkResult, Error<string>>> Think(string input, string username) public async Task<OneOf.OneOf<ThinkResult, Error<string>>> Think(string input, string username)
{ {
username = UsernameCleaner().Replace(username, "");
messages.Add(new() messages.Add(new()
{ {
Role = "user", Role = "user",
Content = input, Content = input,
Name = username Name = username
}); });
while (messages.Count > _maxHistory + 2) while (messages.Count > _maxHistory + 2)
{ {
messages.RemoveAt(1); messages.RemoveAt(1);
@@ -103,6 +112,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;

View File

@@ -0,0 +1,102 @@
using NadekoBot.Modules.Searches.Common;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
namespace NadekoBot.Modules.Searches;
public partial class Searches
{
public partial class ReligiousCommands : NadekoModule
{
private readonly IHttpClientFactory _httpFactory;
public ReligiousCommands(IHttpClientFactory httpFactory)
{
_httpFactory = httpFactory;
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task Bible(string book, string chapterAndVerse)
{
var obj = new BibleVerses();
try
{
using var http = _httpFactory.CreateClient();
obj = await http.GetFromJsonAsync<BibleVerses>($"https://bible-api.com/{book} {chapterAndVerse}");
}
catch
{
}
if (obj.Error is not null || obj.Verses is null || obj.Verses.Length == 0)
await Response().Error(obj.Error ?? "No verse found.").SendAsync();
else
{
var v = obj.Verses[0];
await Response()
.Embed(_sender.CreateEmbed()
.WithOkColor()
.WithTitle($"{v.BookName} {v.Chapter}:{v.Verse}")
.WithDescription(v.Text))
.SendAsync();
}
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task Quran(string ayah)
{
using var http = _httpFactory.CreateClient();
var obj = await http.GetFromJsonAsync<QuranResponse<QuranAyah>>($"https://api.alquran.cloud/v1/ayah/{Uri.EscapeDataString(ayah)}/editions/en.asad,ar.alafasy");
if(obj is null or not { Code: 200 })
{
await Response().Error("No verse found.").SendAsync();
return;
}
var english = obj.Data[0];
var arabic = obj.Data[1];
await using var audio = await http.GetStreamAsync(arabic.Audio);
await Response()
.Embed(_sender.CreateEmbed()
.WithOkColor()
.AddField("Arabic", arabic.Text)
.AddField("English", english.Text)
.WithFooter(arabic.Number.ToString()))
.File(audio, Uri.EscapeDataString(ayah) + ".mp3")
.SendAsync();
}
}
}
public sealed class QuranResponse<T>
{
[JsonPropertyName("code")]
public int Code { get; set; }
[JsonPropertyName("status")]
public string Status { get; set; }
[JsonPropertyName("data")]
public T[] Data { get; set; }
}
public sealed class QuranAyah
{
[JsonPropertyName("number")]
public int Number { get; set; }
[JsonPropertyName("audio")]
public string Audio { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("text")]
public string Text { get; set; }
}

View File

@@ -32,20 +32,21 @@ public sealed class SearxSearchService : SearchServiceBase, INService
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, INService
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, INService
+ $"&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;
} }
} }

View File

@@ -528,34 +528,6 @@ public partial class Searches : NadekoModule<SearchesService>
} }
} }
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task Bible(string book, string chapterAndVerse)
{
var obj = new BibleVerses();
try
{
using var http = _httpFactory.CreateClient();
obj = await http.GetFromJsonAsync<BibleVerses>($"https://bible-api.com/{book} {chapterAndVerse}");
}
catch
{
}
if (obj.Error is not null || obj.Verses is null || obj.Verses.Length == 0)
await Response().Error(obj.Error ?? "No verse found.").SendAsync();
else
{
var v = obj.Verses[0];
await Response()
.Embed(_sender.CreateEmbed()
.WithOkColor()
.WithTitle($"{v.BookName} {v.Chapter}:{v.Verse}")
.WithDescription(v.Text))
.SendAsync();
}
}
[Cmd] [Cmd]
public async Task Steam([Leftover] string query) public async Task Steam([Leftover] string query)
{ {

View File

@@ -251,7 +251,7 @@ public sealed class AiAssistantService
return false; return false;
await _cbs.RunChatterBot(sg, msg, channel, sess, query); await _cbs.RunChatterBot(sg, msg, channel, sess, query);
return false; return true;
} }
var commandString = GetCommandString(model); var commandString = GetCommandString(model);

View File

@@ -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;
} }

View File

@@ -459,8 +459,10 @@ public partial class Utility : NadekoModule
public async Task StickerAdd(string name = null, string description = null, params string[] tags) public async Task StickerAdd(string name = null, string description = null, params string[] tags)
{ {
string format; string format;
Stream stream; Stream stream = null;
try
{
if (ctx.Message.Stickers.Count is 1 && ctx.Message.Stickers.First() is SocketSticker ss) if (ctx.Message.Stickers.Count is 1 && ctx.Message.Stickers.First() is SocketSticker ss)
{ {
name ??= ss.Name; name ??= ss.Name;
@@ -471,6 +473,40 @@ public partial class Utility : NadekoModule
using var http = _httpFactory.CreateClient(); using var http = _httpFactory.CreateClient();
stream = await http.GetStreamAsync(ss.GetStickerUrl()); stream = await http.GetStreamAsync(ss.GetStickerUrl());
} }
else if (ctx.Message.Attachments.Count is 1 && name is not null)
{
if (tags.Length == 0)
tags = [name];
if (ctx.Message.Attachments.Count != 1)
{
await Response().Error(strs.sticker_error).SendAsync();
return;
}
var attach = ctx.Message.Attachments.First();
if (attach.Size > 512_000 || attach.Width != 300 || attach.Height != 300)
{
await Response().Error(strs.sticker_error).SendAsync();
return;
}
format = attach.Filename
.Split('.')
.Last()
.ToLowerInvariant();
if (string.IsNullOrWhiteSpace(format) || (format != "png" && format != "apng"))
{
await Response().Error(strs.sticker_error).SendAsync();
return;
}
using var http = _httpFactory.CreateClient();
stream = await http.GetStreamAsync(attach.Url);
}
else else
{ {
await Response().Error(strs.sticker_error).SendAsync(); await Response().Error(strs.sticker_error).SendAsync();
@@ -479,9 +515,6 @@ public partial class Utility : NadekoModule
try try
{ {
if (tags.Length == 0)
tags = [name];
await ctx.Guild.CreateStickerAsync( await ctx.Guild.CreateStickerAsync(
name, name,
stream, stream,
@@ -492,14 +525,16 @@ public partial class Utility : NadekoModule
await ctx.OkAsync(); await ctx.OkAsync();
} }
catch (Exception ex) catch
(Exception ex)
{ {
Log.Warning(ex, "Error occurred while adding a sticker: {Message}", ex.Message); Log.Warning(ex, "Error occurred while adding a sticker: {Message}", ex.Message);
await Response().Error(strs.error_occured).SendAsync(); await Response().Error(strs.error_occured).SendAsync();
} }
}
finally finally
{ {
await stream.DisposeAsync(); await (stream?.DisposeAsync() ?? ValueTask.CompletedTask);
} }
} }

View File

@@ -480,7 +480,8 @@ public partial class Xp : NadekoModule<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 : NadekoModule<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 : NadekoModule<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)

View File

@@ -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.3</Version>
<!-- Output/build --> <!-- Output/build -->
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory> <RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
@@ -73,7 +73,7 @@
<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" />

View File

@@ -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));
} }
} }

View File

@@ -31,10 +31,10 @@ public sealed class Creds : IBotCredentials
[Comment(""" [Comment("""
Pledge 5$ or more on https://patreon.com/nadekobot and connect your discord account to Patreon. Pledge 5$ or more on https://patreon.com/nadekobot and connect your discord account to Patreon.
Go to https://dashy.nadeko.bot and login with your discord account Go to https://dashy.nadeko.bot/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.
""")] """)]
public string NadekoAiToken { get; set; } public string NadekoAiToken { get; set; }
@@ -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>();

View File

@@ -6,14 +6,16 @@ public interface INadekoInteractionService
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 NadekoInteractionBase Create<T>( public NadekoInteractionBase 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);
NadekoInteractionBase Create( NadekoInteractionBase Create(
ulong userId, ulong userId,

View File

@@ -8,8 +8,9 @@ public sealed class NadekoButtonInteractionHandler : NadekoInteractionBase
ButtonBuilder button, ButtonBuilder button,
Func<SocketMessageComponent, Task> onAction, Func<SocketMessageComponent, Task> onAction,
bool onlyAuthor, bool onlyAuthor,
bool singleUse = true) bool singleUse = true,
: base(client, authorId, button.CustomId, onAction, onlyAuthor, singleUse) bool clearAfter = true)
: base(client, authorId, button.CustomId, onAction, onlyAuthor, singleUse, clearAfter)
{ {
Button = button; Button = button;
} }

View File

@@ -12,6 +12,7 @@ public abstract class NadekoInteractionBase
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 NadekoInteractionBase( public NadekoInteractionBase(
DiscordSocketClient client, DiscordSocketClient client,
@@ -19,13 +20,16 @@ public abstract class NadekoInteractionBase
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,12 +40,10 @@ public abstract class NadekoInteractionBase
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;
if (_clearAfter)
await msg.ModifyAsync(m => m.Components = new ComponentBuilder().Build()); await msg.ModifyAsync(m => m.Components = new ComponentBuilder().Build());
} }
@@ -59,10 +61,14 @@ public abstract class NadekoInteractionBase
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
{ {
if (_singleUse)
_interactionCompletedSource.TrySetResult(true); _interactionCompletedSource.TrySetResult(true);
await ExecuteOnActionAsync(smc); await ExecuteOnActionAsync(smc);

View File

@@ -13,25 +13,30 @@ public class NadekoInteractionService : INadekoInteractionService, INService
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 NadekoButtonInteractionHandler(_client, => new NadekoButtonInteractionHandler(_client,
userId, userId,
button, button,
onTrigger, onTrigger,
onlyAuthor: true, onlyAuthor: true,
singleUse: singleUse); singleUse: singleUse,
clearAfter: clearAfter);
public NadekoInteractionBase Create<T>( public NadekoInteractionBase 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 NadekoInteractionBase Create( public NadekoInteractionBase Create(
ulong userId, ulong userId,

View File

@@ -1,45 +0,0 @@
#nullable disable
namespace NadekoBot.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;
}
}
}

View File

@@ -12,8 +12,8 @@ public partial class ResponseBuilder
private readonly DiscordSocketClient _client; private readonly DiscordSocketClient _client;
private int currentPage; private int currentPage;
private NadekoButtonInteractionHandler left; private NadekoButtonInteractionHandler? left;
private NadekoButtonInteractionHandler right; private NadekoButtonInteractionHandler? right;
private NadekoInteractionBase? extra; private NadekoInteractionBase? 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());

View File

@@ -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();
}
} }

View File

@@ -1,5 +1,5 @@
# DO NOT CHANGE # DO NOT CHANGE
version: 7 version: 9
# Bot token. Do not share with anyone ever -> https://discordapp.com/developers/applications/ # Bot token. Do not share with anyone ever -> https://discordapp.com/developers/applications/
token: '' token: ''
# List of Ids of the users who have bot owner permissions # List of Ids of the users who have bot owner permissions
@@ -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 NadekoBot.Coordinator to start the bot, and it will correctly override this value. # Also, in that case you should be using NadekoBot.Coordinator to start the bot, and it will correctly override this value.
totalShards: 1 totalShards: 1
# Pledge 5$ or more on https://patreon.com/nadekobot and connect your discord account to Patreon.
# Go to https://dashy.nadeko.bot/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.
nadekoAiToken:
# 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).
@@ -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.

View File

@@ -1192,6 +1192,8 @@ xpreset:
- xpreset - xpreset
bible: bible:
- bible - bible
quran:
- quran
edit: edit:
- edit - edit
delete: delete:
@@ -1401,3 +1403,5 @@ cleanupguilddata:
- cleanupguilddata - cleanupguilddata
prompt: prompt:
- prompt - prompt
honeypot:
- honeypot

View File

@@ -2289,7 +2289,11 @@ emojiremove:
- emotes: - emotes:
desc: "The list of emojis to be removed from the server." desc: "The list of emojis to be removed from the server."
stickeradd: stickeradd:
desc: Adds the sticker from your message to this server. Send the sticker along with this command (in the same message). desc: |-
Adds the sticker from your message to this server.
Send the sticker along with this command (in the same message).
Alternatively you can upload an image along with this command but you have to specify the name.
The image must be 300x300 in .png or .apng format and up to 512KB in size.
ex: ex:
- '' - ''
- name "description" tag1 tag2 tagN - name "description" tag1 tag2 tagN
@@ -4076,6 +4080,16 @@ bible:
desc: "The name of the biblical book being referenced." desc: "The name of the biblical book being referenced."
chapterAndVerse: chapterAndVerse:
desc: "The reference to a specific passage in the Bible, such as 'Genesis 3:15'" desc: "The reference to a specific passage in the Bible, such as 'Genesis 3:15'"
quran:
desc: |-
Shows the text of an ayah of the Quran, as well as the recitation by Alafasy.
Supply surah:ayah, or ayah number. For instance, 262 or 2:255 will both get you Ayat Al Kursi
ex:
- 2:255
- 262
params:
- ayah:
desc: "The number of the ayah in the Quran, for example 2:255."
edit: edit:
desc: Edits bot's message, you have to specify message ID and new text. You can optionally specify target channel. Supports embeds. desc: Edits bot's message, you have to specify message ID and new text. You can optionally specify target channel. Supports embeds.
ex: ex:
@@ -4523,4 +4537,12 @@ prompt:
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:
- {}

View File

@@ -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.",
@@ -1067,8 +1067,7 @@
"xpshop_already_owned": "You already own this item.", "xpshop_already_owned": "You already own this item.",
"xpshop_item_not_found": "An item with that key doesn't exist.", "xpshop_item_not_found": "An item with that key doesn't exist.",
"xpshop_website": "You can see the list of all Xp Shop items here: <https://xpshop.nadeko.bot>", "xpshop_website": "You can see the list of all Xp Shop items here: <https://xpshop.nadeko.bot>",
"sticker_invalid_size": "Stickers must be exactly 300x300 pixels.", "sticker_error": "You must either send a sticker along with this command, or upload a 300x300 .png or .apng image. Up to 512KB in size.",
"sticker_error": "You must either send a sticker along with this command, or upload a 300x300 .png or .apng image.",
"sticker_missing_name": "Please specify a name for the sticker.", "sticker_missing_name": "Please specify a name for the sticker.",
"thread_deleted": "Thread Deleted", "thread_deleted": "Thread Deleted",
"thread_created": "Thread Created", "thread_created": "Thread Created",
@@ -1101,5 +1100,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."
} }