Initial commit

This commit is contained in:
2024-09-14 19:38:04 +03:30
commit 481e8e9af8
9 changed files with 365 additions and 0 deletions

32
Services/DbInitializer.cs Normal file
View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using JsonFlatFileDataStore;
using mstdnCats.Models;
namespace mstdnCats.Services
{
public class DbInitializer
{
public static Task<IDocumentCollection<Post>> SetupDb(string _dbname)
{
// Setup DB
IDocumentCollection<Post>? collection = null;
try
{
// Initialize DB
var store = new DataStore($"{_dbname}.json", minifyJson: false);
collection = store.GetCollection<Post>();
}
catch
{
return Task.FromResult<IDocumentCollection<Post>>(null);
}
// Return collection
return Task.FromResult(collection);
}
}
}

View File

@@ -0,0 +1,97 @@
using JsonFlatFileDataStore;
using Microsoft.Extensions.Logging;
using mstdnCats.Models;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types.ReplyMarkups;
namespace mstdnCats.Services
{
public class HandlePostAction
{
public static async Task HandleCallbackQuery(CallbackQuery callbackQuery, IDocumentCollection<Post> _db, TelegramBotClient _bot, ILogger<MastodonBot>? logger)
{
// Extract media ID from callback query data
string[] parts = callbackQuery.Data.Split('-');
if (parts.Length != 2)
{
logger?.LogError("Invalid callback query data format.");
return;
}
string action = parts[0];
string mediaId = parts[1];
var post = _db.AsQueryable().FirstOrDefault(p => p.MediaAttachments.Any(m => m.MediaId == mediaId));
if (post == null)
{
logger?.LogInformation("No matching post found.");
return;
}
// Approve the media attachment
if (action == "approve")
{
var mediaAttachment = post.MediaAttachments.FirstOrDefault(m => m.MediaId == mediaId);
if (mediaAttachment != null)
{
mediaAttachment.Approved = true;
bool updated = await _db.UpdateOneAsync(p => p.mstdnPostId == post.mstdnPostId, post);
if (updated)
{
// Send the media attachment to the channel
await _bot.SendPhotoAsync(Environment.GetEnvironmentVariable("CHANNEL_NUMID"), post.MediaAttachments.First().Url, caption: $"Message from " + $"<a href=\"" + post.Account.Url + "\">" + post.Account.DisplayName + " </a>", parseMode: ParseMode.Html
, replyMarkup: new InlineKeyboardMarkup(InlineKeyboardButton.WithUrl("View on Mastodon", post.Url)));
await _bot.AnswerCallbackQueryAsync(callbackQuery.Id, "Media attachment approved and sent to channel.");
await _bot.DeleteMessageAsync(callbackQuery.Message.Chat.Id, callbackQuery.Message.MessageId);
logger?.LogTrace($"Media attachment {mediaId} approved.");
}
else
{
logger?.LogError($"Failed to update the media attachment {mediaId}. Record might not exist or was not found.");
}
}
else
{
logger?.LogError($"No media attachment found with MediaId {mediaId}.");
}
}
// Reject the media attachment
else if (action == "reject")
{
// Check if the post has only one attachment, if so, do not delete it, else delete the associated attachment
if (post.MediaAttachments.Count == 1 && post.MediaAttachments.First().MediaId == mediaId)
{
await _bot.AnswerCallbackQueryAsync(callbackQuery.Id, "Post has only one attachment. No deletion performed.");
await _bot.DeleteMessageAsync(callbackQuery.Message.Chat.Id, callbackQuery.Message.MessageId);
logger?.LogTrace($"Post {post.mstdnPostId} has only one attachment. No deletion performed.");
}
else
{
post.MediaAttachments.RemoveAll(m => m.MediaId == mediaId);
await _db.UpdateOneAsync(p => p.mstdnPostId == post.mstdnPostId, post);
await _bot.AnswerCallbackQueryAsync(callbackQuery.Id, "Media attachment rejected.");
await _bot.DeleteMessageAsync(callbackQuery.Message.Chat.Id, callbackQuery.Message.MessageId);
logger?.LogTrace($"Media attachment {mediaId} removed from post {post.mstdnPostId}.");
}
}
else
{
logger?.LogError("Invalid action specified.");
}
}
}
}

