How can I write unit test for Telegram bot in C#? - c#

I wrote the telegram bot, and it works perfectly, BUT I still didn't understand how can I write the unit test for this? I mean I have to make a fake messages from my bot. I realized that I need to use moq instead real telegramm bot client. But I still didn't understand how exactly it may look like?
Here is my method, I want UnitTesting:
private readonly string _token = "<token value>";
private ITelegramBotClient _client;
public async void Comunicate()
{
_client = new TelegramBotClient(_token);
var receiverOptions = new ReceiverOptions
{
AllowedUpdates = { }
};
_client.StartReceiving(HandleUpdateAsync, HandleErrorAsync, receiverOptions, cancellationToken: _cts.Token);
var me = await _client.GetMeAsync();
Console.WriteLine($"Start listening for #{me.Username}");
Console.ReadLine();
async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
{
if (update.Type != UpdateType.Message)
return;
if (update.Message!.Type != MessageType.Text)
return;
var chatId = update.Message.Chat.Id;
var messageText = update.Message.Text;
// here messages processed, I want to process fake messages without telegramm API using
string valuteCode = "";
string requredDate = "";
BotActions botActions = ProcessTextMessage(messageText, ref valuteCode, ref requredDate);
string replyMessage = GetReply(botActions, requredDate, valuteCode);
Console.WriteLine($"Received a '{messageText}' message in chat {chatId}.");
Message sentMessage = await botClient.SendTextMessageAsync(
chatId: chatId,
text: replyMessage,
cancellationToken: cancellationToken);
}
Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception, CancellationToken cancellationToken)
{
var ErrorMessage = exception switch
{
ApiRequestException apiRequestException
=> $"Telegram API Error:\n[{apiRequestException.ErrorCode}]\n{apiRequestException.Message}",
_ => exception.ToString()
};
Console.WriteLine(ErrorMessage);
return Task.CompletedTask;
}
In Unit test I just have to call method Comunicate() and send ho handler fake testing messages without real messages sending in telegram. Unfortunatelly I don't understand how to do it. I know that I can create another bot just for testing this one but this not the way I want to go.
I would be grateful for any help.

Related

How to get a message for editing Discord .NET

I am doing a command for my bot to edit a message sent by it
But I need to get the message to edit it
I found a function that obtains it but returns an IMessage which does not have the ModifyAsync() function
This is my code:
[Command("editmessage")]
public async Task EditMessage(string sMessageId, string TextMessage) {
ulong MessageId = Convert.ToUInt64(sMessageId);
var Message = Context.Channel.GetMessageAsync(MessageId);
await Message.ModifyAsync(msg => msg.Content = TextMessage); //This gives me an error because "IMessage" does not contain a definition for "ModifyAsync"
}
Is there a way to go from IMessage to IUserMessage? (IUserMessage if it has the ModifyAsync() function)
Forgive me for my bad english
What you need to do is await for the operation to complete asynchronously via await. Don't use .Result here as that is a blocking operation.
[Command("editmessage")]
public async Task EditMessage(string messageId, string textMessage)
{
var id = Convert.ToUInt64(messageId);
var msg = await Context.Channel.GetMessageAsync(id);
if ( msg is IUserMessage message )
{
await message.ModifyAsync(m => m.Content = textMessage);
}
}
Taking into account what I have been told, I did tests I found this solution:
[Command("editmessage")]
public async Task EditMessage(string sMessageId, string TextMessage) {
ulong MessageId = Convert.ToUInt64(sMessageId);
var _Message = Context.Channel.GetMessageAsync(MessageId).Result; //It is important to put the `.Result`
var Message = _Message as IUserMessage;
await Message.ModifyAsync(msg => msg.Content = TextMessage);
}

Why can't my bot dm users a welcome message? (Discord.net C#)

I've been working on a discord bot for the past few days, and I managed to get one of the functions working: (a command that sets a message it's supposed to dm users when they join). I can't seem to get the bot to send the actual message.
private async Task Join(SocketGuildUser UID)
{
if (UID.IsBot || UID.IsWebhook) return;
Welcometxt= File.ReadAllText([FILE]);
await UID.SendMessageAsync("Your Message Was Sucessfully set!");
}
private async Task HandleCommandAsync(SocketMessage arg)
{
var message = arg as SocketUserMessage;
var context = new SocketCommandContext(_client, message);
if (message.Author.IsBot) return;
int argPos = 0;
if (message.HasStringPrefix("!", ref argPos))
{
var result = await _commands.ExecuteAsync(context, argPos, _services);
if (!result.IsSuccess) Console.WriteLine(result.ErrorReason);
}
}
When i check the logs it gives a null reference error with the var message and context, I tried googling the error and changing up the code but to no avail any advice? I believe the error is in one of these two methods but im not 100% positive
I guess you can use UserJoined event to achieve this.
Define event handler for UserJoined
public async Task UserJoined(SocketGuildUser user)
{
await user.SendMessageAsync("Hello");
}
Register it
private readonly DiscordSocketClient _client = new DiscordSocketClient();
private readonly CommandService _commandService = new CommandService();
public async Task MainAsync()
{
....
_client.UserJoined += UserJoined;
....
}
I'm not sure enough if this is related, but check if the Server Member Intents is on

