Compare commits

...

29 Commits

Author SHA1 Message Date
77f6928c9e Merge remote-tracking branch 'upstream/master' 2020-12-21 16:21:49 +01:00
594b8be769
wrong .NET version 2020-12-20 23:46:21 +01:00
4dcd67f71b
Escape title 2020-12-20 23:43:17 +01:00
6729c09010 NLog downgrade 2020-11-05 21:31:50 +01:00
e97bea90e9
Downgrade NLog for now 2020-11-05 19:53:39 +01:00
e39f0969b0
Reformat 2020-11-05 19:52:44 +01:00
ca825ca7cb Merge remote-tracking branch 'upstream/master' 2020-11-05 19:34:46 +01:00
e8b2c0dc60
Fix #4 Crash when hitting rate-limit 2020-11-05 19:23:42 +01:00
5437cb2ab2 Disable some Logger Info 2020-05-03 19:07:36 +02:00
2bc253ed4f Merge remote-tracking branch 'upstream/master' 2020-05-03 19:01:09 +02:00
29a59bc33c More CleanRSS 2020-05-03 18:58:55 +02:00
403dd11fa9
Add more replacements 2020-05-03 18:52:56 +02:00
c728f62946 Merge upstream 2020-04-01 14:52:17 +02:00
Andreas Bielawski
a44d4b27a3
Fix #3 Subscribe to channels by their ID 2020-03-31 12:52:45 +02:00
Andreas Bielawski
262d77d1e1
Replace "var" with their types 2020-03-31 11:20:12 +02:00
4994417c3d Merge remote-tracking branch 'upstream/master' 2020-03-13 22:45:30 +01:00
Andreas Bielawski
24ee7f9052
More replacements 2020-03-13 22:43:40 +01:00
Andreas Bielawski
605475bc61
Fix #1 Trim Feedproxy 2020-03-13 22:40:42 +01:00
a77fb812eb RikoBot changes 2020-03-13 14:29:07 +01:00
Andreas Bielawski
bf3e8a65e1
Repair "www" removal 2020-03-13 13:57:10 +01:00
Andreas Bielawski
30cdf087cc
Use StringBuilder 2020-03-07 01:11:58 +01:00
Andreas
af1ae0883c
Update ci.yml 2020-02-17 20:19:58 +01:00
Andreas
85dc187d51
Update ci.yml 2020-02-17 20:15:26 +01:00
Andreas
b8ffe648ca
Update ci.yml 2020-02-17 20:10:42 +01:00
Andreas
54183c534a
Update ci.yml 2020-02-17 20:07:01 +01:00
Andreas
fb36bf8dff
Update ci.yml 2020-02-17 20:04:48 +01:00
Andreas
24bfffa7fa
Fix GitHub Action 2020-02-17 19:45:26 +01:00
Andreas
1ccac01c0a
Add GitHub Actions 2020-02-17 19:42:21 +01:00
Andreas Bielawski
3df3bfb261
Made instructions a bit clearer 2020-01-30 21:02:12 +01:00
9 changed files with 280 additions and 130 deletions

51
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,51 @@
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 3.1.101
- name: Build linux-x64
run: dotnet publish -c debug -r linux-x64 /p:PublishSingleFile=true
- name: Build linux-arm
run: dotnet publish -c debug -r linux-arm /p:PublishSingleFile=true
- name: Build win-x64
run: dotnet publish -c debug -r win-x64 /p:PublishSingleFile=true
- name: Build osx-x64
run: dotnet publish -c debug -r osx-x64 /p:PublishSingleFile=true
- name: Upload linux-x64
uses: actions/upload-artifact@v1.0.0
with:
# Artifact name
name: RSSBot-linux-x64-${{github.sha}}
# Directory containing files to upload
path: bin/Debug/netcoreapp3.1/linux-x64/publish/
- name: Upload linux-arm
uses: actions/upload-artifact@v1.0.0
with:
# Artifact name
name: RSSBot-linux-arm-${{github.sha}}
# Directory containing files to upload
path: bin/Debug/netcoreapp3.1/linux-arm/publish/
- name: Upload win-x64
uses: actions/upload-artifact@v1.0.0
with:
# Artifact name
name: RSSBot-win-x64-${{github.sha}}
# Directory containing files to upload
path: bin/Debug/netcoreapp3.1/win-x64/publish/
- name: Upload osx-x64
uses: actions/upload-artifact@v1.0.0
with:
# Artifact name
name: RSSBot-osx-x64-${{github.sha}}
# Directory containing files to upload
path: bin/Debug/netcoreapp3.1/osx-x64/publish/

