Added .stondel which makes the bot delete stream online messages after the stream goes offline

This commit is contained in:
Kwoth
2022-04-27 21:57:24 +02:00
parent 24a9a02cc3
commit 39ae070c9d
10 changed files with 2907 additions and 4 deletions

View File

@@ -12,6 +12,7 @@ Experimental changelog. Mostly based on [keepachangelog](https://keepachangelog.
- Embed arrays use color hex values instead of an integer - Embed arrays use color hex values instead of an integer
- Old embed format will still work - Old embed format will still work
- There shouldn't be any breaking changes - There shouldn't be any breaking changes
- Added `.stondel` command which, when toggled, will make the bot delete online stream messages on the server when the stream goes offline
### Fixed ### Fixed

View File

@@ -92,6 +92,7 @@ public class GuildConfig : DbEntity
public List<FeedSub> FeedSubs { get; set; } = new(); public List<FeedSub> FeedSubs { get; set; } = new();
public IndexedCollection<ReactionRoleMessage> ReactionRoleMessages { get; set; } = new(); public IndexedCollection<ReactionRoleMessage> ReactionRoleMessages { get; set; } = new();
public bool NotifyStreamOffline { get; set; } public bool NotifyStreamOffline { get; set; }
public bool DeleteStreamOnlineMessage { get; set; }
public List<GroupName> SelfAssignableRoleGroupNames { get; set; } public List<GroupName> SelfAssignableRoleGroupNames { get; set; }
public int WarnExpireHours { get; set; } public int WarnExpireHours { get; set; }
public WarnExpireAction WarnExpireAction { get; set; } = WarnExpireAction.Clear; public WarnExpireAction WarnExpireAction { get; set; } = WarnExpireAction.Clear;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace NadekoBot.Migrations
{
public partial class stondel : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "DeleteStreamOnlineMessage",
table: "GuildConfigs",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "DeleteStreamOnlineMessage",
table: "GuildConfigs");
}
}
}

View File

@@ -809,6 +809,9 @@ namespace NadekoBot.Migrations
b.Property<bool>("DeleteMessageOnCommand") b.Property<bool>("DeleteMessageOnCommand")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<bool>("DeleteStreamOnlineMessage")
.HasColumnType("INTEGER");
b.Property<string>("DmGreetMessageText") b.Property<string>("DmGreetMessageText")
.HasColumnType("TEXT"); .HasColumnType("TEXT");

View File

@@ -121,6 +121,18 @@ public partial class Searches
else else
await ReplyConfirmLocalizedAsync(strs.stream_off_disabled); await ReplyConfirmLocalizedAsync(strs.stream_off_disabled);
} }
[Cmd]
[RequireContext(ContextType.Guild)]
[UserPerm(GuildPerm.ManageMessages)]
public async partial Task StreamOnlineDelete()
{
var newValue = _service.ToggleStreamOnlineDelete(ctx.Guild.Id);
if (newValue)
await ReplyConfirmLocalizedAsync(strs.stream_online_delete_enabled);
else
await ReplyConfirmLocalizedAsync(strs.stream_online_delete_disabled);
}
[Cmd] [Cmd]
[RequireContext(ContextType.Guild)] [RequireContext(ContextType.Guild)]

View File

