notify, minesweeper, migrations

renames, refactors
remind optimized wait
This commit is contained in:
Toastie 2024-12-08 17:07:17 +13:00
parent 204db02cd9
commit 6bc55cd97f
Signed by: toastie_t0ast
GPG key ID: 27F3B6855AFD40A4
32 changed files with 8283 additions and 245 deletions

View file

@ -74,6 +74,35 @@ public abstract class EllieContext : DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
#region Notify
modelBuilder.Entity<Notify>(e =>
{
e.HasAlternateKey(x => new
{
x.GuildId,
x.Event
});
});
#endregion
#region TempRoles
modelBuilder.Entity<TempRole>(e =>
{
e.HasAlternateKey(x => new
{
x.GuildId,
x.UserId,
x.RoleId
});
e.HasIndex(x => x.ExpiresAt);
});
#endregion
#region GuildColors
modelBuilder.Entity<GuildColors>()
@ -449,7 +478,6 @@ public abstract class EllieContext : DbContext
xps.HasIndex(x => x.UserId);
xps.HasIndex(x => x.GuildId);
xps.HasIndex(x => x.Xp);
xps.HasIndex(x => x.AwardedXp);
#endregion

View file

@ -20,7 +20,6 @@ public static class UserXpExtensions
{
Xp = 0,
UserId = userId,
NotifyOnLevelUp = XpNotificationLocation.None,
GuildId = guildId
});
}

View file

@ -36,29 +36,37 @@ public class GuildConfig : DbEntity
public HashSet<FilterChannelId> FilterInvitesChannelIds { get; set; } = new();
public HashSet<FilterLinksChannelId> FilterLinksChannelIds { get; set; } = new();
//public bool FilterLinks { get; set; }
//public HashSet<FilterLinksChannelId> FilterLinksChannels { get; set; } = new HashSet<FilterLinksChannelId>();
public bool FilterWords { get; set; }
public HashSet<FilteredWord> FilteredWords { get; set; } = new();
public HashSet<FilterWordsChannelId> FilterWordsChannelIds { get; set; } = new();
// mute
public HashSet<MutedUserId> MutedUsers { get; set; } = new();
public string MuteRoleName { get; set; }
// chatterbot
public bool CleverbotEnabled { get; set; }
// protection
public AntiRaidSetting AntiRaidSetting { get; set; }
public AntiSpamSetting AntiSpamSetting { get; set; }
public AntiAltSetting AntiAltSetting { get; set; }
// time
public string Locale { get; set; }
public string TimeZoneId { get; set; }
// timers
public HashSet<UnmuteTimer> UnmuteTimers { get; set; } = new();
public HashSet<UnbanTimer> UnbanTimer { get; set; } = new();
public HashSet<UnroleTimer> UnroleTimer { get; set; } = new();
// vcrole
public HashSet<VcRoleInfo> VcRoleInfos { get; set; }
// aliases
public HashSet<CommandAlias> CommandAliases { get; set; } = new();
public bool WarningsInitialized { get; set; }
public HashSet<SlowmodeIgnoredUser> SlowmodeIgnoredUsers { get; set; }

View file

@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
namespace EllieBot.Db.Models;
public class Notify
{
[Key]
public int Id { get; set; }
public ulong GuildId { get; set; }
public ulong ChannelId { get; set; }
public NotifyEvent Event { get; set; }
[MaxLength(10_000)]
public string Message { get; set; } = string.Empty;
}
public enum NotifyEvent
{
UserLevelUp
}

View file

@ -0,0 +1,12 @@
namespace EllieBot.Db.Models;
public class TempRole
{
public int Id { get; set; }
public ulong GuildId { get; set; }
public bool Remove { get; set; }
public ulong RoleId { get; set; }
public ulong UserId { get; set; }
public DateTime ExpiresAt { get; set; }
}

View file

@ -6,6 +6,4 @@ public class UserXpStats : DbEntity
public ulong UserId { get; set; }
public ulong GuildId { get; set; }
public long Xp { get; set; }
public long AwardedXp { get; set; }
public XpNotificationLocation NotifyOnLevelUp { get; set; }
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,107 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace EllieBot.Migrations.PostgreSql
{
/// <inheritdoc />
public partial class awardedxpandnotifyremoved : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "ix_userxpstats_awardedxp",
table: "userxpstats");
migrationBuilder.DropColumn(
name: "awardedxp",
table: "userxpstats");
migrationBuilder.DropColumn(
name: "notifyonlevelup",
table: "userxpstats");
migrationBuilder.DropColumn(
name: "dateadded",
table: "sargroup");
migrationBuilder.CreateTable(
name: "notify",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
channelid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
@event = table.Column<int>(name: "event", type: "integer", nullable: false),
message = table.Column<string>(type: "character varying(10000)", maxLength: 10000, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_notify", x => x.id);
table.UniqueConstraint("ak_notify_guildid_event", x => new { x.guildid, x.@event });
});
migrationBuilder.CreateTable(
name: "temprole",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
guildid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
remove = table.Column<bool>(type: "boolean", nullable: false),
roleid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
userid = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
expiresat = table.Column<DateTime>(type: "timestamp without time zone", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_temprole", x => x.id);
table.UniqueConstraint("ak_temprole_guildid_userid_roleid", x => new { x.guildid, x.userid, x.roleid });
});
migrationBuilder.CreateIndex(
name: "ix_temprole_expiresat",
table: "temprole",
column: "expiresat");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "notify");
migrationBuilder.DropTable(
name: "temprole");
migrationBuilder.AddColumn<long>(
name: "awardedxp",
table: "userxpstats",
type: "bigint",
nullable: false,
defaultValue: 0L);
migrationBuilder.AddColumn<int>(
name: "notifyonlevelup",
table: "userxpstats",
type: "integer",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<DateTime>(
name: "dateadded",
table: "sargroup",
type: "timestamp without time zone",
nullable: true);
migrationBuilder.CreateIndex(
name: "ix_userxpstats_awardedxp",
table: "userxpstats",
column: "awardedxp");
}
}
}

View file

