Добавил фичи
All checks were successful
Local Deploy with Docker / build-and-deploy (push) Successful in 4s

This commit is contained in:
Dmitrii Prokudin 2024-12-26 03:32:41 +03:00
parent 931e01fcd6
commit 99df15fb31
7 changed files with 340 additions and 77 deletions

View File

@ -35,5 +35,5 @@ jobs:
-e DATABASE_NAME=telegram_bot \
-e DATABASE_USER=postgres \
-e DATABASE_PASSWORD=${{ secrets.DB_PASSWORD }} \
-e BOT_TOKEN=${{ secrets.BOT_TOKEN }} \
-e BotSettings_BotToken=${{ secrets.BOT_TOKEN }} \
telegram-bot:latest

View File

@ -0,0 +1,72 @@
using System.Data;
using Dapper;
using Microsoft.Extensions.Configuration;
using Npgsql;
using UserOfTheDayBot.Model;
namespace UserOfTheDayBot.Data;
public interface IUserOfTheDayRepository
{
Task RecordUserOfTheDayAsync(long chatId, long userId, DateTime date, UserOfTheDayType type);
Task<bool> IsUserOfTheDaySelectedAsync(long chatId, DateTime date, UserOfTheDayType type);
Task<bool> IsUserAlreadySelectedAsync(long chatId, DateTime date, long userId);
Task<Dictionary<(long userId, string userName), (int userOfTheDayCount, int loserOfTheDayCount)>> GetUserStatisticsAsync(long chatId);
}
public class UserOfTheDayRepository : IUserOfTheDayRepository
{
private readonly string _connectionString;
public UserOfTheDayRepository(IConfiguration configuration)
{
_connectionString = configuration["DatabaseSettings:ConnectionString"];
}
public async Task RecordUserOfTheDayAsync(long chatId, long userId, DateTime date, UserOfTheDayType type)
{
using (IDbConnection db = new NpgsqlConnection(_connectionString))
{
string query = "INSERT INTO user_of_the_day (chat_id, user_id, date, type) VALUES (@ChatId, @UserId, @Date, @Type)";
await db.ExecuteAsync(query, new { ChatId = chatId, UserId = userId, Date = date, Type = type });
}
}
public async Task<bool> IsUserOfTheDaySelectedAsync(long chatId, DateTime date, UserOfTheDayType type)
{
using (IDbConnection db = new NpgsqlConnection(_connectionString))
{
string query = "SELECT COUNT(1) FROM user_of_the_day WHERE chat_id = @ChatId AND date = @Date AND type = @Type";
return await db.ExecuteScalarAsync<bool>(query, new { ChatId = chatId, Date = date, Type = type });
}
}
public async Task<bool> IsUserAlreadySelectedAsync(long chatId, DateTime date, long userId)
{
using (IDbConnection db = new NpgsqlConnection(_connectionString))
{
string query = "SELECT COUNT(1) FROM user_of_the_day WHERE chat_id = @ChatId AND date = @Date AND user_id = @UserId";
return await db.ExecuteScalarAsync<bool>(query, new { ChatId = chatId, Date = date, UserId = userId });
}
}
public async Task<Dictionary<(long userId, string userName), (int userOfTheDayCount, int loserOfTheDayCount)>> GetUserStatisticsAsync(long chatId)
{
using (IDbConnection db = new NpgsqlConnection(_connectionString))
{
string query = @"SELECT u.user_id, u.user_name,
SUM(CASE WHEN ud.type = 0 THEN 1 ELSE 0 END) AS user_of_the_day_count,
SUM(CASE WHEN ud.type = 1 THEN 1 ELSE 0 END) AS loser_of_the_day_count
FROM user_of_the_day ud
JOIN users u ON ud.user_id = u.user_id AND ud.chat_id = u.chat_id
WHERE ud.chat_id = @ChatId
GROUP BY u.user_id, u.user_name";
var results = await db.QueryAsync<(long userId, string userName, int userOfTheDayCount, int loserOfTheDayCount)>(query, new { ChatId = chatId });
return results.ToDictionary(
row => (row.userId, row.userName),
row => (row.userOfTheDayCount, row.loserOfTheDayCount)
);
}
}
}