TLSharp error when uploading several files

I'm using TLSharp. My goal is to send files to the user. I created ASP.NET Core Web API service and make HTTP request when I need to send file.
It works well with one file but every time when I get 2 or more requests in a short period of time I get an error:
System.InvalidOperationException: invalid checksum! skip.
Controller:
[Route("upload/{driveId}")]
public async Task<ActionResult> Upload(string driveId)
{
var ms = new MemoryStream();
var file = service.Files.Get(driveId);
string filename = file.Execute().Name;
await file.DownloadAsync(ms);
ms.Position = 0;
new FileExtensionContentTypeProvider().TryGetContentType(filename, out var mime);
var stream = new StreamReader(ms, true);
await _client.SendFileToBot(filename, mime, stream, driveId);
return Ok();
}
SendFileToBot method:
public async Task SendFileToBot(string filename, string mime, StreamReader stream)
{
var found = await client.SearchUserAsync("username", 1);
//find user
var userToSend = found.Users
.Where(x => x.GetType() == typeof(TLUser))
.Cast<TLUser>()
.FirstOrDefault(x => x.Id == 1234567);
var fileResult = await client.UploadFile(filename, stream);
var attr = new TLVector<TLAbsDocumentAttribute>()
{
new TLDocumentAttributeFilename { FileName = filename }
};
var bot = new TLInputPeerUser() { UserId = userToSend.Id, AccessHash = userToSend.AccessHash.Value };
await client.SendUploadedDocument(bot, fileResult, "caption", mime, attr);
}
When the requests are sent together (or in short period of time), they're sent in a single packet to Telegram server and this error occurs. I need help with this error. I've tried to use Task.Delay but it doesn't help.
How can I handle requests to avoid this error?
According this issue, you are not first person who received this error.
Seems like there are something request/response validation issues when using multithreading in TLSharp library.
There is one stable workaround for such type of problems.
Make all upload requests synchronous
Actually, they will be asynchronous, but with one-task-at-one-time access
This dirty but workable solution can be achieved by creating task queue:
public class TaskQueue
{
private readonly SemaphoreSlim _semaphoreSlim;
public TaskQueue()
{
_semaphoreSlim = new SemaphoreSlim(1, 1); // Max threads limited to 1.
}
public async Task<T> Enqueue<T>(Func<Task<T>> taskGenerator)
{
await _semaphoreSlim.WaitAsync();
try
{
return await taskGenerator();
}
finally
{
_semaphoreSlim.Release();
}
}
public async Task Enqueue(Func<Task> taskGenerator)
{
await _semaphoreSlim.WaitAsync();
try
{
await taskGenerator();
}
finally
{
_semaphoreSlim.Release();
}
}
}
Now you must register queue as singleton in Startup.cs file to be sure that your asp.net core application using one task queue instance to perform uploading on telegram servers:
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddSingleton<TaskQueue>();
//...
}
Next, get your task queue instance in your api controller constructor like:
private readonly TaskQueue taskQueue;
public MyController(TaskQueue taskQueue)
{
this.taskQueue = taskQueue
}
Then, just use it in all of your api methods:
// Code from your API method...
await taskQueue.Enqueue(() => client.SendUploadedDocument(bot, fileResult, "caption", mime, attr));
This will make all requests to telegram servers through TLSharp library synchronous and prevent multithreading issues like in a question.
To be honest, it's just a workaround, not solution of this problem. I'm sure that this issue on github about checksum error must be investigated more detailed and fixed if it possible.

Server-side C# Websocket, how run a loop while listening to the cancellation token?

