Compare commits
No commits in common. "f9b21520fbb6937a33097c83f303d81533d2fb64" and "a4adabb3ea3853cbec6bfda4543a3f8e9a491ffa" have entirely different histories.
f9b21520fb
...
a4adabb3ea
50 changed files with 467 additions and 566 deletions
|
@ -1,7 +1,6 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using EllieBot.Modules.Music.Services;
|
using EllieBot.Modules.Music.Services;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
using EllieBot.Modules.Utility;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Music;
|
namespace EllieBot.Modules.Music;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
namespace EllieBot.Db.Models;
|
namespace EllieBot.Db.Models;
|
||||||
|
|
||||||
public class MusicPlaylist : DbEntity
|
public class MusicPlaylist : DbEntity
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using EllieBot.Modules.Permissions.Services;
|
using EllieBot.Modules.Permissions.Services;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
namespace EllieBot.Modules.Permissions.Services;
|
namespace EllieBot.Modules.Permissions.Services;
|
||||||
|
|
||||||
public readonly struct ServerFilterSettings
|
public readonly struct ServerFilterSettings
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Permissions.Services;
|
namespace EllieBot.Modules.Permissions.Services;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Permissions.Common;
|
namespace EllieBot.Modules.Permissions.Common;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Permissions.Common;
|
namespace EllieBot.Modules.Permissions.Common;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
namespace EllieBot.Modules.Permissions.Common;
|
namespace EllieBot.Modules.Permissions.Common;
|
||||||
|
|
||||||
public class PermissionsCollection<T> : IndexedCollection<T>
|
public class PermissionsCollection<T> : IndexedCollection<T>
|
||||||
|
|
|
@ -149,7 +149,8 @@ public class PermissionService : IExecPreCommand, IEService
|
||||||
returnMsg = "You need Admin permissions in order to use permission commands.";
|
returnMsg = "You need Admin permissions in order to use permission commands.";
|
||||||
if (pc.Verbose)
|
if (pc.Verbose)
|
||||||
{
|
{
|
||||||
try { await _sender.Response(channel).Error(returnMsg).SendAsync(); }
|
try
|
||||||
|
{ await _sender.Response(channel).Error(returnMsg).SendAsync(); }
|
||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +162,8 @@ public class PermissionService : IExecPreCommand, IEService
|
||||||
returnMsg = $"You need the {Format.Bold(role.Name)} role in order to use permission commands.";
|
returnMsg = $"You need the {Format.Bold(role.Name)} role in order to use permission commands.";
|
||||||
if (pc.Verbose)
|
if (pc.Verbose)
|
||||||
{
|
{
|
||||||
try { await _sender.Response(channel).Error(returnMsg).SendAsync(); }
|
try
|
||||||
|
{ await _sender.Response(channel).Error(returnMsg).SendAsync(); }
|
||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -130,7 +130,7 @@ public class CryptoService : IEService
|
||||||
await _getCryptoLock.WaitAsync();
|
await _getCryptoLock.WaitAsync();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var data = await _cache.GetOrAddAsync(new("nadeko:crypto_data"),
|
var data = await _cache.GetOrAddAsync(new("ellie:crypto_data"),
|
||||||
async () =>
|
async () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
using AngleSharp;
|
using AngleSharp;
|
||||||
using CsvHelper;
|
using CsvHelper;
|
||||||
using CsvHelper.Configuration;
|
using CsvHelper.Configuration;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
|
@ -23,32 +22,46 @@ public sealed class DefaultStockDataService : IStockDataService, IEService
|
||||||
|
|
||||||
using var http = _httpClientFactory.CreateClient();
|
using var http = _httpClientFactory.CreateClient();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var quoteHtmlPage = $"https://finance.yahoo.com/quote/{query.ToUpperInvariant()}";
|
var quoteHtmlPage = $"https://finance.yahoo.com/quote/{query.ToUpperInvariant()}";
|
||||||
|
|
||||||
var config = Configuration.Default.WithDefaultLoader();
|
var config = Configuration.Default.WithDefaultLoader();
|
||||||
using var document = await BrowsingContext.New(config).OpenAsync(quoteHtmlPage);
|
using var document = await BrowsingContext.New(config).OpenAsync(quoteHtmlPage);
|
||||||
|
var divElem =
|
||||||
var tickerName = document.QuerySelector("div.top > .left > .container > h1")
|
document.QuerySelector(
|
||||||
?.TextContent;
|
"#quote-header-info > div:nth-child(2) > div > div > h1");
|
||||||
|
var tickerName = (divElem)?.TextContent;
|
||||||
if (tickerName is null)
|
|
||||||
return default;
|
|
||||||
|
|
||||||
var marketcap = document
|
var marketcap = document
|
||||||
.QuerySelector("li > span > fin-streamer[data-field='marketCap']")
|
.QuerySelectorAll("table")
|
||||||
|
.Skip(1)
|
||||||
|
.First()
|
||||||
|
.QuerySelector("tbody > tr > td:nth-child(2)")
|
||||||
?.TextContent;
|
?.TextContent;
|
||||||
|
|
||||||
|
|
||||||
var volume = document.QuerySelector("li > span > fin-streamer[data-field='regularMarketVolume']")
|
var volume = document.QuerySelector("td[data-test='AVERAGE_VOLUME_3MONTH-value']")
|
||||||
?.TextContent;
|
?.TextContent;
|
||||||
|
|
||||||
var close = document.QuerySelector("li > span > fin-streamer[data-field='regularMarketPreviousClose']")
|
var close= document.QuerySelector("td[data-test='PREV_CLOSE-value']")
|
||||||
?.TextContent
|
?.TextContent ?? "0";
|
||||||
?? "0";
|
|
||||||
|
|
||||||
var price = document.QuerySelector("fin-streamer.livePrice > span")
|
var price = document
|
||||||
?.TextContent
|
.QuerySelector("#quote-header-info")
|
||||||
?? "0";
|
?.QuerySelector("fin-streamer[data-field='regularMarketPrice']")
|
||||||
|
?.TextContent ?? close;
|
||||||
|
|
||||||
|
// var data = await http.GetFromJsonAsync<YahooQueryModel>(
|
||||||
|
// $"https://query1.finance.yahoo.com/v7/finance/quote?symbols={query}");
|
||||||
|
//
|
||||||
|
// if (data is null)
|
||||||
|
// return default;
|
||||||
|
|
||||||
|
// var symbol = data.QuoteResponse.Result.FirstOrDefault();
|
||||||
|
|
||||||
|
// if (symbol is null)
|
||||||
|
// return default;
|
||||||
|
|
||||||
return new()
|
return new()
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using CodeHollow.FeedReader;
|
#nullable disable
|
||||||
|
using CodeHollow.FeedReader;
|
||||||
using EllieBot.Modules.Searches.Services;
|
using EllieBot.Modules.Searches.Services;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
@ -16,21 +17,19 @@ public partial class Searches
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.ManageMessages)]
|
[UserPerm(GuildPerm.ManageMessages)]
|
||||||
[Priority(1)]
|
[Priority(1)]
|
||||||
public Task YtUploadNotif(string url, [Leftover] string? message = null)
|
public Task YtUploadNotif(string url, [Leftover] string message = null)
|
||||||
=> YtUploadNotif(url, null, message);
|
=> YtUploadNotif(url, null, message);
|
||||||
|
|
||||||
[Cmd]
|
[Cmd]
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.ManageMessages)]
|
[UserPerm(GuildPerm.ManageMessages)]
|
||||||
[Priority(2)]
|
[Priority(2)]
|
||||||
public Task YtUploadNotif(string url, ITextChannel? channel = null, [Leftover] string? message = null)
|
public Task YtUploadNotif(string url, ITextChannel channel = null, [Leftover] string message = null)
|
||||||
{
|
{
|
||||||
var m = _ytChannelRegex.Match(url);
|
var m = _ytChannelRegex.Match(url);
|
||||||
if (!m.Success)
|
if (!m.Success)
|
||||||
return Response().Error(strs.invalid_input).SendAsync();
|
return Response().Error(strs.invalid_input).SendAsync();
|
||||||
|
|
||||||
channel ??= ctx.Channel as ITextChannel;
|
|
||||||
|
|
||||||
if (!((IGuildUser)ctx.User).GetPermissions(channel).MentionEveryone)
|
if (!((IGuildUser)ctx.User).GetPermissions(channel).MentionEveryone)
|
||||||
message = message?.SanitizeAllMentions();
|
message = message?.SanitizeAllMentions();
|
||||||
|
|
||||||
|
@ -43,7 +42,7 @@ public partial class Searches
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.ManageMessages)]
|
[UserPerm(GuildPerm.ManageMessages)]
|
||||||
[Priority(0)]
|
[Priority(0)]
|
||||||
public Task Feed(string url, [Leftover] string? message = null)
|
public Task Feed(string url, [Leftover] string message = null)
|
||||||
=> Feed(url, null, message);
|
=> Feed(url, null, message);
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,7 +50,7 @@ public partial class Searches
|
||||||
[RequireContext(ContextType.Guild)]
|
[RequireContext(ContextType.Guild)]
|
||||||
[UserPerm(GuildPerm.ManageMessages)]
|
[UserPerm(GuildPerm.ManageMessages)]
|
||||||
[Priority(1)]
|
[Priority(1)]
|
||||||
public async Task Feed(string url, ITextChannel? channel = null, [Leftover] string? message = null)
|
public async Task Feed(string url, ITextChannel channel = null, [Leftover] string message = null)
|
||||||
{
|
{
|
||||||
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri)
|
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri)
|
||||||
|| (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps))
|
|| (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps))
|
||||||
|
@ -60,11 +59,10 @@ public partial class Searches
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
channel ??= (ITextChannel)ctx.Channel;
|
|
||||||
|
|
||||||
if (!((IGuildUser)ctx.User).GetPermissions(channel).MentionEveryone)
|
if (!((IGuildUser)ctx.User).GetPermissions(channel).MentionEveryone)
|
||||||
message = message?.SanitizeAllMentions();
|
message = message?.SanitizeAllMentions();
|
||||||
|
|
||||||
|
channel ??= (ITextChannel)ctx.Channel;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await FeedReader.ReadAsync(url);
|
await FeedReader.ReadAsync(url);
|
||||||
|
|
|
@ -79,15 +79,6 @@ public class FeedsService : IEService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DateTime? GetPubDate(FeedItem item)
|
|
||||||
{
|
|
||||||
if (item.PublishingDate is not null)
|
|
||||||
return item.PublishingDate;
|
|
||||||
if (item.SpecificItem is AtomFeedItem atomItem)
|
|
||||||
return atomItem.UpdatedDate;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<EmbedBuilder> TrackFeeds()
|
public async Task<EmbedBuilder> TrackFeeds()
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
|
@ -103,32 +94,24 @@ public class FeedsService : IEService
|
||||||
{
|
{
|
||||||
var feed = await FeedReader.ReadAsync(rssUrl);
|
var feed = await FeedReader.ReadAsync(rssUrl);
|
||||||
|
|
||||||
var items = new List<(FeedItem Item, DateTime LastUpdate)>();
|
var items = feed
|
||||||
foreach (var item in feed.Items)
|
.Items.Select(item => (Item: item,
|
||||||
{
|
LastUpdate: item.PublishingDate?.ToUniversalTime()
|
||||||
var pubDate = GetPubDate(item);
|
?? (item.SpecificItem as AtomFeedItem)?.UpdatedDate?.ToUniversalTime()))
|
||||||
|
.Where(data => data.LastUpdate is not null)
|
||||||
if (pubDate is null)
|
.Select(data => (data.Item, LastUpdate: (DateTime)data.LastUpdate))
|
||||||
continue;
|
.OrderByDescending(data => data.LastUpdate)
|
||||||
|
.Reverse() // start from the oldest
|
||||||
items.Add((item, pubDate.Value.ToUniversalTime()));
|
.ToList();
|
||||||
|
|
||||||
// show at most 3 items if you're behind
|
|
||||||
if (items.Count > 2)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (items.Count == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!_lastPosts.TryGetValue(kvp.Key, out var lastFeedUpdate))
|
if (!_lastPosts.TryGetValue(kvp.Key, out var lastFeedUpdate))
|
||||||
{
|
{
|
||||||
lastFeedUpdate = _lastPosts[kvp.Key] = items[0].LastUpdate;
|
lastFeedUpdate = _lastPosts[kvp.Key] =
|
||||||
|
items.Any() ? items[items.Count - 1].LastUpdate : DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var index = 1; index <= items.Count; index++)
|
foreach (var (feedItem, itemUpdateDate) in items)
|
||||||
{
|
{
|
||||||
var (feedItem, itemUpdateDate) = items[^index];
|
|
||||||
if (itemUpdateDate <= lastFeedUpdate)
|
if (itemUpdateDate <= lastFeedUpdate)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -185,26 +168,27 @@ public class FeedsService : IEService
|
||||||
if (!string.IsNullOrWhiteSpace(feedItem.Description))
|
if (!string.IsNullOrWhiteSpace(feedItem.Description))
|
||||||
embed.WithDescription(desc.TrimTo(2048));
|
embed.WithDescription(desc.TrimTo(2048));
|
||||||
|
|
||||||
|
//send the created embed to all subscribed channels
|
||||||
var tasks = new List<Task>();
|
var feedSendTasks = kvp.Value
|
||||||
|
.Where(x => x.GuildConfig is not null)
|
||||||
foreach (var val in kvp.Value)
|
.Select(x =>
|
||||||
{
|
{
|
||||||
var ch = _client.GetGuild(val.GuildConfig.GuildId).GetTextChannel(val.ChannelId);
|
var ch = _client.GetGuild(x.GuildConfig.GuildId)
|
||||||
|
?.GetTextChannel(x.ChannelId);
|
||||||
|
|
||||||
if (ch is null)
|
if (ch is null)
|
||||||
continue;
|
return null;
|
||||||
|
|
||||||
var sendTask = _sender.Response(ch)
|
return _sender.Response(ch)
|
||||||
.Embed(embed)
|
.Embed(embed)
|
||||||
.Text(string.IsNullOrWhiteSpace(val.Message)
|
.Text(string.IsNullOrWhiteSpace(x.Message)
|
||||||
? string.Empty
|
? string.Empty
|
||||||
: val.Message)
|
: x.Message)
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
tasks.Add(sendTask);
|
})
|
||||||
}
|
.Where(x => x is not null);
|
||||||
|
|
||||||
allSendTasks.Add(tasks.WhenAll());
|
allSendTasks.Add(feedSendTasks.WhenAll());
|
||||||
|
|
||||||
// as data retrieval was successful, reset error counter
|
// as data retrieval was successful, reset error counter
|
||||||
ClearErrors(rssUrl);
|
ClearErrors(rssUrl);
|
||||||
|
|
312
src/EllieBot/Modules/Searches/PathOfExileCommands.cs
Normal file
312
src/EllieBot/Modules/Searches/PathOfExileCommands.cs
Normal file
|
@ -0,0 +1,312 @@
|
||||||
|
#nullable disable
|
||||||
|
using EllieBot.Modules.Searches.Common;
|
||||||
|
using EllieBot.Modules.Searches.Services;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Searches;
|
||||||
|
|
||||||
|
public partial class Searches
|
||||||
|
{
|
||||||
|
[Group]
|
||||||
|
public partial class PathOfExileCommands : EllieModule<SearchesService>
|
||||||
|
{
|
||||||
|
private const string POE_URL = "https://www.pathofexile.com/character-window/get-characters?accountName=";
|
||||||
|
private const string PON_URL = "http://poe.ninja/api/Data/GetCurrencyOverview?league=";
|
||||||
|
private const string POGS_URL = "http://pathofexile.gamepedia.com/api.php?action=opensearch&search=";
|
||||||
|
|
||||||
|
private const string POG_URL =
|
||||||
|
"https://pathofexile.gamepedia.com/api.php?action=browsebysubject&format=json&subject=";
|
||||||
|
|
||||||
|
private const string POGI_URL =
|
||||||
|
"https://pathofexile.gamepedia.com/api.php?action=query&prop=imageinfo&iiprop=url&format=json&titles=File:";
|
||||||
|
|
||||||
|
private const string PROFILE_URL = "https://www.pathofexile.com/account/view-profile/";
|
||||||
|
|
||||||
|
private readonly IHttpClientFactory _httpFactory;
|
||||||
|
|
||||||
|
private Dictionary<string, string> currencyDictionary = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
{ "Chaos Orb", "Chaos Orb" },
|
||||||
|
{ "Orb of Alchemy", "Orb of Alchemy" },
|
||||||
|
{ "Jeweller's Orb", "Jeweller's Orb" },
|
||||||
|
{ "Exalted Orb", "Exalted Orb" },
|
||||||
|
{ "Mirror of Kalandra", "Mirror of Kalandra" },
|
||||||
|
{ "Vaal Orb", "Vaal Orb" },
|
||||||
|
{ "Orb of Alteration", "Orb of Alteration" },
|
||||||
|
{ "Orb of Scouring", "Orb of Scouring" },
|
||||||
|
{ "Divine Orb", "Divine Orb" },
|
||||||
|
{ "Orb of Annulment", "Orb of Annulment" },
|
||||||
|
{ "Master Cartographer's Sextant", "Master Cartographer's Sextant" },
|
||||||
|
{ "Journeyman Cartographer's Sextant", "Journeyman Cartographer's Sextant" },
|
||||||
|
{ "Apprentice Cartographer's Sextant", "Apprentice Cartographer's Sextant" },
|
||||||
|
{ "Blessed Orb", "Blessed Orb" },
|
||||||
|
{ "Orb of Regret", "Orb of Regret" },
|
||||||
|
{ "Gemcutter's Prism", "Gemcutter's Prism" },
|
||||||
|
{ "Glassblower's Bauble", "Glassblower's Bauble" },
|
||||||
|
{ "Orb of Fusing", "Orb of Fusing" },
|
||||||
|
{ "Cartographer's Chisel", "Cartographer's Chisel" },
|
||||||
|
{ "Chromatic Orb", "Chromatic Orb" },
|
||||||
|
{ "Orb of Augmentation", "Orb of Augmentation" },
|
||||||
|
{ "Blacksmith's Whetstone", "Blacksmith's Whetstone" },
|
||||||
|
{ "Orb of Transmutation", "Orb of Transmutation" },
|
||||||
|
{ "Armourer's Scrap", "Armourer's Scrap" },
|
||||||
|
{ "Scroll of Wisdom", "Scroll of Wisdom" },
|
||||||
|
{ "Regal Orb", "Regal Orb" },
|
||||||
|
{ "Chaos", "Chaos Orb" },
|
||||||
|
{ "Alch", "Orb of Alchemy" },
|
||||||
|
{ "Alchs", "Orb of Alchemy" },
|
||||||
|
{ "Jews", "Jeweller's Orb" },
|
||||||
|
{ "Jeweller", "Jeweller's Orb" },
|
||||||
|
{ "Jewellers", "Jeweller's Orb" },
|
||||||
|
{ "Jeweller's", "Jeweller's Orb" },
|
||||||
|
{ "X", "Exalted Orb" },
|
||||||
|
{ "Ex", "Exalted Orb" },
|
||||||
|
{ "Exalt", "Exalted Orb" },
|
||||||
|
{ "Exalts", "Exalted Orb" },
|
||||||
|
{ "Mirror", "Mirror of Kalandra" },
|
||||||
|
{ "Mirrors", "Mirror of Kalandra" },
|
||||||
|
{ "Vaal", "Vaal Orb" },
|
||||||
|
{ "Alt", "Orb of Alteration" },
|
||||||
|
{ "Alts", "Orb of Alteration" },
|
||||||
|
{ "Scour", "Orb of Scouring" },
|
||||||
|
{ "Scours", "Orb of Scouring" },
|
||||||
|
{ "Divine", "Divine Orb" },
|
||||||
|
{ "Annul", "Orb of Annulment" },
|
||||||
|
{ "Annulment", "Orb of Annulment" },
|
||||||
|
{ "Master Sextant", "Master Cartographer's Sextant" },
|
||||||
|
{ "Journeyman Sextant", "Journeyman Cartographer's Sextant" },
|
||||||
|
{ "Apprentice Sextant", "Apprentice Cartographer's Sextant" },
|
||||||
|
{ "Blessed", "Blessed Orb" },
|
||||||
|
{ "Regret", "Orb of Regret" },
|
||||||
|
{ "Regrets", "Orb of Regret" },
|
||||||
|
{ "Gcp", "Gemcutter's Prism" },
|
||||||
|
{ "Glassblowers", "Glassblower's Bauble" },
|
||||||
|
{ "Glassblower's", "Glassblower's Bauble" },
|
||||||
|
{ "Fusing", "Orb of Fusing" },
|
||||||
|
{ "Fuses", "Orb of Fusing" },
|
||||||
|
{ "Fuse", "Orb of Fusing" },
|
||||||
|
{ "Chisel", "Cartographer's Chisel" },
|
||||||
|
{ "Chisels", "Cartographer's Chisel" },
|
||||||
|
{ "Chance", "Orb of Chance" },
|
||||||
|
{ "Chances", "Orb of Chance" },
|
||||||
|
{ "Chrome", "Chromatic Orb" },
|
||||||
|
{ "Chromes", "Chromatic Orb" },
|
||||||
|
{ "Aug", "Orb of Augmentation" },
|
||||||
|
{ "Augmentation", "Orb of Augmentation" },
|
||||||
|
{ "Augment", "Orb of Augmentation" },
|
||||||
|
{ "Augments", "Orb of Augmentation" },
|
||||||
|
{ "Whetstone", "Blacksmith's Whetstone" },
|
||||||
|
{ "Whetstones", "Blacksmith's Whetstone" },
|
||||||
|
{ "Transmute", "Orb of Transmutation" },
|
||||||
|
{ "Transmutes", "Orb of Transmutation" },
|
||||||
|
{ "Armourers", "Armourer's Scrap" },
|
||||||
|
{ "Armourer's", "Armourer's Scrap" },
|
||||||
|
{ "Wisdom Scroll", "Scroll of Wisdom" },
|
||||||
|
{ "Wisdom Scrolls", "Scroll of Wisdom" },
|
||||||
|
{ "Regal", "Regal Orb" },
|
||||||
|
{ "Regals", "Regal Orb" }
|
||||||
|
};
|
||||||
|
|
||||||
|
public PathOfExileCommands(IHttpClientFactory httpFactory)
|
||||||
|
=> _httpFactory = httpFactory;
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task PathOfExile(string usr, string league = "", int page = 1)
|
||||||
|
{
|
||||||
|
if (--page < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(usr))
|
||||||
|
{
|
||||||
|
await Response().Error("Please provide an account name.").SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var characters = new List<Account>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var http = _httpFactory.CreateClient();
|
||||||
|
var res = await http.GetStringAsync($"{POE_URL}{usr}");
|
||||||
|
characters = JsonConvert.DeserializeObject<List<Account>>(res);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
var embed = _sender.CreateEmbed().WithDescription(GetText(strs.account_not_found)).WithErrorColor();
|
||||||
|
|
||||||
|
await Response().Embed(embed).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(league))
|
||||||
|
characters.RemoveAll(c => c.League != league);
|
||||||
|
|
||||||
|
await Response()
|
||||||
|
.Paginated()
|
||||||
|
.Items(characters)
|
||||||
|
.PageSize(9)
|
||||||
|
.CurrentPage(page)
|
||||||
|
.Page((items, curPage) =>
|
||||||
|
{
|
||||||
|
var embed = _sender.CreateEmbed()
|
||||||
|
.WithAuthor($"Characters on {usr}'s account",
|
||||||
|
"https://web.poecdn.com/image/favicon/ogimage.png",
|
||||||
|
$"{PROFILE_URL}{usr}")
|
||||||
|
.WithOkColor();
|
||||||
|
|
||||||
|
if (characters.Count == 0)
|
||||||
|
return embed.WithDescription("This account has no characters.");
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine($"```{"#",-5}{"Character Name",-23}{"League",-10}{"Class",-13}{"Level",-3}");
|
||||||
|
for (var i = 0; i < items.Count; i++)
|
||||||
|
{
|
||||||
|
var character = items[i];
|
||||||
|
|
||||||
|
sb.AppendLine(
|
||||||
|
$"#{i + 1 + (curPage * 9),-4}{character.Name,-23}{ShortLeagueName(character.League),-10}{character.Class,-13}{character.Level,-3}");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine("```");
|
||||||
|
embed.WithDescription(sb.ToString());
|
||||||
|
|
||||||
|
return embed;
|
||||||
|
})
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task PathOfExileLeagues()
|
||||||
|
{
|
||||||
|
var leagues = new List<Leagues>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var http = _httpFactory.CreateClient();
|
||||||
|
var res = await http.GetStringAsync("http://api.pathofexile.com/leagues?type=main&compact=1");
|
||||||
|
leagues = JsonConvert.DeserializeObject<List<Leagues>>(res);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
var eembed = _sender.CreateEmbed().WithDescription(GetText(strs.leagues_not_found)).WithErrorColor();
|
||||||
|
|
||||||
|
await Response().Embed(eembed).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var embed = _sender.CreateEmbed()
|
||||||
|
.WithAuthor("Path of Exile Leagues",
|
||||||
|
"https://web.poecdn.com/image/favicon/ogimage.png",
|
||||||
|
"https://www.pathofexile.com")
|
||||||
|
.WithOkColor();
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine($"```{"#",-5}{"League Name",-23}");
|
||||||
|
for (var i = 0; i < leagues.Count; i++)
|
||||||
|
{
|
||||||
|
var league = leagues[i];
|
||||||
|
|
||||||
|
sb.AppendLine($"#{i + 1,-4}{league.Id,-23}");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine("```");
|
||||||
|
|
||||||
|
embed.WithDescription(sb.ToString());
|
||||||
|
|
||||||
|
await Response().Embed(embed).SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
public async Task PathOfExileCurrency(
|
||||||
|
string leagueName,
|
||||||
|
string currencyName,
|
||||||
|
string convertName = "Chaos Orb")
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(leagueName))
|
||||||
|
{
|
||||||
|
await Response().Error("Please provide league name.").SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(currencyName))
|
||||||
|
{
|
||||||
|
await Response().Error("Please provide currency name.").SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cleanCurrency = ShortCurrencyName(currencyName);
|
||||||
|
var cleanConvert = ShortCurrencyName(convertName);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var res = $"{PON_URL}{leagueName}";
|
||||||
|
using var http = _httpFactory.CreateClient();
|
||||||
|
var obj = JObject.Parse(await http.GetStringAsync(res));
|
||||||
|
|
||||||
|
var chaosEquivalent = 0.0F;
|
||||||
|
var conversionEquivalent = 0.0F;
|
||||||
|
|
||||||
|
// poe.ninja API does not include a "chaosEquivalent" property for Chaos Orbs.
|
||||||
|
if (cleanCurrency == "Chaos Orb")
|
||||||
|
chaosEquivalent = 1.0F;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var currencyInput = obj["lines"]
|
||||||
|
.Values<JObject>()
|
||||||
|
.Where(i => i["currencyTypeName"].Value<string>() == cleanCurrency)
|
||||||
|
.FirstOrDefault();
|
||||||
|
chaosEquivalent = float.Parse(currencyInput["chaosEquivalent"].ToString(),
|
||||||
|
CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleanConvert == "Chaos Orb")
|
||||||
|
conversionEquivalent = 1.0F;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var currencyOutput = obj["lines"]
|
||||||
|
.Values<JObject>()
|
||||||
|
.Where(i => i["currencyTypeName"].Value<string>() == cleanConvert)
|
||||||
|
.FirstOrDefault();
|
||||||
|
conversionEquivalent = float.Parse(currencyOutput["chaosEquivalent"].ToString(),
|
||||||
|
CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
var embed = _sender.CreateEmbed()
|
||||||
|
.WithAuthor($"{leagueName} Currency Exchange",
|
||||||
|
"https://web.poecdn.com/image/favicon/ogimage.png",
|
||||||
|
"http://poe.ninja")
|
||||||
|
.AddField("Currency Type", cleanCurrency, true)
|
||||||
|
.AddField($"{cleanConvert} Equivalent", chaosEquivalent / conversionEquivalent, true)
|
||||||
|
.WithOkColor();
|
||||||
|
|
||||||
|
await Response().Embed(embed).SendAsync();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
var embed = _sender.CreateEmbed().WithDescription(GetText(strs.ninja_not_found)).WithErrorColor();
|
||||||
|
|
||||||
|
await Response().Embed(embed).SendAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ShortCurrencyName(string str)
|
||||||
|
{
|
||||||
|
if (currencyDictionary.ContainsValue(str))
|
||||||
|
return str;
|
||||||
|
|
||||||
|
var currency = currencyDictionary[str];
|
||||||
|
|
||||||
|
return currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ShortLeagueName(string str)
|
||||||
|
{
|
||||||
|
var league = str.Replace("Hardcore", "HC", StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
|
||||||
|
return league;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,6 @@
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using EllieBot.Modules.Searches.Common;
|
using EllieBot.Modules.Searches.Common;
|
||||||
using EllieBot.Modules.Searches.Services;
|
using EllieBot.Modules.Searches.Services;
|
||||||
using EllieBot.Modules.Utility;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
|
@ -169,7 +168,7 @@ public partial class Searches : EllieModule<SearchesService>
|
||||||
.AddField("Rating", movie.ImdbRating, true)
|
.AddField("Rating", movie.ImdbRating, true)
|
||||||
.AddField("Genre", movie.Genre, true)
|
.AddField("Genre", movie.Genre, true)
|
||||||
.AddField("Year", movie.Year, true)
|
.AddField("Year", movie.Year, true)
|
||||||
.WithImageUrl(Uri.IsWellFormedUriString(movie.Poster, UriKind.Absolute) ? movie.Poster : null))
|
.WithImageUrl(movie.Poster))
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -126,7 +126,7 @@ public class SearchesService : IEService
|
||||||
{
|
{
|
||||||
query = query.Trim().ToLowerInvariant();
|
query = query.Trim().ToLowerInvariant();
|
||||||
|
|
||||||
return await _c.GetOrAddAsync(new($"nadeko_weather_{query}"),
|
return await _c.GetOrAddAsync(new($"ellie_weather_{query}"),
|
||||||
async () => await GetWeatherDataFactory(query),
|
async () => await GetWeatherDataFactory(query),
|
||||||
TimeSpan.FromHours(3));
|
TimeSpan.FromHours(3));
|
||||||
}
|
}
|
||||||
|
@ -156,7 +156,7 @@ public class SearchesService : IEService
|
||||||
public Task<((string Address, DateTime Time, string TimeZoneName), TimeErrors?)> GetTimeDataAsync(string arg)
|
public Task<((string Address, DateTime Time, string TimeZoneName), TimeErrors?)> GetTimeDataAsync(string arg)
|
||||||
=> GetTimeDataFactory(arg);
|
=> GetTimeDataFactory(arg);
|
||||||
|
|
||||||
//return _cache.GetOrAddCachedDataAsync($"nadeko_time_{arg}",
|
//return _cache.GetOrAddCachedDataAsync($"ellie_time_{arg}",
|
||||||
// GetTimeDataFactory,
|
// GetTimeDataFactory,
|
||||||
// arg,
|
// arg,
|
||||||
// TimeSpan.FromMinutes(1));
|
// TimeSpan.FromMinutes(1));
|
||||||
|
|
|
@ -40,7 +40,7 @@ public partial class SearchesConfig : ICloneable<SearchesConfig>
|
||||||
|
|
||||||
[Comment("""
|
[Comment("""
|
||||||
Set the searx instance urls in case you want to use 'searx' for either img or web search.
|
Set the searx instance urls in case you want to use 'searx' for either img or web search.
|
||||||
Nadeko will use a random one for each request.
|
Ellie will use a random one for each request.
|
||||||
Use a fully qualified url. Example: `https://my-searx-instance.mydomain.com`
|
Use a fully qualified url. Example: `https://my-searx-instance.mydomain.com`
|
||||||
Instances specified must support 'format=json' query parameter.
|
Instances specified must support 'format=json' query parameter.
|
||||||
- In case you're running your own searx instance, set
|
- In case you're running your own searx instance, set
|
||||||
|
@ -57,7 +57,7 @@ public partial class SearchesConfig : ICloneable<SearchesConfig>
|
||||||
|
|
||||||
[Comment("""
|
[Comment("""
|
||||||
Set the invidious instance urls in case you want to use 'invidious' for `.youtube` search
|
Set the invidious instance urls in case you want to use 'invidious' for `.youtube` search
|
||||||
Nadeko will use a random one for each request.
|
Ellie will use a random one for each request.
|
||||||
These instances may be used for music queue functionality in the future.
|
These instances may be used for music queue functionality in the future.
|
||||||
Use a fully qualified url. Example: https://my-invidious-instance.mydomain.com
|
Use a fully qualified url. Example: https://my-invidious-instance.mydomain.com
|
||||||
|
|
||||||
|
|
|
@ -1,314 +0,0 @@
|
||||||
using EllieBot.Common.ModuleBehaviors;
|
|
||||||
using EllieBot.Modules.Administration;
|
|
||||||
using EllieBot.Modules.Games.Services;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http.Json;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
|
||||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Utility;
|
|
||||||
|
|
||||||
public enum GetCommandErrorResult
|
|
||||||
{
|
|
||||||
RateLimitHit,
|
|
||||||
NotAuthorized,
|
|
||||||
Disregard,
|
|
||||||
Unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class AiAssistantService
|
|
||||||
: IAiAssistantService, IReadyExecutor,
|
|
||||||
IExecOnMessage,
|
|
||||||
IEService
|
|
||||||
{
|
|
||||||
private IReadOnlyCollection<AiCommandModel> _commands = [];
|
|
||||||
|
|
||||||
private readonly IBotStrings _strings;
|
|
||||||
private readonly IHttpClientFactory _httpFactory;
|
|
||||||
private readonly CommandService _cmds;
|
|
||||||
private readonly IBotCredsProvider _credsProvider;
|
|
||||||
private readonly DiscordSocketClient _client;
|
|
||||||
private readonly ICommandHandler _cmdHandler;
|
|
||||||
private readonly BotConfigService _bcs;
|
|
||||||
private readonly IMessageSenderService _sender;
|
|
||||||
|
|
||||||
private readonly JsonSerializerOptions _serializerOptions = new();
|
|
||||||
private readonly IPermissionChecker _permChecker;
|
|
||||||
private readonly IBotCache _botCache;
|
|
||||||
private readonly ChatterBotService _cbs;
|
|
||||||
|
|
||||||
public AiAssistantService(
|
|
||||||
DiscordSocketClient client,
|
|
||||||
IBotStrings strings,
|
|
||||||
IHttpClientFactory httpFactory,
|
|
||||||
CommandService cmds,
|
|
||||||
IBotCredsProvider credsProvider,
|
|
||||||
ICommandHandler cmdHandler,
|
|
||||||
BotConfigService bcs,
|
|
||||||
IPermissionChecker permChecker,
|
|
||||||
IBotCache botCache,
|
|
||||||
ChatterBotService cbs,
|
|
||||||
IMessageSenderService sender)
|
|
||||||
{
|
|
||||||
_client = client;
|
|
||||||
_strings = strings;
|
|
||||||
_httpFactory = httpFactory;
|
|
||||||
_cmds = cmds;
|
|
||||||
_credsProvider = credsProvider;
|
|
||||||
_cmdHandler = cmdHandler;
|
|
||||||
_bcs = bcs;
|
|
||||||
_sender = sender;
|
|
||||||
_permChecker = permChecker;
|
|
||||||
_botCache = botCache;
|
|
||||||
_cbs = cbs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<OneOf.OneOf<EllieCommandCallModel, GetCommandErrorResult>> TryGetCommandAsync(
|
|
||||||
ulong userId,
|
|
||||||
string prompt,
|
|
||||||
IReadOnlyCollection<AiCommandModel> commands,
|
|
||||||
string prefix)
|
|
||||||
{
|
|
||||||
using var content = new StringContent(
|
|
||||||
JsonSerializer.Serialize(new
|
|
||||||
{
|
|
||||||
query = prompt,
|
|
||||||
commands = commands.ToDictionary(x => x.Name,
|
|
||||||
x => new AiCommandModel()
|
|
||||||
{
|
|
||||||
Desc = string.Format(x.Desc ?? "", prefix),
|
|
||||||
Params = x.Params,
|
|
||||||
Name = x.Name
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
Encoding.UTF8,
|
|
||||||
"application/json"
|
|
||||||
);
|
|
||||||
|
|
||||||
using var request = new HttpRequestMessage();
|
|
||||||
request.Method = HttpMethod.Post;
|
|
||||||
// request.RequestUri = new("https://nai.nadeko.bot/get-command");
|
|
||||||
request.RequestUri = new("https://nai.nadeko.bot/get-command");
|
|
||||||
request.Content = content;
|
|
||||||
|
|
||||||
var creds = _credsProvider.GetCreds();
|
|
||||||
|
|
||||||
request.Headers.TryAddWithoutValidation("x-auth-token", creds.EllieAiToken);
|
|
||||||
request.Headers.TryAddWithoutValidation("x-auth-userid", userId.ToString());
|
|
||||||
|
|
||||||
|
|
||||||
using var client = _httpFactory.CreateClient();
|
|
||||||
|
|
||||||
// todo customize according to the bot's config
|
|
||||||
// - CurrencyName
|
|
||||||
// -
|
|
||||||
|
|
||||||
using var response = await client.SendAsync(request);
|
|
||||||
|
|
||||||
if (response.StatusCode == HttpStatusCode.TooManyRequests)
|
|
||||||
{
|
|
||||||
return GetCommandErrorResult.RateLimitHit;
|
|
||||||
}
|
|
||||||
else if (response.StatusCode == HttpStatusCode.Unauthorized)
|
|
||||||
{
|
|
||||||
return GetCommandErrorResult.NotAuthorized;
|
|
||||||
}
|
|
||||||
|
|
||||||
var funcModel = await response.Content.ReadFromJsonAsync<CommandPromptResultModel>();
|
|
||||||
|
|
||||||
|
|
||||||
if (funcModel?.Name == "disregard")
|
|
||||||
{
|
|
||||||
Log.Warning("Disregarding the prompt: {Prompt}", prompt);
|
|
||||||
return GetCommandErrorResult.Disregard;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (funcModel is null)
|
|
||||||
return GetCommandErrorResult.Unknown;
|
|
||||||
|
|
||||||
var comModel = new EllieCommandCallModel()
|
|
||||||
{
|
|
||||||
Name = funcModel.Name,
|
|
||||||
Arguments = funcModel.Arguments
|
|
||||||
.OrderBy(param => _commands.FirstOrDefault(x => x.Name == funcModel.Name)
|
|
||||||
?.Params
|
|
||||||
.Select((x, i) => (x, i))
|
|
||||||
.Where(x => x.x.Name == param.Key)
|
|
||||||
.Select(x => x.i)
|
|
||||||
.FirstOrDefault())
|
|
||||||
.Select(x => x.Value)
|
|
||||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
|
||||||
.ToArray(),
|
|
||||||
Remaining = funcModel.Remaining
|
|
||||||
};
|
|
||||||
|
|
||||||
return comModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IReadOnlyCollection<AiCommandModel> GetCommands()
|
|
||||||
=> _commands;
|
|
||||||
|
|
||||||
public Task OnReadyAsync()
|
|
||||||
{
|
|
||||||
var cmds = _cmds.Commands
|
|
||||||
.Select(x => (MethodName: x.Summary, CommandName: x.Aliases[0]))
|
|
||||||
.Where(x => !x.MethodName.Contains("///"))
|
|
||||||
.Distinct()
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var funcs = new List<AiCommandModel>();
|
|
||||||
foreach (var (method, cmd) in cmds)
|
|
||||||
{
|
|
||||||
var commandStrings = _strings.GetCommandStrings(method);
|
|
||||||
|
|
||||||
if (commandStrings is null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
funcs.Add(new()
|
|
||||||
{
|
|
||||||
Name = cmd,
|
|
||||||
Desc = commandStrings?.Desc?.Replace("currency", "flowers") ?? string.Empty,
|
|
||||||
Params = commandStrings?.Params.FirstOrDefault()
|
|
||||||
?.Select(x => new AiCommandParamModel()
|
|
||||||
{
|
|
||||||
Desc = x.Value.Desc,
|
|
||||||
Name = x.Key,
|
|
||||||
})
|
|
||||||
.ToArray()
|
|
||||||
?? []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_commands = funcs;
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Priority
|
|
||||||
=> 2;
|
|
||||||
|
|
||||||
public async Task<bool> ExecOnMessageAsync(IGuild guild, IUserMessage msg)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(_credsProvider.GetCreds().EllieAiToken))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (guild is not SocketGuild sg)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var nadekoId = _client.CurrentUser.Id;
|
|
||||||
|
|
||||||
var channel = msg.Channel as ITextChannel;
|
|
||||||
if (channel is null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var normalMention = $"<@{nadekoId}> ";
|
|
||||||
var nickMention = $"<@!{nadekoId}> ";
|
|
||||||
string query;
|
|
||||||
if (msg.Content.StartsWith(normalMention, StringComparison.InvariantCulture))
|
|
||||||
query = msg.Content[normalMention.Length..].Trim();
|
|
||||||
else if (msg.Content.StartsWith(nickMention, StringComparison.InvariantCulture))
|
|
||||||
query = msg.Content[nickMention.Length..].Trim();
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var success = await TryExecuteAiCommand(guild, msg, channel, query);
|
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> TryExecuteAiCommand(
|
|
||||||
IGuild guild,
|
|
||||||
IUserMessage msg,
|
|
||||||
ITextChannel channel,
|
|
||||||
string query)
|
|
||||||
{
|
|
||||||
// check permissions
|
|
||||||
var pcResult = await _permChecker.CheckPermsAsync(
|
|
||||||
guild,
|
|
||||||
msg.Channel,
|
|
||||||
msg.Author,
|
|
||||||
"Utility",
|
|
||||||
"prompt"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!pcResult.IsAllowed)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
using var _ = channel.EnterTypingState();
|
|
||||||
|
|
||||||
var result = await TryGetCommandAsync(msg.Author.Id, query, _commands, _cmdHandler.GetPrefix(guild.Id));
|
|
||||||
|
|
||||||
if (result.TryPickT0(out var model, out var error))
|
|
||||||
{
|
|
||||||
if (model.Name == ".ai_chat")
|
|
||||||
{
|
|
||||||
if (guild is not SocketGuild sg)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var sess = _cbs.GetOrCreateSession(guild.Id);
|
|
||||||
if (sess is null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
await _cbs.RunChatterBot(sg, msg, channel, sess, query);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var commandString = GetCommandString(model);
|
|
||||||
|
|
||||||
var msgTask = _sender.Response(channel)
|
|
||||||
.Embed(_sender.CreateEmbed()
|
|
||||||
.WithOkColor()
|
|
||||||
.WithAuthor(msg.Author.GlobalName,
|
|
||||||
msg.Author.RealAvatarUrl().ToString())
|
|
||||||
.WithDescription(commandString))
|
|
||||||
.SendAsync();
|
|
||||||
|
|
||||||
|
|
||||||
await _cmdHandler.TryRunCommand(
|
|
||||||
(SocketGuild)guild,
|
|
||||||
(ISocketMessageChannel)channel,
|
|
||||||
new DoAsUserMessage((SocketUserMessage)msg, msg.Author, commandString));
|
|
||||||
|
|
||||||
var cmdMsg = await msgTask;
|
|
||||||
|
|
||||||
cmdMsg.DeleteAfter(5);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error == GetCommandErrorResult.Disregard)
|
|
||||||
{
|
|
||||||
// await msg.ErrorAsync();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var key = new TypedKey<bool>($"sub_error:{msg.Author.Id}:{error}");
|
|
||||||
|
|
||||||
if (!await _botCache.AddAsync(key, true, TimeSpan.FromDays(1), overwrite: false))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var errorMsg = error switch
|
|
||||||
{
|
|
||||||
GetCommandErrorResult.RateLimitHit
|
|
||||||
=> "You've spent your daily requests quota.",
|
|
||||||
GetCommandErrorResult.NotAuthorized
|
|
||||||
=> "In order to use this command you have to have a 5$ or higher subscription at <https://patreon.com/nadekobot>",
|
|
||||||
GetCommandErrorResult.Unknown
|
|
||||||
=> "The service is temporarily unavailable.",
|
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
|
||||||
};
|
|
||||||
|
|
||||||
await _sender.Response(channel)
|
|
||||||
.Error(errorMsg)
|
|
||||||
.SendAsync();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetCommandString(EllieCommandCallModel res)
|
|
||||||
=> $"{_bcs.Data.Prefix}{res.Name} {res.Arguments.Select((x, i) => GetParamString(x, i + 1 == res.Arguments.Count)).Join(" ")}";
|
|
||||||
|
|
||||||
private static string GetParamString(string val, bool isLast)
|
|
||||||
=> isLast ? val : "\"" + val + "\"";
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Utility;
|
|
||||||
|
|
||||||
public sealed class AiCommandModel
|
|
||||||
{
|
|
||||||
[JsonPropertyName("name")]
|
|
||||||
public required string Name { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("desc")]
|
|
||||||
public required string Desc { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("params")]
|
|
||||||
public required IReadOnlyList<AiCommandParamModel> Params { get; set; }
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Utility;
|
|
||||||
|
|
||||||
public sealed class AiCommandParamModel
|
|
||||||
{
|
|
||||||
[JsonPropertyName("name")]
|
|
||||||
public required string Name { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("desc")]
|
|
||||||
public required string Desc { get; set; }
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Utility;
|
|
||||||
|
|
||||||
public sealed class CommandPromptResultModel
|
|
||||||
{
|
|
||||||
[JsonPropertyName("name")]
|
|
||||||
public required string Name { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("arguments")]
|
|
||||||
public required Dictionary<string, string> Arguments { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("remaining")]
|
|
||||||
[JsonConverter(typeof(NumberToStringConverter))]
|
|
||||||
public required string Remaining { get; set; }
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace EllieBot.Modules.Utility;
|
|
||||||
|
|
||||||
public sealed class EllieCommandCallModel
|
|
||||||
{
|
|
||||||
public required string Name { get; set; }
|
|
||||||
public required IReadOnlyList<string> Arguments { get; set; }
|
|
||||||
public required string Remaining { get; set; }
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
using OneOf;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Utility;
|
|
||||||
|
|
||||||
public interface IAiAssistantService
|
|
||||||
{
|
|
||||||
Task<OneOf<EllieCommandCallModel, GetCommandErrorResult>> TryGetCommandAsync(
|
|
||||||
ulong userId,
|
|
||||||
string prompt,
|
|
||||||
IReadOnlyCollection<AiCommandModel> commands,
|
|
||||||
string prefix);
|
|
||||||
|
|
||||||
IReadOnlyCollection<AiCommandModel> GetCommands();
|
|
||||||
|
|
||||||
Task<bool> TryExecuteAiCommand(
|
|
||||||
IGuild guild,
|
|
||||||
IUserMessage msg,
|
|
||||||
ITextChannel channel,
|
|
||||||
string query);
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
using EllieBot.Modules.Administration;
|
|
||||||
|
|
||||||
namespace EllieBot.Modules.Utility;
|
|
||||||
|
|
||||||
public partial class UtilityCommands
|
|
||||||
{
|
|
||||||
public class PromptCommands : EllieModule<IAiAssistantService>
|
|
||||||
{
|
|
||||||
[Cmd]
|
|
||||||
[RequireContext(ContextType.Guild)]
|
|
||||||
public async Task Prompt([Leftover] string query)
|
|
||||||
{
|
|
||||||
await ctx.Channel.TriggerTypingAsync();
|
|
||||||
var res = await _service.TryExecuteAiCommand(ctx.Guild, ctx.Message, (ITextChannel)ctx.Channel, query);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetCommandString(EllieCommandCallModel res)
|
|
||||||
=> $"{_bcs.Data.Prefix}{res.Name} {res.Arguments.Select((x, i) => GetParamString(x, i + 1 == res.Arguments.Count)).Join(" ")}";
|
|
||||||
|
|
||||||
private static string GetParamString(string val, bool isLast)
|
|
||||||
=> isLast ? val : "\"" + val + "\"";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
namespace EllieBot.Modules.Utility;
|
namespace EllieBot.Modules.Utility;
|
||||||
|
|
||||||
public partial class Utility
|
public partial class Utility
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using EllieBot.Modules.Patronage;
|
using EllieBot.Modules.Patronage;
|
||||||
|
|
||||||
|
@ -144,9 +144,9 @@ public partial class Utility
|
||||||
true)
|
true)
|
||||||
.WithOkColor();
|
.WithOkColor();
|
||||||
|
|
||||||
var mPatron = await _ps.GetPatronAsync(user.Id);
|
var patron = await _ps.GetPatronAsync(user.Id);
|
||||||
|
|
||||||
if (mPatron is {} patron && patron.Tier != PatronTier.None)
|
if (patron.Tier != PatronTier.None)
|
||||||
{
|
{
|
||||||
embed.WithFooter(patron.Tier switch
|
embed.WithFooter(patron.Tier switch
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using CommandLine;
|
using CommandLine;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Utility.Services;
|
namespace EllieBot.Modules.Utility.Services;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable warnings
|
#nullable disable warnings
|
||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using EllieBot.Common.Yml;
|
using EllieBot.Common.Yml;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable warnings
|
#nullable disable warnings
|
||||||
using LinqToDB;
|
using LinqToDB;
|
||||||
using LinqToDB.EntityFrameworkCore;
|
using LinqToDB.EntityFrameworkCore;
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
|
|
@ -113,9 +113,10 @@ public partial class Utility
|
||||||
foreach (var rem in rems)
|
foreach (var rem in rems)
|
||||||
{
|
{
|
||||||
var when = rem.When;
|
var when = rem.When;
|
||||||
|
var diff = when - DateTime.UtcNow;
|
||||||
embed.AddField(
|
embed.AddField(
|
||||||
$"#{++i + (page * 10)} {rem.When:HH:mm yyyy-MM-dd} UTC "
|
$"#{++i + (page * 10)} {rem.When:HH:mm yyyy-MM-dd} UTC "
|
||||||
+ $"{TimestampTag.FromDateTime(when)}",
|
+ $"(in {diff.ToPrettyStringHm()})",
|
||||||
$@"`Target:` {(rem.IsPrivate ? "DM" : "Channel")}
|
$@"`Target:` {(rem.IsPrivate ? "DM" : "Channel")}
|
||||||
`TargetId:` {rem.ChannelId}
|
`TargetId:` {rem.ChannelId}
|
||||||
`Message:` {rem.Message?.TrimTo(50)}");
|
`Message:` {rem.Message?.TrimTo(50)}");
|
||||||
|
@ -202,15 +203,16 @@ public partial class Utility
|
||||||
await uow.SaveChangesAsync();
|
await uow.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
// var gTime = ctx.Guild is null ? time : TimeZoneInfo.ConvertTime(time, _tz.GetTimeZoneOrUtc(ctx.Guild.Id));
|
var gTime = ctx.Guild is null ? time : TimeZoneInfo.ConvertTime(time, _tz.GetTimeZoneOrUtc(ctx.Guild.Id));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Response()
|
await Response()
|
||||||
.Confirm($"\u23f0 {GetText(strs.remind2(
|
.Confirm($"\u23f0 {GetText(strs.remind(
|
||||||
Format.Bold(!isPrivate ? $"<#{targetId}>" : ctx.User.Username),
|
Format.Bold(!isPrivate ? $"<#{targetId}>" : ctx.User.Username),
|
||||||
Format.Bold(message),
|
Format.Bold(message),
|
||||||
TimestampTag.FromDateTime(DateTime.UtcNow.Add(ts), TimestampTagStyles.Relative),
|
ts.ToPrettyStringHm(),
|
||||||
TimestampTag.FormatFromDateTime(time, TimestampTagStyles.ShortDateTime)))}")
|
gTime,
|
||||||
|
gTime))}")
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using EllieBot.Db.Models;
|
using EllieBot.Db.Models;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Utility.Services;
|
namespace EllieBot.Modules.Utility.Services;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using EllieBot.Modules.Utility.Services;
|
using EllieBot.Modules.Utility.Services;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace EllieBot.Modules.Utility.Common;
|
namespace EllieBot.Modules.Utility.Common;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
#nullable disable
|
#nullable disable
|
||||||
namespace EllieBot.Modules.Utility;
|
namespace EllieBot.Modules.Utility;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
namespace EllieBot.Modules.Utility.Common.Exceptions;
|
namespace EllieBot.Modules.Utility.Common.Exceptions;
|
||||||
|
|
||||||
public class StreamRoleNotFoundException : Exception
|
public class StreamRoleNotFoundException : Exception
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
namespace EllieBot.Modules.Utility.Common.Exceptions;
|
namespace EllieBot.Modules.Utility.Common.Exceptions;
|
||||||
|
|
||||||
public class StreamRolePermissionException : Exception
|
public class StreamRolePermissionException : Exception
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable disable
|
#nullable disable
|
||||||
namespace EllieBot.Modules.Utility.Common;
|
namespace EllieBot.Modules.Utility.Common;
|
||||||
|
|
||||||
public enum StreamRoleListType
|
public enum StreamRoleListType
|
||||||
|
|
Loading…
Reference in a new issue