mirror of
https://github.com/Bubuni-Team/telegram-bot.git
synced 2026-07-31 00:29:24 +03:00
Merge pull request #6 from CrazyHackGUT/refactor/reputation
WIP: Move reputation to separate module.
This commit is contained in:
@@ -9,9 +9,9 @@ using Telegram.Bot;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
|
||||
namespace West.TelegramBot.ChatManagement.Handler
|
||||
namespace Kruzya.TelegramBot.Core
|
||||
{
|
||||
public abstract class ManagementHandler : BotEventHandler
|
||||
public abstract class CommonHandler : BotEventHandler
|
||||
{
|
||||
protected readonly CoreContext Db;
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace West.TelegramBot.ChatManagement.Handler
|
||||
|
||||
protected User? RepliedUser => Message?.ReplyToMessage?.From;
|
||||
|
||||
protected ManagementHandler(CoreContext db)
|
||||
protected CommonHandler(CoreContext db)
|
||||
{
|
||||
Db = db;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Kruzya.TelegramBot.Core.Data;
|
||||
|
||||
public interface IReputationEntity
|
||||
{
|
||||
public long UserId { get; set; }
|
||||
public long ChatId { get; set; }
|
||||
public double Value { get; set; }
|
||||
}
|
||||
@@ -45,7 +45,7 @@ public static class DbContextExtension
|
||||
dbContext.Update(entity);
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException e)
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
dbContext.Add(entity);
|
||||
dbContext.SaveChanges();
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Kruzya.TelegramBot.Core.Data;
|
||||
using Telegram.Bot.Types;
|
||||
|
||||
namespace Kruzya.TelegramBot.Core.Service
|
||||
{
|
||||
public interface IReputation
|
||||
{
|
||||
public Task<double> GetUserReputationAsync(Chat chat, User user);
|
||||
public Task<double> SetUserReputationAsync(Chat chat, User user, double value);
|
||||
public Task<double> GetReputationDiffAsync(Chat chat, User sender, User receiver);
|
||||
public Task IncrementReputationAsync(Chat chat, User user, double diff);
|
||||
public Task<IEnumerable<IReputationEntity>> GetChatRatingAsync(Chat chat, int limit = 10, int offset = 0);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BotFramework.Abstractions;
|
||||
using Kruzya.TelegramBot.Core.Cache;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types;
|
||||
|
||||
namespace Kruzya.TelegramBot.Core.Service
|
||||
@@ -11,10 +16,17 @@ namespace Kruzya.TelegramBot.Core.Service
|
||||
public class UserService
|
||||
{
|
||||
private readonly List<long> _superUsers;
|
||||
|
||||
private readonly Dictionary<long, string> _statusMap;
|
||||
private readonly ICacheStorage<long, User> _userCache;
|
||||
private readonly ITelegramBotClient _bot;
|
||||
private readonly ILogger<UserService> _logger;
|
||||
|
||||
public UserService(IConfiguration configuration)
|
||||
|
||||
public UserService(
|
||||
IConfiguration configuration,
|
||||
IBotInstance bot,
|
||||
ICacheStorage<long, User> userCache,
|
||||
ILogger<UserService> logger)
|
||||
{
|
||||
_superUsers = configuration.GetSection("SuperUsers")
|
||||
.GetChildren()
|
||||
@@ -24,6 +36,41 @@ namespace Kruzya.TelegramBot.Core.Service
|
||||
_statusMap = configuration.GetSection("CustomMemberStatus")
|
||||
.GetChildren()
|
||||
.ToDictionary(x => Convert.ToInt64(x.Key), x => x.Value);
|
||||
|
||||
_userCache = userCache;
|
||||
_bot = bot.BotClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<User> GetUser(long userId, long chatId, bool bypassCache = false)
|
||||
{
|
||||
if (bypassCache || !_userCache.TryGetValue(userId, out var user))
|
||||
{
|
||||
if (bypassCache)
|
||||
{
|
||||
_logger.LogDebug("Bypassing cache for {UserId}.", userId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("User {UserId} is not found in cache. Fetching from Telegram API.", userId);
|
||||
}
|
||||
|
||||
user = await GetUserByChat(chatId, userId);
|
||||
CacheUser(user);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
private void CacheUser(User user)
|
||||
{
|
||||
// TODO: config entry for TTL
|
||||
_userCache.SetValue(user.Id, user, Convert.ToInt32(TimeSpan.FromHours(1).TotalSeconds));
|
||||
}
|
||||
|
||||
private async Task<User> GetUserByChat(long chatId, long userId)
|
||||
{
|
||||
return (await _bot.GetChatMemberAsync(chatId, userId)).User;
|
||||
}
|
||||
|
||||
public bool IsUserSuper(User user)
|
||||
|
||||
@@ -14,6 +14,7 @@ COPY ./modules/ChatManagement/*.csproj ./modules/ChatManagement/
|
||||
COPY ./modules/Entertainment/*.csproj ./modules/Entertainment/
|
||||
COPY ./modules/UserGroupTag/*.csproj ./modules/UserGroupTag/
|
||||
COPY ./modules/ChatQuotes/*.csproj ./modules/ChatQuotes/
|
||||
COPY ./modules/Reputation/*.csproj ./modules/Reputation/
|
||||
RUN dotnet restore Kruzya.TelegramBot.sln
|
||||
|
||||
# Copy all project files
|
||||
|
||||
@@ -23,7 +23,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserGroupTag", "modules\Use
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Entertainment", "modules\Entertainment\Entertainment.csproj", "{1D62101B-C2B1-4E79-8F7A-690E44E3EE81}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatQuotes", "modules\ChatQuotes\ChatQuotes.csproj", "{04F16325-8F44-4250-9B2E-0F77E8F65CBF}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChatQuotes", "modules\ChatQuotes\ChatQuotes.csproj", "{04F16325-8F44-4250-9B2E-0F77E8F65CBF}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reputation", "modules\Reputation\Reputation.csproj", "{03F3C7D5-6FEF-4A83-9191-501091BFC1AC}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -71,6 +73,10 @@ Global
|
||||
{04F16325-8F44-4250-9B2E-0F77E8F65CBF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{04F16325-8F44-4250-9B2E-0F77E8F65CBF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{04F16325-8F44-4250-9B2E-0F77E8F65CBF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{03F3C7D5-6FEF-4A83-9191-501091BFC1AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{03F3C7D5-6FEF-4A83-9191-501091BFC1AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{03F3C7D5-6FEF-4A83-9191-501091BFC1AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{03F3C7D5-6FEF-4A83-9191-501091BFC1AC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -84,6 +90,7 @@ Global
|
||||
{5B89E22A-78CF-4037-BE0D-5F88EF47022B} = {C7821F15-DEDD-474F-A575-A296D4B58F10}
|
||||
{1D62101B-C2B1-4E79-8F7A-690E44E3EE81} = {C7821F15-DEDD-474F-A575-A296D4B58F10}
|
||||
{04F16325-8F44-4250-9B2E-0F77E8F65CBF} = {C7821F15-DEDD-474F-A575-A296D4B58F10}
|
||||
{03F3C7D5-6FEF-4A83-9191-501091BFC1AC} = {C7821F15-DEDD-474F-A575-A296D4B58F10}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {5BA73C3B-D5FC-4942-9091-504325CDC308}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using Kruzya.TelegramBot.Core;
|
||||
|
||||
namespace West.TelegramBot.ChatManagement
|
||||
namespace West.TelegramBot.ChatManagement;
|
||||
|
||||
public class ChatManagement : Module
|
||||
{
|
||||
public class ChatManagement : Module
|
||||
{
|
||||
public ChatManagement(Core core) : base(core)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,15 @@
|
||||
using System.Threading.Tasks;
|
||||
using BotFramework.Attributes;
|
||||
using BotFramework.Enums;
|
||||
using Kruzya.TelegramBot.Core;
|
||||
using Kruzya.TelegramBot.Core.Data;
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types;
|
||||
|
||||
namespace West.TelegramBot.ChatManagement.Handler
|
||||
namespace West.TelegramBot.ChatManagement.Handler;
|
||||
|
||||
public class Ban : CommonHandler
|
||||
{
|
||||
public class Ban : ManagementHandler
|
||||
{
|
||||
public Ban(CoreContext db) : base(db) {}
|
||||
|
||||
[Command("ban", CommandParseMode.Both)]
|
||||
@@ -39,5 +40,4 @@ namespace West.TelegramBot.ChatManagement.Handler
|
||||
CanAddWebPagePreviews = true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BotFramework.Attributes;
|
||||
using BotFramework.Enums;
|
||||
using BotFramework.Utils;
|
||||
using Kruzya.TelegramBot.Core.Data;
|
||||
using Kruzya.TelegramBot.Core.Extensions;
|
||||
using Kruzya.TelegramBot.Core.Service;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
|
||||
namespace West.TelegramBot.ChatManagement.Handler
|
||||
{
|
||||
public class Reputation : ManagementHandler
|
||||
{
|
||||
private readonly UserService _userService;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private static readonly List<string> RepUpChars = new()
|
||||
{
|
||||
"+",
|
||||
"👍",
|
||||
"👍🏼",
|
||||
"👍🏽",
|
||||
"👍🏾",
|
||||
"👍🏿",
|
||||
"👍🏻"
|
||||
};
|
||||
private static readonly List<string> RepDownChars = new()
|
||||
{
|
||||
"-",
|
||||
"👎",
|
||||
"👎🏼",
|
||||
"👎🏽",
|
||||
"👎🏾",
|
||||
"👎🏿",
|
||||
"👎🏻"
|
||||
};
|
||||
|
||||
public Reputation(CoreContext db, UserService userService, ILogger<Reputation> logger) : base(db)
|
||||
{
|
||||
_userService = userService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[Command("whois", CommandParseMode.Both)]
|
||||
public async Task HandleWhois()
|
||||
{
|
||||
var user = RepliedUser ?? From;
|
||||
var message = $"{user.ToHtml()}\n";
|
||||
if (user.IsBot)
|
||||
{
|
||||
message += "<code>Бот</code>";
|
||||
}
|
||||
else
|
||||
{
|
||||
message += $"<code>{await GetChatMemberStatusString(user)}</code>\n";
|
||||
message += $"<code>Репутация: {await GetUserReputation(user)}\n";
|
||||
message += $"Предупреждения: {(await GetWarnOption(user)).GetValue<int>()}</code>";
|
||||
}
|
||||
|
||||
await Reply(message);
|
||||
}
|
||||
|
||||
[Message(InChat.Public, MessageFlag.HasText | MessageFlag.HasSticker)]
|
||||
public async Task HandleReputation()
|
||||
{
|
||||
var text = Message?.Text;
|
||||
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
text = Message?.Sticker?.Emoji;
|
||||
}
|
||||
if (text == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var receiver = RepliedUser;
|
||||
var canChangeRep = receiver is { IsBot: false } && !From.IsBot && receiver.Id != From.Id;
|
||||
|
||||
if (canChangeRep && (RepUpChars.Contains(text) || RepDownChars.Contains(text)))
|
||||
{
|
||||
var receiverRepOption = await GetReputationOption(receiver!);
|
||||
var receiverRepCount = receiverRepOption.GetValue<double>();
|
||||
var senderRepCount = (await GetReputationOption(From)).GetValue<double>();
|
||||
|
||||
if (senderRepCount < 0)
|
||||
{
|
||||
await Reply(new HtmlString()
|
||||
.Code("Ты не можешь голосовать с отрицательной репутацией.")
|
||||
.ToString()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var diff = Math.Round(senderRepCount == 0 ? 1 : Math.Sqrt(senderRepCount), 2);
|
||||
_logger.LogDebug("Calculated diff: {Diff}", diff);
|
||||
|
||||
string action;
|
||||
if (RepUpChars.Contains(text))
|
||||
{
|
||||
receiverRepCount += diff;
|
||||
action = "увеличил";
|
||||
}
|
||||
else
|
||||
{
|
||||
receiverRepCount -= diff;
|
||||
action = "уменьшил";
|
||||
}
|
||||
|
||||
_logger.LogDebug("Saving receiver rep with value: {Value}", receiverRepCount);
|
||||
receiverRepOption.SetValue(receiverRepCount);
|
||||
Db.AddOrUpdate(receiverRepOption);
|
||||
_logger.LogDebug("Updated value: {Value}", receiverRepOption.GetValue<double>());
|
||||
|
||||
await Reply($"{From.ToHtml()}<code>({await GetUserReputation(From)}) " +
|
||||
$"{action} репутацию </code>{receiver.ToHtml()}<code>({Math.Round(receiverRepCount, 2)})</code>");
|
||||
}
|
||||
}
|
||||
|
||||
[ParametrizedCommand(InChat.Public, "setrep", CommandParseMode.Both)]
|
||||
public async Task HandleSetRep(double reputation)
|
||||
{
|
||||
if (!_userService.IsUserSuper(From))
|
||||
{
|
||||
_logger.LogInformation("{User} attempted to call /setrep in {Chat}", From, Chat.Title);
|
||||
return;
|
||||
}
|
||||
|
||||
if (RepliedUser == null || RepliedUser.IsBot)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
reputation = Math.Round(reputation, 2);
|
||||
var opt = await GetReputationOption(RepliedUser);
|
||||
opt.SetValue(reputation);
|
||||
Db.AddOrUpdate(opt);
|
||||
|
||||
await Reply(
|
||||
new HtmlString()
|
||||
.Code("Пользователю ")
|
||||
.UserMention(RepliedUser)
|
||||
.Code($" установлена репутация: {reputation}")
|
||||
.ToString()
|
||||
);
|
||||
}
|
||||
|
||||
private async Task<double> GetUserReputation(User user)
|
||||
{
|
||||
return Math.Round((await GetReputationOption(user)).GetValue<double>(), 2);
|
||||
}
|
||||
|
||||
private async Task<BotUserValue> GetReputationOption(User user)
|
||||
{
|
||||
return await Db.UserValues.FindOrCreateOption(Chat.Id, user.Id, "reputation");
|
||||
}
|
||||
|
||||
private async Task<string> GetChatMemberStatusString(User user)
|
||||
{
|
||||
var customStatus = _userService.GetUserCustomStatus(user.Id);
|
||||
if (customStatus != null)
|
||||
{
|
||||
return customStatus;
|
||||
}
|
||||
|
||||
var chatMember = await Bot.GetChatMemberAsync(Chat.Id, user.Id);
|
||||
return chatMember.Status switch
|
||||
{
|
||||
ChatMemberStatus.Creator => "Владелец",
|
||||
ChatMemberStatus.Restricted => "Заблокированный",
|
||||
ChatMemberStatus.Administrator => "Администратор",
|
||||
_ => "Пользователь"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
using System.Threading.Tasks;
|
||||
using BotFramework.Attributes;
|
||||
using BotFramework.Enums;
|
||||
using Kruzya.TelegramBot.Core;
|
||||
using Kruzya.TelegramBot.Core.Data;
|
||||
using Kruzya.TelegramBot.Core.Extensions;
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Exceptions;
|
||||
|
||||
namespace West.TelegramBot.ChatManagement.Handler
|
||||
namespace West.TelegramBot.ChatManagement.Handler;
|
||||
|
||||
class Rules : CommonHandler
|
||||
{
|
||||
class Rules : ManagementHandler
|
||||
{
|
||||
[Command(InChat.Public, "setrules", CommandParseMode.Both)]
|
||||
public async Task HandleSetRules()
|
||||
{
|
||||
@@ -43,5 +44,4 @@ namespace West.TelegramBot.ChatManagement.Handler
|
||||
}
|
||||
|
||||
public Rules(CoreContext db) : base(db) {}
|
||||
}
|
||||
}
|
||||
@@ -4,15 +4,16 @@ using System;
|
||||
using System.Threading.Tasks;
|
||||
using BotFramework.Attributes;
|
||||
using BotFramework.Enums;
|
||||
using Kruzya.TelegramBot.Core;
|
||||
using Kruzya.TelegramBot.Core.Data;
|
||||
using Kruzya.TelegramBot.Core.Extensions;
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
|
||||
namespace West.TelegramBot.ChatManagement.Handler
|
||||
namespace West.TelegramBot.ChatManagement.Handler;
|
||||
|
||||
public class Warn : CommonHandler
|
||||
{
|
||||
public class Warn : ManagementHandler
|
||||
{
|
||||
public Warn(CoreContext db) : base(db) {}
|
||||
|
||||
[Command("warn", CommandParseMode.Both)]
|
||||
@@ -58,5 +59,4 @@ namespace West.TelegramBot.ChatManagement.Handler
|
||||
disableNotification: true,
|
||||
parseMode: ParseMode.Html);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BotFramework.Attributes;
|
||||
using BotFramework.Enums;
|
||||
using Kruzya.TelegramBot.Core;
|
||||
using Kruzya.TelegramBot.Core.Data;
|
||||
using Kruzya.TelegramBot.Core.Extensions;
|
||||
using Kruzya.TelegramBot.Core.Service;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
|
||||
namespace West.TelegramBot.ChatManagement.Handler;
|
||||
|
||||
public class WhoIs : CommonHandler
|
||||
{
|
||||
private readonly IReputation? _reputation;
|
||||
private readonly UserService _userService;
|
||||
|
||||
public WhoIs(CoreContext db, UserService userService, IServiceProvider provider) : base(db)
|
||||
{
|
||||
_reputation = provider.GetService<IReputation>();
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
[Command("whois", CommandParseMode.Both)]
|
||||
public async Task HandleWhoIs()
|
||||
{
|
||||
var user = RepliedUser ?? From;
|
||||
var message = $"{user.ToHtml()}\n";
|
||||
if (user.IsBot)
|
||||
{
|
||||
message += "<code>Бот</code>";
|
||||
}
|
||||
else
|
||||
{
|
||||
message += $"<code>{await GetChatMemberStatusString(user)}</code>\n";
|
||||
if (_reputation != null)
|
||||
{
|
||||
message += $"<code>Репутация: {await _reputation.GetUserReputationAsync(Chat, user)}\n</code>";
|
||||
}
|
||||
message += $"<code>Предупреждения: {(await GetWarnOption(user)).GetValue<int>()}</code>";
|
||||
}
|
||||
|
||||
await Reply(message);
|
||||
}
|
||||
|
||||
private async Task<string> GetChatMemberStatusString(User user)
|
||||
{
|
||||
var customStatus = _userService.GetUserCustomStatus(user.Id);
|
||||
if (customStatus != null)
|
||||
{
|
||||
return customStatus;
|
||||
}
|
||||
|
||||
var chatMember = await Bot.GetChatMemberAsync(Chat.Id, user.Id);
|
||||
return chatMember.Status switch
|
||||
{
|
||||
ChatMemberStatus.Creator => "Владелец",
|
||||
ChatMemberStatus.Restricted => "Заблокированный",
|
||||
ChatMemberStatus.Administrator => "Администратор",
|
||||
_ => "Пользователь"
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Kruzya.TelegramBot.Core.EF;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace West.TelegramBot.Reputation.Data;
|
||||
|
||||
public class ReputationContext : AutoSaveDbContext
|
||||
{
|
||||
public DbSet<UserReputation> UserReputation { get; set; } = null!;
|
||||
|
||||
public ReputationContext(DbContextOptions<ReputationContext> options) : base(options)
|
||||
{
|
||||
Database.EnsureCreated();
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<UserReputation>().HasKey(e => new {e.ChatId, e.UserId});
|
||||
modelBuilder.Entity<UserReputation>().HasIndex(e => e.ChatId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Kruzya.TelegramBot.Core.Data;
|
||||
|
||||
namespace West.TelegramBot.Reputation.Data;
|
||||
|
||||
public class UserReputation : IReputationEntity
|
||||
{
|
||||
public long UserId { get; set; }
|
||||
public long ChatId { get; set; }
|
||||
public double Value { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#nullable enable
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
|
||||
namespace West.TelegramBot.Reputation.Data;
|
||||
|
||||
public class ReputationContextFactory : IDesignTimeDbContextFactory<ReputationContext>
|
||||
{
|
||||
public ReputationContext CreateDbContext(string[] args)
|
||||
{
|
||||
var optionsBuilder = new DbContextOptionsBuilder<ReputationContext>();
|
||||
if (args.Length != 1)
|
||||
{
|
||||
throw new InvalidDataException("You should pass DSN as CLI argument in order to use this.");
|
||||
}
|
||||
var dsn = args[0];
|
||||
optionsBuilder.UseMySql(dsn, ServerVersion.AutoDetect(dsn));
|
||||
|
||||
return new ReputationContext(optionsBuilder.Options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using BotFramework;
|
||||
using BotFramework.Attributes;
|
||||
using BotFramework.Enums;
|
||||
using Kruzya.TelegramBot.Core.Data;
|
||||
using Kruzya.TelegramBot.Core.Service;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using West.TelegramBot.Reputation.Data;
|
||||
|
||||
namespace West.TelegramBot.Reputation.Handler;
|
||||
|
||||
public class Debug : BotEventHandler
|
||||
{
|
||||
private readonly CoreContext _coreContext;
|
||||
private readonly ReputationContext _reputationContext;
|
||||
private readonly UserService _userService;
|
||||
|
||||
|
||||
public Debug(CoreContext coreContext, ReputationContext reputationContext, UserService userService)
|
||||
{
|
||||
_coreContext = coreContext;
|
||||
_reputationContext = reputationContext;
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
[Command(InChat.Public, "dbg_migrate", CommandParseMode.Both)]
|
||||
public async Task HandleMigrate()
|
||||
{
|
||||
if (!_userService.IsUserSuper(From))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var reputationValues = _coreContext.UserValues.AsNoTracking()
|
||||
.Where(value => value.Name == "reputation").Include(v => v.BotUser);
|
||||
|
||||
foreach (var reputationValue in reputationValues)
|
||||
{
|
||||
var value = reputationValue.GetValue<double>();
|
||||
if (value == 0D)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var userReputation = new UserReputation
|
||||
{
|
||||
ChatId = reputationValue.BotUser.ChatId,
|
||||
UserId = reputationValue.BotUser.UserId,
|
||||
Value = value
|
||||
};
|
||||
|
||||
var repList = _reputationContext.UserReputation;
|
||||
if (repList.Contains(userReputation))
|
||||
{
|
||||
repList.Update(userReputation);
|
||||
}
|
||||
else
|
||||
{
|
||||
repList.Add(userReputation);
|
||||
}
|
||||
}
|
||||
|
||||
await _reputationContext.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
#nullable enable
|
||||
|
||||
using BotFramework.Attributes;
|
||||
using BotFramework.Enums;
|
||||
using BotFramework.Utils;
|
||||
using Kruzya.TelegramBot.Core;
|
||||
using Kruzya.TelegramBot.Core.Data;
|
||||
using Kruzya.TelegramBot.Core.Extensions;
|
||||
using Kruzya.TelegramBot.Core.Service;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Telegram.Bot.Types;
|
||||
|
||||
namespace West.TelegramBot.Reputation.Handler;
|
||||
|
||||
public class Reputation : CommonHandler
|
||||
{
|
||||
private readonly UserService _userService;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IReputation _reputationService;
|
||||
|
||||
private static readonly List<string> RepUpChars = new()
|
||||
{
|
||||
"+",
|
||||
"👍",
|
||||
"👍🏼",
|
||||
"👍🏽",
|
||||
"👍🏾",
|
||||
"👍🏿",
|
||||
"👍🏻"
|
||||
};
|
||||
private static readonly List<string> RepDownChars = new()
|
||||
{
|
||||
"-",
|
||||
"👎",
|
||||
"👎🏼",
|
||||
"👎🏽",
|
||||
"👎🏾",
|
||||
"👎🏿",
|
||||
"👎🏻"
|
||||
};
|
||||
|
||||
public Reputation(
|
||||
CoreContext db,
|
||||
UserService userService,
|
||||
IReputation reputationService,
|
||||
ILogger<Reputation> logger) : base(db)
|
||||
{
|
||||
_reputationService = reputationService;
|
||||
_userService = userService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[Message(InChat.Public, MessageFlag.HasText | MessageFlag.HasSticker)]
|
||||
public async Task HandleReputation()
|
||||
{
|
||||
var text = Message?.Text;
|
||||
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
text = Message?.Sticker?.Emoji;
|
||||
}
|
||||
if (text == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var receiver = RepliedUser;
|
||||
var canChangeRep = receiver is { IsBot: false } && !From.IsBot && receiver.Id != From.Id;
|
||||
|
||||
if (canChangeRep && (RepUpChars.Contains(text) || RepDownChars.Contains(text)))
|
||||
{
|
||||
var senderRepCount = await GetUserReputation(From);
|
||||
|
||||
if (senderRepCount < 0)
|
||||
{
|
||||
await Reply(new HtmlString()
|
||||
.Code("Ты не можешь голосовать с отрицательной репутацией.")
|
||||
.ToString()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var diff = await _reputationService.GetReputationDiffAsync(Chat, From, receiver);
|
||||
_logger.LogDebug("Calculated diff: {Diff}", diff);
|
||||
|
||||
string action;
|
||||
if (RepUpChars.Contains(text))
|
||||
{
|
||||
action = "увеличил";
|
||||
}
|
||||
else
|
||||
{
|
||||
diff *= -1;
|
||||
action = "уменьшил";
|
||||
}
|
||||
|
||||
await _reputationService.IncrementReputationAsync(Chat, receiver, diff);
|
||||
|
||||
var receiverRepCount = await GetUserReputation(receiver!);
|
||||
_logger.LogDebug("Updated value: {Value}", receiverRepCount);
|
||||
|
||||
await Reply($"{From.ToHtml()}<code>({await GetUserReputation(From)}) " +
|
||||
$"{action} репутацию </code>{receiver.ToHtml()}<code>({Math.Round(receiverRepCount, 2)})</code>");
|
||||
}
|
||||
}
|
||||
|
||||
[ParametrizedCommand(InChat.Public, "setrep", CommandParseMode.Both)]
|
||||
public async Task HandleSetRep(double reputation)
|
||||
{
|
||||
if (!_userService.IsUserSuper(From))
|
||||
{
|
||||
_logger.LogInformation("{User} attempted to call /setrep in {Chat}", From, Chat.Title);
|
||||
return;
|
||||
}
|
||||
|
||||
if (RepliedUser == null || RepliedUser.IsBot)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
reputation = await _reputationService.SetUserReputationAsync(Chat, RepliedUser, reputation);
|
||||
await Reply(
|
||||
new HtmlString()
|
||||
.Code("Пользователю ")
|
||||
.UserMention(RepliedUser)
|
||||
.Code($" установлена репутация: {reputation}")
|
||||
.ToString()
|
||||
);
|
||||
}
|
||||
|
||||
[Command(InChat.Public, "top", CommandParseMode.Both)]
|
||||
public async Task HandleTop()
|
||||
{
|
||||
var msg = new HtmlString();
|
||||
msg.TextBr("Топ репутации чата: ");
|
||||
var i = 1;
|
||||
foreach (var rep in await _reputationService.GetChatRatingAsync(Chat))
|
||||
{
|
||||
var user = await _userService.GetUser(rep.UserId, rep.ChatId);
|
||||
msg.Text($"{i}. ").UserMention(user).Text($" ({rep.Value})").Br();
|
||||
i++;
|
||||
}
|
||||
|
||||
await Bot.SendHtmlStringAsync(Chat, msg);
|
||||
}
|
||||
|
||||
private async Task<double> GetUserReputation(User user)
|
||||
{
|
||||
return await _reputationService.GetUserReputationAsync(Chat, user);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using West.TelegramBot.Reputation.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace West.TelegramBot.Reputation.Migrations
|
||||
{
|
||||
[DbContext(typeof(ReputationContext))]
|
||||
[Migration("20220213182703_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "6.0.2")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
modelBuilder.Entity("West.TelegramBot.ChatManagement.Data.UserReputation", b =>
|
||||
{
|
||||
b.Property<long>("ChatId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<double>("Value")
|
||||
.HasColumnType("double");
|
||||
|
||||
b.HasKey("ChatId", "UserId");
|
||||
|
||||
b.HasIndex("ChatId");
|
||||
|
||||
b.ToTable("UserReputation");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace West.TelegramBot.Reputation.Migrations;
|
||||
|
||||
public partial class Initial : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterDatabase()
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UserReputation",
|
||||
columns: table => new
|
||||
{
|
||||
UserId = table.Column<long>(type: "bigint", nullable: false),
|
||||
ChatId = table.Column<long>(type: "bigint", nullable: false),
|
||||
Value = table.Column<double>(type: "double", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_UserReputation", x => new { x.ChatId, x.UserId });
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserReputation_ChatId",
|
||||
table: "UserReputation",
|
||||
column: "ChatId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "UserReputation");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using West.TelegramBot.Reputation.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace West.TelegramBot.Reputation.Migrations;
|
||||
|
||||
[DbContext(typeof(ReputationContext))]
|
||||
partial class ReputationContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "6.0.2")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
modelBuilder.Entity("West.TelegramBot.ChatManagement.Data.UserReputation", b =>
|
||||
{
|
||||
b.Property<long>("ChatId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("UserId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<double>("Value")
|
||||
.HasColumnType("double");
|
||||
|
||||
b.HasKey("ChatId", "UserId");
|
||||
|
||||
b.HasIndex("ChatId");
|
||||
|
||||
b.ToTable("UserReputation");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Kruzya.TelegramBot.Core;
|
||||
using Kruzya.TelegramBot.Core.Service;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using West.TelegramBot.Reputation.Data;
|
||||
using West.TelegramBot.Reputation.Service;
|
||||
|
||||
namespace West.TelegramBot.Reputation;
|
||||
|
||||
public class Reputation : Module
|
||||
{
|
||||
public Reputation(Core core) : base(core)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
var dsn = Configuration.GetConnectionString("Reputation");
|
||||
services.AddDbContext<ReputationContext>(options => options.UseMySql(dsn, ServerVersion.AutoDetect(dsn)));
|
||||
|
||||
services.AddScoped<IReputation, ReputationService>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AssemblyName>TelegramBot.Reputation</AssemblyName>
|
||||
<RootNamespace>West.TelegramBot.Reputation</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Service\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Delete Files="$(OutDir)\TelegramBot.dll" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,73 @@
|
||||
using Kruzya.TelegramBot.Core.Data;
|
||||
using Kruzya.TelegramBot.Core.Extensions;
|
||||
using Kruzya.TelegramBot.Core.Service;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Telegram.Bot.Types;
|
||||
using West.TelegramBot.Reputation.Data;
|
||||
|
||||
namespace West.TelegramBot.Reputation.Service;
|
||||
|
||||
public class ReputationService : IReputation
|
||||
{
|
||||
private readonly ReputationContext _reputationContext;
|
||||
|
||||
public ReputationService(ReputationContext reputationContext)
|
||||
{
|
||||
_reputationContext = reputationContext;
|
||||
}
|
||||
|
||||
public async Task<double> GetUserReputationAsync(Chat chat, User user)
|
||||
{
|
||||
var rep = await FindUserReputation(chat, user);
|
||||
|
||||
return rep?.Value ?? 0;
|
||||
}
|
||||
|
||||
public async Task<double> SetUserReputationAsync(Chat chat, User user, double value)
|
||||
{
|
||||
var rep = await FindUserReputation(chat, user) ?? new UserReputation
|
||||
{
|
||||
ChatId = chat.Id,
|
||||
UserId = user.Id
|
||||
};
|
||||
|
||||
rep.Value = Math.Round(value, 2);
|
||||
_reputationContext.AddOrUpdate(rep);
|
||||
|
||||
return rep.Value;
|
||||
}
|
||||
|
||||
public async Task IncrementReputationAsync(Chat chat, User user, double diff)
|
||||
{
|
||||
await SetUserReputationAsync(chat, user, await GetUserReputationAsync(chat, user) + diff);
|
||||
}
|
||||
|
||||
public async Task<double> GetReputationDiffAsync(Chat chat, User sender, User receiver)
|
||||
{
|
||||
var senderRep = await FindUserReputation(chat, sender);
|
||||
|
||||
if (senderRep == null)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
var senderValue = senderRep.Value;
|
||||
return Math.Round(senderValue == 0 ? 1 : Math.Sqrt(senderValue), 2);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<IReputationEntity>> GetChatRatingAsync(Chat chat, int limit = 10, int offset = 0)
|
||||
{
|
||||
return await _reputationContext.UserReputation
|
||||
.Where(r => r.ChatId == chat.Id && r.Value > 0D)
|
||||
.OrderByDescending(r => r.Value)
|
||||
.Skip(offset)
|
||||
.Take(limit)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
private async Task<UserReputation?> FindUserReputation(Chat chat, User user)
|
||||
{
|
||||
return await _reputationContext.UserReputation.SingleOrDefaultAsync(
|
||||
e => e.ChatId == chat.Id && e.UserId == user.Id);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user