diff --git a/Database/BaseDBMigration.cs b/Database/BaseDBMigration.cs index 74ebfeb..15d8a44 100644 --- a/Database/BaseDBMigration.cs +++ b/Database/BaseDBMigration.cs @@ -47,6 +47,7 @@ CREATE TABLE Contacts ( ContactId INTEGER NOT NULL, status TEXT, MutedUntil TEXT, + DisplayName TEXT, PRIMARY KEY (UserId, ContactId) )"); } diff --git a/Database/Interfaces/IContactRepository.cs b/Database/Interfaces/IContactRepository.cs index 692e6b9..1857dff 100644 --- a/Database/Interfaces/IContactRepository.cs +++ b/Database/Interfaces/IContactRepository.cs @@ -30,6 +30,7 @@ public interface IContactRemover public interface IContactSetter { void SetContactStatus(long SenderTelegramID, long AccepterTelegramID, string status); + bool SetContactDisplayName(int userId, int contactId, string? displayName); } public interface IContactGetter @@ -39,4 +40,5 @@ public interface IContactGetter DateTime? GetMutedUntil(int userId, int contactId); int GetContactIDByLink(string link); int GetContactByTelegramID(long telegramID); + string? GetContactDisplayName(int userId, int contactId); } diff --git a/Database/Migrations/AddContactDisplayName.cs b/Database/Migrations/AddContactDisplayName.cs new file mode 100644 index 0000000..228f688 --- /dev/null +++ b/Database/Migrations/AddContactDisplayName.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2024-2025 ZenonEl +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Эта программа является свободным программным обеспечением: вы можете распространять и/или изменять +// её на условиях Стандартной общественной лицензии GNU Affero, опубликованной +// Фондом свободного программного обеспечения, либо версии 3 лицензии, либо +// (по вашему выбору) любой более поздней версии. + +using FluentMigrator; + +namespace TelegramMediaRelayBot.Database.Migrations; + +[Migration(20260329)] +public class AddContactDisplayName : Migration +{ + public override void Up() + { + if (!Schema.Table("Contacts").Column("DisplayName").Exists()) + { + Alter.Table("Contacts") + .AddColumn("DisplayName").AsString(255).Nullable(); + } + } + + public override void Down() + { + Delete.Column("DisplayName").FromTable("Contacts"); + } +} diff --git a/Database/Repositories/SqLite/Contacts.cs b/Database/Repositories/SqLite/Contacts.cs index fe25155..d591586 100644 --- a/Database/Repositories/SqLite/Contacts.cs +++ b/Database/Repositories/SqLite/Contacts.cs @@ -292,10 +292,10 @@ public class SqliteContactSetter(string connectionString) : IContactSetter public void SetContactStatus(long SenderTelegramID, long AccepterTelegramID, string status) { const string query = @" - UPDATE Contacts - SET Status = @Status + UPDATE Contacts + SET Status = @Status WHERE UserId = @UserId AND ContactId = @ContactId"; - + SqliteContactGetter contactGetter = new(_connectionString); SqliteUserGetter userGetter = new(_connectionString); @@ -303,7 +303,7 @@ UPDATE Contacts { try { - connection.Execute(query, new + connection.Execute(query, new { Status = status, UserId = userGetter.GetUserIDbyTelegramID(SenderTelegramID), @@ -316,6 +316,28 @@ UPDATE Contacts } } } + + public bool SetContactDisplayName(int userId, int contactId, string? displayName) + { + const string query = @" + UPDATE Contacts + SET DisplayName = @displayName + WHERE UserId = @userId AND ContactId = @contactId"; + + using (var connection = new SqliteConnection(_connectionString)) + { + try + { + int affected = connection.Execute(query, new { userId, contactId, displayName }); + return affected > 0; + } + catch (Exception ex) + { + Log.Error("Error editing database: " + ex.Message); + return false; + } + } + } } public class SqliteContactGetter(string connectionString) : IContactGetter @@ -434,4 +456,22 @@ public int GetContactByTelegramID(long telegramID) return -1; } } + + public string? GetContactDisplayName(int userId, int contactId) + { + const string query = @" + SELECT DisplayName + FROM Contacts + WHERE UserId = @userId AND ContactId = @contactId"; + try + { + using var connection = new SqliteConnection(_connectionString); + return connection.QueryFirstOrDefault(query, new { userId, contactId }); + } + catch (Exception ex) + { + Log.Error(ex, "An error occurred in the method {MethodName}", nameof(GetContactDisplayName)); + return null; + } + } } \ No newline at end of file diff --git a/Resources/texts.resx b/Resources/texts.resx index cf9c56a..b744dc5 100644 --- a/Resources/texts.resx +++ b/Resources/texts.resx @@ -173,6 +173,23 @@ Please specify the group ID for work: Specify the contact ID to delete: + + + To rename a contact, provide their ID or link. + + + Enter a new display name for this contact: + + + Reset to original + + + Display name changed to: {0} + + + Display name has been reset to the original. + + To mute a person (you won't receive videos from them), you need to provide either their ID or their link. diff --git a/Resources/texts.ru-RU.resx b/Resources/texts.ru-RU.resx index c3e4810..e130a92 100644 --- a/Resources/texts.ru-RU.resx +++ b/Resources/texts.ru-RU.resx @@ -172,6 +172,23 @@ ID: {0} Укажите ID контактов для удаления: + + + Чтобы переименовать контакт, укажите его ID или ссылку. + + + Введите новое отображаемое имя для этого контакта: + + + Сбросить к оригиналу + + + Отображаемое имя изменено на: {0} + + + Отображаемое имя сброшено к оригиналу. + + Чтобы замутить человека (вы не будете получать от него медиа) вам нужно указать либо его ID либо его ссылку diff --git a/TelegramBot/Handlers/ICallBackQuery/CallbackNames.cs b/TelegramBot/Handlers/ICallBackQuery/CallbackNames.cs index c7b2243..862afdc 100644 --- a/TelegramBot/Handlers/ICallBackQuery/CallbackNames.cs +++ b/TelegramBot/Handlers/ICallBackQuery/CallbackNames.cs @@ -32,6 +32,7 @@ public static class CallbackNames public const string ViewContacts = "view_contacts"; public const string MuteContact = "mute_contact"; public const string UnmuteContact = "unmute_contact"; + public const string RenameContact = "edit_contact_name"; public const string DeleteContact = "delete_contact"; // Settings diff --git a/TelegramBot/Handlers/ICallBackQuery/IContactsCallbackQuery.cs b/TelegramBot/Handlers/ICallBackQuery/IContactsCallbackQuery.cs index bf65789..144f2da 100644 --- a/TelegramBot/Handlers/ICallBackQuery/IContactsCallbackQuery.cs +++ b/TelegramBot/Handlers/ICallBackQuery/IContactsCallbackQuery.cs @@ -220,6 +220,31 @@ public async Task ExecuteAsync(Update update, ITelegramBotClient botClient, Canc } } +public class RenameContactCommand : IBotCallbackQueryHandlers +{ + private readonly IContactSetter _contactSetterRepository; + private readonly IContactGetter _contactGetterRepository; + private readonly IUserGetter _userGetter; + + public RenameContactCommand( + IContactSetter contactSetterRepository, + IContactGetter contactGetterRepository, + IUserGetter userGetter) + { + _contactSetterRepository = contactSetterRepository; + _contactGetterRepository = contactGetterRepository; + _userGetter = userGetter; + } + + public string Name => "edit_contact_name"; + + public async Task ExecuteAsync(Update update, ITelegramBotClient botClient, CancellationToken ct) + { + long chatId = update.CallbackQuery!.Message!.Chat.Id; + await Contacts.RenameContact(botClient, update, chatId, _contactSetterRepository, _contactGetterRepository, _userGetter); + } +} + public class DeleteContactCommand : IBotCallbackQueryHandlers { private readonly IContactRemover _contactRemoverRepository; diff --git a/TelegramBot/Menu/Contacts.cs b/TelegramBot/Menu/Contacts.cs index bfa855f..42f7a5e 100644 --- a/TelegramBot/Menu/Contacts.cs +++ b/TelegramBot/Menu/Contacts.cs @@ -73,6 +73,18 @@ public static async Task ViewContacts(ITelegramBotClient botClient, Update updat await CommonUtilities.SendMessage(botClient, update, KeyboardUtils.GetViewContactsKeyboardMarkup(), cancellationToken, $"{Config.GetResourceString("YourContacts")}\n{string.Join("\n", contactUsersInfo)}"); } + public static async Task RenameContact( + ITelegramBotClient botClient, + Update update, + long chatId, + IContactSetter contactSetterRepository, + IContactGetter contactGetterRepository, + IUserGetter userGetter) + { + await botClient.SendMessage(update.CallbackQuery!.Message!.Chat.Id, Config.GetResourceString("RenameContactInstructions"), cancellationToken: cancellationToken); + UserSessionManager.Set(chatId, new ProcessRenameContactState(contactSetterRepository, contactGetterRepository, userGetter)); + } + public static async Task EditContactGroup( ITelegramBotClient botClient, Update update, diff --git a/TelegramBot/States/RenameContact.cs b/TelegramBot/States/RenameContact.cs new file mode 100644 index 0000000..b5c6eab --- /dev/null +++ b/TelegramBot/States/RenameContact.cs @@ -0,0 +1,121 @@ +// Copyright (C) 2024-2025 ZenonEl +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Эта программа является свободным программным обеспечением: вы можете распространять и/или изменять +// её на условиях Стандартной общественной лицензии GNU Affero, опубликованной +// Фондом свободного программного обеспечения, либо версии 3 лицензии, либо +// (по вашему выбору) любой более поздней версии. + +using TelegramMediaRelayBot.Database.Interfaces; +using TelegramMediaRelayBot.TelegramBot.Utils; + + +namespace TelegramMediaRelayBot; + +public class ProcessRenameContactState : IUserState +{ + public UserRenameContactState currentState; + + private int userId { get; set; } + private int targetContactId { get; set; } + private readonly IContactSetter _contactSetter; + private readonly IContactGetter _contactGetter; + private readonly IUserGetter _userGetter; + + public ProcessRenameContactState( + IContactSetter contactSetter, + IContactGetter contactGetter, + IUserGetter userGetter + ) + { + currentState = UserRenameContactState.WaitingForLinkOrID; + _contactSetter = contactSetter; + _contactGetter = contactGetter; + _userGetter = userGetter; + } + + public string GetCurrentState() + { + return currentState.ToString(); + } + + public async Task ProcessState(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken) + { + long chatId = CommonUtilities.GetIDfromUpdate(update); + if (CommonUtilities.CheckNonZeroID(chatId)) return; + + if (!UserSessionManager.TryGetValue(chatId, out IUserState? value)) + { + return; + } + + var userState = (ProcessRenameContactState)value; + + switch (userState.currentState) + { + case UserRenameContactState.WaitingForLinkOrID: + int contactId; + if (int.TryParse(update.Message!.Text, out contactId)) + { + List allowedIds = await _contactGetter.GetAllContactUserTGIds(_userGetter.GetUserIDbyTelegramID(update.Message.Chat.Id)); + string name = _userGetter.GetUserNameByID(contactId); + if (name == "" || !allowedIds.Contains(_userGetter.GetTelegramIDbyUserID(contactId))) + { + await CommonUtilities.AlertMessageAndShowMenu(botClient, update, chatId, Config.GetResourceString("NoUserFoundByID")); + return; + } + await botClient.SendMessage(chatId, string.Format(Config.GetResourceString("WillWorkWithContact"), contactId, name), cancellationToken: cancellationToken, + replyMarkup: ReplyKeyboardUtils.GetSingleButtonKeyboardMarkup(Config.GetResourceString("NextButtonText"))); + } + else + { + string link = update.Message.Text!; + contactId = _contactGetter.GetContactIDByLink(link); + List allowedIds = await _contactGetter.GetAllContactUserTGIds(_userGetter.GetUserIDbyTelegramID(update.Message.Chat.Id)); + + if (contactId == -1 || !allowedIds.Contains(_userGetter.GetTelegramIDbyUserID(contactId))) + { + await CommonUtilities.AlertMessageAndShowMenu(botClient, update, chatId, Config.GetResourceString("NoUserFoundByLink")); + return; + } + string name = _userGetter.GetUserNameByID(contactId); + await botClient.SendMessage(chatId, string.Format(Config.GetResourceString("WillWorkWithContact"), contactId, name), cancellationToken: cancellationToken); + } + userState.userId = _userGetter.GetUserIDbyTelegramID(chatId); + userState.targetContactId = contactId; + await botClient.SendMessage(chatId, Config.GetResourceString("InputNewDisplayName"), cancellationToken: cancellationToken, + replyMarkup: ReplyKeyboardUtils.GetSingleButtonKeyboardMarkup(Config.GetResourceString("ResetDisplayNameButtonText"))); + userState.currentState = UserRenameContactState.WaitingForNewName; + break; + + case UserRenameContactState.WaitingForNewName: + if (await CommonUtilities.HandleStateBreakCommand(botClient, update, chatId)) return; + + string newName = update.Message!.Text!; + string? displayName = newName.Equals(Config.GetResourceString("ResetDisplayNameButtonText"), StringComparison.OrdinalIgnoreCase) + ? null + : newName; + + await ReplyKeyboardUtils.RemoveReplyMarkup(botClient, chatId, cancellationToken); + + bool success = _contactSetter.SetContactDisplayName(userState.userId, userState.targetContactId, displayName); + UserSessionManager.Remove(chatId); + + if (success) + { + string resultText = displayName != null + ? string.Format(Config.GetResourceString("DisplayNameSet"), displayName) + : Config.GetResourceString("DisplayNameReset"); + await CommonUtilities.AlertMessageAndShowMenu(botClient, update, chatId, resultText); + } + else + { + await CommonUtilities.AlertMessageAndShowMenu(botClient, update, chatId, Config.GetResourceString("ActionCancelledError")); + } + break; + } + } +} diff --git a/TelegramBot/States/States.cs b/TelegramBot/States/States.cs index 3608180..704589c 100644 --- a/TelegramBot/States/States.cs +++ b/TelegramBot/States/States.cs @@ -58,6 +58,13 @@ public enum UserInboundState Finish } +public enum UserRenameContactState +{ + WaitingForLinkOrID, + WaitingForNewName, + Finish +} + public enum UsersStandardState { ProcessAction,