41
Data/UserRepository.cs Normal file
View File

@ -0,0 +1,41 @@
using System.Data;
using Dapper;
using Microsoft.Extensions.Configuration;
using Npgsql;
namespace UserOfTheDayBot.Data;
public interface IUserRepository
{
Task<int> RegisterUserAsync(long chatId, long userId, string userName);
Task<IEnumerable<(long userId, string userName)>> GetUsersWithNamesAsync(long chatId);
}
public class UserRepository : IUserRepository
{
private readonly string _connectionString;
public UserRepository(IConfiguration configuration)
{
_connectionString = configuration["DatabaseSettings:ConnectionString"];
}
public async Task<int> RegisterUserAsync(long chatId, long userId, string userName)
{
using (IDbConnection db = new NpgsqlConnection(_connectionString))
{
string query = "INSERT INTO users (chat_id, user_id, user_name) VALUES (@ChatId, @UserId, @UserName) ON CONFLICT (chat_id, user_id) DO NOTHING";
return await db.ExecuteAsync(query, new { ChatId = chatId, UserId = userId, UserName = userName });
}
}
public async Task<IEnumerable<(long userId, string userName)>> GetUsersWithNamesAsync(long chatId)
{
using (IDbConnection db = new NpgsqlConnection(_connectionString))
{
string query = "SELECT user_id, user_name FROM users WHERE chat_id = @ChatId";
return await db.QueryAsync<(long, string)>(query, new { ChatId = chatId });
}
}
}

View File

@ -0,0 +1,7 @@
namespace UserOfTheDayBot.Model;
public enum UserOfTheDayType
{
UserOfTheDay = 0,
LoserOfTheDay = 1
}

View File

@ -6,89 +6,37 @@ using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using Dapper;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Npgsql;
using UserOfTheDayBot.Data;
using UserOfTheDayBot.Services;
class Program
public class Program
{
private static string botToken = "7814259349:AAEasTnDpX5s5PrQcR5ihI9pOsmp2Ocv-m0"; // Укажите токен вашего бота
private static string connectionString = "Host=host.docker.internal;Port=5432;Database=telegram_bot;Username=postgres;Password=postgres";
public static async Task Main(string[] args)
{
var host = Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
config.AddEnvironmentVariables(); // Добавление переменных среды
})
.ConfigureServices((context, services) =>
{
services.AddSingleton<IUserRepository, UserRepository>();
services.AddSingleton<IUserOfTheDayRepository, UserOfTheDayRepository>();
services.AddSingleton<BotService>();
private static readonly TelegramBotClient botClient = new TelegramBotClient(botToken);
static async Task Main(string[] args)
// TelegramBotClient зависит от IConfiguration для получения токена
services.AddSingleton<ITelegramBotClient>(provider =>
{
Console.WriteLine("Bot is running...");
// Управление токеном завершения
var cts = new CancellationTokenSource();
Console.CancelKeyPress += (_, _) => cts.Cancel();
botClient.StartReceiving(UpdateHandler, ErrorHandler, cancellationToken: cts.Token);
Console.WriteLine("Press Ctrl+C to exit...");
await Task.Delay(Timeout.Infinite, cts.Token);
}
private static async Task UpdateHandler(ITelegramBotClient bot, Update update, CancellationToken cancellationToken)
{
Console.WriteLine("Received an message");
if (update.Type == UpdateType.Message && update.Message?.Text != null)
{
var message = update.Message;
if (message.Text.StartsWith("/help"))
{
await bot.SendTextMessageAsync(message.Chat.Id, "Уйди. Попробуйте /reg", cancellationToken: cancellationToken);
}
else if (message.Text.StartsWith("/reg"))
{
await RegisterUser(message, cancellationToken);
}
else
{
await bot.SendTextMessageAsync(message.Chat.Id, "Неизвестная команда. Попробуйте /reg", cancellationToken: cancellationToken);
}
}
}
private static async Task RegisterUser(Message message, CancellationToken cancellationToken)
{
try
{
using (IDbConnection db = new NpgsqlConnection(connectionString))
{
string query = "INSERT INTO users (chat_id, user_id, user_name) VALUES (@ChatId, @UserId, @UserName) ON CONFLICT (chat_id, user_id) DO NOTHING";
var parameters = new
{
ChatId = message.Chat.Id,
UserId = message.From.Id,
UserName = message.From.FirstName
};
int rowsAffected = await db.ExecuteAsync(query, parameters);
if (rowsAffected > 0)
{
await botClient.SendTextMessageAsync(message.Chat.Id, "Вы успешно зарегистрированы!", cancellationToken: cancellationToken);
}
else
{
await botClient.SendTextMessageAsync(message.Chat.Id, "Вы уже зарегистрированы.", cancellationToken: cancellationToken);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
await botClient.SendTextMessageAsync(message.Chat.Id, "Произошла ошибка при регистрации. Попробуйте позже.", cancellationToken: cancellationToken);
}
}
private static Task ErrorHandler(ITelegramBotClient bot, Exception exception, CancellationToken cancellationToken)
{
Console.WriteLine($"Error: {exception.Message}");
return Task.CompletedTask;
var configuration = provider.GetRequiredService<IConfiguration>();
var botToken = configuration["BotSettings:BotToken"];
return new TelegramBotClient(botToken);
});
})
.Build();
}
}