@ -1817,6 +1817,42 @@ namespace EllieBot.Migrations.PostgreSql
b.ToTable("ncpixel", (string)null);
});
modelBuilder.Entity("EllieBot.Db.Models.Notify", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<decimal>("ChannelId")
.HasColumnType("numeric(20,0)")
.HasColumnName("channelid");
b.Property<int>("Event")
.HasColumnType("integer")
.HasColumnName("event");
b.Property<decimal>("GuildId")
.HasColumnType("numeric(20,0)")
.HasColumnName("guildid");
b.Property<string>("Message")
.IsRequired()
.HasMaxLength(10000)
.HasColumnType("character varying(10000)")
.HasColumnName("message");
b.HasKey("Id")
.HasName("pk_notify");
b.HasAlternateKey("GuildId", "Event")
.HasName("ak_notify_guildid_event");
b.ToTable("notify", (string)null);
});
modelBuilder.Entity("EllieBot.Db.Models.PatronUser", b =>
{
b.Property<decimal>("UserId")
@ -2339,10 +2375,6 @@ namespace EllieBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
b.Property<int>("GroupNumber")
.HasColumnType("integer")
.HasColumnName("groupnumber");
@ -2706,6 +2738,47 @@ namespace EllieBot.Migrations.PostgreSql
b.ToTable("streamrolewhitelisteduser", (string)null);
});
modelBuilder.Entity("EllieBot.Db.Models.TempRole", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("ExpiresAt")
.HasColumnType("timestamp without time zone")
.HasColumnName("expiresat");
b.Property<decimal>("GuildId")
.HasColumnType("numeric(20,0)")
.HasColumnName("guildid");
b.Property<bool>("Remove")
.HasColumnType("boolean")
.HasColumnName("remove");
b.Property<decimal>("RoleId")
.HasColumnType("numeric(20,0)")
.HasColumnName("roleid");
b.Property<decimal>("UserId")
.HasColumnType("numeric(20,0)")
.HasColumnName("userid");
b.HasKey("Id")
.HasName("pk_temprole");
b.HasAlternateKey("GuildId", "UserId", "RoleId")
.HasName("ak_temprole_guildid_userid_roleid");
b.HasIndex("ExpiresAt")
.HasDatabaseName("ix_temprole_expiresat");
b.ToTable("temprole", (string)null);
});
modelBuilder.Entity("EllieBot.Db.Models.TodoModel", b =>
{
b.Property<int>("Id")
@ -2862,10 +2935,6 @@ namespace EllieBot.Migrations.PostgreSql
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<long>("AwardedXp")
.HasColumnType("bigint")
.HasColumnName("awardedxp");
b.Property<DateTime?>("DateAdded")
.HasColumnType("timestamp without time zone")
.HasColumnName("dateadded");
@ -2874,10 +2943,6 @@ namespace EllieBot.Migrations.PostgreSql
.HasColumnType("numeric(20,0)")
.HasColumnName("guildid");
b.Property<int>("NotifyOnLevelUp")
.HasColumnType("integer")
.HasColumnName("notifyonlevelup");
b.Property<decimal>("UserId")
.HasColumnType("numeric(20,0)")
.HasColumnName("userid");
@ -2889,9 +2954,6 @@ namespace EllieBot.Migrations.PostgreSql
b.HasKey("Id")
.HasName("pk_userxpstats");
b.HasIndex("AwardedXp")
.HasDatabaseName("ix_userxpstats_awardedxp");
b.HasIndex("GuildId")
.HasDatabaseName("ix_userxpstats_guildid");

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,106 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace EllieBot.Migrations
{
/// <inheritdoc />
public partial class awardedxpandnotifyremoved : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_UserXpStats_AwardedXp",
table: "UserXpStats");
migrationBuilder.DropColumn(
name: "AwardedXp",
table: "UserXpStats");
migrationBuilder.DropColumn(
name: "NotifyOnLevelUp",
table: "UserXpStats");
migrationBuilder.DropColumn(
name: "DateAdded",
table: "SarGroup");
migrationBuilder.CreateTable(
name: "Notify",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
Event = table.Column<int>(type: "INTEGER", nullable: false),
Message = table.Column<string>(type: "TEXT", maxLength: 10000, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Notify", x => x.Id);
table.UniqueConstraint("AK_Notify_GuildId_Event", x => new { x.GuildId, x.Event });
});
migrationBuilder.CreateTable(
name: "TempRole",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
Remove = table.Column<bool>(type: "INTEGER", nullable: false),
RoleId = table.Column<ulong>(type: "INTEGER", nullable: false),
UserId = table.Column<ulong>(type: "INTEGER", nullable: false),
ExpiresAt = table.Column<DateTime>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_TempRole", x => x.Id);
table.UniqueConstraint("AK_TempRole_GuildId_UserId_RoleId", x => new { x.GuildId, x.UserId, x.RoleId });
});
migrationBuilder.CreateIndex(
name: "IX_TempRole_ExpiresAt",
table: "TempRole",
column: "ExpiresAt");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Notify");
migrationBuilder.DropTable(
name: "TempRole");
migrationBuilder.AddColumn<long>(
name: "AwardedXp",
table: "UserXpStats",
type: "INTEGER",
nullable: false,
defaultValue: 0L);
migrationBuilder.AddColumn<int>(
name: "NotifyOnLevelUp",
table: "UserXpStats",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<DateTime>(
name: "DateAdded",
table: "SarGroup",
type: "TEXT",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_UserXpStats_AwardedXp",
table: "UserXpStats",
column: "AwardedXp");
}
}
}

View file

