commit 90a70b60c21072a04570bbaa5d24f1e26dd9c4e2 Author: Andreas Bielawski Date: Thu Jan 30 20:57:41 2020 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d4a57f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# IDE and config +.idea/ +.vs/ +config.ini +NLog.config + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ diff --git a/Bot.cs b/Bot.cs new file mode 100644 index 0000000..1168d2e --- /dev/null +++ b/Bot.cs @@ -0,0 +1,125 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using NLog; +using StackExchange.Redis; +using Telegram.Bot; +using Telegram.Bot.Args; +using Telegram.Bot.Types; +using Telegram.Bot.Types.Enums; + +namespace RSSBot { + public static class Bot { + public static ITelegramBotClient BotClient; + public static User BotInfo; + public static readonly List RssBotFeeds = new List(); + public static Timer JobQueue; + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private static HashSet Handlers; + + private static void Main(string[] args) { + Configuration.Parse(); + BotClient = new TelegramBotClient(Configuration.BotToken); + try { + BotInfo = BotClient.GetMeAsync().Result; + } catch (AggregateException) { + Logger.Fatal("Login fehlgeschlagen, Bot-Token prüfen!"); + return; + } + + AppDomain.CurrentDomain.ProcessExit += OnProcessExit; + Console.CancelKeyPress += delegate { Save(); }; + + // Read subscribed feeds from Redis + ReadAllFeeds(); + + // Add handlers + Handlers = new HashSet { + new RegexHandler($"^/start(?:@{BotInfo.Username})?$", Commands.Welcome), + new RegexHandler($"^/help(?:@{BotInfo.Username})?$", Commands.Help), + new RegexHandler($"^/rss(?:@{BotInfo.Username})?$", Commands.Show), + new RegexHandler($"^/rss(?:@{BotInfo.Username})? (@?[A-z0-9_]+)$", Commands.Show), + new RegexHandler( + $"^/show(?:@{BotInfo.Username})? (http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&~+]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)$", + Commands.ShowAvailableFeeds), + new RegexHandler( + $"^/sub(?:@{BotInfo.Username})? (http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&~+]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)$", + Commands.Subscribe), + new RegexHandler( + $"^/sub(?:@{BotInfo.Username})? (http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&~+]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+) (@?[A-z0-9_]+)$$", + Commands.Subscribe), + new RegexHandler( + $"^/del(?:@{BotInfo.Username})? (http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&~+]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)$", + Commands.Unsubscribe), + new RegexHandler( + $"^/del(?:@{BotInfo.Username})? (http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&~+]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+) (@?[A-z0-9_]+)$$", + Commands.Unsubscribe), + }; + + JobQueue = new Timer(e => { Commands.Sync(); }, null, TimeSpan.FromSeconds(5), + TimeSpan.FromMilliseconds(-1)); + + Logger.Info($"Bot gestartet: {BotInfo.FirstName}, AKA {BotInfo.Username} ({BotInfo.Id})."); + + BotClient.OnMessage += Bot_OnMessage; + BotClient.StartReceiving(); + Console.ReadLine(); + BotClient.StopReceiving(); + Save(); + } + + private static void ReadAllFeeds() { + RedisValue[] allFeedUrls = Configuration.Database.SetMembers($"{Configuration.RedisHash}:feeds"); + foreach (RedisValue feedUrl in allFeedUrls) { + HashSet subs = new HashSet(); + RedisValue[] allSubs = Configuration.Database.SetMembers($"{Configuration.RedisHash}:{feedUrl}:subs"); + foreach (RedisValue sub in allSubs) { + subs.Add(Convert.ToInt64(sub)); + } + + string lastEntry = Configuration.Database.HashGet($"{Configuration.RedisHash}:{feedUrl}", "last_entry"); + + RssBotFeed feed = new RssBotFeed(feedUrl, lastEntry, subs); + RssBotFeeds.Add(feed); + } + } + + private static void OnProcessExit(object? sender, EventArgs e) { + Save(); + } + + private static void Bot_OnMessage(object? sender, MessageEventArgs messageEventArgs) { + var message = messageEventArgs.Message; + if (message == null || message.Type != MessageType.Text) return; + if (!Configuration.Admins.Contains(message.From.Id)) { + return; + } + + foreach (RegexHandler handler in Handlers.Where(handler => handler.HandleUpdate(message))) { + handler.ProcessUpdate(message); + } + } + + public static async void Save() { + if (RssBotFeeds.Count > 0) { + Logger.Info("Speichere Daten..."); + } + + foreach (RssBotFeed feed in RssBotFeeds) { + string feedKey = $"{Configuration.RedisHash}:{feed.Url}"; + if (string.IsNullOrWhiteSpace(feed.LastEntry)) continue; + + await Configuration.Database.HashSetAsync(feedKey, "last_entry", feed.LastEntry); + foreach (long chatId in feed.Subs) { + await Configuration.Database.SetAddAsync($"{feedKey}:subs", chatId); + } + + await Configuration.Database.SetAddAsync($"{Configuration.RedisHash}:feeds", feed.Url); + } + + Logger.Info("Gespeichert!"); + } + } +} \ No newline at end of file diff --git a/Commands.cs b/Commands.cs new file mode 100644 index 0000000..86a41f2 --- /dev/null +++ b/Commands.cs @@ -0,0 +1,284 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Web; +using CodeHollow.FeedReader; +using NLog; +using Telegram.Bot.Exceptions; +using Telegram.Bot.Types; +using Telegram.Bot.Types.Enums; + +namespace RSSBot { + public static class Commands { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + public static async void Welcome(Message message, GroupCollection matches) { + await Bot.BotClient.SendTextMessageAsync( + message.Chat, + "Willkommen beim RSS-Bot!\nSende /help, um zu starten.", + ParseMode.Html + ); + } + + public static async void Help(Message message, GroupCollection matches) { + await Bot.BotClient.SendTextMessageAsync( + message.Chat, + "/rss [Chat]: Abonnierte Feeds anzeigen\n" + + "/sub Feed-URL [Chat]: Feed abonnieren\n" + + "/del Feed-URL [Chat]: Feed löschen\n" + + "/show Feed-URL [Chat]: Feeds auf dieser Seite anzeigen\n" + + "[Chat] ist ein optionales Argument mit dem @Kanalnamen.", + ParseMode.Html + ); + } + + public static async void Subscribe(Message message, GroupCollection args) { + string url = args[1].Value; + long chatId = message.Chat.Id; + RssBotFeed feed = new RssBotFeed(url); + + await Bot.BotClient.SendChatActionAsync(message.Chat, ChatAction.Typing); + + if (args.Count > 2) { + string chatName = args[2].Value; + if (!chatName.StartsWith("@")) { + chatName = $"@{chatName}"; + } + Chat chatInfo; + try { + chatInfo = await Bot.BotClient.GetChatAsync(chatName); + } catch { + await Bot.BotClient.SendTextMessageAsync(message.Chat, "❌ Dieser Kanal existiert nicht."); + return; + } + + chatId = chatInfo.Id; + + if (!await Utils.IsBotAdmin(chatId)) { + await Bot.BotClient.SendTextMessageAsync(message.Chat, + "❌ Du musst den Bot als Administrator zu diesem Kanal hinzufügen."); + return; + } + } + + try { + await feed.Check(); + } catch { + await Bot.BotClient.SendTextMessageAsync( + message.Chat, + "❌ Kein gültiger RSS-Feed." + ); + return; + } + + // Check if we already have the feed + RssBotFeed existingFeed = Bot.RssBotFeeds + .FirstOrDefault(x => x.Url.ToLower().Equals(feed.Url.ToLower())); + if (existingFeed == null) { + Bot.RssBotFeeds.Add(feed); + } else { + feed = existingFeed; + } + + // Check if chat already subscribed + if (feed.Subs.Contains(chatId)) { + await Bot.BotClient.SendTextMessageAsync(message.Chat, "✅ Dieser Feed wurde bereits abonniert."); + } else { + feed.Subs.Add(chatId); + await Bot.BotClient.SendTextMessageAsync(message.Chat, "✅ Feed abonniert!"); + Bot.Save(); + } + } + + public static async void Unsubscribe(Message message, GroupCollection args) { + string url = args[1].Value; + long chatId = message.Chat.Id; + RssBotFeed feed = Bot.RssBotFeeds + .FirstOrDefault(x => x.Url.ToLower().Equals(url.ToLower())); + + if (args.Count > 2) { + string chatName = args[2].Value; + if (!chatName.StartsWith("@")) { + chatName = $"@{chatName}"; + } + Chat chatInfo; + try { + chatInfo = await Bot.BotClient.GetChatAsync(chatName); + } catch { + await Bot.BotClient.SendTextMessageAsync(message.Chat, "❌ Dieser Kanal existiert nicht."); + return; + } + + chatId = chatInfo.Id; + + if (!await Utils.IsBotAdmin(chatId)) { + await Bot.BotClient.SendTextMessageAsync(message.Chat, + "❌ Du musst den Bot als Administrator zu diesem Kanal hinzufügen."); + return; + } + } + + if (feed == null || !feed.Subs.Contains(chatId)) { + await Bot.BotClient.SendTextMessageAsync(message.Chat, "❌ Feed wurde nicht abonniert."); + return; + } + + feed.Cleanup(chatId); + if (feed.Subs.Count == 0) { + Bot.RssBotFeeds.Remove(feed); + } + + await Bot.BotClient.SendTextMessageAsync(message.Chat, "✅ Feed deabonniert!"); + Bot.Save(); + } + + public static async void Show(Message message, GroupCollection args) { + long chatId = message.Chat.Id; + string chatTitle = message.Chat.Type.Equals(ChatType.Private) ? message.Chat.FirstName : message.Chat.Title; + await Bot.BotClient.SendChatActionAsync(message.Chat, ChatAction.Typing); + + if (args.Count > 1) { + string chatName = args[1].Value; + if (!chatName.StartsWith("@")) { + chatName = $"@{chatName}"; + } + Chat chatInfo; + + try { + chatInfo = await Bot.BotClient.GetChatAsync(chatName); + } catch { + await Bot.BotClient.SendTextMessageAsync(message.Chat, "❌ Dieser Kanal existiert nicht."); + return; + } + + chatId = chatInfo.Id; + chatTitle = chatInfo.Title; + + if (!await Utils.IsBotAdmin(chatId)) { + await Bot.BotClient.SendTextMessageAsync(message.Chat, + "❌ Du musst den Bot als Administrator zu diesem Kanal hinzufügen."); + return; + } + } + + string text; + + List feeds = Bot.RssBotFeeds.Where(x => x.Subs.Contains(chatId)).ToList(); + + if (feeds.Count < 1) { + text = "❌ Keine Feeds abonniert."; + } else { + text = $"{HttpUtility.HtmlEncode(chatTitle)} hat abonniert:\n"; + for (int i = 0; i < feeds.Count; i++) { + text += $"{i + 1}) {feeds[i].Url}\n"; + } + } + await Bot.BotClient.SendTextMessageAsync(message.Chat, text, ParseMode.Html, true); + } + + public static async void Sync() { + Logger.Info(("================================")); + bool hadEntries = false; + foreach (RssBotFeed feed in Bot.RssBotFeeds.ToList()) { + Logger.Info(feed.Url); + try { + await feed.Check(); + } catch (Exception e) { + Logger.Warn($"FEHLER: {e.Message}"); + continue; + } + + if (feed.NewEntries.Count == 0) { + Logger.Info("Keine neuen Beiträge."); + continue; + } + + hadEntries = true; + Logger.Info(feed.NewEntries.Count == 1 + ? "1 neuer Beitrag" + : $"{feed.NewEntries.Count} neue Beiträge"); + + foreach (FeedItem entry in feed.NewEntries) { + string postTitle = "Kein Titel"; + if (!string.IsNullOrWhiteSpace(entry.Title)) { + postTitle = Utils.StripHtml(entry.Title); + } + + string postLink = feed.MainLink; + string linkName = postLink; + if (!string.IsNullOrWhiteSpace(entry.Link)) { + postLink = entry.Link; + // FeedProxy URLs + GroupCollection feedProxy = + Utils.ReturnMatches(postLink, "^https?://feedproxy.google.com/~r/(.+)/.*"); + linkName = feedProxy.Count > 1 ? feedProxy[1].Value : new Uri(postLink).Host; + } + + // Remove "www." + int index = linkName.IndexOf("www.", StringComparison.Ordinal); + if (index > 0) { + linkName = linkName.Remove(index, 4); + } + + string content = ""; + if (!string.IsNullOrWhiteSpace(entry.Content)) { + content = Utils.ProcessContent(entry.Content); // magic processing missing + } else if (!string.IsNullOrWhiteSpace(entry.Description)) { + content = Utils.ProcessContent(entry.Description); + } + + string text = $"{postTitle}\n{feed.Title}\n{content}"; + text += $"\nWeiterlesen auf {linkName}"; + + // Send + foreach (long chatId in feed.Subs.ToList()) { + try { + await Bot.BotClient.SendTextMessageAsync(chatId, text, ParseMode.Html, true, true); + } catch (ApiRequestException e) { + if (e.ErrorCode.Equals(403)) { + Logger.Warn(e.Message); + feed.Cleanup(chatId); + if (feed.Subs.Count == 0) { // was last subscriber + Bot.RssBotFeeds.Remove(feed); + } + } else { + Logger.Error($"{e.ErrorCode}: {e.Message}"); + } + } + } + } + } + + Logger.Info("Nächster Check in 60 Sekunden"); + + if (hadEntries) { + Bot.Save(); + } + + Bot.JobQueue.Change(TimeSpan.FromMinutes(1), TimeSpan.FromMilliseconds(-1)); + } + + public static async void ShowAvailableFeeds(Message message, GroupCollection args) { + string url = args[1].Value; + IEnumerable feeds; + try { + feeds = await FeedReader.GetFeedUrlsFromUrlAsync(url); + } catch { + await Bot.BotClient.SendTextMessageAsync(message.Chat, "❌ Sete konnte nicht erreicht werden."); + return; + } + + List htmlFeedLinks = feeds.ToList(); + if (htmlFeedLinks.Count == 0) { + await Bot.BotClient.SendTextMessageAsync(message.Chat, "❌ Keine Feeds gefunden."); + return; + } + + string text = htmlFeedLinks.Aggregate("Feeds gefunden:\n", + (current, feedLink) => current + $"* {Utils.StripHtml(feedLink.Title)}\n"); + + await Bot.BotClient.SendTextMessageAsync(message.Chat, text, ParseMode.Html, true); + } + } +} \ No newline at end of file diff --git a/Configuration.cs b/Configuration.cs new file mode 100644 index 0000000..30642d1 --- /dev/null +++ b/Configuration.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using IniParser; +using IniParser.Model; +using NLog; +using NLog.Config; +using NLog.Targets; +using StackExchange.Redis; + +namespace RSSBot { + public static class Configuration { + public static string BotToken; + private static ConnectionMultiplexer _redis; + public static IDatabase Database; + public static string RedisHash; + public static List Admins; + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + public static void Parse() { + if (!File.Exists("NLog.config")) { + Console.WriteLine("NLog.config nicht gefunden, setze auf INFO..."); + LoggingConfiguration config = new LoggingConfiguration(); + + ConsoleTarget logconsole = new ConsoleTarget("logconsole"); + config.AddRule(LogLevel.Info, LogLevel.Fatal, logconsole); + logconsole.Layout = "${longdate} - ${logger} - ${level:uppercase=true} - ${message}"; + LogManager.Configuration = config; + } else { + LogManager.LoadConfiguration("NLog.config"); + } + + FileIniDataParser parser = new FileIniDataParser(); + if (!File.Exists("config.ini")) { + Logger.Fatal("config.ini nicht gefunden."); + Environment.Exit(1); + } + + IniData data = parser.ReadFile("config.ini"); + + BotToken = data["DEFAULT"]["token"]; + string host = data["REDIS"]["host"] ?? "127.0.0.1"; + string port = data["REDIS"]["port"] ?? "6379"; + string configuration = data["REDIS"]["configuration"] ?? $"{host}:{port}"; + + RedisHash = data["REDIS"]["hash"] ?? "telegram:rssbot"; + int db = 0; + try { + db = int.Parse(data["REDIS"]["db"] ?? "0"); + } catch (FormatException) { + Logger.Fatal("Keine valide Datenbanknummer."); + Environment.Exit(1); + } + + string admins = data["ADMIN"]["id"]; + + if (string.IsNullOrWhiteSpace(BotToken)) { + Logger.Fatal("Bitte Bot-Token in der config.ini angeben."); + Environment.Exit(1); + } + + try { + Admins = admins.Split(",").Select(int.Parse).ToList(); + } catch (FormatException) { + Logger.Fatal("Admin-IDs sind keine Integer."); + Environment.Exit(1); + } + + Logger.Info("Verbinde mit Redis..."); + // TODO: Sockets + try { + _redis = ConnectionMultiplexer.Connect(configuration); + } catch (RedisConnectionException) { + Logger.Fatal("Redis-Verbindung fehlgeschlagen."); + Environment.Exit(1); + } + + Database = _redis.GetDatabase(db); + } + } +} \ No newline at end of file diff --git a/NLog.config.example b/NLog.config.example new file mode 100644 index 0000000..86ba154 --- /dev/null +++ b/NLog.config.example @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c4db22f --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +RSS Bot for Telegram +===================== +1. **Install .NET Core Runtime >= 3.1 and Redis** +2. **Copy config:** `cp config.ini.example config.ini` +3. **Insert bot token** in `config.ini` + 1. Adjust Redis settings if needed +4. **Insert your Telegram ID as admin** (send `@Brawlbot id` inside a chat to get yours) +5. Optional: Copy the `NLog.config.example` next to the config.ini as `NLog.config` +6. Run the `RSSBot` executable + +(c) 2020 Andreas Bielawski diff --git a/RSSBot.csproj b/RSSBot.csproj new file mode 100644 index 0000000..cb2138e --- /dev/null +++ b/RSSBot.csproj @@ -0,0 +1,25 @@ + + + + Exe + netcoreapp3.1 + win-x64;osx-x64;linux-x64;linux-arm + 1.0.0 + + + false + RSSBot + Brawl + RSSBot for Telegram + 2020 Brawl + + + + + + + + + + + diff --git a/RSSBot.sln b/RSSBot.sln new file mode 100644 index 0000000..a566643 --- /dev/null +++ b/RSSBot.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29709.97 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RSSBot", "RSSBot.csproj", "{8229A450-2EB5-4E3B-B6A5-647DB7363D86}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8229A450-2EB5-4E3B-B6A5-647DB7363D86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8229A450-2EB5-4E3B-B6A5-647DB7363D86}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8229A450-2EB5-4E3B-B6A5-647DB7363D86}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8229A450-2EB5-4E3B-B6A5-647DB7363D86}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {365EEFBA-E5A1-419A-828F-54CA76CCE745} + EndGlobalSection +EndGlobal diff --git a/RegexHandler.cs b/RegexHandler.cs new file mode 100644 index 0000000..765e6e1 --- /dev/null +++ b/RegexHandler.cs @@ -0,0 +1,46 @@ +using System; +using System.Text.RegularExpressions; +using Telegram.Bot.Types; + +namespace RSSBot { + /// + /// RegexHandler for Telegram Bots. + /// + public class RegexHandler { + private string Pattern; + private Action CallbackFunction; + + /// + /// Constructor for the RegexHandler. + /// + /// Regex pattern + /// Callback function to call when the update should be processed + public RegexHandler(string pattern, Action callback) { + Pattern = pattern; + CallbackFunction = callback; + } + + /// + /// Checks whether the update should be handled by this handler. + /// + /// Telegram Message object + /// true if the update should be handled + public bool HandleUpdate(Message message) { + return Regex.IsMatch(message.Text, + Pattern, + RegexOptions.IgnoreCase); + } + + /// + /// Calls the assoicated callback function. + /// + /// Telegram Message object + public void ProcessUpdate(Message message) { + GroupCollection matches = Regex.Match(message.Text, + Pattern, + RegexOptions.IgnoreCase + ).Groups; + CallbackFunction(message, matches); + } + } +} \ No newline at end of file diff --git a/RssBotFeed.cs b/RssBotFeed.cs new file mode 100644 index 0000000..e8f9bb4 --- /dev/null +++ b/RssBotFeed.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using CodeHollow.FeedReader; + +namespace RSSBot { + public class RssBotFeed { + public readonly string Url; + public string LastEntry; + public readonly HashSet Subs = new HashSet(); + public string MainLink { get; private set; } + public string Title { get; private set; } + public List NewEntries { get; private set; } + + public RssBotFeed(string url, string lastEntry = null, HashSet subs = null) { + Url = url; + if (!string.IsNullOrWhiteSpace(lastEntry)) { + LastEntry = lastEntry; + } + + if (subs != null) { + Subs = subs; + } + } + + public async Task Check() { + Feed feed = await FeedReader.ReadAsync(Url); + if (string.IsNullOrWhiteSpace(feed.Link)) { + throw new Exception("Kein gültiger RSS-Feed."); + } + + MainLink = feed.Link; + Title = feed.Title; + + if (feed.Items == null || feed.Items.Count <= 0) return; + NewEntries = string.IsNullOrWhiteSpace(LastEntry) + ? feed.Items.ToList() + : GetNewEntries(feed.Items); + + LastEntry = string.IsNullOrWhiteSpace(feed.Items.First().Id) + ? feed.Items.First().Link + : feed.Items.First().Id; + } + + private List GetNewEntries(IEnumerable entries) { + List newEntries = new List(); + foreach (FeedItem entry in entries) { + if (!string.IsNullOrWhiteSpace(entry.Id)) { + if (entry.Id.Equals(LastEntry)) { + break; + } + + newEntries.Add(entry); + } else { + if (entry.Link.Equals(LastEntry)) { + break; + } + + newEntries.Add(entry); + } + } + + newEntries.Reverse(); + return newEntries; + } + + public override string ToString() { + return $"RSS-Feed: '{Url}'"; + } + + public void Cleanup(long chatId) { + Subs.Remove(chatId); + string feedKey = $"{Configuration.RedisHash}:{Url}"; + Configuration.Database.SetRemove($"{feedKey}:subs", chatId); + + // No subscribers, delete all references + if (Subs.Count != 0 || !Configuration.Database.KeyExists(feedKey)) return; + Configuration.Database.KeyDelete(feedKey); + Configuration.Database.KeyDelete($"{feedKey}:subs"); + Configuration.Database.SetRemove($"{Configuration.RedisHash}:feeds", Url); + } + } +} \ No newline at end of file diff --git a/Utils.cs b/Utils.cs new file mode 100644 index 0000000..fa5ad7e --- /dev/null +++ b/Utils.cs @@ -0,0 +1,89 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Telegram.Bot.Types; +using Telegram.Bot.Types.Enums; + +namespace RSSBot { + public static class Utils { + public static string StripHtml(string input) { + return Regex.Replace(input, "<.*?>", String.Empty).Trim(); + } + + private static string CleanRss(string input) { + string[] replacements = { + "[…]", + "[bilder]", + "[boerse]", + "[mehr]", + "[video]", + "...[more]", + "[more]", + "[liveticker]", + "[livestream]", + "[multimedia]", + "[sportschau]", + "[phoenix]", + "[swr]", + "[ndr]", + "[mdr]", + "[rbb]", + "[wdr]", + "[hr]", + "[br]", + "Click for full.", + "Read more »", + "Read more", + "...Read More", + "(more…)", + "View On WordPress", + "Continue reading →", + "(RSS generated with FetchRss)", + "-- Delivered by Feed43 service", + "Meldung bei www.tagesschau.de lesen" + }; + + string[] regexReplacements = { + "Der Beitrag.*erschien zuerst auf .+.", + "The post.*appeared first on .+.", + "http://www.serienjunkies.de/.*.html" + }; + + input = replacements.Aggregate(input, (current, replacement) => current.Replace(replacement, "")); + input = regexReplacements.Aggregate(input, + (current, replacement) => Regex.Replace(current, replacement, "")); + + return input; + } + + public static string ProcessContent(string input) { + string content = StripHtml(input); + content = CleanRss(content); + if (content.Length > 250) { + content = content.Substring(0, 250) + "..."; + } + + return content; + } + + public static bool IsCommand(string messageText, string command) { + return Regex.Match(messageText, + $"^/{command}(?:@{Bot.BotInfo.Username})?$", + RegexOptions.IgnoreCase + ).Success; + } + + public static GroupCollection ReturnMatches(string text, string pattern) { + return Regex.Match(text, + pattern, + RegexOptions.IgnoreCase + ).Groups; + } + + public static async Task IsBotAdmin(long chatId) { + ChatMember chatMember = await Bot.BotClient.GetChatMemberAsync(chatId, Bot.BotClient.BotId); + return chatMember.Status.Equals(ChatMemberStatus.Administrator); + } + } +} \ No newline at end of file diff --git a/config.ini.example b/config.ini.example new file mode 100644 index 0000000..60df9f0 --- /dev/null +++ b/config.ini.example @@ -0,0 +1,13 @@ +[DEFAULT] +token = 1337:1234567890abcdefgh + +[REDIS] +#db = 0 +#host = localhost +#port = 6379 +#socket_path = /home/user/.redis/sock +#hash = telegram:rssbot + + +[ADMIN] +id = 12345678 diff --git a/publish.bat b/publish.bat new file mode 100644 index 0000000..40895ac --- /dev/null +++ b/publish.bat @@ -0,0 +1,7 @@ +@echo off +rd /s /q bin\Release\ +dotnet publish -c release -r win-x64 /p:PublishSingleFile=true /p:PublishReadyToRun=true +dotnet publish -c release -r linux-x64 /p:PublishSingleFile=true +dotnet publish -c release -r linux-arm /p:PublishSingleFile=true +pause +exit