In my Vue/.NET Core 2.3 project I have started replacing standard AJAX calls with Websockets where I'm implementing a Stock pricing streaming service to update the front end as when stock prices get updated in real time. The previous price update used to send a interval request from the FE every 5 seconds to get the new prices from method StockProcess().GetStockPricing(symbol)
The new Websocket implementation below is now using the same backend GetStockPricing() code however the check loop now is in the back end, so while client is connected the method will keep sending prices back.
The implementation works fine perfectly as a principle, if there is a price change, the method will send the update to the client, perfect.
APIController.cs
[HttpGet("GetStockPricingAsync", Name = "GetStockPricingAsync")]
public async Task GetStockPricingAsync(string symbol)
{
var isSocketRequest = ControllerContext.HttpContext.WebSockets.IsWebSocketRequest;
if (isSocketRequest)
{
WebSocket webSocket = await ControllerContext.HttpContext.WebSockets.AcceptWebSocketAsync();
await _mainController.GetStockPricingAsync(ControllerContext.HttpContext, webSocket, symbol);
}
else
{
ControllerContext.HttpContext.Response.StatusCode = 400;
}
}
Implementation.cs
public async Task GetStockPricingAsync(HttpContext context, WebSocket webSocket, string symbol)
{
var lastUpdated = DateTime.MinValue;
bool isNotCancelled = true;
byte[] requestBuffer = new byte[4194304];
while (webSocket.State == WebSocketState.Open || webSocket.State == WebSocketState.CloseSent)
{
while (isNotCancelled)
{
var price = new StockProcess().GetStockPricing(symbol);
if (lastUpdated != price.LastPriceDate)
{
lastUpdated = price.LastPriceDate;
var json = JsonConvert.SerializeObject(new ServerData(price, _eventMessage), _jsonSerializerSettings);
requestBuffer = Encoding.ASCII.GetBytes(json);
var arraySegment = new ArraySegment<byte>(requestBuffer);
await webSocket.SendAsync(arraySegment, WebSocketMessageType.Text, true, CancellationToken.None);
}
Thread.Sleep(300);
}
WebSocketReceiveResult result = await webSocket.ReceiveAsync(requestBuffer, CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
{
isNotCancelled = false
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
break;
}
}
}
The issue is I cannot cancel the running GetStockPricing loop:
The problem is with line WebSocketReceiveResult result = await webSocket.ReceiveAsync(requestBuffer, CancellationToken.None); does not get executed since the loop is always running, if I move this line to be executed before the loop then the loop does not get triggered as well, so I'm unable to get both loop running and ReceiveAsync listening for a cancellation token. Is there a way to achieve this? running the loop while listening for cancellation tokens?
try listening for it using a loop inside System.Threading.Tasks.Task Run
Task.Run(() => CancellationTokenFunc(...), ...);
static void CancellationTokenFunc(){
while(true){
//check for cancel
}
}
do this before the webSocket.State loop

Discord Bot Event for Members Joining Channel

I want my Discord bot to greet members when they join a channel. I have been unable to find an event that fires when this happens. I have tried myClient.UserJoined += MyMethod; and others but they never get fired as I hope. Here is my main code:
public class Program
{
private DiscordSocketClient _client;
private CommandService _commands;
private IServiceProvider _services;
static void Main(string[] args)
=> new Program().RunBotAsync().GetAwaiter().GetResult();
public async Task RunBotAsync()
{
_client = new DiscordSocketClient();
_commands = new CommandService();
_services = new ServiceCollection()
.AddSingleton(_client)
.AddSingleton(_commands)
.BuildServiceProvider();
string botToken = // removed
_client.Log += Log;
await RegisterCommandsAsync();
await _client.LoginAsync(TokenType.Bot, botToken);
await _client.StartAsync();
await Task.Delay(-1);
}
private Task Log(LogMessage arg)
{
Console.WriteLine(arg);
return Task.CompletedTask;
}
public async Task RegisterCommandsAsync()
{
_client.MessageReceived += HandleCommandAsync;
_client.UserJoined += JoinedAsync; // Something like this to notify bot when someone has joined chat?
await _commands.AddModulesAsync(Assembly.GetEntryAssembly());
}
private Task JoinedAsync(SocketGuildUser arg)
{
throw new NotImplementedException();
}
private async Task HandleCommandAsync(SocketMessage arg)
{
var message = arg as SocketUserMessage;
if(message is null || message.Author.IsBot)
{
return;
}
int argPos = 0;
if (message.HasStringPrefix("!", ref argPos))
{
var context = new SocketCommandContext(_client, message);
await _commands.ExecuteAsync(context, argPos);
}
}
}
Thanks, and let me know if I can provide any more information.
Edit: The suggested link implements the UserJoined event, which only seems to trigger when a new member joins the channel. I need something that triggers everytime anyone logs in to the channel, even existing members.
Judging by the edit, I think you may have a slight mis conception of how the channels work.
Users join a guild, after which, they have become part of the guild.
After they join a guild, they are part of it, and the channels they are allowed to see. Hence there is no need to log into channels anymore.
Now what I think you want to achieve is sending a message in a channel / to a user whenever they switch from the offline state to the online state.
For this you could use the UserUpdated event. Where you can check the previous and the current status of a user, and send a message accordingly.
_client.UserUpdated += async (before, after) =>
{
// Check if the user was offline, and now no longer is
if(before.Status == UserStatus.Offline && after.Status != UserStatus.Offline)
{
// Find some channel to send the message to
var channel = e.Server.FindChannels("Hello-World", ChannelType.Text);
// Send the message you wish to send
await channel.SendMessage(after.Name + " has come online!");
}
}

Categories

Resources