187
Services/BotService.cs Normal file
View File

@ -0,0 +1,187 @@
using System.Security.Cryptography;
using Microsoft.Extensions.Configuration;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using UserOfTheDayBot.Data;
using UserOfTheDayBot.Model;
namespace UserOfTheDayBot.Services;
public class BotService
{
private readonly ITelegramBotClient _botClient;
private readonly IUserRepository _userRepository;
private readonly IUserOfTheDayRepository _userOfTheDayRepository;
public BotService(IConfiguration configuration, IUserRepository userRepository, IUserOfTheDayRepository userOfTheDayRepository)
{
_botClient = new TelegramBotClient(configuration["BotSettings:BotToken"] ?? Environment.GetEnvironmentVariable("BotSettings_BotToken"));
_userRepository = userRepository;
_userOfTheDayRepository = userOfTheDayRepository;
}
public async Task HandleUpdateAsync(Update update, CancellationToken cancellationToken)
{
if (update.Type == UpdateType.Message && update.Message?.Text != null)
{
var message = update.Message;
if (message.Text.StartsWith("/help"))
{
await _botClient.SendTextMessageAsync(message.Chat.Id, "Уйди. Попробуйте /reg", cancellationToken: cancellationToken);
}
else if (message.Text.StartsWith("/reg"))
{
await RegisterUserAsync(message, cancellationToken);
}
else if (message.Text.StartsWith("/useroftheday"))
{
await HandleUserOfTheDayCommandAsync(message, UserOfTheDayType.UserOfTheDay, cancellationToken);
}
else if (message.Text.StartsWith("/loser"))
{
await HandleUserOfTheDayCommandAsync(message, UserOfTheDayType.LoserOfTheDay, cancellationToken);
}
else if (message.Text.StartsWith("/stat"))
{
await HandleStatCommandAsync(message, cancellationToken);
}
else
{
await _botClient.SendTextMessageAsync(message.Chat.Id, "Неизвестная команда. Попробуйте /reg", cancellationToken: cancellationToken);
}
}
}
private async Task RegisterUserAsync(Message message, CancellationToken cancellationToken)
{
try
{
int rowsAffected = await _userRepository.RegisterUserAsync(message.Chat.Id, message.From.Id, message.From.FirstName);
if (rowsAffected > 0)
{
await _botClient.SendTextMessageAsync(message.Chat.Id, "Вы успешно зарегистрированы!", cancellationToken: cancellationToken);
}
else
{
await _botClient.SendTextMessageAsync(message.Chat.Id, "Вы уже зарегистрированы.", cancellationToken: cancellationToken);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
await _botClient.SendTextMessageAsync(message.Chat.Id, "Произошла ошибка при регистрации. Попробуйте позже.", cancellationToken: cancellationToken);
}
}
private async Task HandleUserOfTheDayCommandAsync(Message message, UserOfTheDayType type, CancellationToken cancellationToken)
{
try
{
var today = DateTime.UtcNow.Date;
var chatId = message.Chat.Id;
if (await _userOfTheDayRepository.IsUserOfTheDaySelectedAsync(chatId, today, type))
{
var responseMessage = type == UserOfTheDayType.UserOfTheDay ? "Пользователь дня уже выбран." : "Неудачник дня уже выбран.";
await _botClient.SendTextMessageAsync(chatId, responseMessage, cancellationToken: cancellationToken);
return;
}
var selectedUser = await SelectUserOfTheDayAsync(chatId, today, type);
if (selectedUser.HasValue)
{
var responseMessage = type == UserOfTheDayType.UserOfTheDay ? "Пользователь дня" : "Неудачник дня";
await _botClient.SendTextMessageAsync(chatId, $"{responseMessage}: {selectedUser.Value}", cancellationToken: cancellationToken);
}
else
{
await _botClient.SendTextMessageAsync(chatId, "В чате нет зарегистрированных пользователей.", cancellationToken: cancellationToken);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
await _botClient.SendTextMessageAsync(message.Chat.Id, "Произошла ошибка при выборе пользователя дня. Попробуйте позже.", cancellationToken: cancellationToken);
}
}
private async Task<long?> SelectUserOfTheDayAsync(long chatId, DateTime date, UserOfTheDayType type)
{
var users = (await _userRepository.GetUsersWithNamesAsync(chatId)).ToList();
if (users.Any())
{
using (var rng = RandomNumberGenerator.Create())
{
while (users.Count > 0)
{
var randomIndex = GetRandomIndex(users.Count, rng);
var selectedUser = users[randomIndex];
if (!await _userOfTheDayRepository.IsUserAlreadySelectedAsync(chatId, date, selectedUser.userId))
{
await _userOfTheDayRepository.RecordUserOfTheDayAsync(chatId, selectedUser.userId, date, type);
return selectedUser.userId;
}
users.RemoveAt(randomIndex);
}
}
}
return null;
}
private async Task HandleStatCommandAsync(Message message, CancellationToken cancellationToken)
{
try
{
var chatId = message.Chat.Id;
var stats = await _userOfTheDayRepository.GetUserStatisticsAsync(chatId);
if (stats.Any())
{
var userOfTheDaySection = "Пользователь дня:\n";
var loserOfTheDaySection = "Неудачник дня:\n";
var sortedUserOfTheDay = stats.OrderByDescending(x => x.Value.userOfTheDayCount);
var sortedLoserOfTheDay = stats.OrderByDescending(x => x.Value.loserOfTheDayCount);
foreach (var stat in sortedUserOfTheDay)
{
userOfTheDaySection += $"{stat.Key.userName}: {stat.Value.userOfTheDayCount} раз(а)\n";
}
foreach (var stat in sortedLoserOfTheDay)
{
loserOfTheDaySection += $"{stat.Key.userName}: {stat.Value.loserOfTheDayCount} раз(а)\n";
}
var response = userOfTheDaySection + "\n" + loserOfTheDaySection;
await _botClient.SendTextMessageAsync(chatId, response, cancellationToken: cancellationToken);
}
else
{
await _botClient.SendTextMessageAsync(chatId, "Нет данных о пользователях.", cancellationToken: cancellationToken);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
await _botClient.SendTextMessageAsync(message.Chat.Id, "Произошла ошибка при получении статистики. Попробуйте позже.", cancellationToken: cancellationToken);
}
}
private static int GetRandomIndex(int max, RandomNumberGenerator rng)
{
var buffer = new byte[4];
rng.GetBytes(buffer);
var randomValue = BitConverter.ToUInt32(buffer, 0);
return (int)(randomValue % max);
}
}

8
appsettings.json Normal file
View File

@ -0,0 +1,8 @@
{
"BotSettings": {
"BotToken": "7571336725:AAGkzgAricQPWWsAinDCDKZivomXXy36Qpo"
},
"DatabaseSettings": {
"ConnectionString": "Host=host.docker.internal;Port=5432;Database=telegram_bot;Username=postgres;Password=postgres"
}
}