mirror of
https://gitlab.com/Kwoth/nadekobot.git
synced 2025-09-10 17:28:27 -04:00
Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
49ff0dd27a | ||
|
2053296154 | ||
|
42fc0c263d | ||
|
cf1d950308 | ||
|
0fdccea31c | ||
|
2f8f62afcb | ||
|
570f39d4f8 | ||
|
40f1774655 | ||
|
fddd0f2340 | ||
|
86f9d901fe | ||
|
eaab60898f |
35
CHANGELOG.md
35
CHANGELOG.md
@@ -2,6 +2,41 @@
|
||||
|
||||
Mostly based on [keepachangelog](https://keepachangelog.com/en/1.0.0/) except date format. a-c-f-r-o
|
||||
|
||||
## [5.3.9] - 30.01.2025
|
||||
|
||||
## Added
|
||||
|
||||
- Added `.todo archive done <name>`
|
||||
- Creates an archive of only currently completed todos
|
||||
- An alternative to ".todo archive add <name>" which moves all todos to an archive
|
||||
|
||||
## Changed
|
||||
|
||||
- Increased todo and archive limits slightly
|
||||
- Global nadeko captcha patron ad will show 12.5% of the time now, down from 20%, and be smaller
|
||||
- `.remind` now has a 1 year max timeout, up from 2 months
|
||||
|
||||
## Fixed
|
||||
|
||||
- Captcha is now slightly bigger, with larger margin, to mitigate phone edge issues
|
||||
- Fixed `.stock` command, unless there is some ip blocking going on
|
||||
|
||||
## [5.3.8] - 27.01.2025
|
||||
|
||||
## Fixed
|
||||
|
||||
- `.temprole` now correctly adds a role
|
||||
- `.h temprole` also shows the correct overload now
|
||||
|
||||
## [5.3.7] - 21.01.2025
|
||||
|
||||
## Changed
|
||||
|
||||
- You can now run `.prune` in DMs
|
||||
- It deletes only bot messages
|
||||
- You can't specify a number of messages to delete (100 default)
|
||||
- Updated command list
|
||||
|
||||
## [5.3.6] - 20.01.2025
|
||||
|
||||
## Added
|
||||
|
@@ -32,6 +32,25 @@ public partial class Administration
|
||||
}
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.DM)]
|
||||
[NadekoOptions<PruneOptions>]
|
||||
public async Task Prune()
|
||||
{
|
||||
var progressMsg = await Response().Pending(strs.prune_progress(0, 100)).SendAsync();
|
||||
var progress = GetProgressTracker(progressMsg);
|
||||
|
||||
var result = await _service.PruneWhere(ctx.Channel,
|
||||
100,
|
||||
x => x.Author.Id == ctx.Client.CurrentUser.Id,
|
||||
progress);
|
||||
|
||||
ctx.Message.DeleteAfter(3);
|
||||
|
||||
await SendResult(result);
|
||||
await progressMsg.DeleteAsync();
|
||||
}
|
||||
|
||||
//deletes her own messages, no perm required
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
@@ -114,9 +133,9 @@ public partial class Administration
|
||||
await progressMsg.ModifyAsync(props =>
|
||||
{
|
||||
props.Embed = CreateEmbed()
|
||||
.WithPendingColor()
|
||||
.WithDescription(GetText(strs.prune_progress(deleted, total)))
|
||||
.Build();
|
||||
.WithPendingColor()
|
||||
.WithDescription(GetText(strs.prune_progress(deleted, total)))
|
||||
.Build();
|
||||
});
|
||||
}
|
||||
catch
|
||||
|
@@ -18,7 +18,7 @@ public class PruneService : INService
|
||||
}
|
||||
|
||||
public async Task<PruneResult> PruneWhere(
|
||||
ITextChannel channel,
|
||||
IMessageChannel channel,
|
||||
int amount,
|
||||
Func<IMessage, bool> predicate,
|
||||
IProgress<(int deleted, int total)> progress,
|
||||
@@ -30,13 +30,14 @@ public class PruneService : INService
|
||||
|
||||
var originalAmount = amount;
|
||||
|
||||
var gid = (channel as ITextChannel)?.GuildId ?? channel.Id;
|
||||
using var cancelSource = new CancellationTokenSource();
|
||||
if (!_pruningGuilds.TryAdd(channel.GuildId, cancelSource))
|
||||
if (!_pruningGuilds.TryAdd(gid, cancelSource))
|
||||
return PruneResult.AlreadyRunning;
|
||||
|
||||
try
|
||||
{
|
||||
if (!await _ps.LimitHitAsync(LimitedFeatureName.Prune, channel.Guild.OwnerId))
|
||||
if (channel is ITextChannel tc && !await _ps.LimitHitAsync(LimitedFeatureName.Prune, tc.Guild.OwnerId))
|
||||
{
|
||||
return PruneResult.FeatureLimit;
|
||||
}
|
||||
@@ -74,9 +75,9 @@ public class PruneService : INService
|
||||
singleDeletable.Add(x);
|
||||
}
|
||||
|
||||
if (bulkDeletable.Count > 0)
|
||||
if (channel is ITextChannel tc2 && bulkDeletable.Count > 0)
|
||||
{
|
||||
await channel.DeleteMessagesAsync(bulkDeletable);
|
||||
await tc2.DeleteMessagesAsync(bulkDeletable);
|
||||
amount -= msgs.Length;
|
||||
progress.Report((originalAmount - amount, originalAmount));
|
||||
await Task.Delay(2000, cancelSource.Token);
|
||||
@@ -97,7 +98,7 @@ public class PruneService : INService
|
||||
}
|
||||
finally
|
||||
{
|
||||
_pruningGuilds.TryRemove(channel.GuildId, out _);
|
||||
_pruningGuilds.TryRemove(gid, out _);
|
||||
}
|
||||
|
||||
return PruneResult.Success;
|
||||
|
@@ -221,7 +221,7 @@ public partial class Administration
|
||||
[RequireContext(ContextType.Guild)]
|
||||
[UserPerm(GuildPerm.Administrator)]
|
||||
[BotPerm(GuildPerm.ManageRoles)]
|
||||
public async Task TempRole(ParsedTimespan timespan, IUser user, [Leftover] IRole role)
|
||||
public async Task TempRole(ParsedTimespan timespan, IGuildUser user, [Leftover] IRole role)
|
||||
{
|
||||
if (!await CheckRoleHierarchy(role))
|
||||
{
|
||||
@@ -231,6 +231,7 @@ public partial class Administration
|
||||
return;
|
||||
}
|
||||
|
||||
await user.AddRoleAsync(role);
|
||||
await _tempRoleService.AddTempRoleAsync(ctx.Guild.Id, role.Id, user.Id, timespan.Time);
|
||||
|
||||
|
||||
|
@@ -162,15 +162,15 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
|
||||
if (password is not null)
|
||||
{
|
||||
var img = GetPasswordImage(password);
|
||||
var img = _captchaService.GetPasswordImage(password);
|
||||
await using var stream = await img.ToStreamAsync();
|
||||
var toSend = Response()
|
||||
.File(stream, "timely.png");
|
||||
|
||||
#if GLOBAL_NADEKO
|
||||
if (_rng.Next(0, 5) == 0)
|
||||
if (_rng.Next(0, 8) == 0)
|
||||
toSend = toSend
|
||||
.Confirm("[Sub on Patreon](https://patreon.com/nadekobot) to remove captcha.")
|
||||
.Text("*[Sub on Patreon](https://patreon.com/nadekobot) to remove captcha.*");
|
||||
#endif
|
||||
|
||||
var captchaMessage = await toSend.SendAsync();
|
||||
@@ -194,39 +194,6 @@ public partial class Gambling : GamblingModule<GamblingService>
|
||||
await ClaimTimely();
|
||||
}
|
||||
|
||||
private Image<Rgba32> GetPasswordImage(string password)
|
||||
{
|
||||
var img = new Image<Rgba32>(50, 24);
|
||||
|
||||
var font = _fonts.NotoSans.CreateFont(22);
|
||||
var outlinePen = new SolidPen(Color.Black, 0.5f);
|
||||
var strikeoutRun = new RichTextRun
|
||||
{
|
||||
Start = 0,
|
||||
End = password.GetGraphemeCount(),
|
||||
Font = font,
|
||||
StrikeoutPen = new SolidPen(Color.White, 4),
|
||||
TextDecorations = TextDecorations.Strikeout
|
||||
};
|
||||
// draw password on the image
|
||||
img.Mutate(x =>
|
||||
{
|
||||
x.DrawText(new RichTextOptions(font)
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
FallbackFontFamilies = _fonts.FallBackFonts,
|
||||
Origin = new(25, 12),
|
||||
TextRuns = [strikeoutRun]
|
||||
},
|
||||
password,
|
||||
Brushes.Solid(Color.White),
|
||||
outlinePen);
|
||||
});
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
private async Task ClaimTimely()
|
||||
{
|
||||
var period = Config.Timely.Cooldown;
|
||||
|
@@ -17,7 +17,7 @@ public sealed class CaptchaService(FontProvider fonts, IBotCache cache, IPatrona
|
||||
|
||||
public Image<Rgba32> GetPasswordImage(string password)
|
||||
{
|
||||
var img = new Image<Rgba32>(50, 24);
|
||||
var img = new Image<Rgba32>(60, 34);
|
||||
|
||||
var font = fonts.NotoSans.CreateFont(22);
|
||||
var outlinePen = new SolidPen(Color.Black, 0.5f);
|
||||
@@ -39,7 +39,7 @@ public sealed class CaptchaService(FontProvider fonts, IBotCache cache, IPatrona
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
FallbackFontFamilies = fonts.FallBackFonts,
|
||||
Origin = new(25, 12),
|
||||
Origin = new(30, 15),
|
||||
TextRuns = [strikeoutRun]
|
||||
},
|
||||
password,
|
||||
|
@@ -33,9 +33,9 @@ public partial class Games
|
||||
.File(stream, "timely.png");
|
||||
|
||||
#if GLOBAL_NADEKO
|
||||
if (_rng.Next(0, 5) == 0)
|
||||
if (_rng.Next(0, 8) == 0)
|
||||
toSend = toSend
|
||||
.Confirm("[Sub on Patreon](https://patreon.com/nadekobot) to remove captcha.")
|
||||
.Text("*[Sub on Patreon](https://patreon.com/nadekobot) to remove captcha.*");
|
||||
#endif
|
||||
var captcha = await toSend.SendAsync();
|
||||
|
||||
|
@@ -2,6 +2,8 @@
|
||||
using CsvHelper;
|
||||
using CsvHelper.Configuration;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
@@ -9,54 +11,57 @@ namespace NadekoBot.Modules.Searches;
|
||||
public sealed class DefaultStockDataService : IStockDataService, INService
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IBotCache _cache;
|
||||
|
||||
public DefaultStockDataService(IHttpClientFactory httpClientFactory)
|
||||
=> _httpClientFactory = httpClientFactory;
|
||||
public DefaultStockDataService(IHttpClientFactory httpClientFactory, IBotCache cache)
|
||||
=> (_httpClientFactory, _cache) = (httpClientFactory, cache);
|
||||
|
||||
private static TypedKey<StockData> GetStockDataKey(string query)
|
||||
=> new($"stockdata:{query}");
|
||||
|
||||
public async Task<StockData?> GetStockDataAsync(string query)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(query);
|
||||
|
||||
return await _cache.GetOrAddAsync(GetStockDataKey(query.Trim().ToLowerInvariant()),
|
||||
() => GetStockDataInternalAsync(query),
|
||||
expiry: TimeSpan.FromHours(1));
|
||||
}
|
||||
|
||||
public async Task<StockData?> GetStockDataInternalAsync(string query)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!query.IsAlphaNumeric())
|
||||
return default;
|
||||
|
||||
using var http = _httpClientFactory.CreateClient();
|
||||
var info = await GetNasdaqDataResponse<NasdaqSummaryResponse>(
|
||||
$"https://api.nasdaq.com/api/quote/{query}/summary?assetclass=stocks");
|
||||
|
||||
var quoteHtmlPage = $"https://finance.yahoo.com/quote/{query.ToUpperInvariant()}";
|
||||
|
||||
var config = Configuration.Default.WithDefaultLoader();
|
||||
using var document = await BrowsingContext.New(config).OpenAsync(quoteHtmlPage);
|
||||
|
||||
var tickerName = document.QuerySelector("div.top > .left > .container > h1")
|
||||
?.TextContent;
|
||||
|
||||
if (tickerName is null)
|
||||
if (info?.Data is not { } d || d.SummaryData is not { } sd)
|
||||
return default;
|
||||
|
||||
var marketcap = document
|
||||
.QuerySelector("li > span > fin-streamer[data-field='marketCap']")
|
||||
?.TextContent;
|
||||
|
||||
var closePrice = double.Parse(sd.PreviousClose.Value?.Substring(1) ?? "0",
|
||||
NumberStyles.Any,
|
||||
CultureInfo.InvariantCulture);
|
||||
|
||||
var volume = document.QuerySelector("li > span > fin-streamer[data-field='regularMarketVolume']")
|
||||
?.TextContent;
|
||||
|
||||
var close = document.QuerySelector("li > span > fin-streamer[data-field='regularMarketPreviousClose']")
|
||||
?.TextContent
|
||||
?? "0";
|
||||
|
||||
var price = document.QuerySelector("fin-streamer.livePrice > span")
|
||||
?.TextContent
|
||||
?? "0";
|
||||
var price = d.BidAsk.Bid.Value.IndexOf('*') is var idx and > 0
|
||||
&& double.TryParse(d.BidAsk.Bid.Value.Substring(1, idx - 1),
|
||||
NumberStyles.Any,
|
||||
CultureInfo.InvariantCulture,
|
||||
out var bid)
|
||||
? bid
|
||||
: double.NaN;
|
||||
|
||||
return new()
|
||||
{
|
||||
Name = tickerName,
|
||||
Symbol = query,
|
||||
Price = double.Parse(price, NumberStyles.Any, CultureInfo.InvariantCulture),
|
||||
Close = double.Parse(close, NumberStyles.Any, CultureInfo.InvariantCulture),
|
||||
MarketCap = marketcap,
|
||||
DailyVolume = (long)double.Parse(volume ?? "0", NumberStyles.Any, CultureInfo.InvariantCulture),
|
||||
Name = query,
|
||||
Symbol = info.Data.Symbol,
|
||||
Price = price,
|
||||
Close = closePrice,
|
||||
MarketCap = sd.MarketCap.Value,
|
||||
DailyVolume =
|
||||
(long)double.Parse(sd.AverageVolume.Value ?? "0", NumberStyles.Any, CultureInfo.InvariantCulture),
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -66,6 +71,36 @@ public sealed class DefaultStockDataService : IStockDataService, INService
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<NasdaqDataResponse<T>?> GetNasdaqDataResponse<T>(string url)
|
||||
{
|
||||
using var httpClient = _httpClientFactory.CreateClient("google:search");
|
||||
|
||||
var req = new HttpRequestMessage(HttpMethod.Get,
|
||||
url)
|
||||
{
|
||||
Headers =
|
||||
{
|
||||
{ "Host", "api.nasdaq.com" },
|
||||
{ "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0" },
|
||||
{ "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" },
|
||||
{ "Accept-Language", "en-US,en;q=0.5" },
|
||||
{ "Accept-Encoding", "gzip, deflate, br, zstd" },
|
||||
{ "Connection", "keep-alive" },
|
||||
{ "Upgrade-Insecure-Requests", "1" },
|
||||
{ "Sec-Fetch-Dest", "document" },
|
||||
{ "Sec-Fetch-Mode", "navigate" },
|
||||
{ "Sec-Fetch-Site", "none" },
|
||||
{ "Sec-Fetch-User", "?1" },
|
||||
{ "Priority", "u=0, i" },
|
||||
{ "TE", "trailers" }
|
||||
}
|
||||
};
|
||||
var res = await httpClient.SendAsync(req);
|
||||
|
||||
var info = await res.Content.ReadFromJsonAsync<NasdaqDataResponse<T>>();
|
||||
return info;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<SymbolData>> SearchSymbolAsync(string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
@@ -91,22 +126,37 @@ public sealed class DefaultStockDataService : IStockDataService, INService
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static CsvConfiguration _csvConfig = new(CultureInfo.InvariantCulture);
|
||||
private static TypedKey<IReadOnlyCollection<CandleData>> GetCandleDataKey(string query)
|
||||
=> new($"candledata:{query}");
|
||||
|
||||
public async Task<IReadOnlyCollection<CandleData>> GetCandleDataAsync(string query)
|
||||
=> await _cache.GetOrAddAsync(GetCandleDataKey(query),
|
||||
async () => await GetCandleDataInternalAsync(query),
|
||||
expiry: TimeSpan.FromHours(4))
|
||||
?? [];
|
||||
|
||||
public async Task<IReadOnlyCollection<CandleData>> GetCandleDataInternalAsync(string query)
|
||||
{
|
||||
using var http = _httpClientFactory.CreateClient();
|
||||
await using var resStream = await http.GetStreamAsync(
|
||||
$"https://query1.finance.yahoo.com/v7/finance/download/{query}"
|
||||
+ $"?period1={DateTime.UtcNow.Subtract(30.Days()).ToTimestamp()}"
|
||||
+ $"&period2={DateTime.UtcNow.ToTimestamp()}"
|
||||
+ "&interval=1d");
|
||||
|
||||
using var textReader = new StreamReader(resStream);
|
||||
using var csv = new CsvReader(textReader, _csvConfig);
|
||||
var records = csv.GetRecords<YahooFinanceCandleData>().ToArray();
|
||||
var now = DateTime.UtcNow;
|
||||
var fromdate = now.Subtract(30.Days()).ToString("yyyy-MM-dd");
|
||||
var todate = now.ToString("yyyy-MM-dd");
|
||||
|
||||
return records
|
||||
.Map(static x => new CandleData(x.Open, x.Close, x.High, x.Low, x.Volume));
|
||||
var res = await GetNasdaqDataResponse<NasdaqChartResponse>(
|
||||
$"https://api.nasdaq.com/api/quote/{query}/chart?assetclass=stocks"
|
||||
+ $"&fromdate={fromdate}"
|
||||
+ $"&todate={todate}");
|
||||
|
||||
if (res?.Data?.Chart is not { } chart)
|
||||
return Array.Empty<CandleData>();
|
||||
|
||||
|
||||
return chart.Select(d => new CandleData(d.Z.Open,
|
||||
d.Z.Close,
|
||||
d.Z.High,
|
||||
d.Z.Low,
|
||||
(long)double.Parse(d.Z.Volume, NumberStyles.Any, CultureInfo.InvariantCulture)))
|
||||
.ToList();
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public sealed class NasdaqChartResponse
|
||||
{
|
||||
public required NasdaqChartResponseData[] Chart { get; init; }
|
||||
|
||||
public sealed class NasdaqChartResponseData
|
||||
{
|
||||
public required CandleData Z { get; init; }
|
||||
|
||||
public sealed class CandleData
|
||||
{
|
||||
public required decimal High { get; init; }
|
||||
public required decimal Low { get; init; }
|
||||
public required decimal Open { get; init; }
|
||||
public required decimal Close { get; init; }
|
||||
public required string Volume { get; init; }
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public sealed class NasdaqDataResponse<T>
|
||||
{
|
||||
public required T? Data { get; init; }
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace NadekoBot.Modules.Searches;
|
||||
|
||||
public sealed class NasdaqSummaryResponse
|
||||
{
|
||||
public required string Symbol { get; init; }
|
||||
|
||||
public required NasdaqSummaryResponseData SummaryData { get; init; }
|
||||
public required NasdaqSummaryBidAsk BidAsk { get; init; }
|
||||
|
||||
public sealed class NasdaqSummaryBidAsk
|
||||
{
|
||||
[JsonPropertyName("Bid * Size")]
|
||||
public required NasdaqBid Bid { get; init; }
|
||||
|
||||
public sealed class NasdaqBid
|
||||
{
|
||||
public required string Value { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class NasdaqSummaryResponseData
|
||||
{
|
||||
public required PreviousCloseData PreviousClose { get; init; }
|
||||
public required MarketCapData MarketCap { get; init; }
|
||||
public required AverageVolumeData AverageVolume { get; init; }
|
||||
|
||||
public sealed class PreviousCloseData
|
||||
{
|
||||
public required string Value { get; init; }
|
||||
}
|
||||
|
||||
public sealed class MarketCapData
|
||||
{
|
||||
public required string Value { get; init; }
|
||||
}
|
||||
|
||||
public sealed class AverageVolumeData
|
||||
{
|
||||
public required string Value { get; init; }
|
||||
}
|
||||
}
|
||||
}
|
@@ -183,7 +183,7 @@ public partial class Utility
|
||||
{
|
||||
var time = DateTime.UtcNow + ts;
|
||||
|
||||
if (ts > TimeSpan.FromDays(60))
|
||||
if (ts > TimeSpan.FromDays(366))
|
||||
return false;
|
||||
|
||||
if (ctx.Guild is not null)
|
||||
|
@@ -150,7 +150,26 @@ public partial class Utility
|
||||
[Cmd]
|
||||
public async Task TodoArchiveAdd([Leftover] string name)
|
||||
{
|
||||
var result = await _service.ArchiveTodosAsync(ctx.User.Id, name);
|
||||
var result = await _service.ArchiveTodosAsync(ctx.User.Id, name, false);
|
||||
if (result == ArchiveTodoResult.NoTodos)
|
||||
{
|
||||
await Response().Error(strs.todo_no_todos).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == ArchiveTodoResult.MaxLimitReached)
|
||||
{
|
||||
await Response().Error(strs.todo_archive_max_limit).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.OkAsync();
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async Task TodoArchiveDone([Leftover] string name)
|
||||
{
|
||||
var result = await _service.ArchiveTodosAsync(ctx.User.Id, name, true);
|
||||
if (result == ArchiveTodoResult.NoTodos)
|
||||
{
|
||||
await Response().Error(strs.todo_no_todos).SendAsync();
|
||||
@@ -193,7 +212,7 @@ public partial class Utility
|
||||
|
||||
foreach (var archivedList in items)
|
||||
{
|
||||
eb.AddField($"id: {archivedList.Id.ToString()}", archivedList.Name, true);
|
||||
eb.AddField($"id: {new kwum(archivedList.Id)}", archivedList.Name, true);
|
||||
}
|
||||
|
||||
return eb;
|
||||
@@ -202,7 +221,7 @@ public partial class Utility
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async Task TodoArchiveShow(int id)
|
||||
public async Task TodoArchiveShow(kwum id)
|
||||
{
|
||||
var list = await _service.GetArchivedTodoListAsync(ctx.User.Id, id);
|
||||
if (list == null || list.Items.Count == 0)
|
||||
@@ -234,7 +253,7 @@ public partial class Utility
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
public async Task TodoArchiveDelete(int id)
|
||||
public async Task TodoArchiveDelete(kwum id)
|
||||
{
|
||||
if (!await _service.ArchiveDeleteAsync(ctx.User.Id, id))
|
||||
{
|
||||
|
@@ -6,8 +6,8 @@ namespace NadekoBot.Modules.Utility;
|
||||
|
||||
public sealed class TodoService : INService
|
||||
{
|
||||
private const int ARCHIVE_MAX_COUNT = 9;
|
||||
private const int TODO_MAX_COUNT = 27;
|
||||
private const int ARCHIVE_MAX_COUNT = 18;
|
||||
private const int TODO_MAX_COUNT = 36;
|
||||
|
||||
private readonly DbService _db;
|
||||
|
||||
@@ -111,7 +111,7 @@ public sealed class TodoService : INService
|
||||
.DeleteAsync();
|
||||
}
|
||||
|
||||
public async Task<ArchiveTodoResult> ArchiveTodosAsync(ulong userId, string name)
|
||||
public async Task<ArchiveTodoResult> ArchiveTodosAsync(ulong userId, string name, bool onlyDone)
|
||||
{
|
||||
// create a new archive
|
||||
|
||||
@@ -140,7 +140,7 @@ public sealed class TodoService : INService
|
||||
|
||||
var updated = await ctx
|
||||
.GetTable<TodoModel>()
|
||||
.Where(x => x.UserId == userId && x.ArchiveId == null)
|
||||
.Where(x => x.UserId == userId && (!onlyDone || x.IsDone) && x.ArchiveId == null)
|
||||
.Set(x => x.ArchiveId, inserted.Id)
|
||||
.UpdateAsync();
|
||||
|
||||
@@ -204,4 +204,5 @@ public sealed class TodoService : INService
|
||||
.Where(x => x.UserId == userId && x.Id == todoId)
|
||||
.FirstOrDefaultAsyncLinqToDB();
|
||||
}
|
||||
|
||||
}
|
@@ -4,7 +4,7 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>true</ImplicitUsings>
|
||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||
<Version>5.3.6</Version>
|
||||
<Version>5.3.9</Version>
|
||||
|
||||
<!-- Output/build -->
|
||||
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
||||
|
@@ -1441,6 +1441,11 @@ todoarchivedelete:
|
||||
- del
|
||||
- remove
|
||||
- rm
|
||||
todoarchivedone:
|
||||
- done
|
||||
- compelete
|
||||
- finish
|
||||
- completed
|
||||
todoedit:
|
||||
- edit
|
||||
- change
|
||||
|
@@ -974,7 +974,7 @@
|
||||
"Module": "Administration",
|
||||
"Options": null,
|
||||
"Requirements": [
|
||||
"Bot Owner Only"
|
||||
"Administrator Server Permission"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -990,7 +990,7 @@
|
||||
"Module": "Administration",
|
||||
"Options": null,
|
||||
"Requirements": [
|
||||
"Bot Owner Only"
|
||||
"Administrator Server Permission"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1008,7 +1008,7 @@
|
||||
"Module": "Administration",
|
||||
"Options": null,
|
||||
"Requirements": [
|
||||
"Bot Owner Only"
|
||||
"Administrator Server Permission"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -4045,6 +4045,51 @@
|
||||
"ManageMessages Server Permission"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Aliases": [
|
||||
".fish",
|
||||
".fi"
|
||||
],
|
||||
"Description": "Attempt to catch a fish.\nDifferent fish live in different places, at different times of day and in different weather.",
|
||||
"Usage": [
|
||||
".fish"
|
||||
],
|
||||
"Submodule": "FishCommands",
|
||||
"Module": "Games",
|
||||
"Options": null,
|
||||
"Requirements": []
|
||||
},
|
||||
{
|
||||
"Aliases": [
|
||||
".fishspot",
|
||||
".fisp",
|
||||
".fish?"
|
||||
],
|
||||
"Description": "Shows information about the current fish spot, weather and time.",
|
||||
"Usage": [
|
||||
".fishspot"
|
||||
],
|
||||
"Submodule": "FishCommands",
|
||||
"Module": "Games",
|
||||
"Options": null,
|
||||
"Requirements": []
|
||||
},
|
||||
{
|
||||
"Aliases": [
|
||||
".fishlist",
|
||||
".fili",
|
||||
".fishes",
|
||||
".fil"
|
||||
],
|
||||
"Description": "Look at your fish catalogue.\nShows how many of each fish you caught and what was the highest quality.\nFor each caught fish, it also shows its required spot, time of day and weather.",
|
||||
"Usage": [
|
||||
".fishlist"
|
||||
],
|
||||
"Submodule": "FishCommands",
|
||||
"Module": "Games",
|
||||
"Options": null,
|
||||
"Requirements": []
|
||||
},
|
||||
{
|
||||
"Aliases": [
|
||||
".hangmanlist"
|
||||
|
@@ -4524,6 +4524,13 @@ todoarchiveadd:
|
||||
params:
|
||||
- name:
|
||||
desc: "The name of the archive to be created."
|
||||
todoarchivedone:
|
||||
desc: Creates a new archive with the specified name using only completed current todos.
|
||||
ex:
|
||||
- Success!
|
||||
params:
|
||||
- name:
|
||||
desc: "The name of the archive to be created."
|
||||
todoarchivelist:
|
||||
desc: Lists all archived todo lists.
|
||||
ex:
|
||||
@@ -4852,11 +4859,11 @@ temprole:
|
||||
- '15m @User Jail'
|
||||
- '7d @Newbie Trial Member'
|
||||
params:
|
||||
- days:
|
||||
- time:
|
||||
desc: "The time after which the role is automatically removed."
|
||||
- user:
|
||||
user:
|
||||
desc: "The user to give the role to."
|
||||
- role:
|
||||
role:
|
||||
desc: "The role to give to the user."
|
||||
minesweeper:
|
||||
desc: |-
|
||||
|
Reference in New Issue
Block a user