Initial commit

This commit is contained in:
Andreas Bielawski 2020-01-30 20:57:41 +01:00
commit 90a70b60c2
No known key found for this signature in database
GPG Key ID: D2073645DC2C3DE5
13 changed files with 822 additions and 0 deletions

19
.gitignore vendored Normal file
View File

@ -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/

125
Bot.cs Normal file
View File

@ -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<RssBotFeed> RssBotFeeds = new List<RssBotFeed>();
public static Timer JobQueue;
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private static HashSet<RegexHandler> 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<RegexHandler> {
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<long> subs = new HashSet<long>();
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!");
}
}
}

284
Commands.cs Normal file
View File

@ -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,
"<b>Willkommen beim RSS-Bot!</b>\nSende /help, um zu starten.",
ParseMode.Html
);
}
public static async void Help(Message message, GroupCollection matches) {
await Bot.BotClient.SendTextMessageAsync(
message.Chat,
"<b>/rss</b> <i>[Chat]</i>: Abonnierte Feeds anzeigen\n" +
"<b>/sub</b> <i>Feed-URL</i> <i>[Chat]</i>: Feed abonnieren\n" +
"<b>/del</b> <i>Feed-URL</i> <i>[Chat]</i>: Feed löschen\n" +
"<b>/show</b> <i>Feed-URL</i> <i>[Chat]</i>: Feeds auf dieser Seite anzeigen\n" +
"<i>[Chat]</i> 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<RssBotFeed> feeds = Bot.RssBotFeeds.Where(x => x.Subs.Contains(chatId)).ToList();
if (feeds.Count < 1) {
text = "❌ Keine Feeds abonniert.";
} else {
text = $"<strong>{HttpUtility.HtmlEncode(chatTitle)}</strong> hat abonniert:\n";
for (int i = 0; i < feeds.Count; i++) {
text += $"<strong>{i + 1}</strong>) {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 = $"<b>{postTitle}</b>\n<i>{feed.Title}</i>\n{content}";
text += $"\n<a href=\"{postLink}\">Weiterlesen auf {linkName}</a>";
// 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<HtmlFeedLink> feeds;
try {
feeds = await FeedReader.GetFeedUrlsFromUrlAsync(url);
} catch {
await Bot.BotClient.SendTextMessageAsync(message.Chat, "❌ Sete konnte nicht erreicht werden.");
return;
}
List<HtmlFeedLink> 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 + $"* <a href=\"{feedLink.Url}\">{Utils.StripHtml(feedLink.Title)}</a>\n");
await Bot.BotClient.SendTextMessageAsync(message.Chat, text, ParseMode.Html, true);
}
}
}

82
Configuration.cs Normal file
View File

@ -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<int> 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);
}
}
}

12
NLog.config.example Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets>
<target name="logconsole" xsi:type="ColoredConsole" layout="${longdate} - ${logger} - ${level:uppercase=true} - ${message}" />
</targets>
<rules>
<logger name="*" minlevel="Info" writeTo="logconsole" />
</rules>
</nlog>

11
README.md Normal file
View File

@ -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

25
RSSBot.csproj Normal file
View File

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64;linux-arm</RuntimeIdentifiers>
<Version>1.0.0</Version>
<!-- <PublishReadyToRun>true</PublishReadyToRun>-->
<!-- <PublishSingleFile>true</PublishSingleFile>-->
<SelfContained>false</SelfContained>
<Title>RSSBot</Title>
<Authors>Brawl</Authors>
<Description>RSSBot for Telegram</Description>
<Copyright>2020 Brawl</Copyright>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CodeHollow.FeedReader" Version="1.2.1" />
<PackageReference Include="ini-parser" Version="2.5.2" />
<PackageReference Include="NLog" Version="4.6.8" />
<PackageReference Include="StackExchange.Redis" Version="2.0.601" />
<PackageReference Include="Telegram.Bot" Version="15.2.0" />
</ItemGroup>
</Project>

25
RSSBot.sln Normal file
View File

@ -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

46
RegexHandler.cs Normal file
View File

@ -0,0 +1,46 @@
using System;
using System.Text.RegularExpressions;
using Telegram.Bot.Types;
namespace RSSBot {
/// <summary>
/// RegexHandler for Telegram Bots.
/// </summary>
public class RegexHandler {
private string Pattern;
private Action<Message, GroupCollection> CallbackFunction;
/// <summary>
/// Constructor for the RegexHandler.
/// </summary>
/// <param name="pattern">Regex pattern</param>
/// <param name="callback">Callback function to call when the update should be processed</param>
public RegexHandler(string pattern, Action<Message, GroupCollection> callback) {
Pattern = pattern;
CallbackFunction = callback;
}
/// <summary>
/// Checks whether the update should be handled by this handler.
/// </summary>
/// <param name="message">Telegram Message object</param>
/// <returns>true if the update should be handled</returns>
public bool HandleUpdate(Message message) {
return Regex.IsMatch(message.Text,
Pattern,
RegexOptions.IgnoreCase);
}
/// <summary>
/// Calls the assoicated callback function.
/// </summary>
/// <param name="message">Telegram Message object</param>
public void ProcessUpdate(Message message) {
GroupCollection matches = Regex.Match(message.Text,
Pattern,
RegexOptions.IgnoreCase
).Groups;
CallbackFunction(message, matches);
}
}
}

84
RssBotFeed.cs Normal file
View File

@ -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<long> Subs = new HashSet<long>();
public string MainLink { get; private set; }
public string Title { get; private set; }
public List<FeedItem> NewEntries { get; private set; }
public RssBotFeed(string url, string lastEntry = null, HashSet<long> 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<FeedItem> GetNewEntries(IEnumerable<FeedItem> entries) {
List<FeedItem> newEntries = new List<FeedItem>();
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);
}
}
}

89
Utils.cs Normal file
View File

@ -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<bool> IsBotAdmin(long chatId) {
ChatMember chatMember = await Bot.BotClient.GetChatMemberAsync(chatId, Bot.BotClient.BotId);
return chatMember.Status.Equals(ChatMemberStatus.Administrator);
}
}
}

13
config.ini.example Normal file
View File

@ -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

7
publish.bat Normal file
View File

@ -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