New feature: Web api and a custom webpage to show a random cat.
This commit is contained in:
31
Program.cs
31
Program.cs
@@ -1,5 +1,7 @@
|
||||
using CatsOfMastodonBot.Models;
|
||||
using CatsOfMastodonBot.Services;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using mstdnCats.Services;
|
||||
using Telegram.Bot;
|
||||
@@ -30,6 +32,21 @@ public class MastodonBot
|
||||
var db = await DbInitializer.SetupDb(config.MONGODB_CONNECTION_STRING, config.DB_NAME);
|
||||
logger.LogInformation("DB setup done");
|
||||
|
||||
// Web server setup
|
||||
var host = Host.CreateDefaultBuilder()
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.UseKestrel(options =>
|
||||
{
|
||||
options.ListenAnyIP(5005); // Listen on port 5005
|
||||
});
|
||||
ServerStartup.Serverstartup(db);
|
||||
webBuilder.UseStartup<ServerStartup>();
|
||||
})
|
||||
.Build();
|
||||
|
||||
await host.RunAsync();
|
||||
|
||||
// Setup bot
|
||||
var bot = new TelegramBotClient(config.BOT_TOKEN);
|
||||
|
||||
@@ -41,13 +58,14 @@ public class MastodonBot
|
||||
logger.LogInformation("Setup complete");
|
||||
logger.LogInformation($"Bot is running as {me.FirstName} - instance: {config.INSTANCE}");
|
||||
|
||||
// Handle bot updates
|
||||
// Handle bot updates - For glass buttons functionality
|
||||
async Task OnUpdate(Update update)
|
||||
{
|
||||
switch (update)
|
||||
{
|
||||
case { CallbackQuery: { } callbackQuery }:
|
||||
{
|
||||
// Send a new cat picture
|
||||
if (callbackQuery.Data == "new_random")
|
||||
{
|
||||
await HandleStartMessage.HandleStartMessageAsync(callbackQuery.Message, bot, db, logger,
|
||||
@@ -55,11 +73,14 @@ public class MastodonBot
|
||||
break;
|
||||
}
|
||||
|
||||
else
|
||||
// Approve or reject a post
|
||||
else if (callbackQuery.Data.Contains("approve-") || callbackQuery.Data.Contains("reject-"))
|
||||
{
|
||||
await HandlePostAction.HandleCallbackQuery(callbackQuery, db, bot, logger);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: logger.LogInformation($"Received unhandled update {update.Type}"); break;
|
||||
}
|
||||
@@ -72,14 +93,16 @@ public class MastodonBot
|
||||
{
|
||||
if (message.Text == "/start" && message.Chat.Type == ChatType.Private)
|
||||
await HandleStartMessage.HandleStartMessageAsync(message, bot, db, logger);
|
||||
else if (message.Text == "/backup")
|
||||
|
||||
else if (message.Text == "/backup" && message.Chat.Type == ChatType.Private)
|
||||
await HandleDbBackup.HandleDbBackupAsync(bot, logger, config.DB_NAME, config.ADMIN_NUMID, db);
|
||||
|
||||
// Send a message to prompt user to send /start and recieve their cat photo only if its from a telegram user and not a channel
|
||||
else if (message.Chat.Type == ChatType.Private)
|
||||
await HandleStartMessage.HandleStartMessageAsync(message, bot, db, logger);
|
||||
}
|
||||
|
||||
// Set a timer to fetch and process posts every 15 minutes
|
||||
// Set a timer to fetch and process posts every 10 minutes
|
||||
_postFetchTimer = new Timer(async _ => await RunCheck.runAsync(db, bot, config.TAG, logger, config.INSTANCE),
|
||||
null, TimeSpan.Zero, TimeSpan.FromMinutes(10));
|
||||
// Another timer to automatically backup the DB every 6 hour
|
||||
|
63
Web/ServerStartup.cs
Executable file
63
Web/ServerStartup.cs
Executable file
@@ -0,0 +1,63 @@
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using MongoDB.Driver;
|
||||
using MongoDB.Driver.Linq;
|
||||
using mstdnCats.Models;
|
||||
|
||||
namespace mstdnCats.Services;
|
||||
|
||||
public class ServerStartup
|
||||
{
|
||||
private static IMongoCollection<Post> _db;
|
||||
public static void Serverstartup(IMongoCollection<Post> db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
app.UseRouting();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapGet("/", async context =>
|
||||
{
|
||||
var assembly = Assembly.GetEntryAssembly();
|
||||
var resourceName = "mstdnCats.Web.wwwroot.index.html"; // Full resource name
|
||||
|
||||
using (var stream = assembly.GetManifestResourceStream(resourceName))
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
context.Response.StatusCode = 500;
|
||||
await context.Response.WriteAsync("Something went wrong in our side.");
|
||||
return;
|
||||
}
|
||||
|
||||
using (var reader = new StreamReader(stream, Encoding.UTF8))
|
||||
{
|
||||
context.Response.ContentType = "text/html";
|
||||
await context.Response.WriteAsync(reader.ReadToEnd());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
endpoints.MapGet("/api/gimme", async context =>
|
||||
{
|
||||
// Api endpoint
|
||||
// Choose all media attachments that are approved
|
||||
var mediaAttachmentsToSelect = await _db.AsQueryable()
|
||||
.Where(post => post.MediaAttachments.Any(media => media.Approved))
|
||||
.ToListAsync();
|
||||
|
||||
// Select random approved media attachment
|
||||
var selectedPost = mediaAttachmentsToSelect[new Random().Next(mediaAttachmentsToSelect.Count)];
|
||||
|
||||
// Send as JSON
|
||||
await context.Response.WriteAsJsonAsync(selectedPost);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
293
Web/wwwroot/index.html
Executable file
293
Web/wwwroot/index.html
Executable file
@@ -0,0 +1,293 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Cats of Mastodon!</title><style>
|
||||
body {
|
||||
font-family: 'Comic Neue', cursive;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
background-color: #1e1e1e;
|
||||
color: #e0e0e0;
|
||||
margin: 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.mastodon-title {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.welcome-message {
|
||||
margin-bottom: 20px;
|
||||
font-size: 1.2em;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.welcome-message span {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.post-container {
|
||||
background-color: #282828;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||
padding: 20px;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
text-align: center;
|
||||
transition: transform 0.2s ease-in-out;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.post-container:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.post-title-inner {
|
||||
margin-bottom: 10px;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
color: #ffb347;
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.cat-text {
|
||||
color: #bbdefb;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.post-image {
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 15px;
|
||||
object-fit: cover;
|
||||
max-height: 500px;
|
||||
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2);
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.post-image.loading {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.image-loading-spinner {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #6364ff;
|
||||
border-radius: 50%;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.post-image.loading + .image-loading-spinner {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mastodon-button,
|
||||
.neutral-button {
|
||||
background-color: #404040;
|
||||
color: #e0e0e0;
|
||||
border: none;
|
||||
padding: 12px 18px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
transition: background-color 0.3s ease, transform 0.2s ease-in-out;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mastodon-button {
|
||||
background-color: #6364ff;
|
||||
}
|
||||
|
||||
.mastodon-button:hover {
|
||||
background-color: #5253e0;
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
.neutral-button:hover {
|
||||
background-color: #505050;
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
display: none;
|
||||
border: 6px solid #f3f3f3;
|
||||
border-top: 6px solid #6364ff;
|
||||
border-radius: 50%;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
.post-container.loading .loading-spinner {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.post-container.loading .post-image,
|
||||
.post-container.loading .button-container,
|
||||
.post-container.loading .post-title-inner {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
font-size: 0.8em;
|
||||
color: #909090;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.footer-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2 class="mastodon-title">Cats of Mastodon!</h2>
|
||||
<p class="welcome-message">Welcome to Daily Catventures! Get your daily dose of purr-fectly adorable feline fun! <span>✨</span><br>Posts gathered across Mastodon 🤝</p>
|
||||
|
||||
<div class="post-container">
|
||||
<div class="post-content" style="display: none;">
|
||||
<div class="post-title-inner">
|
||||
<span class="user-name"></span><span class="cat-text">'s cat!</span>
|
||||
</div>
|
||||
<div class="image-container">
|
||||
<img class="post-image" src="" alt="Cat Photo">
|
||||
<div class="image-loading-spinner"></div>
|
||||
</div>
|
||||
<div class="button-container">
|
||||
<a class="mastodon-button" href="" target="_blank">View on Mastodon</a>
|
||||
<button class="neutral-button" onclick="loadNewPost()">Show me another cat!</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="loading-spinner"></div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<span>© 2024 Mahdium</span>
|
||||
<a href="https://mahdium.ir" class="footer-button">mahdium.ir</a>
|
||||
<a href="https://gitlab.com/mahdium/cats-of-mastodon-telegram-bot" class="footer-button">Source Code</a>
|
||||
</div>
|
||||
<script>
|
||||
const postContainer = document.querySelector('.post-container');
|
||||
const postContent = document.querySelector('.post-content');
|
||||
const userNameSpan = document.querySelector('.post-content .user-name');
|
||||
const postImage = document.querySelector('.post-content .post-image');
|
||||
const mastodonLink = document.querySelector('.post-content .mastodon-button');
|
||||
const imageLoadingSpinner = document.querySelector('.image-container .image-loading-spinner');
|
||||
|
||||
function loadNewPost() {
|
||||
postContainer.classList.add('loading');
|
||||
postContent.style.display = 'none';
|
||||
postImage.classList.add('loading');
|
||||
|
||||
fetch('/api/gimme')
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
postContainer.classList.remove('loading');
|
||||
postContent.style.display = 'block';
|
||||
|
||||
if (!data || !data.account || !data.media_attachments || data.media_attachments.length === 0) {
|
||||
console.error("Invalid API response format:", data);
|
||||
alert("Invalid data received from the server.");
|
||||
postImage.classList.remove('loading');
|
||||
return;
|
||||
}
|
||||
|
||||
userNameSpan.textContent = data.account.display_name;
|
||||
|
||||
let imageUrl = data.media_attachments[0].remote_url;
|
||||
if (imageUrl) {
|
||||
imageUrl = imageUrl.replace('/original/', '/small/');
|
||||
} else if (data.media_attachments[0].PreviewUrl) {
|
||||
imageUrl = data.media_attachments[0].PreviewUrl;
|
||||
} else {
|
||||
console.warn("No RemoteUrl or PreviewUrl found, using placeholder");
|
||||
postImage.src = "placeholder-no-image.png";
|
||||
postImage.classList.remove('loading');
|
||||
return;
|
||||
}
|
||||
|
||||
postImage.onload = () => {
|
||||
postImage.classList.remove('loading');
|
||||
};
|
||||
|
||||
postImage.onerror = () => {
|
||||
console.error("Error loading image:", imageUrl);
|
||||
postImage.src = "placeholder-error.png";
|
||||
postImage.classList.remove('loading');
|
||||
};
|
||||
|
||||
postImage.src = imageUrl;
|
||||
mastodonLink.href = data.Url;
|
||||
})
|
||||
.catch(error => {
|
||||
postContainer.classList.remove('loading');
|
||||
postImage.classList.remove('loading');
|
||||
console.error("Error fetching data:", error);
|
||||
alert("Failed to load a new post. Please try again later.");
|
||||
});
|
||||
}
|
||||
|
||||
loadNewPost();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -15,4 +15,11 @@
|
||||
<PackageReference Include="Telegram.Bot" Version="22.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Web\wwwroot\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Web\wwwroot\index.html" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
Reference in New Issue
Block a user