@ -1356,6 +1356,33 @@ namespace EllieBot.Migrations
b.ToTable("NCPixel");
});
modelBuilder.Entity("EllieBot.Db.Models.Notify", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<ulong>("ChannelId")
.HasColumnType("INTEGER");
b.Property<int>("Event")
.HasColumnType("INTEGER");
b.Property<ulong>("GuildId")
.HasColumnType("INTEGER");
b.Property<string>("Message")
.IsRequired()
.HasMaxLength(10000)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasAlternateKey("GuildId", "Event");
b.ToTable("Notify");
});
modelBuilder.Entity("EllieBot.Db.Models.PatronUser", b =>
{
b.Property<ulong>("UserId")
@ -1744,9 +1771,6 @@ namespace EllieBot.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime?>("DateAdded")
.HasColumnType("TEXT");
b.Property<int>("GroupNumber")
.HasColumnType("INTEGER");
@ -2016,6 +2040,36 @@ namespace EllieBot.Migrations
b.ToTable("StreamRoleWhitelistedUser");
});
modelBuilder.Entity("EllieBot.Db.Models.TempRole", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("TEXT");
b.Property<ulong>("GuildId")
.HasColumnType("INTEGER");
b.Property<bool>("Remove")
.HasColumnType("INTEGER");
b.Property<ulong>("RoleId")
.HasColumnType("INTEGER");
b.Property<ulong>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasAlternateKey("GuildId", "UserId", "RoleId");
b.HasIndex("ExpiresAt");
b.ToTable("TempRole");
});
modelBuilder.Entity("EllieBot.Db.Models.TodoModel", b =>
{
b.Property<int>("Id")
@ -2130,18 +2184,12 @@ namespace EllieBot.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<long>("AwardedXp")
.HasColumnType("INTEGER");
b.Property<DateTime?>("DateAdded")
.HasColumnType("TEXT");
b.Property<ulong>("GuildId")
.HasColumnType("INTEGER");
b.Property<int>("NotifyOnLevelUp")
.HasColumnType("INTEGER");
b.Property<ulong>("UserId")
.HasColumnType("INTEGER");
@ -2150,8 +2198,6 @@ namespace EllieBot.Migrations
b.HasKey("Id");
b.HasIndex("AwardedXp");
b.HasIndex("GuildId");
b.HasIndex("UserId");

View file

@ -46,7 +46,7 @@ public partial class Administration : EllieModule<AdministrationService>
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.ManageGuild)]
public async Task ImageOnlyChannel(StoopidTime time = null)
public async Task ImageOnlyChannel(ParsedTimespan timespan = null)
{
var newValue = await _somethingOnly.ToggleImageOnlyChannelAsync(ctx.Guild.Id, ctx.Channel.Id);
if (newValue)
@ -54,12 +54,12 @@ public partial class Administration : EllieModule<AdministrationService>
else
await Response().Pending(strs.imageonly_disable).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.ManageGuild)]
public async Task LinkOnlyChannel(StoopidTime time = null)
public async Task LinkOnlyChannel(ParsedTimespan timespan = null)
{
var newValue = await _somethingOnly.ToggleLinkOnlyChannelAsync(ctx.Guild.Id, ctx.Channel.Id);
if (newValue)
@ -72,10 +72,10 @@ public partial class Administration : EllieModule<AdministrationService>
[RequireContext(ContextType.Guild)]
[UserPerm(ChannelPerm.ManageChannels)]
[BotPerm(ChannelPerm.ManageChannels)]
public async Task Slowmode(StoopidTime time = null)
public async Task Slowmode(ParsedTimespan timespan = null)
{
var seconds = (int?)time?.Time.TotalSeconds ?? 0;
if (time is not null && (time.Time < TimeSpan.FromSeconds(0) || time.Time > TimeSpan.FromHours(6)))
var seconds = (int?)timespan?.Time.TotalSeconds ?? 0;
if (timespan is not null && (timespan.Time < TimeSpan.FromSeconds(0) || timespan.Time > TimeSpan.FromHours(6)))
return;
await ((ITextChannel)ctx.Channel).ModifyAsync(tcp =>
@ -221,7 +221,7 @@ public partial class Administration : EllieModule<AdministrationService>
[BotPerm(GuildPerm.ManageChannels)]
public async Task CreaTxtChanl([Leftover] string channelName)
{
var txtCh = await ctx.Guild.CreateTextChannelAsync(channelName);
var txtCh = await ctx.Guild.CreateTextChannelAsync(channelName);
await Response().Confirm(strs.createtextchan(Format.Bold(txtCh.Name))).SendAsync();
}
@ -298,18 +298,18 @@ public partial class Administration : EllieModule<AdministrationService>
[RequireContext(ContextType.Guild)]
[UserPerm(ChannelPerm.ManageMessages)]
[BotPerm(ChannelPerm.ManageMessages)]
public Task Delete(ulong messageId, StoopidTime time = null)
=> Delete((ITextChannel)ctx.Channel, messageId, time);
public Task Delete(ulong messageId, ParsedTimespan timespan = null)
=> Delete((ITextChannel)ctx.Channel, messageId, timespan);
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task Delete(ITextChannel channel, ulong messageId, StoopidTime time = null)
=> await InternalMessageAction(channel, messageId, time, msg => msg.DeleteAsync());
public async Task Delete(ITextChannel channel, ulong messageId, ParsedTimespan timespan = null)
=> await InternalMessageAction(channel, messageId, timespan, msg => msg.DeleteAsync());
private async Task InternalMessageAction(
ITextChannel channel,
ulong messageId,
StoopidTime time,
ParsedTimespan timespan,
Func<IMessage, Task> func)
{
var userPerms = ((SocketGuildUser)ctx.User).GetPermissions(channel);
@ -334,13 +334,13 @@ public partial class Administration : EllieModule<AdministrationService>
return;
}
if (time is null)
if (timespan is null)
await msg.DeleteAsync();
else if (time.Time <= TimeSpan.FromDays(7))
else if (timespan.Time <= TimeSpan.FromDays(7))
{
_ = Task.Run(async () =>
{
await Task.Delay(time.Time);
await Task.Delay(timespan.Time);
await msg.DeleteAsync();
});
}
@ -360,11 +360,11 @@ public partial class Administration : EllieModule<AdministrationService>
{
if (ctx.Channel is not SocketTextChannel stc)
return;
await stc.CreateThreadAsync(name, message: ctx.Message.ReferencedMessage);
await ctx.OkAsync();
}
[Cmd]
[BotPerm(ChannelPermission.ManageThreads)]
[UserPerm(ChannelPermission.ManageThreads)]
@ -380,7 +380,7 @@ public partial class Administration : EllieModule<AdministrationService>
await Response().Error(strs.not_found).SendAsync();
return;
}
await t.DeleteAsync();
await ctx.OkAsync();
}
@ -406,7 +406,7 @@ public partial class Administration : EllieModule<AdministrationService>
await Response().Confirm(strs.autopublish_disable).SendAsync();
}
}
[Cmd]
[UserPerm(GuildPerm.ManageNicknames)]
[BotPerm(GuildPerm.ChangeNickname)]
@ -450,8 +450,9 @@ public partial class Administration : EllieModule<AdministrationService>
public async Task SetServerBanner([Leftover] string img = null)
{
// Tier2 or higher is required to set a banner.
if (ctx.Guild.PremiumTier is PremiumTier.Tier1 or PremiumTier.None) return;
if (ctx.Guild.PremiumTier is PremiumTier.Tier1 or PremiumTier.None)
return;
var result = await _service.SetServerBannerAsync(ctx.Guild, img);
switch (result)
@ -472,7 +473,7 @@ public partial class Administration : EllieModule<AdministrationService>
throw new ArgumentOutOfRangeException();
}
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPermission.ManageGuild)]

View file

