Compare commits

..

2 commits

Author SHA1 Message Date
85e8c48f90
Updated CHANGELOG.md 2024-08-21 20:26:27 +12:00
3a25433ec8
Quote commands slightly changed and some of them renamed. Added a lot of new aliases. Notable rename is .liqu to .qli
Quotes now follow the same naming pattern as Expression commands
Code vastly improved
2024-08-21 20:22:31 +12:00
7 changed files with 271 additions and 175 deletions

View file

@ -2,6 +2,26 @@
Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except date format. a-c-f-r-o
## Unreleased
### Added
### Changed
- `.quote` commands cleaned up and improved
- All quote commands now start with `.q<whatever>` and follow the same naming pattern as Expression commands
- `.liqu` renamed to `.qli`
- `.quotesearch` / `.qse` is now paginated for easier searching
- `.whosplaying` is now paginated
- `.setgame` renamed to`.setactivity` and now supports custom text activity. You don't have to specify playing, listening etc before the activity
- Clarified and added some embed / placeholder links to command help where needed
- dev: A lot of code cleanup and internal improvements
### Fixed
- Fixed `.xpcurrew` breaking xp gain if user gains 0 xp from being in a voice channel
- Fixed a bug in `.gatari` command
## [5.1.7] - 09.08.2024
### Fixed
@ -12,83 +32,83 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
### Added
- `'serverlist` is now paginated
- `.serverlist` is now paginated
### Changed
- `'listservers` renamed to `'serverlist`
- `.listservers` renamed to `.serverlist`
### Fixed
- `'afk` messages can no longer ping, and the response is moved to DMs to avoid abuse
- Possible fix for `'remind` timestamp
- `.afk` messages can no longer ping, and the response is moved to DMs to avoid abuse
- Possible fix for `.remind` timestamp
### Removed
- Removed old bloat / semi broken / dumb commands
- `'memelist` / `'memegen` (too inconvenient to use)
- `'activity` (useless owner-only command)
- `'rafflecur` (Just use raffle and then award manually instead)
- `'rollduel` (we had this command?)
- You can no longer bet on `'connect4`
- `'economy` Removed.
- Was buggy and didn't really show the real state of the economy.
- `.memelist` / `.memegen` (too inconvenient to use)
- `.activity` (useless owner-only command)
- `.rafflecur` (Just use raffle and then award manually instead)
- `.rollduel` (we had this command?)
- You can no longer bet on `.connect4`
- `.economy` Removed.
- Was buggy and didn.t really show the real state of the economy.
- It might come back improved in the future
- `'mal` Removed. Useless information / semi broken
- `.mal` Removed. Useless information / semi broken
## [5.1.5] - 01.08.2024
### Added
- Added: Added a `'afk <msg>?` command which sets an afk message which will trigger whenever someone pings you
- Added: Added a `.afk <msg>?` command which sets an afk message which will trigger whenever someone pings you
- Message will when you type a message in any channel that the bot sees, or after 8 hours, whichever comes first
- The specified message will be prefixed with "The user is afk: "
- The afk message will disappear 30 seconds after being triggered
### Changed
- Bot now shows a message when 'prune fails due to already running error
- Bot now shows a message when .prune fails due to already running error
- Updated some bet descriptions to include 'all' 'half' usage instructions
- Updated some command strings
- dev: Vastly simplified marmalade creation using dotnet templates, docs updated
- Slight refactor of 'wiki, 'time, 'catfact, 'wikia, 'define, 'bible and 'quran commands, no significant change in functionality
- Slight refactor of .wiki, .time, .catfact, .wikia, .define, .bible and .quran commands, no significant change in functionality
### Fixed
- 'coins will no longer show double minus sign for negative changes
- .coins will no longer show double minus sign for negative changes
- You can once again disable cleverbot responses using fake 'cleverbot:response' module name in permission commands
### Removed
- Removed 'rip command
- Removed .rip command
## [5.1.4] - 15.07.2024
### Added
- Added `'coins` command which lists top 10 cryptos ordered by marketcap
- Added Clubs rank in the leaderboard to `'clubinfo`
- Added `.coins` command which lists top 10 cryptos ordered by marketcap
- Added Clubs rank in the leaderboard to `.clubinfo`
- Bot owners can now check other people's bank balance (Not server owners, only bot owner, the person who is hosting the bot)
- You can now send multiple waifu gifts at once to waifus. For example `'waifugift 3xRose @user` will give that user 3 roses
- You can now send multiple waifu gifts at once to waifus. For example `.waifugift 3xRose @user` will give that user 3 roses
- The format is `<NUMBER>x<ITEM>`, no spaces
- Added `'boosttest` command
- Added `.boosttest` command
### Changed
- Updated command strings to clarify `'say` and `'send` usages
- Updated command strings to clarify `.say` and `.send` usages
### Fixed
- Fixed `'waifugift` help string
- Fixed `.waifugift` help string
### Removed
- Removed selfhost button from `'donate` command, no idea why it was there in the first place
- Removed selfhost button from `.donate` command, no idea why it was there in the first place
## [5.1.3] - 08.07.2024
### Added
- Added `'quran` command, which will show the provided ayah in english and arabic, including recitation by Alafasy
- Added `.quran` command, which will show the provided ayah in english and arabic, including recitation by Alafasy
### Changed
@ -96,7 +116,7 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
### Fixed
- Fixed `'stickeradd` it now properly supports 300x300 image uploads.
- 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
- Honeypot commands now actually works
@ -111,15 +131,15 @@ Mostly based on [keepachangelog](https://keepachangelog.com/en/1.1.0/) except da
### Added
- Added `'honeypot` command, which automatically softbans (ban and immediate unban) any user who posts in that channel.
- 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?
- Fixed `.betdraw` not respecting maxbet
- Fixed `.xpshop` pagination for real this time?
## [5.1.0] - 28.06.2024

View file

@ -1,6 +1,50 @@
namespace EllieBot.Modules.Utility;
using EllieBot.Db.Models;
namespace EllieBot.Modules.Utility;
public interface IQuoteService
{
/// <summary>
/// Delete all quotes created by the author in a guild
/// </summary>
/// <param name="guildId">ID of the guild</param>
/// <param name="userId">ID of the user</param>
/// <returns>Number of deleted qutoes</returns>
Task<int> DeleteAllAuthorQuotesAsync(ulong guildId, ulong userId);
/// <summary>
/// Delete all quotes in a guild
/// </summary>
/// <param name="guildId">ID of the guild</param>
/// <returns>Number of deleted qutoes</returns>
Task<int> DeleteAllQuotesAsync(ulong guildId);
Task<IReadOnlyCollection<Quote>> GetAllQuotesAsync(ulong guildId, int page, OrderType order);
Task<Quote?> GetQuoteByKeywordAsync(ulong guildId, string keyword);
Task<IReadOnlyCollection<Quote>> SearchQuoteKeywordTextAsync(
ulong guildId,
string? keyword,
string text);
Task<IReadOnlyCollection<Quote>> GetGuildQuotesAsync(ulong guildId);
Task<int> RemoveAllByKeyword(ulong guildId, string keyword);
Task<Quote?> GetQuoteByIdAsync(ulong guildId, kwum quoteId);
Task<Quote> AddQuoteAsync(
ulong guildId,
ulong authorId,
string authorName,
string keyword,
string text);
Task<Quote?> EditQuoteAsync(ulong authorId, int quoteId, string text);
Task<bool> DeleteQuoteAsync(
ulong guildId,
ulong authorId,
bool isQuoteManager,
int quoteId);
Task<bool> ImportQuotesAsync(ulong guildId, string input);
}

View file

@ -1,6 +1,4 @@
#nullable disable warnings
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using EllieBot.Common.Yml;
using EllieBot.Db.Models;
using YamlDotNet.Serialization;
@ -11,7 +9,7 @@ namespace EllieBot.Modules.Utility;
public partial class Utility
{
[Group]
public partial class QuoteCommands : EllieModule<QuoteService>
public partial class QuoteCommands : EllieModule
{
private const string PREPEND_EXPORT =
"""
@ -48,19 +46,19 @@ public partial class Utility
[Cmd]
[RequireContext(ContextType.Guild)]
[Priority(1)]
public Task ListQuotes(OrderType order = OrderType.Keyword)
=> ListQuotes(1, order);
public Task QuoteList(OrderType order = OrderType.Keyword)
=> QuoteList(1, order);
[Cmd]
[RequireContext(ContextType.Guild)]
[Priority(0)]
public async Task ListQuotes(int page = 1, OrderType order = OrderType.Keyword)
public async Task QuoteList(int page = 1, OrderType order = OrderType.Keyword)
{
page -= 1;
if (page < 0)
return;
var quotes = await _service.GetAllQuotesAsync(ctx.Guild.Id, page, order);
var quotes = await _qs.GetAllQuotesAsync(ctx.Guild.Id, page, order);
if (quotes.Count == 0)
{
@ -85,7 +83,7 @@ public partial class Utility
keyword = keyword.ToUpperInvariant();
var quote = await _service.GetQuoteByKeywordAsync(ctx.Guild.Id, keyword);
var quote = await _qs.GetQuoteByKeywordAsync(ctx.Guild.Id, keyword);
if (quote is null)
return;
@ -97,7 +95,6 @@ public partial class Utility
await Response()
.Text($"`{new kwum(quote.Id)}` 📣 " + text)
.Sanitize()
.SendAsync();
}
@ -106,7 +103,7 @@ public partial class Utility
[RequireContext(ContextType.Guild)]
public async Task QuoteShow(kwum quoteId)
{
var quote = await _service.GetQuoteByIdAsync(ctx.Guild.Id, quoteId);
var quote = await _qs.GetQuoteByIdAsync(ctx.Guild.Id, quoteId);
if (quote is null)
{
@ -179,7 +176,7 @@ public partial class Utility
keyword = keyword?.ToUpperInvariant();
var quotes = await _service.SearchQuoteKeywordTextAsync(ctx.Guild.Id, keyword, textOrAuthor);
var quotes = await _qs.SearchQuoteKeywordTextAsync(ctx.Guild.Id, keyword, textOrAuthor);
await Response()
.Paginated()
@ -218,7 +215,7 @@ public partial class Utility
if (quoteId < 0)
return;
var quote = await _service.GetQuoteByIdAsync(ctx.Guild.Id, quoteId);
var quote = await _qs.GetQuoteByIdAsync(ctx.Guild.Id, quoteId);
if (quote is null)
{
@ -226,8 +223,8 @@ public partial class Utility
return;
}
var infoText = $"*`{new kwum(quote.Id)}` added by {quote.AuthorName.SanitizeAllMentions()}* 🗯️ "
+ quote.Keyword.ToLowerInvariant().SanitizeAllMentions()
var infoText = $"*`{new kwum(quote.Id)}` added by {quote.AuthorName}* 🗯️ "
+ quote.Keyword.ToLowerInvariant()
+ ":\n";
@ -236,7 +233,6 @@ public partial class Utility
text = await repSvc.ReplaceAsync(text, repCtx);
await Response()
.Text(infoText + text)
.Sanitize()
.SendAsync();
}
@ -248,26 +244,14 @@ public partial class Utility
if (string.IsNullOrWhiteSpace(keyword) || string.IsNullOrWhiteSpace(text))
return;
keyword = keyword.ToUpperInvariant();
var quote = await _qs.AddQuoteAsync(ctx.Guild.Id, ctx.User.Id, ctx.User.Username, keyword, text);
Quote q;
await using (var uow = _db.GetDbContext())
{
uow.Set<Quote>()
.Add(q = new()
{
AuthorId = ctx.Message.Author.Id,
AuthorName = ctx.Message.Author.Username,
GuildId = ctx.Guild.Id,
Keyword = keyword,
Text = text
});
await uow.SaveChangesAsync();
}
await Response().Confirm(strs.quote_added_new(Format.Code(new kwum(q.Id).ToString()))).SendAsync();
await Response()
.Confirm(strs.quote_added_new(Format.Code(new kwum(quote.Id).ToString())))
.SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task QuoteEdit(kwum quoteId, [Leftover] string text)
@ -277,19 +261,7 @@ public partial class Utility
return;
}
Quote q;
await using (var uow = _db.GetDbContext())
{
var intId = (int)quoteId;
var result = await uow.GetTable<Quote>()
.Where(x => x.Id == intId && x.AuthorId == ctx.User.Id)
.Set(x => x.Text, text)
.UpdateWithOutputAsync((del, ins) => ins);
q = result.FirstOrDefault();
await uow.SaveChangesAsync();
}
var q = await _qs.EditQuoteAsync(ctx.User.Id, quoteId, text);
if (q is not null)
{
@ -309,33 +281,19 @@ public partial class Utility
}
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task QuoteDelete(kwum quoteId)
{
var hasManageMessages = ((IGuildUser)ctx.Message.Author).GuildPermissions.ManageMessages;
var success = false;
string response;
await using (var uow = _db.GetDbContext())
{
var q = uow.Set<Quote>().GetById(quoteId);
if (q?.GuildId != ctx.Guild.Id || (!hasManageMessages && q.AuthorId != ctx.Message.Author.Id))
response = GetText(strs.quotes_remove_none);
else
{
uow.Set<Quote>().Remove(q);
await uow.SaveChangesAsync();
success = true;
response = GetText(strs.quote_deleted(new kwum(quoteId)));
}
}
var success = await _qs.DeleteQuoteAsync(ctx.Guild.Id, ctx.User.Id, hasManageMessages, quoteId);
if (success)
await Response().Confirm(response).SendAsync();
await Response().Confirm(strs.quote_deleted(quoteId)).SendAsync();
else
await Response().Error(response).SendAsync();
await Response().Error(strs.quotes_remove_none).SendAsync();
}
[Cmd]
@ -368,16 +326,9 @@ public partial class Utility
if (string.IsNullOrWhiteSpace(keyword))
return;
keyword = keyword.ToUpperInvariant();
await _qs.RemoveAllByKeyword(ctx.Guild.Id, keyword.ToUpperInvariant());
await using (var uow = _db.GetDbContext())
{
await _service.RemoveAllByKeyword(ctx.Guild.Id, keyword.ToUpperInvariant());
await uow.SaveChangesAsync();
}
await Response().Confirm(strs.quotes_deleted(Format.Bold(keyword.SanitizeAllMentions()))).SendAsync();
await Response().Confirm(strs.quotes_deleted(Format.Bold(keyword))).SendAsync();
}
[Cmd]
@ -385,7 +336,7 @@ public partial class Utility
[UserPerm(GuildPerm.Administrator)]
public async Task QuotesExport()
{
var quotes = _service.GetForGuild(ctx.Guild.Id).ToList();
var quotes = await _qs.GetGuildQuotesAsync(ctx.Guild.Id);
var exprsDict = quotes.GroupBy(x => x.Keyword)
.ToDictionary(x => x.Key, x => x.Select(ExportedQuote.FromModel));
@ -400,7 +351,7 @@ public partial class Utility
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[Ratelimit(300)]
#if GLOBAL_ELLIE
#if GLOBAL_NADEKO
[OwnerOnly]
#endif
public async Task QuotesImport([Leftover] string? input = null)
@ -428,7 +379,7 @@ public partial class Utility
}
}
var succ = await ImportExprsAsync(ctx.Guild.Id, input);
var succ = await _qs.ImportQuotesAsync(ctx.Guild.Id, input);
if (!succ)
{
await Response().Error(strs.expr_import_invalid_data).SendAsync();
@ -437,56 +388,5 @@ public partial class Utility
await ctx.OkAsync();
}
private async Task<bool> ImportExprsAsync(ulong guildId, string input)
{
Dictionary<string, List<ExportedQuote>> data;
try
{
data = Yaml.Deserializer.Deserialize<Dictionary<string, List<ExportedQuote>>>(input);
if (data.Sum(x => x.Value.Count) == 0)
return false;
}
catch
{
return false;
}
await using var uow = _db.GetDbContext();
foreach (var entry in data)
{
var keyword = entry.Key;
await uow.Set<Quote>()
.AddRangeAsync(entry.Value.Where(quote => !string.IsNullOrWhiteSpace(quote.Txt))
.Select(quote => new Quote
{
GuildId = guildId,
Keyword = keyword,
Text = quote.Txt,
AuthorId = quote.Aid,
AuthorName = quote.An
}));
}
await uow.SaveChangesAsync();
return true;
}
public class ExportedQuote
{
public string Id { get; set; }
public string An { get; set; }
public ulong Aid { get; set; }
public string Txt { get; set; }
public static ExportedQuote FromModel(Quote quote)
=> new()
{
Id = ((kwum)quote.Id).ToString(),
An = quote.AuthorName,
Aid = quote.AuthorId,
Txt = quote.Text
};
}
}
}

View file

@ -1,6 +1,8 @@
#nullable disable warnings
using LinqToDB;
using LinqToDB.Data;
using LinqToDB.EntityFrameworkCore;
using EllieBot.Common.Yml;
using EllieBot.Db.Models;
namespace EllieBot.Modules.Utility;
@ -45,19 +47,19 @@ public sealed class QuoteService : IQuoteService, IEService
return deleted;
}
public async Task<IReadOnlyList<Quote>> GetAllQuotesAsync(ulong guildId, int page, OrderType order)
public async Task<IReadOnlyCollection<Quote>> GetAllQuotesAsync(ulong guildId, int page, OrderType order)
{
await using var uow = _db.GetDbContext();
var q = uow.Set<Quote>()
.ToLinqToDBTable()
.Where(x => x.GuildId == guildId);
if (order == OrderType.Keyword)
q = q.OrderBy(x => x.Keyword);
else
q = q.OrderBy(x => x.Id);
return await q.Skip(15 * page).Take(15).ToArrayAsync();
return await q.Skip(15 * page).Take(15).ToArrayAsyncLinqToDB();
}
public async Task<Quote?> GetQuoteByKeywordAsync(ulong guildId, string keyword)
@ -97,11 +99,12 @@ public sealed class QuoteService : IQuoteService, IEService
return toReturn;
}
public IEnumerable<Quote> GetForGuild(ulong guildId)
public async Task<IReadOnlyCollection<Quote>> GetGuildQuotesAsync(ulong guildId)
{
using var uow = _db.GetDbContext();
var quotes = uow.GetTable<Quote>()
.Where(x => x.GuildId == guildId);
await using var uow = _db.GetDbContext();
var quotes = await uow.GetTable<Quote>()
.Where(x => x.GuildId == guildId)
.ToListAsyncLinqToDB();
return quotes;
}
@ -124,7 +127,96 @@ public sealed class QuoteService : IQuoteService, IEService
var quote = await uow.GetTable<Quote>()
.Where(x => x.Id == quoteId && x.GuildId == guildId)
.FirstAsyncLinqToDB();
.FirstOrDefaultAsyncLinqToDB();
return quote;
}
public async Task<Quote> AddQuoteAsync(
ulong guildId,
ulong authorId,
string authorName,
string keyword,
string text)
{
keyword = keyword.ToUpperInvariant();
Quote q;
await using var uow = _db.GetDbContext();
uow.Set<Quote>()
.Add(q = new()
{
AuthorId = authorId,
AuthorName = authorName,
GuildId = guildId,
Keyword = keyword,
Text = text
});
await uow.SaveChangesAsync();
return q;
}
public async Task<Quote?> EditQuoteAsync(ulong authorId, int quoteId, string text)
{
await using var uow = _db.GetDbContext();
var result = await uow.GetTable<Quote>()
.Where(x => x.Id == quoteId && x.AuthorId == authorId)
.Set(x => x.Text, text)
.UpdateWithOutputAsync((del, ins) => ins);
var q = result.FirstOrDefault();
return q;
}
public async Task<bool> DeleteQuoteAsync(
ulong guildId,
ulong authorId,
bool isQuoteManager,
int quoteId)
{
await using var uow = _db.GetDbContext();
var q = uow.Set<Quote>().GetById(quoteId);
var count = await uow.GetTable<Quote>()
.Where(x => x.GuildId == guildId && x.Id == quoteId)
.Where(x => isQuoteManager || (x.AuthorId == authorId))
.DeleteAsync();
return count > 0;
}
public async Task<bool> ImportQuotesAsync(ulong guildId, string input)
{
Dictionary<string?, List<ExportedQuote?>> data;
try
{
data = Yaml.Deserializer.Deserialize<Dictionary<string?, List<ExportedQuote?>>>(input);
}
catch (Exception ex)
{
Log.Warning(ex, "Quote import failed: {Message}", ex.Message);
return false;
}
var toImport = data.SelectMany(x => x.Value.Select(v => (Key: x.Key, Value: v)))
.Where(x => !string.IsNullOrWhiteSpace(x.Key) && !string.IsNullOrWhiteSpace(x.Value?.Txt));
await using var uow = _db.GetDbContext();
await uow.GetTable<Quote>()
.BulkCopyAsync(toImport
.Select(q => new Quote
{
GuildId = guildId,
Keyword = q.Key,
Text = q.Value.Txt,
AuthorId = q.Value.Aid,
AuthorName = q.Value.An
}));
return true;
}
}

View file

@ -0,0 +1,20 @@
using EllieBot.Db.Models;
namespace EllieBot.Modules.Utility;
public class ExportedQuote
{
public required string Id { get; init; }
public required string An { get; init; }
public required ulong Aid { get; init; }
public required string Txt { get; init; }
public static ExportedQuote FromModel(Quote quote)
=> new()
{
Id = ((kwum)quote.Id).ToString(),
An = quote.AuthorName,
Aid = quote.AuthorId,
Txt = quote.Text
};
}

View file

@ -343,28 +343,57 @@ allcmdcooldowns:
- cmdcds
quoteadd:
- quoteadd
- qa
- qadd
- quadd
- .
quoteedit:
- quoteedit
- qe
- que
- qedit
quoteprint:
- quoteprint
- qp
- qup
- ..
- qprint
quoteshow:
- quoteshow
- qsh
- qshow
- qushow
quotesearch:
- quotesearch
- qs
- qse
- qsearch
quoteid:
- quoteid
- qid
quotedelete:
- quotedelete
- qd
- qdel
- qdelete
quotedeleteauthor:
- quotedeleteauthor
- qda
- qdelauth
quotesexport:
- quotesexport
- qex
- qexport
quotesimport:
- quotesimport
- qim
- qimp
- qimport
quotelist:
- quotelist
- qli
- quli
- qulist
draw:
- draw
drawnew:
@ -761,9 +790,6 @@ autotranslate:
- autotranslate
- at
- autotrans
listquotes:
- listquotes
- liqu
typedel:
- typedel
typelist:
@ -1210,12 +1236,6 @@ linkonlychannel:
- linkssonly
coordreload:
- coordreload
quotesexport:
- quotesexport
- qexport
quotesimport:
- quotesimport
- qimport
showembed:
- showembed
# EllieExpressions

View file

@ -2502,7 +2502,7 @@ autotranslate:
params:
- autoDelete:
desc: "The option to automatically remove translated messages from the chat."
listquotes:
quotelist:
desc: Lists all quotes on the server ordered alphabetically or by ID. 15 Per page.
ex:
- 3