34
Services/PostResolver.cs Normal file
View File

@@ -0,0 +1,34 @@
using System.Text.Json;
using Microsoft.Extensions.Logging;
using mstdnCats.Models;
namespace mstdnCats.Services
{
public sealed class PostResolver
{
public static async Task<List<Post>?> GetPostsAsync(string tag, ILogger<MastodonBot>? logger, string instance = "https://haminoa.net")
{
// Get posts
HttpClient _httpClient = new HttpClient();
// Get posts from mastodon api (40 latest posts)
var response = await _httpClient.GetAsync($"{instance}/api/v1/timelines/tag/{tag}?limit=40");
// Print out ratelimit info
logger?.LogInformation("Remaining requests: " + response.Headers.GetValues("X-RateLimit-Remaining").First() + "time to reset: " + response.Headers.GetValues("X-RateLimit-Reset").First());
// Check if response is ok
if (
response.StatusCode == System.Net.HttpStatusCode.OK ||
response.Content.Headers.ContentType.MediaType.Contains("application/json") ||
response.Headers.TryGetValues("X-RateLimit-Remaining", out var remaining) && int.Parse(remaining.First()) != 0
)
{
// Deserialize response based on 'Post' model
return JsonSerializer.Deserialize<List<Post>>(await response.Content.ReadAsStringAsync());
}
else return null;
}
}
}

44
Services/ProcessPosts.cs Normal file
View File

@@ -0,0 +1,44 @@
using JsonFlatFileDataStore;
using Microsoft.Extensions.Logging;
using mstdnCats.Models;
using Telegram.Bot;
using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types.ReplyMarkups;
namespace mstdnCats.Services
{
public class ProcessPosts
{
public static async Task<List<MediaAttachment>> checkAndInsertPostsAsync(IDocumentCollection<Post> _db, TelegramBotClient _bot, List<Post> fetchedPosts, ILogger<MastodonBot>? logger)
{
// Get existing posts
var existingPosts = _db.AsQueryable().Select(x => x.mstdnPostId).ToArray();
logger?.LogInformation($"Recieved posts to proccess: {fetchedPosts.Count} - total existing posts: {existingPosts.Length}");
int newPosts = 0;
// Process posts
foreach (Post post in fetchedPosts)
{
// Check if post already exists
if (!existingPosts.Contains(post.mstdnPostId) && post.MediaAttachments.Count > 0)
{
// Send approve or reject message to admin
foreach (var media in post.MediaAttachments)
{
await _bot.SendPhotoAsync(Environment.GetEnvironmentVariable("ADMIN_NUMID"), media.PreviewUrl, caption: $"<a href=\"" + post.Url + "\"> Mastodon </a>", parseMode: ParseMode.Html
, replyMarkup: new InlineKeyboardMarkup().AddButton("Approve", $"approve-{media.MediaId}").AddButton("Reject", $"reject-{media.MediaId}"));
}
// Insert post
await _db.InsertOneAsync(post);
newPosts++;
}
}
logger?.LogInformation($"Proccesing done, stats: received {fetchedPosts.Count} posts, inserted {newPosts} new posts.");
// Return list of media attachments
var alldbpostsattachmentlist = _db.AsQueryable().SelectMany(x => x.MediaAttachments).ToList();
return alldbpostsattachmentlist;
}
}
}

34
Services/RunCheck.cs Normal file
View File

@@ -0,0 +1,34 @@
using JsonFlatFileDataStore;
using Microsoft.Extensions.Logging;
using mstdnCats.Models;
using Telegram.Bot;
namespace mstdnCats.Services
{
public class RunCheck
{
public static async Task<bool> runAsync(IDocumentCollection<Post> _db, TelegramBotClient _bot, string _tag, ILogger<MastodonBot>? logger, string _instance = "https://haminoa.net")
{
// Run check
try
{
// First get posts
var posts = await PostResolver.GetPostsAsync(_tag, logger, _instance);
if (posts == null)
{
logger?.LogCritical("Unable to get posts");
}
// Then process them
await ProcessPosts.checkAndInsertPostsAsync(_db, _bot, posts, logger);
}
catch (Exception ex)
{
logger?.LogCritical("Error while running check: " + ex.Message);
}
return true;
}
}
}