@ -72,18 +72,18 @@ public partial class Administration
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles | GuildPerm.MuteMembers)]
[Priority(1)]
public async Task Mute(StoopidTime time, IGuildUser user, [Leftover] string reason = "")
public async Task Mute(ParsedTimespan timespan, IGuildUser user, [Leftover] string reason = "")
{
if (time.Time < TimeSpan.FromMinutes(1) || time.Time > TimeSpan.FromDays(49))
if (timespan.Time < TimeSpan.FromMinutes(1) || timespan.Time > TimeSpan.FromDays(49))
return;
try
{
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
return;
await _service.TimedMute(user, ctx.User, time.Time, reason: reason);
await _service.TimedMute(user, ctx.User, timespan.Time, reason: reason);
await Response().Confirm(strs.user_muted_time(Format.Bold(user.ToString()),
(int)time.Time.TotalMinutes)).SendAsync();
(int)timespan.Time.TotalMinutes)).SendAsync();
}
catch (Exception ex)
{
@ -133,18 +133,18 @@ public partial class Administration
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageRoles)]
[Priority(1)]
public async Task ChatMute(StoopidTime time, IGuildUser user, [Leftover] string reason = "")
public async Task ChatMute(ParsedTimespan timespan, IGuildUser user, [Leftover] string reason = "")
{
if (time.Time < TimeSpan.FromMinutes(1) || time.Time > TimeSpan.FromDays(49))
if (timespan.Time < TimeSpan.FromMinutes(1) || timespan.Time > TimeSpan.FromDays(49))
return;
try
{
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
return;
await _service.TimedMute(user, ctx.User, time.Time, MuteType.Chat, reason);
await _service.TimedMute(user, ctx.User, timespan.Time, MuteType.Chat, reason);
await Response().Confirm(strs.user_chat_mute_time(Format.Bold(user.ToString()),
(int)time.Time.TotalMinutes)).SendAsync();
(int)timespan.Time.TotalMinutes)).SendAsync();
}
catch (Exception ex)
{
@ -193,18 +193,18 @@ public partial class Administration
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.MuteMembers)]
[Priority(1)]
public async Task VoiceMute(StoopidTime time, IGuildUser user, [Leftover] string reason = "")
public async Task VoiceMute(ParsedTimespan timespan, IGuildUser user, [Leftover] string reason = "")
{
if (time.Time < TimeSpan.FromMinutes(1) || time.Time > TimeSpan.FromDays(49))
if (timespan.Time < TimeSpan.FromMinutes(1) || timespan.Time > TimeSpan.FromDays(49))
return;
try
{
if (!await VerifyMutePermissions((IGuildUser)ctx.User, user))
return;
await _service.TimedMute(user, ctx.User, time.Time, MuteType.Voice, reason);
await _service.TimedMute(user, ctx.User, timespan.Time, MuteType.Voice, reason);
await Response().Confirm(strs.user_voice_mute_time(Format.Bold(user.ToString()),
(int)time.Time.TotalMinutes)).SendAsync();
(int)timespan.Time.TotalMinutes)).SendAsync();
}
catch
{

View file

@ -0,0 +1,92 @@
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using EllieBot.Common.ModuleBehaviors;
using EllieBot.Db.Models;
namespace EllieBot.Modules.Administration;
public sealed class NotifyService : IReadyExecutor, IEService
{
private readonly DbService _db;
private readonly IMessageSenderService _mss;
private readonly DiscordSocketClient _client;
private readonly IBotCreds _creds;
public NotifyService(
DbService db,
IMessageSenderService mss,
DiscordSocketClient client,
IBotCreds creds)
{
_db = db;
_mss = mss;
_client = client;
_creds = creds;
}
public async Task OnReadyAsync()
{
// .Where(x => Linq2DbExpressions.GuildOnShard(guildId,
// _creds.TotalShards,
// _client.ShardId))
}
public async Task EnableAsync(
ulong guildId,
ulong channelId,
NotifyEvent nEvent,
string message)
{
await using var uow = _db.GetDbContext();
await uow.GetTable<Notify>()
.InsertOrUpdateAsync(() => new()
{
GuildId = guildId,
ChannelId = channelId,
Event = nEvent,
Message = message,
},
(_) => new()
{
Message = message,
ChannelId = channelId
},
() => new()
{
GuildId = guildId,
Event = nEvent
});
}
public async Task DisableAsync(ulong guildId, NotifyEvent nEvent)
{
await using var uow = _db.GetDbContext();
var deleted = await uow.GetTable<Notify>()
.Where(x => x.GuildId == guildId && x.Event == nEvent)
.DeleteAsync();
if (deleted > 0)
return;
}
}
public partial class Administration
{
public class NotifyCommands : EllieModule<NotifyService>
{
[Cmd]
[OwnerOnly]
public async Task Notify(NotifyEvent nEvent, [Leftover] string message = null)
{
if (string.IsNullOrWhiteSpace(message))
{
await _service.DisableAsync(ctx.Guild.Id, nEvent);
await Response().Confirm(strs.notify_off(nEvent)).SendAsync();
return;
}
await _service.EnableAsync(ctx.Guild.Id, ctx.Channel.Id, nEvent, message);
await Response().Confirm(strs.notify_on(nEvent.ToString())).SendAsync();
}
}
}

View file

@ -28,17 +28,17 @@ public partial class Administration
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task AntiAlt(
StoopidTime minAge,
ParsedTimespan minAge,
PunishmentAction action,
[Leftover] StoopidTime punishTime = null)
[Leftover] ParsedTimespan punishTimespan = null)
{
var minAgeMinutes = (int)minAge.Time.TotalMinutes;
var punishTimeMinutes = (int?)punishTime?.Time.TotalMinutes ?? 0;
var punishTimeMinutes = (int?)punishTimespan?.Time.TotalMinutes ?? 0;
if (minAgeMinutes < 1 || punishTimeMinutes < 0)
return;
var minutes = (int?)punishTime?.Time.TotalMinutes ?? 0;
var minutes = (int?)punishTimespan?.Time.TotalMinutes ?? 0;
if (action is PunishmentAction.TimeOut && minutes < 1)
minutes = 1;
@ -53,7 +53,7 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
public async Task AntiAlt(StoopidTime minAge, PunishmentAction action, [Leftover] IRole role)
public async Task AntiAlt(ParsedTimespan minAge, PunishmentAction action, [Leftover] IRole role)
{
var minAgeMinutes = (int)minAge.Time.TotalMinutes;
@ -86,8 +86,8 @@ public partial class Administration
int userThreshold,
int seconds,
PunishmentAction action,
[Leftover] StoopidTime punishTime)
=> InternalAntiRaid(userThreshold, seconds, action, punishTime);
[Leftover] ParsedTimespan punishTimespan)
=> InternalAntiRaid(userThreshold, seconds, action, punishTimespan);
[Cmd]
[RequireContext(ContextType.Guild)]
@ -100,7 +100,7 @@ public partial class Administration
int userThreshold,
int seconds = 10,
PunishmentAction action = PunishmentAction.Mute,
StoopidTime punishTime = null)
ParsedTimespan punishTimespan = null)
{
if (action == PunishmentAction.AddRole)
{
@ -120,13 +120,13 @@ public partial class Administration
return;
}
if (punishTime is not null)
if (punishTimespan is not null)
{
if (!_service.IsDurationAllowed(action))
await Response().Error(strs.prot_cant_use_time).SendAsync();
}
var time = (int?)punishTime?.Time.TotalMinutes ?? 0;
var time = (int?)punishTimespan?.Time.TotalMinutes ?? 0;
if (time is < 0 or > 60 * 24)
return;
@ -170,8 +170,8 @@ public partial class Administration
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[Priority(1)]
public Task AntiSpam(int messageCount, PunishmentAction action, [Leftover] StoopidTime punishTime)
=> InternalAntiSpam(messageCount, action, punishTime);
public Task AntiSpam(int messageCount, PunishmentAction action, [Leftover] ParsedTimespan punishTimespan)
=> InternalAntiSpam(messageCount, action, punishTimespan);
[Cmd]
[RequireContext(ContextType.Guild)]
@ -183,19 +183,19 @@ public partial class Administration
private async Task InternalAntiSpam(
int messageCount,
PunishmentAction action,
StoopidTime timeData = null,
ParsedTimespan timespanData = null,
IRole role = null)
{
if (messageCount is < 2 or > 10)
return;
if (timeData is not null)
if (timespanData is not null)
{
if (!_service.IsDurationAllowed(action))
await Response().Error(strs.prot_cant_use_time).SendAsync();
}
var time = (int?)timeData?.Time.TotalMinutes ?? 0;
var time = (int?)timespanData?.Time.TotalMinutes ?? 0;
if (time is < 0 or > 60 * 24)
return;

View file

@ -1,4 +1,6 @@
#nullable disable
using Google.Protobuf.WellKnownTypes;
using EllieBot.Common.TypeReaders.Models;
using SixLabors.ImageSharp.PixelFormats;
using Color = SixLabors.ImageSharp.Color;
@ -13,13 +15,18 @@ public partial class Administration
Excl
}
private readonly TempRoleService _tempRoleService;
private readonly IServiceProvider _services;
private StickyRolesService _stickyRoleSvc;
public RoleCommands(IServiceProvider services, StickyRolesService stickyRoleSvc)
public RoleCommands(
IServiceProvider services,
StickyRolesService stickyRoleSvc,
TempRoleService tempRoleService)
{
_services = services;
_stickyRoleSvc = stickyRoleSvc;
_tempRoleService = tempRoleService;
}
[Cmd]
@ -34,13 +41,16 @@ public partial class Administration
return;
try
{
await targetUser.AddRoleAsync(roleToAdd, new RequestOptions()
{
AuditLogReason = $"Added by [{ctx.User.Username}]"
});
await targetUser.AddRoleAsync(roleToAdd,
new RequestOptions()
{
AuditLogReason = $"Added by [{ctx.User.Username}]"
});
await Response().Confirm(strs.setrole(Format.Bold(roleToAdd.Name),
Format.Bold(targetUser.ToString()))).SendAsync();
await Response()
.Confirm(strs.setrole(Format.Bold(roleToAdd.Name),
Format.Bold(targetUser.ToString())))
.SendAsync();
}
catch (Exception ex)
{
@ -62,8 +72,10 @@ public partial class Administration
try
{
await targetUser.RemoveRoleAsync(roleToRemove);
await Response().Confirm(strs.remrole(Format.Bold(roleToRemove.Name),
Format.Bold(targetUser.ToString()))).SendAsync();
await Response()
.Confirm(strs.remrole(Format.Bold(roleToRemove.Name),
Format.Bold(targetUser.ToString())))
.SendAsync();
}
catch
{
@ -204,5 +216,29 @@ public partial class Administration
await Response().Confirm(strs.sticky_roles_disabled).SendAsync();
}
}
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.Administrator)]
[BotPerm(GuildPerm.ManageRoles)]
public async Task TempRole(ParsedTimespan timespan, IUser user, [Leftover] IRole role)
{
if (!await CheckRoleHierarchy(role))
{
await Response()
.Error(strs.hierarchy)
.SendAsync();
return;
}
await _tempRoleService.AddTempRoleAsync(ctx.Guild.Id, role.Id, user.Id, timespan.Time);
await Response()
.Confirm(strs.temp_role_added(user.Mention,
Format.Bold(role.Name),
TimestampTag.FromDateTime(DateTime.UtcNow.Add(timespan.Time), TimestampTagStyles.Relative)))
.SendAsync();
}
}
}

