support for rss and atoms feeds added.
This commit is contained in:
parent
e12c29dda5
commit
45b696bab8
1985
src/NadekoBot/Migrations/20170921185313_feeds.Designer.cs
generated
Normal file
1985
src/NadekoBot/Migrations/20170921185313_feeds.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
59
src/NadekoBot/Migrations/20170921185313_feeds.cs
Normal file
59
src/NadekoBot/Migrations/20170921185313_feeds.cs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace NadekoBot.Migrations
|
||||||
|
{
|
||||||
|
public partial class feeds : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "FeedSub",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
ChannelId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||||
|
DateAdded = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||||
|
GuildConfigId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
Url = table.Column<string>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_FeedSub", x => x.Id);
|
||||||
|
table.UniqueConstraint("AK_FeedSub_GuildConfigId_Url", x => new { x.GuildConfigId, x.Url });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_FeedSub_GuildConfigs_GuildConfigId",
|
||||||
|
column: x => x.GuildConfigId,
|
||||||
|
principalTable: "GuildConfigs",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "FeedSub");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<DateTime>(
|
||||||
|
name: "LastLevelUp",
|
||||||
|
table: "UserXpStats",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new DateTime(2017, 9, 15, 5, 48, 8, 665, DateTimeKind.Local),
|
||||||
|
oldClrType: typeof(DateTime),
|
||||||
|
oldType: "TEXT",
|
||||||
|
oldDefaultValue: new DateTime(2017, 9, 21, 20, 53, 13, 307, DateTimeKind.Local));
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<DateTime>(
|
||||||
|
name: "LastLevelUp",
|
||||||
|
table: "DiscordUser",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new DateTime(2017, 9, 15, 5, 48, 8, 660, DateTimeKind.Local),
|
||||||
|
oldClrType: typeof(DateTime),
|
||||||
|
oldType: "TEXT",
|
||||||
|
oldDefaultValue: new DateTime(2017, 9, 21, 20, 53, 13, 305, DateTimeKind.Local));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,13 @@
|
|||||||
using System;
|
// <auto-generated />
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.Internal;
|
||||||
using NadekoBot.Services.Database;
|
using NadekoBot.Services.Database;
|
||||||
using NadekoBot.Services.Database.Models;
|
using NadekoBot.Services.Database.Models;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace NadekoBot.Migrations
|
namespace NadekoBot.Migrations
|
||||||
{
|
{
|
||||||
@ -13,8 +16,9 @@ namespace NadekoBot.Migrations
|
|||||||
{
|
{
|
||||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "1.1.1");
|
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b =>
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.AntiRaidSetting", b =>
|
||||||
{
|
{
|
||||||
@ -466,7 +470,7 @@ namespace NadekoBot.Migrations
|
|||||||
|
|
||||||
b.Property<DateTime>("LastLevelUp")
|
b.Property<DateTime>("LastLevelUp")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasDefaultValue(new DateTime(2017, 9, 15, 5, 48, 8, 660, DateTimeKind.Local));
|
.HasDefaultValue(new DateTime(2017, 9, 21, 20, 53, 13, 305, DateTimeKind.Local));
|
||||||
|
|
||||||
b.Property<DateTime>("LastXpGain");
|
b.Property<DateTime>("LastXpGain");
|
||||||
|
|
||||||
@ -546,6 +550,27 @@ namespace NadekoBot.Migrations
|
|||||||
b.ToTable("ExcludedItem");
|
b.ToTable("ExcludedItem");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.FeedSub", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<ulong>("ChannelId");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DateAdded");
|
||||||
|
|
||||||
|
b.Property<int>("GuildConfigId");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasAlternateKey("GuildConfigId", "Url");
|
||||||
|
|
||||||
|
b.ToTable("FeedSub");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b =>
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@ -1366,7 +1391,7 @@ namespace NadekoBot.Migrations
|
|||||||
|
|
||||||
b.Property<DateTime>("LastLevelUp")
|
b.Property<DateTime>("LastLevelUp")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasDefaultValue(new DateTime(2017, 9, 15, 5, 48, 8, 665, DateTimeKind.Local));
|
.HasDefaultValue(new DateTime(2017, 9, 21, 20, 53, 13, 307, DateTimeKind.Local));
|
||||||
|
|
||||||
b.Property<int>("NotifyOnLevelUp");
|
b.Property<int>("NotifyOnLevelUp");
|
||||||
|
|
||||||
@ -1693,6 +1718,14 @@ namespace NadekoBot.Migrations
|
|||||||
.HasForeignKey("XpSettingsId");
|
.HasForeignKey("XpSettingsId");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.FeedSub", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig", "GuildConfig")
|
||||||
|
.WithMany("FeedSubs")
|
||||||
|
.HasForeignKey("GuildConfigId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b =>
|
modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
|
b.HasOne("NadekoBot.Services.Database.Models.GuildConfig")
|
||||||
@ -1945,6 +1978,7 @@ namespace NadekoBot.Migrations
|
|||||||
.HasForeignKey("NadekoBot.Services.Database.Models.XpSettings", "GuildConfigId")
|
.HasForeignKey("NadekoBot.Services.Database.Models.XpSettings", "GuildConfigId")
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
});
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -222,61 +222,5 @@ namespace NadekoBot.Modules.Music.Services
|
|||||||
if (MusicPlayers.TryRemove(id, out var mp))
|
if (MusicPlayers.TryRemove(id, out var mp))
|
||||||
await mp.Destroy();
|
await mp.Destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//public Task<SongInfo> ResolveYoutubeSong(string query, string queuerName)
|
|
||||||
//{
|
|
||||||
// _log.Info("Getting video");
|
|
||||||
// //var (link, video) = await GetYoutubeVideo(query);
|
|
||||||
|
|
||||||
// //if (video == null) // do something with this error
|
|
||||||
// //{
|
|
||||||
// // _log.Info("Could not load any video elements based on the query.");
|
|
||||||
// // return null;
|
|
||||||
// //}
|
|
||||||
// ////var m = Regex.Match(query, @"\?t=(?<t>\d*)");
|
|
||||||
// ////int gotoTime = 0;
|
|
||||||
// ////if (m.Captures.Count > 0)
|
|
||||||
// //// int.TryParse(m.Groups["t"].ToString(), out gotoTime);
|
|
||||||
|
|
||||||
// //_log.Info("Creating song info");
|
|
||||||
// //var song = new SongInfo
|
|
||||||
// //{
|
|
||||||
// // Title = video.Title.Substring(0, video.Title.Length - 10), // removing trailing "- You Tube"
|
|
||||||
// // Provider = "YouTube",
|
|
||||||
// // Uri = async () => {
|
|
||||||
// // var vid = await GetYoutubeVideo(query);
|
|
||||||
// // if (vid.Item2 == null)
|
|
||||||
// // throw new HttpRequestException();
|
|
||||||
|
|
||||||
// // return await vid.Item2.GetUriAsync();
|
|
||||||
// // },
|
|
||||||
// // Query = link,
|
|
||||||
// // ProviderType = MusicType.YouTube,
|
|
||||||
// // QueuerName = queuerName
|
|
||||||
// //};
|
|
||||||
// return GetYoutubeVideo(query, queuerName);
|
|
||||||
//}
|
|
||||||
|
|
||||||
//private async Task<SongInfo> GetYoutubeVideo(string query, string queuerName)
|
|
||||||
//{
|
|
||||||
|
|
||||||
|
|
||||||
// //if (string.IsNullOrWhiteSpace(link))
|
|
||||||
// //{
|
|
||||||
// // _log.Info("No song found.");
|
|
||||||
// // return (null, null);
|
|
||||||
// //}
|
|
||||||
// //_log.Info("Getting all videos");
|
|
||||||
// //var allVideos = await Task.Run(async () => { try { return await _yt.GetAllVideosAsync(link).ConfigureAwait(false); } catch { return Enumerable.Empty<YouTubeVideo>(); } }).ConfigureAwait(false);
|
|
||||||
// //var videos = allVideos.Where(v => v.AdaptiveKind == AdaptiveKind.Audio);
|
|
||||||
// //var video = videos
|
|
||||||
// // .Where(v => v.AudioBitrate < 256)
|
|
||||||
// // .OrderByDescending(v => v.AudioBitrate)
|
|
||||||
// // .FirstOrDefault();
|
|
||||||
|
|
||||||
// //return (link, video);
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
106
src/NadekoBot/Modules/Searches/FeedCommands.cs
Normal file
106
src/NadekoBot/Modules/Searches/FeedCommands.cs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
using Discord;
|
||||||
|
using Discord.Commands;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
using Microsoft.SyndicationFeed.Rss;
|
||||||
|
using NadekoBot.Common.Attributes;
|
||||||
|
using NadekoBot.Extensions;
|
||||||
|
using NadekoBot.Modules.Searches.Services;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Xml;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Searches
|
||||||
|
{
|
||||||
|
public partial class Searches
|
||||||
|
{
|
||||||
|
[Group]
|
||||||
|
public class FeedCommands : NadekoSubmodule<FeedsService>
|
||||||
|
{
|
||||||
|
private readonly DiscordSocketClient _client;
|
||||||
|
|
||||||
|
public FeedCommands(DiscordSocketClient client)
|
||||||
|
{
|
||||||
|
_client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
[NadekoCommand, Usage, Description, Aliases]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[RequireUserPermission(GuildPermission.ManageMessages)]
|
||||||
|
public async Task Feed(string url, [Remainder] ITextChannel channel = null)
|
||||||
|
{
|
||||||
|
var success = Uri.TryCreate(url, UriKind.Absolute, out var uri) &&
|
||||||
|
(uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps);
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
channel = channel ?? (ITextChannel)Context.Channel;
|
||||||
|
using (var xmlReader = XmlReader.Create(url, new XmlReaderSettings() { Async = true }))
|
||||||
|
{
|
||||||
|
var reader = new RssFeedReader(xmlReader);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await reader.Read();
|
||||||
|
}
|
||||||
|
catch { success = false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
success = _service.AddFeed(Context.Guild.Id, channel.Id, url);
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
await ReplyConfirmLocalized("feed_added").ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await ReplyErrorLocalized("feed_not_valid").ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[NadekoCommand, Usage, Description, Aliases]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[RequireUserPermission(GuildPermission.ManageMessages)]
|
||||||
|
public async Task FeedRemove(int index)
|
||||||
|
{
|
||||||
|
if (_service.RemoveFeed(Context.Guild.Id, --index))
|
||||||
|
{
|
||||||
|
await ReplyConfirmLocalized("feed_removed").ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
await ReplyErrorLocalized("feed_out_of_range").ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[NadekoCommand, Usage, Description, Aliases]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[RequireUserPermission(GuildPermission.ManageMessages)]
|
||||||
|
public async Task FeedList()
|
||||||
|
{
|
||||||
|
var feeds = _service.GetFeeds(Context.Guild.Id);
|
||||||
|
|
||||||
|
if (!feeds.Any())
|
||||||
|
{
|
||||||
|
await Context.Channel.EmbedAsync(new EmbedBuilder()
|
||||||
|
.WithOkColor()
|
||||||
|
.WithDescription(GetText("feed_no_feed")))
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Context.Channel.SendPaginatedConfirmAsync(_client, 0, (cur) =>
|
||||||
|
{
|
||||||
|
var embed = new EmbedBuilder()
|
||||||
|
.WithOkColor();
|
||||||
|
var i = 0;
|
||||||
|
var fs = string.Join("\n", feeds.Skip(cur * 10)
|
||||||
|
.Take(10)
|
||||||
|
.Select(x => $"`{(cur * 10) + (++i)}.` <#{x.ChannelId}> {x.Url}"));
|
||||||
|
|
||||||
|
return embed.WithDescription(fs);
|
||||||
|
|
||||||
|
}, feeds.Count / 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
213
src/NadekoBot/Modules/Searches/Services/FeedsService.cs
Normal file
213
src/NadekoBot/Modules/Searches/Services/FeedsService.cs
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
using Discord;
|
||||||
|
using Microsoft.SyndicationFeed;
|
||||||
|
using Microsoft.SyndicationFeed.Rss;
|
||||||
|
using NadekoBot.Extensions;
|
||||||
|
using NadekoBot.Services;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Xml;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NadekoBot.Services.Database.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using Discord.WebSocket;
|
||||||
|
|
||||||
|
namespace NadekoBot.Modules.Searches.Services
|
||||||
|
{
|
||||||
|
public class FeedsService : INService
|
||||||
|
{
|
||||||
|
private readonly DbService _db;
|
||||||
|
private readonly ConcurrentDictionary<string, HashSet<FeedSub>> _subs;
|
||||||
|
private readonly DiscordSocketClient _client;
|
||||||
|
private readonly ConcurrentDictionary<string, DateTime> _lastPosts =
|
||||||
|
new ConcurrentDictionary<string, DateTime>();
|
||||||
|
|
||||||
|
public FeedsService(IEnumerable<GuildConfig> gcs, DbService db, DiscordSocketClient client)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
|
||||||
|
_subs = gcs.SelectMany(x => x.FeedSubs)
|
||||||
|
.GroupBy(x => x.Url)
|
||||||
|
.ToDictionary(x => x.Key, x => x.ToHashSet())
|
||||||
|
.ToConcurrent();
|
||||||
|
|
||||||
|
_client = client;
|
||||||
|
|
||||||
|
foreach (var kvp in _subs)
|
||||||
|
{
|
||||||
|
// to make sure rss feeds don't post right away, but
|
||||||
|
// only the updates from after the bot has started
|
||||||
|
_lastPosts.AddOrUpdate(kvp.Key, DateTime.UtcNow, (k, old) => DateTime.UtcNow);
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Task.Run(TrackFeeds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<EmbedBuilder> TrackFeeds()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
foreach (var kvp in _subs)
|
||||||
|
{
|
||||||
|
if (kvp.Value.Count == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
DateTime lastTime;
|
||||||
|
if (!_lastPosts.TryGetValue(kvp.Key, out lastTime))
|
||||||
|
lastTime = _lastPosts.AddOrUpdate(kvp.Key, DateTime.UtcNow, (k, old) => DateTime.UtcNow);
|
||||||
|
|
||||||
|
var rssUrl = kvp.Key;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var xmlReader = XmlReader.Create(rssUrl, new XmlReaderSettings() { Async = true }))
|
||||||
|
{
|
||||||
|
var feedReader = new RssFeedReader(xmlReader);
|
||||||
|
|
||||||
|
var embed = new EmbedBuilder()
|
||||||
|
.WithAuthor(kvp.Key)
|
||||||
|
.WithOkColor();
|
||||||
|
|
||||||
|
while (await feedReader.Read() && feedReader.ElementType != SyndicationElementType.Item)
|
||||||
|
{
|
||||||
|
switch (feedReader.ElementType)
|
||||||
|
{
|
||||||
|
case SyndicationElementType.Link:
|
||||||
|
var uri = await feedReader.ReadLink();
|
||||||
|
embed.WithAuthor(kvp.Key, url: uri.Uri.AbsoluteUri);
|
||||||
|
break;
|
||||||
|
case SyndicationElementType.Content:
|
||||||
|
var content = await feedReader.ReadContent();
|
||||||
|
break;
|
||||||
|
case SyndicationElementType.Category:
|
||||||
|
break;
|
||||||
|
case SyndicationElementType.Image:
|
||||||
|
ISyndicationImage image = await feedReader.ReadImage();
|
||||||
|
embed.WithThumbnailUrl(image.Url.AbsoluteUri);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ISyndicationItem item = await feedReader.ReadItem();
|
||||||
|
if (item.Published.UtcDateTime <= lastTime)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var desc = item.Description.StripHTML();
|
||||||
|
|
||||||
|
lastTime = item.Published.UtcDateTime;
|
||||||
|
var title = string.IsNullOrWhiteSpace(item.Title) ? "-" : item.Title;
|
||||||
|
desc = Format.Code(item.Published.ToString()) + Environment.NewLine + desc;
|
||||||
|
var link = item.Links.FirstOrDefault();
|
||||||
|
if (link != null)
|
||||||
|
desc = $"[link]({link.Uri}) " + desc;
|
||||||
|
|
||||||
|
var img = item.Links.FirstOrDefault(x => x.RelationshipType == "enclosure")?.Uri.AbsoluteUri
|
||||||
|
?? Regex.Match(item.Description, @"src=""(?<src>.*?)""").Groups["src"].ToString();
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(img) && Uri.IsWellFormedUriString(img, UriKind.Absolute))
|
||||||
|
embed.WithImageUrl(img);
|
||||||
|
|
||||||
|
embed.AddField(title, desc);
|
||||||
|
|
||||||
|
//send the created embed to all subscribed channels
|
||||||
|
var sendTasks = kvp.Value
|
||||||
|
.Where(x => x.GuildConfig != null)
|
||||||
|
.Select(x => _client.GetGuild(x.GuildConfig.GuildId)
|
||||||
|
?.GetTextChannel(x.ChannelId))
|
||||||
|
.Where(x => x != null)
|
||||||
|
.Select(x => x.EmbedAsync(embed));
|
||||||
|
|
||||||
|
_lastPosts.AddOrUpdate(kvp.Key, item.Published.UtcDateTime, (k, old) => item.Published.UtcDateTime);
|
||||||
|
|
||||||
|
await Task.WhenAll(sendTasks).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) { Console.WriteLine(ex); }
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(10000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FeedSub> GetFeeds(ulong guildId)
|
||||||
|
{
|
||||||
|
using (var uow = _db.UnitOfWork)
|
||||||
|
{
|
||||||
|
return uow.GuildConfigs.For(guildId, set => set.Include(x => x.FeedSubs))
|
||||||
|
.FeedSubs
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AddFeed(ulong guildId, ulong channelId, string rssFeed)
|
||||||
|
{
|
||||||
|
rssFeed.ThrowIfNull(nameof(rssFeed));
|
||||||
|
|
||||||
|
var fs = new FeedSub()
|
||||||
|
{
|
||||||
|
ChannelId = channelId,
|
||||||
|
Url = rssFeed.Trim().ToLowerInvariant(),
|
||||||
|
};
|
||||||
|
|
||||||
|
using (var uow = _db.UnitOfWork)
|
||||||
|
{
|
||||||
|
var gc = uow.GuildConfigs.For(guildId, set => set.Include(x => x.FeedSubs));
|
||||||
|
|
||||||
|
if (gc.FeedSubs.Contains(fs))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (gc.FeedSubs.Count >= 10)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
gc.FeedSubs.Add(fs);
|
||||||
|
|
||||||
|
//adding all, in case bot wasn't on this guild when it started
|
||||||
|
foreach (var f in gc.FeedSubs)
|
||||||
|
{
|
||||||
|
_subs.AddOrUpdate(f.Url, new HashSet<FeedSub>(), (k, old) =>
|
||||||
|
{
|
||||||
|
old.Add(f);
|
||||||
|
return old;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
uow.Complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RemoveFeed(ulong guildId, int index)
|
||||||
|
{
|
||||||
|
if (index < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
using (var uow = _db.UnitOfWork)
|
||||||
|
{
|
||||||
|
var items = uow.GuildConfigs.For(guildId, set => set.Include(x => x.FeedSubs))
|
||||||
|
.FeedSubs
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (items.Count <= index)
|
||||||
|
return false;
|
||||||
|
var toRemove = items[index];
|
||||||
|
_subs.AddOrUpdate(toRemove.Url, new HashSet<FeedSub>(), (key, old) =>
|
||||||
|
{
|
||||||
|
old.Remove(toRemove);
|
||||||
|
return old;
|
||||||
|
});
|
||||||
|
uow._context.Remove(toRemove);
|
||||||
|
uow.Complete();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -61,6 +61,7 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
|
||||||
<PackageReference Include="NLog" Version="5.0.0-beta10" />
|
<PackageReference Include="NLog" Version="5.0.0-beta10" />
|
||||||
<PackageReference Include="StackExchange.Redis" Version="1.2.6" />
|
<PackageReference Include="StackExchange.Redis" Version="1.2.6" />
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
"profiles": {
|
"profiles": {
|
||||||
"NadekoBot": {
|
"NadekoBot": {
|
||||||
"commandName": "Project"
|
"commandName": "Project"
|
||||||
|
},
|
||||||
|
"Watch": {
|
||||||
|
"executablePath": "C:\\Program Files\\dotnet\\dotnet.exe",
|
||||||
|
"commandLineArgs": "watch run"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
23
src/NadekoBot/Services/Database/Models/FeedSub.cs
Normal file
23
src/NadekoBot/Services/Database/Models/FeedSub.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
namespace NadekoBot.Services.Database.Models
|
||||||
|
{
|
||||||
|
public class FeedSub : DbEntity
|
||||||
|
{
|
||||||
|
public int GuildConfigId { get; set; }
|
||||||
|
public GuildConfig GuildConfig { get; set; }
|
||||||
|
|
||||||
|
public ulong ChannelId { get; set; }
|
||||||
|
public string Url { get; set; }
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Url.GetHashCode() ^ GuildConfigId.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return obj is FeedSub s
|
||||||
|
? s.Url == Url && s.GuildConfigId == GuildConfigId
|
||||||
|
: false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -87,6 +87,7 @@ namespace NadekoBot.Services.Database.Models
|
|||||||
public StreamRoleSettings StreamRole { get; set; }
|
public StreamRoleSettings StreamRole { get; set; }
|
||||||
|
|
||||||
public XpSettings XpSettings { get; set; }
|
public XpSettings XpSettings { get; set; }
|
||||||
|
public List<FeedSub> FeedSubs { get; set; } = new List<FeedSub>();
|
||||||
|
|
||||||
//public List<ProtectionIgnoredChannel> ProtectionIgnoredChannels { get; set; } = new List<ProtectionIgnoredChannel>();
|
//public List<ProtectionIgnoredChannel> ProtectionIgnoredChannels { get; set; } = new List<ProtectionIgnoredChannel>();
|
||||||
}
|
}
|
||||||
|
@ -142,6 +142,9 @@ namespace NadekoBot.Services.Database
|
|||||||
.HasOne(x => x.GuildConfig)
|
.HasOne(x => x.GuildConfig)
|
||||||
.WithOne(x => x.AntiRaidSetting);
|
.WithOne(x => x.AntiRaidSetting);
|
||||||
|
|
||||||
|
modelBuilder.Entity<FeedSub>()
|
||||||
|
.HasAlternateKey(x => new { x.GuildConfigId, x.Url });
|
||||||
|
|
||||||
//modelBuilder.Entity<ProtectionIgnoredChannel>()
|
//modelBuilder.Entity<ProtectionIgnoredChannel>()
|
||||||
// .HasAlternateKey(c => new { c.ChannelId, c.ProtectionType });
|
// .HasAlternateKey(c => new { c.ChannelId, c.ProtectionType });
|
||||||
|
|
||||||
@ -304,6 +307,7 @@ namespace NadekoBot.Services.Database
|
|||||||
.WithOne(x => x.XpSettings);
|
.WithOne(x => x.XpSettings);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
//todo major bug
|
||||||
#region XpRoleReward
|
#region XpRoleReward
|
||||||
modelBuilder.Entity<XpRoleReward>()
|
modelBuilder.Entity<XpRoleReward>()
|
||||||
.HasAlternateKey(x => x.Level);
|
.HasAlternateKey(x => x.Level);
|
||||||
|
@ -44,6 +44,8 @@ namespace NadekoBot.Services.Database.Repositories.Impl
|
|||||||
.Include(gc => gc.SlowmodeIgnoredUsers)
|
.Include(gc => gc.SlowmodeIgnoredUsers)
|
||||||
.Include(gc => gc.AntiSpamSetting)
|
.Include(gc => gc.AntiSpamSetting)
|
||||||
.ThenInclude(x => x.IgnoredChannels)
|
.ThenInclude(x => x.IgnoredChannels)
|
||||||
|
.Include(gc => gc.FeedSubs)
|
||||||
|
.ThenInclude(x => x.GuildConfig)
|
||||||
.Include(gc => gc.FollowedStreams)
|
.Include(gc => gc.FollowedStreams)
|
||||||
.Include(gc => gc.StreamRole)
|
.Include(gc => gc.StreamRole)
|
||||||
.Include(gc => gc.NsfwBlacklistedTags)
|
.Include(gc => gc.NsfwBlacklistedTags)
|
||||||
|
@ -20,7 +20,7 @@ namespace NadekoBot.Services.Impl
|
|||||||
public CultureInfo DefaultCultureInfo { get; private set; } = CultureInfo.CurrentCulture;
|
public CultureInfo DefaultCultureInfo { get; private set; } = CultureInfo.CurrentCulture;
|
||||||
|
|
||||||
private static readonly Dictionary<string, CommandData> _commandData;
|
private static readonly Dictionary<string, CommandData> _commandData;
|
||||||
|
|
||||||
static Localization()
|
static Localization()
|
||||||
{
|
{
|
||||||
_commandData = JsonConvert.DeserializeObject<Dictionary<string, CommandData>>(
|
_commandData = JsonConvert.DeserializeObject<Dictionary<string, CommandData>>(
|
||||||
|
@ -9,6 +9,11 @@ namespace NadekoBot.Extensions
|
|||||||
{
|
{
|
||||||
public static class StringExtensions
|
public static class StringExtensions
|
||||||
{
|
{
|
||||||
|
public static string StripHTML(this string input)
|
||||||
|
{
|
||||||
|
return Regex.Replace(input, "<.*?>", String.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Easy use of fast, efficient case-insensitive Contains check with StringComparison Member Types
|
/// Easy use of fast, efficient case-insensitive Contains check with StringComparison Member Types
|
||||||
/// CurrentCulture, CurrentCultureIgnoreCase, InvariantCulture, InvariantCultureIgnoreCase, Ordinal, OrdinalIgnoreCase
|
/// CurrentCulture, CurrentCultureIgnoreCase, InvariantCulture, InvariantCultureIgnoreCase, Ordinal, OrdinalIgnoreCase
|
||||||
|
@ -872,5 +872,10 @@
|
|||||||
"xp_club_admin_remove": "{0} is no longer club admin.",
|
"xp_club_admin_remove": "{0} is no longer club admin.",
|
||||||
"xp_club_admin_error": "Error. You are either not the owner of the club, or that user is not in your club.",
|
"xp_club_admin_error": "Error. You are either not the owner of the club, or that user is not in your club.",
|
||||||
"nsfw_started": "Started. Reposting every {0}s.",
|
"nsfw_started": "Started. Reposting every {0}s.",
|
||||||
"nsfw_stopped": "Stopped reposting."
|
"nsfw_stopped": "Stopped reposting.",
|
||||||
|
"searches_feed_added": "Feed added.",
|
||||||
|
"searches_feed_not_valid": "Invalid link, or you're already following that feed on this server, or you've reached maximum number of feeds allowed.",
|
||||||
|
"searches_feed_out_of_range": "Index out of range.",
|
||||||
|
"searches_feed_removed": "Feed removed.",
|
||||||
|
"searches_feed_no_feed": "You haven't subscribed to any feeds on this server."
|
||||||
}
|
}
|
@ -2965,5 +2965,26 @@
|
|||||||
"Usage": [
|
"Usage": [
|
||||||
"{0}8ball"
|
"{0}8ball"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"feed": {
|
||||||
|
"cmd": "feed feedadd",
|
||||||
|
"desc": "Subscribes to a feed. Bot will post an update up to once every 10 seconds. You can have up to 10 feeds on one server. All feeds must have unique URLs.",
|
||||||
|
"usage": [
|
||||||
|
"{0}feed https://www.rt.com/rss/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"feedremove": {
|
||||||
|
"cmd": "feedremove feedrm feeddel",
|
||||||
|
"desc": "Stops tracking a feed on the given index. Use `{0}feeds` command to see a list of feeds and their indexes.",
|
||||||
|
"usage": [
|
||||||
|
"{0}feedremove 3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"feedlist": {
|
||||||
|
"cmd": "feeds feedlist",
|
||||||
|
"desc": "Shows the list of feeds you've subscribed to on this server.",
|
||||||
|
"usage": [
|
||||||
|
"{0}feeds"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user