@@ -1,4 +1,6 @@
#nullable disable #nullable disable
using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NadekoBot.Common.ModuleBehaviors; using NadekoBot.Common.ModuleBehaviors;
using NadekoBot.Db; using NadekoBot.Db;
@@ -24,6 +26,7 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
private readonly Dictionary<StreamDataKey, Dictionary<ulong, HashSet<FollowedStream>>> _shardTrackedStreams; private readonly Dictionary<StreamDataKey, Dictionary<ulong, HashSet<FollowedStream>>> _shardTrackedStreams;
private readonly ConcurrentHashSet<ulong> _offlineNotificationServers; private readonly ConcurrentHashSet<ulong> _offlineNotificationServers;
private readonly ConcurrentHashSet<ulong> _deleteOnOfflineServers;
private readonly IPubSub _pubSub; private readonly IPubSub _pubSub;
private readonly IEmbedBuilderService _eb; private readonly IEmbedBuilderService _eb;
@@ -33,6 +36,7 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
private readonly TypedKey<FollowStreamPubData> _streamFollowKey; private readonly TypedKey<FollowStreamPubData> _streamFollowKey;
private readonly TypedKey<FollowStreamPubData> _streamUnfollowKey; private readonly TypedKey<FollowStreamPubData> _streamUnfollowKey;
private readonly ConnectionMultiplexer _redis;
public StreamNotificationService( public StreamNotificationService(
DbService db, DbService db,
@@ -50,6 +54,7 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
_strings = strings; _strings = strings;
_pubSub = pubSub; _pubSub = pubSub;
_eb = eb; _eb = eb;
_redis = redis;
_streamTracker = new(httpFactory, creds, redis, creds.GetCreds().RedisKey(), client.ShardId == 0); _streamTracker = new(httpFactory, creds, redis, creds.GetCreds().RedisKey(), client.ShardId == 0);
_streamsOnlineKey = new("streams.online"); _streamsOnlineKey = new("streams.online");
@@ -71,6 +76,11 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
.Where(gc => gc.NotifyStreamOffline) .Where(gc => gc.NotifyStreamOffline)
.Select(x => x.GuildId) .Select(x => x.GuildId)
.ToList()); .ToList());
_deleteOnOfflineServers = new(guildConfigs
.Where(gc => gc.DeleteStreamOnlineMessage)
.Select(x => x.GuildId)
.ToList());
var followedStreams = guildConfigs.SelectMany(x => x.FollowedStreams).ToList(); var followedStreams = guildConfigs.SelectMany(x => x.FollowedStreams).ToList();
@@ -243,6 +253,44 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
.WhenAll(); .WhenAll();
} }
} }
if (_client.ShardId == 0)
{
foreach (var stream in offlineStreams)
{
await DeleteOnlineMessages(stream);
}
}
}
private async Task DeleteOnlineMessages(StreamData stream)
{
var db = _redis.GetDatabase();
var data = await db.ListRangeAsync($"streams_online_del:{stream.CreateKey()}");
await db.KeyDeleteAsync($"streams_online_del:{stream.CreateKey()}");
foreach (string pair in data)
{
var pairArr = pair.Split(',');
if (pairArr.Length != 2)
continue;
if (!ulong.TryParse(pairArr[0], out var chId) || !ulong.TryParse(pairArr[1], out var msgId))
continue;
try
{
var textChannel = await _client.GetChannelAsync(chId) as ITextChannel;
if (textChannel is null)
continue;
await textChannel.DeleteMessageAsync(msgId);
}
catch
{
continue;
}
}
} }
private async ValueTask HandleStreamsOnline(List<StreamData> onlineStreams) private async ValueTask HandleStreamsOnline(List<StreamData> onlineStreams)
@@ -252,13 +300,13 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
var key = stream.CreateKey(); var key = stream.CreateKey();
if (_shardTrackedStreams.TryGetValue(key, out var fss)) if (_shardTrackedStreams.TryGetValue(key, out var fss))
{ {
await fss.SelectMany(x => x.Value) var messages = await fss.SelectMany(x => x.Value)
.Select(fs => .Select(async fs =>
{ {
var textChannel = _client.GetGuild(fs.GuildId)?.GetTextChannel(fs.ChannelId); var textChannel = _client.GetGuild(fs.GuildId)?.GetTextChannel(fs.ChannelId);
if (textChannel is null) if (textChannel is null)
return Task.CompletedTask; return default;
var rep = new ReplacementBuilder().WithOverride("%user%", () => fs.Username) var rep = new ReplacementBuilder().WithOverride("%user%", () => fs.Username)
.WithOverride("%platform%", () => fs.Type.ToString()) .WithOverride("%platform%", () => fs.Type.ToString())
@@ -266,9 +314,38 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
var message = string.IsNullOrWhiteSpace(fs.Message) ? "" : rep.Replace(fs.Message); var message = string.IsNullOrWhiteSpace(fs.Message) ? "" : rep.Replace(fs.Message);
return textChannel.EmbedAsync(GetEmbed(fs.GuildId, stream), message); var msg = await textChannel.EmbedAsync(GetEmbed(fs.GuildId, stream), message);
// only cache the ids of channel/message pairs
if(_deleteOnOfflineServers.Contains(fs.GuildId))
return (textChannel.Id, msg.Id);
else
return default;
}) })
.WhenAll(); .WhenAll();
// push online stream messages to redis
// when streams go offline, any server which
// has the online stream message deletion feature
// enabled will have the online messages deleted
try
{
var pairs = messages
.Where(x => x != default)
.Select(x => (RedisValue)$"{x.Item1},{x.Item2}")
.ToArray();
if (pairs.Length > 0)
{
var db = _redis.GetDatabase();
await db.ListRightPushAsync($"streams_online_del:{key}", pairs);
}
}
catch
{
}
} }
} }
} }
@@ -484,6 +561,21 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
return newValue; return newValue;
} }
public bool ToggleStreamOnlineDelete(ulong guildId)
{
using var uow = _db.GetDbContext();
var gc = uow.GuildConfigsForId(guildId, set => set);
var newValue = gc.DeleteStreamOnlineMessage = !gc.DeleteStreamOnlineMessage;
uow.SaveChanges();
if (newValue)
_deleteOnOfflineServers.Add(guildId);
else
_deleteOnOfflineServers.TryRemove(guildId);
return newValue;
}
public Task<StreamData> GetStreamDataAsync(string url) public Task<StreamData> GetStreamDataAsync(string url)
=> _streamTracker.GetStreamDataByUrlAsync(url); => _streamTracker.GetStreamDataByUrlAsync(url);