View file

@ -0,0 +1,140 @@
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using EllieBot.Common.ModuleBehaviors;
using EllieBot.Db.Models;
namespace EllieBot.Modules.Administration;
public class TempRoleService : IReadyExecutor, IEService
{
private readonly DbService _db;
private readonly DiscordSocketClient _client;
private readonly IBotCreds _creds;
private TaskCompletionSource<bool> _tcs = new();
public TempRoleService(
DbService db,
DiscordSocketClient client,
IBotCreds creds)
{
_db = db;
_client = client;
_creds = creds;
}
public async Task AddTempRoleAsync(
ulong guildId,
ulong roleId,
ulong userId,
TimeSpan duration)
{
if (duration == TimeSpan.Zero)
{
await using var uow = _db.GetDbContext();
await uow.GetTable<TempRole>()
.Where(x => x.GuildId == guildId && x.UserId == userId)
.DeleteAsync();
return;
}
var until = DateTime.UtcNow.Add(duration);
await using var ctx = _db.GetDbContext();
await ctx.GetTable<TempRole>()
.InsertOrUpdateAsync(() => new()
{
GuildId = guildId,
RoleId = roleId,
UserId = userId,
Remove = false,
ExpiresAt = until
},
(old) => new()
{
ExpiresAt = until,
},
() => new()
{
GuildId = guildId,
UserId = userId,
RoleId = roleId
});
_tcs.TrySetResult(true);
}
public async Task OnReadyAsync()
{
while (true)
{
try
{
_tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
var latest = await _db.GetDbContext()
.GetTable<TempRole>()
.Where(x => Linq2DbExpressions.GuildOnShard(x.GuildId,
_creds.TotalShards,
_client.ShardId))
.OrderBy(x => x.ExpiresAt)
.FirstOrDefaultAsyncLinqToDB();
if (latest == default)
{
await _tcs.Task;
continue;
}
var now = DateTime.UtcNow;
if (latest.ExpiresAt > now)
{
await Task.WhenAny(Task.Delay(latest.ExpiresAt - now), _tcs.Task);
continue;
}
var deleted = await _db.GetDbContext()
.GetTable<TempRole>()
.Where(x => Linq2DbExpressions.GuildOnShard(x.GuildId,
_creds.TotalShards,
_client.ShardId)
&& x.ExpiresAt <= now)
.DeleteWithOutputAsync();
foreach (var d in deleted)
{
try
{
await RemoveRole(d);
}
catch
{
Log.Warning("Unable to remove temp role {RoleId} from user {UserId}",
d.RoleId,
d.UserId);
}
await Task.Delay(1000);
}
}
catch (Exception ex)
{
Log.Error(ex, "Unexpected error occurred in temprole loop");
await Task.Delay(1000);
}
}
}
private async Task RemoveRole(TempRole tempRole)
{
var guild = _client.GetGuild(tempRole.GuildId);
var role = guild?.GetRole(tempRole.RoleId);
if (role is null)
return;
var user = guild?.GetUser(tempRole.UserId);
if (user is null)
return;
await user.RemoveRoleAsync(role);
}
}

View file