43
Bot.cs
View File

@ -19,7 +19,8 @@ namespace RSSBot {
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private static HashSet<RegexHandler> Handlers; private static HashSet<RegexHandler> Handlers;
private static void Main(string[] args) { private static void Main(string[] args)
{
Configuration.Parse(); Configuration.Parse();
BotClient = new TelegramBotClient(Configuration.BotToken); BotClient = new TelegramBotClient(Configuration.BotToken);
try { try {
@ -40,7 +41,8 @@ namespace RSSBot {
new RegexHandler($"^/start(?:@{BotInfo.Username})?$", Commands.Welcome), new RegexHandler($"^/start(?:@{BotInfo.Username})?$", Commands.Welcome),
new RegexHandler($"^/help(?:@{BotInfo.Username})?$", Commands.Help), new RegexHandler($"^/help(?:@{BotInfo.Username})?$", Commands.Help),
new RegexHandler($"^/rss(?:@{BotInfo.Username})?$", Commands.Show), new RegexHandler($"^/rss(?:@{BotInfo.Username})?$", Commands.Show),
new RegexHandler($"^/rss(?:@{BotInfo.Username})? (@?[A-z0-9_]+)$", Commands.Show), new RegexHandler($"^/rss(?:@{BotInfo.Username})? (@[A-z0-9_]+)$", Commands.Show),
new RegexHandler($@"^/rss(?:@{BotInfo.Username})? (-\d+)$", Commands.Show),
new RegexHandler( new RegexHandler(
$"^/show(?:@{BotInfo.Username})? (http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&~+]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)$", $"^/show(?:@{BotInfo.Username})? (http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&~+]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)$",
Commands.ShowAvailableFeeds), Commands.ShowAvailableFeeds),
@ -48,14 +50,20 @@ namespace RSSBot {
$"^/sub(?:@{BotInfo.Username})? (http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&~+]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)$", $"^/sub(?:@{BotInfo.Username})? (http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&~+]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)$",
Commands.Subscribe), Commands.Subscribe),
new RegexHandler( new RegexHandler(
$"^/sub(?:@{BotInfo.Username})? (http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&~+]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+) (@?[A-z0-9_]+)$$", $"^/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(
$@"^/sub(?:@{BotInfo.Username})? (http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&~+]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+) (-\d+)$",
Commands.Subscribe), Commands.Subscribe),
new RegexHandler( new RegexHandler(
$"^/del(?:@{BotInfo.Username})? (http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&~+]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)$", $"^/del(?:@{BotInfo.Username})? (http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&~+]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)$",
Commands.Unsubscribe), Commands.Unsubscribe),
new RegexHandler( new RegexHandler(
$"^/del(?:@{BotInfo.Username})? (http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&~+]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+) (@?[A-z0-9_]+)$$", $"^/del(?:@{BotInfo.Username})? (http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&~+]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+) (@[A-z0-9_]+)$",
Commands.Unsubscribe), Commands.Unsubscribe),
new RegexHandler(
$@"^/del(?:@{BotInfo.Username})? (http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&~+]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+) (-\d+)$",
Commands.Unsubscribe)
}; };
JobQueue = new Timer(e => { Commands.Sync(); }, null, TimeSpan.FromSeconds(5), JobQueue = new Timer(e => { Commands.Sync(); }, null, TimeSpan.FromSeconds(5),
@ -70,7 +78,8 @@ namespace RSSBot {
Save(); Save();
} }
private static void ReadAllFeeds() { private static void ReadAllFeeds()
{
RedisValue[] allFeedUrls = Configuration.Database.SetMembers($"{Configuration.RedisHash}:feeds"); RedisValue[] allFeedUrls = Configuration.Database.SetMembers($"{Configuration.RedisHash}:feeds");
foreach (RedisValue feedUrl in allFeedUrls) { foreach (RedisValue feedUrl in allFeedUrls) {
HashSet<long> subs = new HashSet<long>(); HashSet<long> subs = new HashSet<long>();
@ -86,13 +95,18 @@ namespace RSSBot {
} }
} }
private static void OnProcessExit(object? sender, EventArgs e) { private static void OnProcessExit(object? sender, EventArgs e)
{
Save(); Save();
} }
private static void Bot_OnMessage(object? sender, MessageEventArgs messageEventArgs) { private static void Bot_OnMessage(object? sender, MessageEventArgs messageEventArgs)
var message = messageEventArgs.Message; {
if (message == null || message.Type != MessageType.Text) return; Message message = messageEventArgs.Message;
if (message == null || message.Type != MessageType.Text) {
return;
}
if (!Configuration.Admins.Contains(message.From.Id)) { if (!Configuration.Admins.Contains(message.From.Id)) {
return; return;
} }
@ -102,14 +116,17 @@ namespace RSSBot {
} }
} }
public static async void Save() { public static async void Save()
{
if (RssBotFeeds.Count > 0) { if (RssBotFeeds.Count > 0) {
Logger.Info("Speichere Daten..."); /* Logger.Info("Speichere Daten..."); */
} }
foreach (RssBotFeed feed in RssBotFeeds) { foreach (RssBotFeed feed in RssBotFeeds) {
string feedKey = $"{Configuration.RedisHash}:{feed.Url}"; string feedKey = $"{Configuration.RedisHash}:{feed.Url}";
if (string.IsNullOrWhiteSpace(feed.LastEntry)) continue; if (string.IsNullOrWhiteSpace(feed.LastEntry)) {
continue;
}
await Configuration.Database.HashSetAsync(feedKey, "last_entry", feed.LastEntry); await Configuration.Database.HashSetAsync(feedKey, "last_entry", feed.LastEntry);
foreach (long chatId in feed.Subs) { foreach (long chatId in feed.Subs) {
@ -119,7 +136,7 @@ namespace RSSBot {
await Configuration.Database.SetAddAsync($"{Configuration.RedisHash}:feeds", feed.Url); await Configuration.Database.SetAddAsync($"{Configuration.RedisHash}:feeds", feed.Url);
} }
Logger.Info("Gespeichert!"); /* Logger.Info("Gespeichert!"); */
} }
} }
} }

View File

@ -1,7 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Web; using System.Web;
using CodeHollow.FeedReader; using CodeHollow.FeedReader;
using NLog; using NLog;
@ -13,15 +17,17 @@ namespace RSSBot {
public static class Commands { public static class Commands {
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public static async void Welcome(Message message, GroupCollection matches) { public static async void Welcome(Message message, GroupCollection matches)
{
await Bot.BotClient.SendTextMessageAsync( await Bot.BotClient.SendTextMessageAsync(
message.Chat, message.Chat,
"<b>Willkommen beim RSS-Bot!</b>\nSende /help, um zu starten.", "<b>Willkommen bei RikoBot!</b>\nSende /help, um zu starten.",
ParseMode.Html ParseMode.Html
); );
} }
public static async void Help(Message message, GroupCollection matches) { public static async void Help(Message message, GroupCollection matches)
{
await Bot.BotClient.SendTextMessageAsync( await Bot.BotClient.SendTextMessageAsync(
message.Chat, message.Chat,
"<b>/rss</b> <i>[Chat]</i>: Abonnierte Feeds anzeigen\n" + "<b>/rss</b> <i>[Chat]</i>: Abonnierte Feeds anzeigen\n" +
@ -33,7 +39,8 @@ namespace RSSBot {
); );
} }
public static async void Subscribe(Message message, GroupCollection args) { public static async void Subscribe(Message message, GroupCollection args)
{
string url = args[1].Value; string url = args[1].Value;
long chatId = message.Chat.Id; long chatId = message.Chat.Id;
RssBotFeed feed = new RssBotFeed(url); RssBotFeed feed = new RssBotFeed(url);
@ -41,16 +48,24 @@ namespace RSSBot {
await Bot.BotClient.SendChatActionAsync(message.Chat, ChatAction.Typing); await Bot.BotClient.SendChatActionAsync(message.Chat, ChatAction.Typing);
if (args.Count > 2) { if (args.Count > 2) {
string chatName = args[2].Value;
if (!chatName.StartsWith("@")) {
chatName = $"@{chatName}";
}
Chat chatInfo; Chat chatInfo;
try { string chatName = args[2].Value;
chatInfo = await Bot.BotClient.GetChatAsync(chatName); bool isId = long.TryParse(chatName, out chatId);
} catch {
await Bot.BotClient.SendTextMessageAsync(message.Chat, "❌ Dieser Kanal existiert nicht."); if (isId) {
return; try {
chatInfo = await Bot.BotClient.GetChatAsync(chatId);
} catch {
await Bot.BotClient.SendTextMessageAsync(message.Chat, "❌ Dieser Kanal existiert nicht.");
return;
}
} else {
try {
chatInfo = await Bot.BotClient.GetChatAsync(chatName);
} catch {
await Bot.BotClient.SendTextMessageAsync(message.Chat, "❌ Dieser Kanal existiert nicht.");
return;
}
} }
chatId = chatInfo.Id; chatId = chatInfo.Id;
@ -91,23 +106,32 @@ namespace RSSBot {
} }
} }
public static async void Unsubscribe(Message message, GroupCollection args) { public static async void Unsubscribe(Message message, GroupCollection args)
{
string url = args[1].Value; string url = args[1].Value;
long chatId = message.Chat.Id; long chatId = message.Chat.Id;
RssBotFeed feed = Bot.RssBotFeeds RssBotFeed feed = Bot.RssBotFeeds
.FirstOrDefault(x => x.Url.ToLower().Equals(url.ToLower())); .FirstOrDefault(x => x.Url.ToLower().Equals(url.ToLower()));
if (args.Count > 2) { if (args.Count > 2) {
string chatName = args[2].Value;
if (!chatName.StartsWith("@")) {
chatName = $"@{chatName}";
}
Chat chatInfo; Chat chatInfo;
try { string chatName = args[2].Value;
chatInfo = await Bot.BotClient.GetChatAsync(chatName); bool isId = long.TryParse(chatName, out chatId);
} catch {
await Bot.BotClient.SendTextMessageAsync(message.Chat, "❌ Dieser Kanal existiert nicht."); if (isId) {
return; try {
chatInfo = await Bot.BotClient.GetChatAsync(chatId);
} catch {
await Bot.BotClient.SendTextMessageAsync(message.Chat, "❌ Dieser Kanal existiert nicht.");
return;
}
} else {
try {
chatInfo = await Bot.BotClient.GetChatAsync(chatName);
} catch {
await Bot.BotClient.SendTextMessageAsync(message.Chat, "❌ Dieser Kanal existiert nicht.");
return;
}
} }
chatId = chatInfo.Id; chatId = chatInfo.Id;
@ -133,23 +157,31 @@ namespace RSSBot {
Bot.Save(); Bot.Save();
} }
public static async void Show(Message message, GroupCollection args) { public static async void Show(Message message, GroupCollection args)
{
long chatId = message.Chat.Id; long chatId = message.Chat.Id;
string chatTitle = message.Chat.Type.Equals(ChatType.Private) ? message.Chat.FirstName : message.Chat.Title; string chatTitle = message.Chat.Type.Equals(ChatType.Private) ? message.Chat.FirstName : message.Chat.Title;
await Bot.BotClient.SendChatActionAsync(message.Chat, ChatAction.Typing); 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 { if (args.Count > 1) {
chatInfo = await Bot.BotClient.GetChatAsync(chatName); Chat chatInfo;
} catch { string chatName = args[1].Value;
await Bot.BotClient.SendTextMessageAsync(message.Chat, "❌ Dieser Kanal existiert nicht."); bool isId = long.TryParse(chatName, out chatId);
return;
if (isId) {
try {
chatInfo = await Bot.BotClient.GetChatAsync(chatId);
} catch {
await Bot.BotClient.SendTextMessageAsync(message.Chat, "❌ Dieser Kanal existiert nicht.");
return;
}
} else {
try {
chatInfo = await Bot.BotClient.GetChatAsync(chatName);
} catch {
await Bot.BotClient.SendTextMessageAsync(message.Chat, "❌ Dieser Kanal existiert nicht.");
return;
}
} }
chatId = chatInfo.Id; chatId = chatInfo.Id;
@ -161,24 +193,25 @@ namespace RSSBot {
return; return;
} }
} }
string text;
List<RssBotFeed> feeds = Bot.RssBotFeeds.Where(x => x.Subs.Contains(chatId)).ToList(); List<RssBotFeed> feeds = Bot.RssBotFeeds.Where(x => x.Subs.Contains(chatId)).ToList();
StringBuilder text = new StringBuilder();
if (feeds.Count < 1) { if (feeds.Count < 1) {
text = "❌ Keine Feeds abonniert."; text.Append("❌ Keine Feeds abonniert.");
} else { } else {
text = $"<strong>{HttpUtility.HtmlEncode(chatTitle)}</strong> hat abonniert:\n"; text.Append($"<strong>{HttpUtility.HtmlEncode(chatTitle)}</strong> hat abonniert:\n");
for (int i = 0; i < feeds.Count; i++) { for (int i = 0; i < feeds.Count; i++) {
text += $"<strong>{i + 1}</strong>) {feeds[i].Url}\n"; text.Append($"<strong>{i + 1}</strong>) {feeds[i].Url}\n");
} }
} }
await Bot.BotClient.SendTextMessageAsync(message.Chat, text, ParseMode.Html, true);
await Bot.BotClient.SendTextMessageAsync(message.Chat, text.ToString(), ParseMode.Html, true);
} }
public static async void Sync() { public static async void Sync()
Logger.Info(("================================")); {
Logger.Info("================================");
bool hadEntries = false; bool hadEntries = false;
foreach (RssBotFeed feed in Bot.RssBotFeeds.ToList()) { foreach (RssBotFeed feed in Bot.RssBotFeeds.ToList()) {
Logger.Info(feed.Url); Logger.Info(feed.Url);
@ -190,7 +223,7 @@ namespace RSSBot {
} }
if (feed.NewEntries.Count == 0) { if (feed.NewEntries.Count == 0) {
Logger.Info("Keine neuen Beiträge."); /* Logger.Info("Keine neuen Beiträge."); */
continue; continue;
} }
@ -203,6 +236,7 @@ namespace RSSBot {
string postTitle = "Kein Titel"; string postTitle = "Kein Titel";
if (!string.IsNullOrWhiteSpace(entry.Title)) { if (!string.IsNullOrWhiteSpace(entry.Title)) {
postTitle = Utils.StripHtml(entry.Title); postTitle = Utils.StripHtml(entry.Title);
postTitle = Utils.EscapeHtml(postTitle);
} }
string postLink = feed.MainLink; string postLink = feed.MainLink;
@ -211,46 +245,34 @@ namespace RSSBot {
postLink = entry.Link; postLink = entry.Link;
// FeedProxy URLs // FeedProxy URLs
GroupCollection feedProxy = GroupCollection feedProxy =
Utils.ReturnMatches(postLink, "^https?://feedproxy.google.com/~r/(.+)/.*"); Utils.ReturnMatches(postLink, "^https?://feedproxy.google.com/~r/(.+?)/.*");
linkName = feedProxy.Count > 1 ? feedProxy[1].Value : new Uri(postLink).Host; linkName = feedProxy.Count > 1 ? feedProxy[1].Value : new Uri(postLink).Host;
} }
// Remove "www." // Remove "www."
int index = linkName.IndexOf("www.", StringComparison.Ordinal); int index = linkName.IndexOf("www.", StringComparison.Ordinal);
if (index > 0) { if (index > -1) {
linkName = linkName.Remove(index, 4); linkName = linkName.Remove(index, 4);
} }
string content = ""; string content = "";
if (!string.IsNullOrWhiteSpace(entry.Content)) { if (!string.IsNullOrWhiteSpace(entry.Content)) {
content = Utils.ProcessContent(entry.Content); // magic processing missing content = Utils.ProcessContent(entry.Content);
} else if (!string.IsNullOrWhiteSpace(entry.Description)) { } else if (!string.IsNullOrWhiteSpace(entry.Description)) {
content = Utils.ProcessContent(entry.Description); content = Utils.ProcessContent(entry.Description);
} }
string text = $"<b>{postTitle}</b>\n<i>{feed.Title}</i>\n{content}"; string text = $"<b>[#RSS] {postTitle}</b>\n{content}";
text += $"\n<a href=\"{postLink}\">Weiterlesen auf {linkName}</a>"; text += $"\n<a href=\"{postLink}\">Auf {linkName} ansehen.</a>";
// Send // Send
foreach (long chatId in feed.Subs.ToList()) { foreach (long chatId in feed.Subs.ToList()) {
try { await SendFinishedMessage(chatId, text, feed);
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"); /* Logger.Info("Nächster Check in 60 Sekunden"); */
if (hadEntries) { if (hadEntries) {
Bot.Save(); Bot.Save();
@ -259,7 +281,32 @@ namespace RSSBot {
Bot.JobQueue.Change(TimeSpan.FromMinutes(1), TimeSpan.FromMilliseconds(-1)); Bot.JobQueue.Change(TimeSpan.FromMinutes(1), TimeSpan.FromMilliseconds(-1));
} }
public static async void ShowAvailableFeeds(Message message, GroupCollection args) { private static async Task SendFinishedMessage(long chatId, string text, RssBotFeed feed, int waitTime = 10)
{
try {
await Bot.BotClient.SendTextMessageAsync(chatId, text, ParseMode.Html, true, true);
Thread.Sleep(1000);
} 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}");
}
} catch (HttpRequestException) // likely 429
{
Logger.Warn($"Rate-Limit erreicht, warte {waitTime} Sekunden...");
Thread.Sleep(waitTime * 1000);
await SendFinishedMessage(chatId, text, feed, waitTime * 2);
}
}
public static async void ShowAvailableFeeds(Message message, GroupCollection args)
{
string url = args[1].Value; string url = args[1].Value;
IEnumerable<HtmlFeedLink> feeds; IEnumerable<HtmlFeedLink> feeds;
try { try {
@ -276,7 +323,8 @@ namespace RSSBot {
} }
string text = htmlFeedLinks.Aggregate("Feeds gefunden:\n", string text = htmlFeedLinks.Aggregate("Feeds gefunden:\n",
(current, feedLink) => current + $"* <a href=\"{feedLink.Url}\">{Utils.StripHtml(feedLink.Title)}</a>\n"); (current, feedLink) =>
current + $"* <a href=\"{feedLink.Url}\">{Utils.StripHtml(feedLink.Title)}</a>\n");
await Bot.BotClient.SendTextMessageAsync(message.Chat, text, ParseMode.Html, true); await Bot.BotClient.SendTextMessageAsync(message.Chat, text, ParseMode.Html, true);
} }

View File

@ -18,7 +18,8 @@ namespace RSSBot {
public static List<int> Admins; public static List<int> Admins;
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public static void Parse() { public static void Parse()
{
if (!File.Exists("NLog.config")) { if (!File.Exists("NLog.config")) {
Console.WriteLine("NLog.config nicht gefunden, setze auf INFO..."); Console.WriteLine("NLog.config nicht gefunden, setze auf INFO...");
LoggingConfiguration config = new LoggingConfiguration(); LoggingConfiguration config = new LoggingConfiguration();

View File

@ -1,11 +1,12 @@
RSS Bot for Telegram RSS Bot for Telegram
===================== =====================
1. **Install .NET Core Runtime >= 3.1 and Redis** 1. **Install .NET Core Runtime >= 3.1 and Redis**
2. **Copy config:** `cp config.ini.example config.ini` 2. Download the bot from the [Releases](https://github.com/Brawl345/RSSBot-Sharp/releases) section
3. **Insert bot token** in `config.ini` 3. **Copy config:** `cp config.ini.example config.ini`
4. **Insert bot token** in `config.ini`
1. Adjust Redis settings if needed 1. Adjust Redis settings if needed
4. **Insert your Telegram ID as admin** (send `@Brawlbot id` inside a chat to get yours) 5. **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. Optional: Copy the `NLog.config.example` next to the config.ini as `NLog.config`
6. Run the `RSSBot` executable 7. Run the `RSSBot` executable
(c) 2020 Andreas Bielawski (c) 2020 Andreas Bielawski

View File

@ -4,7 +4,7 @@
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64;linux-arm</RuntimeIdentifiers> <RuntimeIdentifiers>win-x64;osx-x64;linux-x64;linux-arm</RuntimeIdentifiers>
<Version>1.0.0</Version> <Version>1.1.0</Version>
<!-- <PublishReadyToRun>true</PublishReadyToRun>--> <!-- <PublishReadyToRun>true</PublishReadyToRun>-->
<!-- <PublishSingleFile>true</PublishSingleFile>--> <!-- <PublishSingleFile>true</PublishSingleFile>-->
<SelfContained>false</SelfContained> <SelfContained>false</SelfContained>
@ -18,8 +18,8 @@
<PackageReference Include="CodeHollow.FeedReader" Version="1.2.1" /> <PackageReference Include="CodeHollow.FeedReader" Version="1.2.1" />
<PackageReference Include="ini-parser" Version="2.5.2" /> <PackageReference Include="ini-parser" Version="2.5.2" />
<PackageReference Include="NLog" Version="4.6.8" /> <PackageReference Include="NLog" Version="4.6.8" />
<PackageReference Include="StackExchange.Redis" Version="2.0.601" /> <PackageReference Include="StackExchange.Redis" Version="2.1.58" />
<PackageReference Include="Telegram.Bot" Version="15.2.0" /> <PackageReference Include="Telegram.Bot" Version="15.7.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

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

View File

@ -6,14 +6,12 @@ using CodeHollow.FeedReader;
namespace RSSBot { namespace RSSBot {
public class RssBotFeed { public class RssBotFeed {
public readonly HashSet<long> Subs = new HashSet<long>();
public readonly string Url; public readonly string Url;
public string LastEntry; public string LastEntry;
public readonly HashSet<long> Subs = new HashSet<long>();
public string MainLink { get; private set; } public RssBotFeed(string url, string lastEntry = null, HashSet<long> subs = null)
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; Url = url;
if (!string.IsNullOrWhiteSpace(lastEntry)) { if (!string.IsNullOrWhiteSpace(lastEntry)) {
LastEntry = lastEntry; LastEntry = lastEntry;
@ -24,7 +22,12 @@ namespace RSSBot {
} }
} }
public async Task Check() { public string MainLink { get; private set; }
public string Title { get; private set; }
public List<FeedItem> NewEntries { get; private set; }
public async Task Check()
{
Feed feed = await FeedReader.ReadAsync(Url); Feed feed = await FeedReader.ReadAsync(Url);
if (string.IsNullOrWhiteSpace(feed.Link)) { if (string.IsNullOrWhiteSpace(feed.Link)) {
throw new Exception("Kein gültiger RSS-Feed."); throw new Exception("Kein gültiger RSS-Feed.");
@ -33,7 +36,10 @@ namespace RSSBot {
MainLink = feed.Link; MainLink = feed.Link;
Title = feed.Title; Title = feed.Title;
if (feed.Items == null || feed.Items.Count <= 0) return; if (feed.Items == null || feed.Items.Count <= 0) {
return;
}
NewEntries = string.IsNullOrWhiteSpace(LastEntry) NewEntries = string.IsNullOrWhiteSpace(LastEntry)
? feed.Items.ToList() ? feed.Items.ToList()
: GetNewEntries(feed.Items); : GetNewEntries(feed.Items);
@ -43,7 +49,8 @@ namespace RSSBot {
: feed.Items.First().Id; : feed.Items.First().Id;
} }
private List<FeedItem> GetNewEntries(IEnumerable<FeedItem> entries) { private List<FeedItem> GetNewEntries(IEnumerable<FeedItem> entries)
{
List<FeedItem> newEntries = new List<FeedItem>(); List<FeedItem> newEntries = new List<FeedItem>();
foreach (FeedItem entry in entries) { foreach (FeedItem entry in entries) {
if (!string.IsNullOrWhiteSpace(entry.Id)) { if (!string.IsNullOrWhiteSpace(entry.Id)) {
@ -65,17 +72,22 @@ namespace RSSBot {
return newEntries; return newEntries;
} }
public override string ToString() { public override string ToString()
{
return $"RSS-Feed: '{Url}'"; return $"RSS-Feed: '{Url}'";
} }
public void Cleanup(long chatId) { public void Cleanup(long chatId)
{
Subs.Remove(chatId); Subs.Remove(chatId);
string feedKey = $"{Configuration.RedisHash}:{Url}"; string feedKey = $"{Configuration.RedisHash}:{Url}";
Configuration.Database.SetRemove($"{feedKey}:subs", chatId); Configuration.Database.SetRemove($"{feedKey}:subs", chatId);
// No subscribers, delete all references // No subscribers, delete all references
if (Subs.Count != 0 || !Configuration.Database.KeyExists(feedKey)) return; if (Subs.Count != 0 || !Configuration.Database.KeyExists(feedKey)) {
return;
}
Configuration.Database.KeyDelete(feedKey); Configuration.Database.KeyDelete(feedKey);
Configuration.Database.KeyDelete($"{feedKey}:subs"); Configuration.Database.KeyDelete($"{feedKey}:subs");
Configuration.Database.SetRemove($"{Configuration.RedisHash}:feeds", Url); Configuration.Database.SetRemove($"{Configuration.RedisHash}:feeds", Url);

View File

@ -1,19 +1,32 @@
using System; using System.Linq;
using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.Enums;
namespace RSSBot { namespace RSSBot {
public static class Utils { public static class Utils {
public static string StripHtml(string input) { private static readonly Regex RegexHtml = new Regex("<.*?>");
return Regex.Replace(input, "<.*?>", String.Empty).Trim();
public static string StripHtml(string input)
{
return RegexHtml.Replace(input, string.Empty).Trim();
} }
private static string CleanRss(string input) { public static string EscapeHtml(string input)
{
input = input.Replace("<", "&lt;");
input = input.Replace(">", "&gt;");
return input;
}
private static string CleanRss(string input)
{
string[] replacements = { string[] replacements = {
"[←]",
"[…]", "[…]",
"[...]",
"[bilder]", "[bilder]",
"[boerse]", "[boerse]",
"[mehr]", "[mehr]",
@ -36,20 +49,28 @@ namespace RSSBot {
"Read more »", "Read more »",
"Read more", "Read more",
"...Read More", "...Read More",
"...mehr lesen",
"mehr lesen",
"(more…)", "(more…)",
"View On WordPress", "View On WordPress",
"Continue reading →", "Continue reading →",
"» weiterlesen",
"Ein Kommentar.",
"Änderungen zeigen",
"(Feed generated with FetchRSS)",
"(RSS generated with FetchRss)", "(RSS generated with FetchRss)",
"-- Delivered by Feed43 service", "-- Delivered by Feed43 service",
"Meldung bei www.tagesschau.de lesen" "Meldung bei www.tagesschau.de lesen"
}; };
string[] regexReplacements = { string[] regexReplacements = {
"Der Beitrag.*erschien zuerst auf .+.", "Der Beitrag.*erschien zuerst auf .+.",
"The post.*appeared first on .+.", "The post.*appeared first on .+.",
"http://www.serienjunkies.de/.*.html" "http://www.serienjunkies.de/.*.html",
"Nächstältere Version.*",
"Die Seite wurde neu angelegt.*",
"Weiterleitung nach.*erstellt.*"
}; };
input = input.Replace("\n", " ");
input = replacements.Aggregate(input, (current, replacement) => current.Replace(replacement, "")); input = replacements.Aggregate(input, (current, replacement) => current.Replace(replacement, ""));
input = regexReplacements.Aggregate(input, input = regexReplacements.Aggregate(input,
(current, replacement) => Regex.Replace(current, replacement, "")); (current, replacement) => Regex.Replace(current, replacement, ""));
@ -57,8 +78,9 @@ namespace RSSBot {
return input; return input;
} }
public static string ProcessContent(string input) { public static string ProcessContent(string input)
string content = StripHtml(input); {
string content = StripHtml(HttpUtility.HtmlDecode(input));
content = CleanRss(content); content = CleanRss(content);
if (content.Length > 250) { if (content.Length > 250) {
content = content.Substring(0, 250) + "..."; content = content.Substring(0, 250) + "...";
@ -67,23 +89,18 @@ namespace RSSBot {
return content; return content;
} }
public static bool IsCommand(string messageText, string command) { public static GroupCollection ReturnMatches(string text, string pattern)
return Regex.Match(messageText, {
$"^/{command}(?:@{Bot.BotInfo.Username})?$",
RegexOptions.IgnoreCase
).Success;
}
public static GroupCollection ReturnMatches(string text, string pattern) {
return Regex.Match(text, return Regex.Match(text,
pattern, pattern,
RegexOptions.IgnoreCase RegexOptions.IgnoreCase
).Groups; ).Groups;
} }
public static async Task<bool> IsBotAdmin(long chatId) { public static async Task<bool> IsBotAdmin(long chatId)
{
ChatMember chatMember = await Bot.BotClient.GetChatMemberAsync(chatId, Bot.BotClient.BotId); ChatMember chatMember = await Bot.BotClient.GetChatMemberAsync(chatId, Bot.BotClient.BotId);
return chatMember.Status.Equals(ChatMemberStatus.Administrator); return chatMember.Status.Equals(ChatMemberStatus.Administrator);
} }
} }
} }