View File

@@ -518,6 +518,9 @@ streamoffline:
- streamoffline - streamoffline
- sto - sto
- stoff - stoff
streamonlinedelete:
- streamonlinedelte
- stondel
streammessage: streammessage:
- streammsg - streammsg
- stm - stm

View File

@@ -912,6 +912,10 @@ streamoffline:
desc: "Toggles whether the bot will also notify when added streams go offline." desc: "Toggles whether the bot will also notify when added streams go offline."
args: args:
- "" - ""
streamonlinedelete:
desc: "Toggles whether the bot will delete stream online message when the stream goes offline."
args:
- ""
streammessage: streammessage:
desc: "Sets the message which will show when the stream on the specified index comes online. You can use %user% and %platform% placeholders." desc: "Sets the message which will show when the stream on the specified index comes online. You can use %user% and %platform% placeholders."
args: args:

View File

@@ -519,6 +519,8 @@
"stream_no": "No such stream.", "stream_no": "No such stream.",
"stream_off_enabled": "Stream notifications will now show when a stream goes offline.", "stream_off_enabled": "Stream notifications will now show when a stream goes offline.",
"stream_off_disabled": "Stream notifications will no longer show when a stream goes offline.", "stream_off_disabled": "Stream notifications will no longer show when a stream goes offline.",
"stream_online_delete_enabled": "Online stream notifications will now be deleted when the stream goes offline.",
"stream_online_delete_disabled": "Online stream notifications will no longer be deleted when the stream goes offline.",
"stream_not_added": "Stream was not added. Either stream doesn't exist, that platform is unsupported, or you've reached the maximum number of streams allowed.", "stream_not_added": "Stream was not added. Either stream doesn't exist, that platform is unsupported, or you've reached the maximum number of streams allowed.",
"stream_message_reset": "Announcement message for {0} stream has been reset.", "stream_message_reset": "Announcement message for {0} stream has been reset.",
"stream_message_set": "Announcement message when {0} stream goes online has been set.", "stream_message_set": "Announcement message when {0} stream goes online has been set.",