@ -313,7 +313,7 @@ public partial class Administration
int number,
AddRole _,
IRole role,
StoopidTime time = null)
ParsedTimespan timespan = null)
{
var punish = PunishmentAction.AddRole;
@ -324,12 +324,12 @@ public partial class Administration
return;
}
var success = await _service.WarnPunish(ctx.Guild.Id, number, punish, time, role);
var success = await _service.WarnPunish(ctx.Guild.Id, number, punish, timespan, role);
if (!success)
return;
if (time is null)
if (timespan is null)
{
await Response()
.Confirm(strs.warn_punish_set(Format.Bold(punish.ToString()),
@ -341,7 +341,7 @@ public partial class Administration
await Response()
.Confirm(strs.warn_punish_set_timed(Format.Bold(punish.ToString()),
Format.Bold(number.ToString()),
Format.Bold(time.Input)))
Format.Bold(timespan.Input)))
.SendAsync();
}
}
@ -349,7 +349,7 @@ public partial class Administration
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
public async Task WarnPunish(int number, PunishmentAction punish, StoopidTime time = null)
public async Task WarnPunish(int number, PunishmentAction punish, ParsedTimespan timespan = null)
{
// this should never happen. Addrole has its own method with higher priority
// also disallow warn punishment for getting warned
@ -357,15 +357,15 @@ public partial class Administration
return;
// you must specify the time for timeout
if (punish is PunishmentAction.TimeOut && time is null)
if (punish is PunishmentAction.TimeOut && timespan is null)
return;
var success = await _service.WarnPunish(ctx.Guild.Id, number, punish, time);
var success = await _service.WarnPunish(ctx.Guild.Id, number, punish, timespan);
if (!success)
return;
if (time is null)
if (timespan is null)
{
await Response()
.Confirm(strs.warn_punish_set(Format.Bold(punish.ToString()),
@ -377,7 +377,7 @@ public partial class Administration
await Response()
.Confirm(strs.warn_punish_set_timed(Format.Bold(punish.ToString()),
Format.Bold(number.ToString()),
Format.Bold(time.Input)))
Format.Bold(timespan.Input)))
.SendAsync();
}
}
@ -417,17 +417,17 @@ public partial class Administration
[UserPerm(GuildPerm.BanMembers)]
[BotPerm(GuildPerm.BanMembers)]
[Priority(1)]
public Task Ban(StoopidTime time, IUser user, [Leftover] string msg = null)
=> Ban(time, user.Id, msg);
public Task Ban(ParsedTimespan timespan, IUser user, [Leftover] string msg = null)
=> Ban(timespan, user.Id, msg);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.BanMembers)]
[BotPerm(GuildPerm.BanMembers)]
[Priority(0)]
public async Task Ban(StoopidTime time, ulong userId, [Leftover] string msg = null)
public async Task Ban(ParsedTimespan timespan, ulong userId, [Leftover] string msg = null)
{
if (time.Time > TimeSpan.FromDays(49))
if (timespan.Time > TimeSpan.FromDays(49))
return;
var guildUser = await ((DiscordSocketClient)Context.Client).Rest.GetGuildUserAsync(ctx.Guild.Id, userId);
@ -444,7 +444,7 @@ public partial class Administration
{
var defaultMessage = GetText(strs.bandm(Format.Bold(ctx.Guild.Name), msg));
var smartText =
await _service.GetBanUserDmEmbed(Context, guildUser, defaultMessage, msg, time.Time);
await _service.GetBanUserDmEmbed(Context, guildUser, defaultMessage, msg, timespan.Time);
if (smartText is not null)
await Response().User(guildUser).Text(smartText).SendAsync();
}
@ -456,14 +456,14 @@ public partial class Administration
var user = await ctx.Client.GetUserAsync(userId);
var banPrune = await _service.GetBanPruneAsync(ctx.Guild.Id) ?? 7;
await _mute.TimedBan(ctx.Guild, userId, time.Time, (ctx.User + " | " + msg).TrimTo(512), banPrune);
await _mute.TimedBan(ctx.Guild, userId, timespan.Time, (ctx.User + " | " + msg).TrimTo(512), banPrune);
var toSend = CreateEmbed()
.WithOkColor()
.WithTitle("⛔️ " + GetText(strs.banned_user))
.AddField(GetText(strs.username), user?.ToString() ?? userId.ToString(), true)
.AddField("ID", userId.ToString(), true)
.AddField(GetText(strs.duration),
time.Time.ToPrettyStringHm(),
timespan.Time.ToPrettyStringHm(),
true);
if (dmFailed)
@ -601,7 +601,7 @@ public partial class Administration
[UserPerm(GuildPerm.BanMembers)]
[BotPerm(GuildPerm.BanMembers)]
[Priority(1)]
public Task BanMessageTest(StoopidTime duration, [Leftover] string reason = null)
public Task BanMessageTest(ParsedTimespan duration, [Leftover] string reason = null)
=> InternalBanMessageTest(reason, duration.Time);
private async Task InternalBanMessageTest(string reason, TimeSpan? duration)
@ -791,7 +791,7 @@ public partial class Administration
[UserPerm(GuildPerm.ModerateMembers)]
[BotPerm(GuildPerm.ModerateMembers)]
[Priority(2)]
public async Task Timeout(IUser globalUser, StoopidTime time, [Leftover] string msg = null)
public async Task Timeout(IUser globalUser, ParsedTimespan timespan, [Leftover] string msg = null)
{
var user = await ctx.Guild.GetUserAsync(globalUser.Id);
@ -817,7 +817,7 @@ public partial class Administration
dmFailed = true;
}
await user.SetTimeOutAsync(time.Time);
await user.SetTimeOutAsync(timespan.Time);
var toSend = CreateEmbed()
.WithOkColor()

View file

@ -1,5 +1,6 @@
#nullable disable
using EllieBot.Modules.Games.Services;
using System.Text;
namespace EllieBot.Modules.Games;
@ -38,10 +39,72 @@ public partial class Games : EllieModule<GamesService>
return;
var res = _service.GetEightballResponse(ctx.User.Id, question);
await Response().Embed(CreateEmbed()
.WithOkColor()
.WithDescription(ctx.User.ToString())
.AddField("❓ " + GetText(strs.question), question)
.AddField("🎱 " + GetText(strs._8ball), res)).SendAsync();
await Response()
.Embed(CreateEmbed()
.WithOkColor()
.WithDescription(ctx.User.ToString())
.AddField("❓ " + GetText(strs.question), question)
.AddField("🎱 " + GetText(strs._8ball), res))
.SendAsync();
}
private readonly string[] _numberEmojis = ["0⃣", "1⃣", "2⃣", "3⃣", "4⃣", "5⃣", "6⃣", "7⃣", "8⃣", "9⃣"];
[Cmd]
public async Task Minesweeper(int numberOfMines = 12)
{
var boardSizeX = 9;
var boardSizeY = 10;
if (numberOfMines < 1)
{
numberOfMines = 1;
}
else if (numberOfMines > boardSizeX * boardSizeY / 2)
{
numberOfMines = boardSizeX * boardSizeY / 2;
}
var mineIndicies = Enumerable.Range(0, boardSizeX * boardSizeY)
.ToArray()
.Shuffle()
.Take(numberOfMines)
.ToHashSet();
string GetNumberOnCell(int x, int y)
{
var count = 0;
for (var i = -1; i < 2; i++)
{
for (var j = -1; j < 2; j++)
{
if (y + j >= boardSizeY || y + j < 0)
continue;
if (x + i >= boardSizeX || x + i < 0)
continue;
var boardIndex = (y + j) * boardSizeX + (x + i);
if (mineIndicies.Contains(boardIndex))
count++;
}
}
return _numberEmojis[count];
}
var sb = new StringBuilder();
sb.AppendLine($"### Minesweeper [{numberOfMines}\\💣]");
for (var i = 0; i < boardSizeY; i++)
{
for (var j = 0; j < boardSizeX; j++)
{
var emoji = mineIndicies.Contains((i * boardSizeX) + j) ? "💣" : GetNumberOnCell(j, i);
sb.Append($"||{emoji}||");
}
sb.AppendLine();
}
await Response().Text(sb.ToString()).SendAsync();
}
}

View file

@ -9,6 +9,7 @@ public sealed class AfkService : IEService, IReadyExecutor
private readonly MessageSenderService _mss;
private static readonly TimeSpan _maxAfkDuration = 8.Hours();
public AfkService(IBotCache cache, DiscordSocketClient client, MessageSenderService mss)
{
_cache = cache;
@ -19,6 +20,9 @@ public sealed class AfkService : IEService, IReadyExecutor
private static TypedKey<string> GetKey(ulong userId)
=> new($"afk:msg:{userId}");
private static TypedKey<bool> GetRecentlySentKey(ulong userId, ulong channelId)
=> new($"afk:recent:{userId}:{channelId}");
public async Task<bool> SetAfkAsync(ulong userId, string text)
{
var added = await _cache.AddAsync(GetKey(userId), text, _maxAfkDuration, overwrite: true);
@ -43,9 +47,7 @@ public sealed class AfkService : IEService, IReadyExecutor
msg.DeleteAfter(5);
});
}
}
}
catch (Exception ex)
{
@ -61,7 +63,7 @@ public sealed class AfkService : IEService, IReadyExecutor
await Task.Delay(_maxAfkDuration);
_client.MessageReceived -= StopAfk;
});
return added;
}
@ -72,36 +74,29 @@ public sealed class AfkService : IEService, IReadyExecutor
return Task.CompletedTask;
}
private Task TryTriggerAfkMessage(SocketMessage arg)
private Task TryTriggerAfkMessage(SocketMessage sm)
{
if (arg.Author.IsBot || arg.Author.IsWebhook)
if (sm.Author.IsBot || sm.Author.IsWebhook)
return Task.CompletedTask;
if (arg is not IUserMessage uMsg || uMsg.Channel is not ITextChannel tc)
if (sm is not IUserMessage uMsg || uMsg.Channel is not ITextChannel tc)
return Task.CompletedTask;
if ((arg.MentionedUsers.Count is 0 or > 3) && uMsg.ReferencedMessage is null)
if ((sm.MentionedUsers.Count is 0 or > 3) && uMsg.ReferencedMessage is null)
return Task.CompletedTask;
_ = Task.Run(async () =>
{
var botUser = await tc.Guild.GetCurrentUserAsync();
var perms = botUser.GetPermissions(tc);
if (!perms.SendMessages)
return;
ulong mentionedUserId = 0;
if (arg.MentionedUsers.Count <= 3)
if (sm.MentionedUsers.Count <= 3)
{
foreach (var uid in uMsg.MentionedUserIds)
{
if (uid == arg.Author.Id)
if (uid == sm.Author.Id)
continue;
if (arg.Content.StartsWith($"<@{uid}>") || arg.Content.StartsWith($"<@!{uid}>"))
if (sm.Content.StartsWith($"<@{uid}>") || sm.Content.StartsWith($"<@!{uid}>"))
{
mentionedUserId = uid;
break;
@ -115,7 +110,7 @@ public sealed class AfkService : IEService, IReadyExecutor
{
return;
}
mentionedUserId = repliedUserId;
}
@ -125,16 +120,35 @@ public sealed class AfkService : IEService, IReadyExecutor
if (result.TryPickT0(out var msg, out _))
{
var st = SmartText.CreateFrom(msg);
st = $"The user you've pinged (<#{mentionedUserId}>) is AFK: " + st;
var toDelete = await _mss.Response(arg.Channel)
.User(arg.Author)
var toDelete = await _mss.Response(sm.Channel)
.User(sm.Author)
.Message(uMsg)
.Text(st)
.SendAsync();
toDelete.DeleteAfter(30);
var botUser = await tc.Guild.GetCurrentUserAsync();
var perms = botUser.GetPermissions(tc);
if (!perms.SendMessages)
return;
var key = GetRecentlySentKey(mentionedUserId, sm.Channel.Id);
var recent = await _cache.GetAsync(key);
if (!recent.TryPickT0(out _, out _))
{
var chMsg = await _mss.Response(sm.Channel)
.Message(uMsg)
.Pending(strs.user_afk($"<@{mentionedUserId}>"))
.SendAsync();
chMsg.DeleteAfter(5);
await _cache.AddAsync(key, true, expiry: TimeSpan.FromMinutes(5));
}
}
}
catch (HttpException ex)

View file

@ -98,10 +98,10 @@ public partial class Utility
return;
var embed = CreateEmbed()
.WithOkColor()
.WithTitle(GetText(guildId is not null
? strs.reminder_server_list
: strs.reminder_list));
.WithOkColor()
.WithTitle(GetText(guildId is not null
? strs.reminder_server_list
: strs.reminder_list));
List<Reminder> rems;
if (guildId is { } gid)
@ -193,23 +193,14 @@ public partial class Utility
message = message.SanitizeAllMentions();
}
var rem = new Reminder
{
ChannelId = targetId,
IsPrivate = isPrivate,
When = time,
Message = message,
UserId = ctx.User.Id,
ServerId = ctx.Guild?.Id ?? 0
};
await _service.AddReminderAsync(ctx.User.Id,
targetId,
ctx.Guild?.Id,
isPrivate,
time,
message,
ReminderType.User);
await using (var uow = _db.GetDbContext())
{
uow.Set<Reminder>().Add(rem);
await uow.SaveChangesAsync();
}
// var gTime = ctx.Guild is null ? time : TimeZoneInfo.ConvertTime(time, _tz.GetTimeZoneOrUtc(ctx.Guild.Id));
await Response()
.Confirm($"\u23f0 {GetText(strs.remind2(
Format.Bold(!isPrivate ? $"<#{targetId}>" : ctx.User.Username),

View file

@ -21,6 +21,8 @@ public class RemindService : IEService, IReadyExecutor, IRemindService
private readonly IMessageSenderService _sender;
private readonly CultureInfo _culture;
private TaskCompletionSource<bool> _tcs;
public RemindService(
DiscordSocketClient client,
DbService db,
@ -44,8 +46,7 @@ public class RemindService : IEService, IReadyExecutor, IRemindService
public async Task OnReadyAsync()
{
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(15));
while (await timer.WaitForNextTickAsync())
while (true)
{
await OnReminderLoopTickInternalAsync();
}
@ -55,8 +56,7 @@ public class RemindService : IEService, IReadyExecutor, IRemindService
{
try
{
var now = DateTime.UtcNow;
var reminders = await GetRemindersBeforeAsync(now);
var reminders = await GetRemindersBeforeAsync();
if (reminders.Count == 0)
return;
@ -67,7 +67,6 @@ public class RemindService : IEService, IReadyExecutor, IRemindService
{
var executedReminders = group.ToList();
await executedReminders.Select(ReminderTimerAction).WhenAll();
await RemoveReminders(executedReminders.Select(x => x.Id));
await Task.Delay(1500);
}
}
@ -80,21 +79,51 @@ public class RemindService : IEService, IReadyExecutor, IRemindService
private async Task RemoveReminders(IEnumerable<int> reminders)
{
await using var uow = _db.GetDbContext();
await uow.Set<Reminder>()
.ToLinqToDBTable()
await uow.GetTable<Reminder>()
.DeleteAsync(x => reminders.Contains(x.Id));
await uow.SaveChangesAsync();
}
private async Task<List<Reminder>> GetRemindersBeforeAsync(DateTime now)
private async Task<IReadOnlyList<Reminder>> GetRemindersBeforeAsync()
{
await using var uow = _db.GetDbContext();
return await uow.Set<Reminder>()
.ToLinqToDBTable()
.Where(x => Linq2DbExpressions.GuildOnShard(x.ServerId, _creds.TotalShards, _client.ShardId)
&& x.When < now)
.ToListAsyncLinqToDB();
while (true)
{
_tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
await using var uow = _db.GetDbContext();
var earliest = await uow.Set<Reminder>()
.ToLinqToDBTable()
.Where(x => Linq2DbExpressions.GuildOnShard(x.ServerId,
_creds.TotalShards,
_client.ShardId))
.OrderBy(x => x.When)
.FirstOrDefaultAsyncLinqToDB();
if (earliest == default)
{
await _tcs.Task;
continue;
}
var now = DateTime.UtcNow;
if (earliest.When > now)
{
var diff = earliest.When - now;
// Log.Information("Waiting for {Diff}", diff);
await Task.WhenAny(Task.Delay(diff), _tcs.Task);
continue;
}
var reminders = await uow.Set<Reminder>()
.ToLinqToDBTable()
.Where(x => Linq2DbExpressions.GuildOnShard(x.ServerId,
_creds.TotalShards,
_client.ShardId))
.Where(x => x.When <= now)
.DeleteWithOutputAsync();
return reminders;
}
}
public bool TryParseRemindMessage(string input, out RemindObject obj)
@ -243,21 +272,24 @@ public class RemindService : IEService, IReadyExecutor, IRemindService
string message,
ReminderType reminderType)
{
var rem = new Reminder
await using (var ctx = _db.GetDbContext())
{
UserId = userId,
ChannelId = targetId,
ServerId = guildId ?? 0,
IsPrivate = isPrivate,
When = time,
Message = message,
Type = reminderType
};
await ctx.GetTable<Reminder>()
.InsertAsync(() => new Reminder
{
UserId = userId,
ChannelId = targetId,
ServerId = guildId ?? 0,
IsPrivate = isPrivate,
When = time,
Message = message,
Type = reminderType,
DateAdded = DateTime.UtcNow
});
await ctx.SaveChangesAsync();
}
await using var ctx = _db.GetDbContext();
await ctx.Set<Reminder>()
.AddAsync(rem);
await ctx.SaveChangesAsync();
_tcs.SetResult(true);
}
public async Task<List<Reminder>> GetServerReminders(int page, ulong guildId)

View file

@ -110,14 +110,14 @@ public partial class Utility
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageMessages)]
[Priority(0)]
public Task Repeat(StoopidTime interval, [Leftover] string message)
public Task Repeat(ParsedTimespan interval, [Leftover] string message)
=> Repeat(ctx.Channel, null, interval, message);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageMessages)]
[Priority(0)]
public Task Repeat(ITextChannel channel, StoopidTime interval, [Leftover] string message)
public Task Repeat(ITextChannel channel, ParsedTimespan interval, [Leftover] string message)
=> Repeat(channel, null, interval, message);
[Cmd]
@ -138,14 +138,14 @@ public partial class Utility
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageMessages)]
[Priority(2)]
public Task Repeat(GuildDateTime? timeOfDay, StoopidTime? interval, [Leftover] string message)
public Task Repeat(GuildDateTime? timeOfDay, ParsedTimespan? interval, [Leftover] string message)
=> Repeat(ctx.Channel, timeOfDay, interval, message);
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageMessages)]
[Priority(3)]
public async Task Repeat(IMessageChannel channel, GuildDateTime? timeOfDay, StoopidTime? interval,
public async Task Repeat(IMessageChannel channel, GuildDateTime? timeOfDay, ParsedTimespan? interval,
[Leftover] string message)
{
if (channel is not ITextChannel txtCh || txtCh.GuildId != ctx.Guild.Id)

View file

@ -56,25 +56,18 @@ public partial class Xp : EllieModule<XpService>
public async Task XpNotify()
{
var globalSetting = _service.GetNotificationType(ctx.User);
var serverSetting = _service.GetNotificationType(ctx.User.Id, ctx.Guild.Id);
var embed = CreateEmbed()
.WithOkColor()
.AddField(GetText(strs.xpn_setting_global), GetNotifLocationString(globalSetting))
.AddField(GetText(strs.xpn_setting_server), GetNotifLocationString(serverSetting));
.AddField(GetText(strs.xpn_setting_global), GetNotifLocationString(globalSetting));
await Response().Embed(embed).SendAsync();
}
[Cmd]
[RequireContext(ContextType.Guild)]
public async Task XpNotify(NotifyPlace place, XpNotificationLocation type)
public async Task XpNotify(XpNotificationLocation type)
{
if (place == NotifyPlace.Guild)
await _service.ChangeNotificationType(ctx.User.Id, ctx.Guild.Id, type);
else
await _service.ChangeNotificationType(ctx.User, type);
await _service.ChangeNotificationType(ctx.User, type);
await ctx.OkAsync();
}

View file

@ -159,14 +159,6 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
}
}
public sealed class MiniGuildXpStats
{
public long Xp { get; set; }
public XpNotificationLocation NotifyOnLevelUp { get; set; }
public ulong GuildId { get; set; }
public ulong UserId { get; set; }
}
private async Task UpdateXp()
{
try
@ -261,7 +253,6 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
GuildId = guildId,
Xp = group.Key,
DateAdded = DateTime.UtcNow,
NotifyOnLevelUp = XpNotificationLocation.None
},
_ => new()
{
@ -320,8 +311,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
du.UserId,
true,
oldLevel.Level,
newLevel.Level,
du.NotifyOnLevelUp));
newLevel.Level));
}
}
}
@ -339,7 +329,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
bool isServer,
long oldLevel,
long newLevel,
XpNotificationLocation notifyLoc)
XpNotificationLocation notifyLoc = XpNotificationLocation.None)
=> async () =>
{
if (isServer)
@ -634,21 +624,6 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
.ToArrayAsyncLinqToDB();
}
public async Task ChangeNotificationType(ulong userId, ulong guildId, XpNotificationLocation type)
{
await using var uow = _db.GetDbContext();
var user = uow.GetOrCreateUserXpStats(guildId, userId);
user.NotifyOnLevelUp = type;
await uow.SaveChangesAsync();
}
public XpNotificationLocation GetNotificationType(ulong userId, ulong guildId)
{
using var uow = _db.GetDbContext();
var user = uow.GetOrCreateUserXpStats(guildId, userId);
return user.NotifyOnLevelUp;
}
public XpNotificationLocation GetNotificationType(IUser user)
{
using var uow = _db.GetDbContext();
@ -1667,9 +1642,7 @@ public class XpService : IEService, IReadyExecutor, IExecNoCommand
{
GuildId = guildId,
UserId = userId,
AwardedXp = 0,
Xp = lvlStats.TotalXp,
NotifyOnLevelUp = XpNotificationLocation.None,
DateAdded = DateTime.UtcNow
}, (old) => new()
{

View file

@ -3,8 +3,8 @@ namespace EllieBot.Common;
public enum AddRemove
{
Add = int.MinValue,
Remove = int.MinValue + 1,
Rem = int.MinValue + 1,
Rm = int.MinValue + 1
Add = 0,
Remove = 1,
Rem = 1,
Rm = 1,
}

View file

@ -2,7 +2,7 @@
namespace EllieBot.Common.TypeReaders.Models;
public class StoopidTime
public class ParsedTimespan
{
private static readonly Regex _regex = new(
@"^(?:(?<months>\d)mo)?(?:(?<weeks>\d{1,2})w)?(?:(?<days>\d{1,2})d)?(?:(?<hours>\d{1,4})h)?(?:(?<minutes>\d{1,5})m)?(?:(?<seconds>\d{1,6})s)?$",
@ -11,9 +11,9 @@ public class StoopidTime
public string Input { get; set; } = string.Empty;
public TimeSpan Time { get; set; } = default;
private StoopidTime() { }
private ParsedTimespan() { }
public static StoopidTime FromInput(string input)
public static ParsedTimespan FromInput(string input)
{
var m = _regex.Match(input);
@ -52,10 +52,10 @@ public class StoopidTime
};
}
public static implicit operator TimeSpan?(StoopidTime? st)
public static implicit operator TimeSpan?(ParsedTimespan? st)
=> st?.Time;
public static implicit operator StoopidTime(TimeSpan ts)
public static implicit operator ParsedTimespan(TimeSpan ts)
=> new()
{
Input = ts.ToString(),

View file

@ -3,20 +3,20 @@ using EllieBot.Common.TypeReaders.Models;
namespace EllieBot.Common.TypeReaders;
public sealed class StoopidTimeTypeReader : EllieTypeReader<StoopidTime>
public sealed class StoopidTimeTypeReader : EllieTypeReader<ParsedTimespan>
{
public override ValueTask<TypeReaderResult<StoopidTime>> ReadAsync(ICommandContext context, string input)
public override ValueTask<TypeReaderResult<ParsedTimespan>> ReadAsync(ICommandContext context, string input)
{
if (string.IsNullOrWhiteSpace(input))
return new(TypeReaderResult.FromError<StoopidTime>(CommandError.Unsuccessful, "Input is empty."));
return new(TypeReaderResult.FromError<ParsedTimespan>(CommandError.Unsuccessful, "Input is empty."));
try
{
var time = StoopidTime.FromInput(input);
var time = ParsedTimespan.FromInput(input);
return new(TypeReaderResult.FromSuccess(time));
}
catch (Exception ex)
{
return new(TypeReaderResult.FromError<StoopidTime>(CommandError.Exception, ex.Message));
return new(TypeReaderResult.FromError<ParsedTimespan>(CommandError.Exception, ex.Message));
}
}
}

View file

@ -1541,4 +1541,9 @@ servercolorpending:
- pending
- warn
- warning
- pend
- pend
minesweeper:
- minesweeper
- mw
temprole:
- temprole

View file

@ -4830,4 +4830,27 @@ xplevelset:
- level:
desc: "The level to set the user to."
- user:
desc: "The user to set the level of."
desc: "The user to set the level of."
temprole:
desc: |-
Grants a user a temporary role for the specified number of time.
The role must exist and be lower in the role hierarchy than your highest role.
ex:
- '15m @User Jail'
- '7d @Newbie Trial Member'
params:
- days:
desc: "The time after which the role is automatically removed."
- user:
desc: "The user to give the role to."
- role:
desc: "The role to give to the user."
minesweeper:
desc: |-
Creates a spoiler-based minesweeper mini game.
You may specify the number of mines.
ex:
- '15'
params:
- mines:
desc: "The number of mines to create."

View file

@ -1142,5 +1142,9 @@
"server_color_set": "Successfully set a new server color.",
"lasts_until": "Lasts Until",
"winners_count": "Winners",
"level_set": "Level of user {0} set to {1} on this server."
"level_set": "Level of user {0} set to {1} on this server.",
"temp_role_added": "User {0} has been given {1} role temporarily. The role expires {2}",
"user_afk": "User {0} is AFK.",
"notify_on": "Notification message will be sent on this channel when {0} event triggers.",
"notify_off": "Notification message will no longer be sent when {0} event triggers."
}