I've been trying to swap over my code from the 0.9.6 Discord.NET API to the new 1.0.1 API, and it's basically calling for a complete restructure to my code. But I've been having some trouble actually getting the bot up and running first of all.
I set up the code body according to the guide linked here
And while it runs without error, the bot itself is not appearing online in my server.
And before you ask, I had in fact replaced "Bot token here" with the actual bot token.
namespace DiscordBot{
public class Program
{
private CommandService commands;
private DiscordSocketClient client;
private IServiceProvider services;
static void Main(string[] args) => new Program().Start().GetAwaiter().GetResult();
public async Task Start()
{
client = new DiscordSocketClient();
commands = new CommandService();
string token = "<token>";
services = new ServiceCollection()
.BuildServiceProvider();
await InstallCommands();
await client.LoginAsync(TokenType.Bot, token);
await client.StartAsync();
await Task.Delay(-1);
}
public async Task InstallCommands()
{
// Hook the MessageReceived Event into our Command Handler
client.MessageReceived += HandleCommand;
// Discover all of the commands in this assembly and load them.
await commands.AddModulesAsync(Assembly.GetEntryAssembly());
}
public async Task HandleCommand(SocketMessage messageParam)
{
// Don't process the command if it was a System Message
var message = messageParam as SocketUserMessage;
if (message == null) return;
// Create a number to track where the prefix ends and the command begins
int argPos = 0;
// Determine if the message is a command, based on if it starts with '!' or a mention prefix
if (!(message.HasCharPrefix('!', ref argPos) || message.HasMentionPrefix(client.CurrentUser, ref argPos))) return;
// Create a Command Context
var context = new CommandContext(client, message);
// Execute the command. (result does not indicate a return value,
// rather an object stating if the command executed successfully)
var result = await commands.ExecuteAsync(context, argPos, services);
if (!result.IsSuccess)
await context.Channel.SendMessageAsync(result.ErrorReason);
}
}
}
And then for the MyBot.cs class
namespace DiscordBot
{
class MyBot : ModuleBase
{
private CommandService _service;
public MyBot(CommandService service)
{
_service = service;
}
}
}
The first thing you might want to do is add some logging to your bot.
As your code might be correct, but discord could be rejecting your connection for any amount of reason.
After await client.StartAsync(); add
client.Log += (msg) => {return Console.WriteLine(${msg.ToString()}");};`
This will output the message your receive from your client to the console.
Now you also need to configure which message should be send to this event. This can be done when creating your DiscordClient(). So instead of client = new DiscordSocketClient(); You could use
client = new DiscordSocketClient(
new DiscordSocketConfig()
{
LogLevel = LogSeverity.Verbose
}
);
Verbose should give you all the information you need. However you could also use LogSeverity.Debug instead, which is the highest available logging, and therefore would give you all messages.
Now that you have some feedback in your console, go and see what concrete errors you are having.
Also I would recommend first completing the your first bot part of the linked tutorial, instead of stepping into the commands directly. Once you got this working, you can continue onwards
Related
I'm building a Discord bot using Discord.NET which is supposed to report a user joining, leaving, being kicked and being banned to a mod/admin-only auditing channel, except I can't get the relevant function to trigger when a user joins the guild.
I removed my message sending code and replaced it with a console output to determine whether or not the problem is the event not triggering or the send message request not working and found the event wasn't being triggered.
I've enabled all the intents in the Bot management console.
I've added intents according to the documentation:
var socketConfig = new DiscordSocketConfig
{
GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.GuildMembers | GatewayIntents.GuildBans
};
_client = new DiscordSocketClient(socketConfig);
I've then registered the event: (Note: _client = client; is used before this)
client.UserJoined += UserJoined;
And finally written the function to be triggered:
public Task UserJoined(SocketGuildUser user)
{
Console.Write("User ");
Console.Write(user.Username);
Console.WriteLine(" joined.");
return Task.CompletedTask;
}
Is there anything I've not done or done wrong here? I've spent about two days on this with absolutely no idea what the problem is, everything seems to point to me doing everything right yet I'm unable to receive this event from Discord.
Every change I've made I've additionally tried removing and re-adding the bot to my test server. I also don't receive any kind of message similar to [12/8/2018 10:01:30 PM at Gateway] Received Dispatch (GUILD_MEMBER_ADD) as mentioned in this post.
I've included my full Program.cs below:
using System;
using Discord;
using Discord.Net;
using Discord.Commands;
using Discord.WebSocket;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Json;
using Microsoft.Extensions.DependencyInjection;
using BanBot2.Bot.Services;
namespace BanBot2.Bot
{
class Program
{
// setup our fields we assign later
private readonly IConfiguration _iconfig;
private DiscordSocketClient _client;
static void Main(string[] args) => new Program().MainAsync().GetAwaiter().GetResult();
public Program()
{
var socketConfig = new DiscordSocketConfig
{
GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.GuildMembers | GatewayIntents.GuildBans
};
_client = new DiscordSocketClient(socketConfig);
// create the configuration
var _builder = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile(path: "application.json");
// build the configuration and assign to _config
_iconfig = _builder.Build();
}
public async Task MainAsync()
{
// call ConfigureServices to create the ServiceCollection/Provider for passing around the services
using (var services = ConfigureServices())
{
// get the client and assign to client
// you get the services via GetRequiredService<T>
var client = services.GetRequiredService<DiscordSocketClient>();
_client = client;
// setup logging and the ready event
client.Log += LogAsync;
client.Ready += ReadyAsync;
client.UserJoined += UserJoined;
services.GetRequiredService<CommandService>().Log += LogAsync;
// this is where we get the Token value from the configuration file, and start the bot
await client.LoginAsync(TokenType.Bot, _iconfig["Token"]);
await client.StartAsync();
// we get the CommandHandler class here and call the InitializeAsync method to start things up for the CommandHandler service
await services.GetRequiredService<CommandHandler>().InitializeAsync();
await Task.Delay(-1);
}
}
public Task UserJoined(SocketGuildUser user)
{
Console.Write("User #");
Console.Write(user.Username);
Console.WriteLine(" joined.");
return Task.CompletedTask;
}
private Task LogAsync(LogMessage log)
{
Console.WriteLine(log.ToString());
return Task.CompletedTask;
}
private Task ReadyAsync()
{
Console.WriteLine($"Connected as -> [{_client.CurrentUser}] :)");
return Task.CompletedTask;
}
// this method handles the ServiceCollection creation/configuration and builds out the service provider we can call on later
private ServiceProvider ConfigureServices()
{
// this returns a ServiceProvider that is used later to call for those services
// we can add types we have access to here, hence adding the new using statement:
// using BanBot2.Bot.Services;
// the config we build is also added, which comes in handy for setting the command prefix!
return new ServiceCollection()
.AddSingleton(_iconfig)
.AddSingleton<DiscordSocketClient>()
.AddSingleton<CommandService>()
.AddSingleton<CommandHandler>()
.BuildServiceProvider();
}
}
}
You create an instance of DiscordSocketClient and include the appropriate DiscordSocketConfig
//good
var socketConfig = new DiscordSocketConfig
{
GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.GuildMembers | GatewayIntents.GuildBans
};
_client = new DiscordSocketClient(socketConfig);
However, in ConfigureServices you should be adding the DiscordSocketClient that you created previously to the ServiceProvider. Instead you do this
//bad
.AddSingleton<DiscordSocketClient>()
while what you should have done is
//good
.AddSingleton(_client)
Because you didn't add an instance of the client to your service provider in configure services, retrieving the client from the service provider will actually instantiate a new DiscordSocketClient as one does not yet exist in the provider.
var client = services.GetRequiredService<DiscordSocketClient>();
tl;dr - ensure you are only using a single instance of DiscordSocketClient. This instance should be configured with the appropriate intents.
The bot in Discord
As you can see, it responds to the request, but then doesn't do anything with the number I've entered. It just sits there.
Here is my code for that command (currently it's just copied from the DSharp tutorial website, I planned to modify it significantly, but it won't even do the standard):
[Command("waitforcode"), Description("Waits for a response containing a generated code.")]
public async Task WaitForCode(CommandContext ctx)
{
// first retrieve the interactivity module from the client
var interactivity = ctx.Client.GetInteractivity();
// generate a code
var codebytes = new byte[8];
using (var rng = RandomNumberGenerator.Create())
rng.GetBytes(codebytes);
var code = BitConverter.ToString(codebytes).ToLower().Replace("-", "");
// announce the code
await ctx.RespondAsync($"The first one to type the following code gets a reward: `{code}`");
// wait for anyone who types it
var msg = await interactivity.WaitForMessageAsync(xm => xm.Content.Contains(code), TimeSpan.FromSeconds(60));
if (!msg.TimedOut)
{
// announce the winner
await ctx.RespondAsync($"And the winner is: {msg.Result.Author.Mention}");
}
else
{
await ctx.RespondAsync("Nobody? Really?");
}
}
If anybody could help me, that would be great!
I am using Azure Event Hub library for C# in WebApi App. And I got this exception on message sending:
"Message":"An error has occurred.",
"ExceptionMessage":"Cannot allocate more handles to the current session or connection. The maximum number of handles allowed is 4999. Please free up resources and try again., referenceId: d975f39c71a14ae5915c9adca322e110_G15"
"ExceptionType":"Microsoft.ServiceBus.Messaging.QuotaExceededException"
I thought that instantiantion of EventHubProducerClient one time and reusing it instead of creating its instance on each message sending (with IAsyncDisposable pattern) will help as mentioned here
EventHub Exception :Cannot allocate more handles to the current session or connection. But it did not.
I believe there might be some more global issue. Might be missing something.
I am using single event hub with 7 consumer groups each of which is used by separate web application (single); well actually there is additional consumer group ($Default) but it is not used;
For receiving messages I use EventProcessorClient;
I use Azure.Messaging.EventHubs 5.2.0 and Azure.Messaging.EventHubs.Processor 5.2.0 packages;
Here is the whole code (did everything according to quickstart):
public class EventHubService : SubscriberBase
{
private readonly Action<string> errorHandler;
private readonly BlobContainerClient blobContainerClient;
private readonly EventProcessorClient eventProcessorClient;
private readonly EventHubProducerClient eventHubProducerClient;
private readonly int eventsToCheckpoint;
private readonly Timer checkpointTimer;
private int eventsSinceLastCheckpoint;
private bool shouldUpdateCheckpoint;
public EventHubService(EventHubSettings settings, Action<string> errorHandler) : base()
{
this.errorHandler = errorHandler;
eventHubProducerClient = new EventHubProducerClient(settings.ConnectionString, settings.EventHubName);
if (!String.IsNullOrWhiteSpace(settings.GroupId))
{
eventManager = new EventManager();
blobContainerClient = new BlobContainerClient(settings.StorageConnectionString, settings.BlobContainerName);
eventProcessorClient = new EventProcessorClient(blobContainerClient, settings.GroupId, settings.ConnectionString, settings.EventHubName);
eventsToCheckpoint = settings.EventsToUpdateCheckpoint;
checkpointTimer = new Timer(settings.IntervalToUpdateCheckpoint.TotalMilliseconds);
checkpointTimer.Elapsed += (sender, eventArgs) => shouldUpdateCheckpoint = true;
}
}
public override void Start()
{
eventProcessorClient.ProcessErrorAsync += HandleError;
eventProcessorClient.ProcessEventAsync += ProcessEventData;
eventProcessorClient.StartProcessingAsync().Wait();
checkpointTimer.Start();
}
public override async Task Stop()
{
try
{
checkpointTimer.Stop();
await eventProcessorClient.StopProcessingAsync();
}
finally
{
eventProcessorClient.ProcessEventAsync -= ProcessEventData;
eventProcessorClient.ProcessErrorAsync -= HandleError;
}
}
public override async Task Publish(string topic, JObject message)
{
using (EventDataBatch eventBatch = await eventHubProducerClient.CreateBatchAsync())
{
var #event = new Event(topic, message);
string json = #event.ToString(Formatting.None);
byte[] bytes = Encoding.UTF8.GetBytes(json);
var eventData = new EventData(bytes);
eventBatch.TryAdd(eventData);
await eventHubProducerClient.SendAsync(eventBatch);
}
}
private async Task ProcessEventData(ProcessEventArgs eventArgs)
{
if (eventArgs.CancellationToken.IsCancellationRequested)
{
return;
}
if (++eventsSinceLastCheckpoint >= eventsToCheckpoint)
{
eventsSinceLastCheckpoint = 0;
shouldUpdateCheckpoint = true;
}
if (shouldUpdateCheckpoint)
{
await eventArgs.UpdateCheckpointAsync();
shouldUpdateCheckpoint = false;
}
string json = Encoding.UTF8.GetString(eventArgs.Data.Body.ToArray());
var #event = new Event(json);
eventManager.TryRaise(#event);
}
private Task HandleError(ProcessErrorEventArgs eventArgs)
{
if (!eventArgs.CancellationToken.IsCancellationRequested)
{
errorHandler.Invoke($"[P:{eventArgs.PartitionId}][O:{eventArgs.Operation}] {eventArgs.Exception.Message}");
}
return Task.CompletedTask;
}
}
I have found some info in Service Bus Quotas like:
Number of concurrent receive requests on a queue, topic, or subscription entity (5000).
Subsequent receive requests are rejected, and an exception is received
by the calling code. This quota applies to the combined number
of concurrent receive operations across all subscriptions on a topic.
But still can't figure how to deal with it.
Please help.
Thanks.
This is indeed the answer EventHub Exception :Cannot allocate more handles to the current session or connection.
I did similar "fix" for Azure Event Hub library for NET Core but I have forgotten that I am also using Azure Event Hub library for NET Framework!
So I have instantiated EventHubProducerClient one time and reusing it now.
Seems working fine.
My bad. Was not attentive enough.
In my case, except creating only one instance of Client, use only one instance of sender.
I used method CreateSender each time when send a messsage, it also generates an exception
I'm working on a Winforms app that executes SQL Procedures through a SignalR client. I'm relatively new to using SignalR and am still wrapping my head around it.
I start off by running my connection method to establish a connection with my SignalR service. I have two addresses configured ready for when I puslish but the DEV configuration leads to the SignalR service I am hosting locally.
Connection to SignalR (ConnectHub)
private async Task ConnectHub()
{
string hubAddress = "";
#if DEBUG
HubAddress = ConfigurationManager.AppSettings["HubAddress_DEV"];
#else
HubAddress = ConfigurationManager.AppSettings["HubAddress_PROD"];
#endif
if (string.IsNullOrEmpty(hubAddress))
{
MessageBox.Show("Hub Address is missing from configuration.");
}
ConnectionHandler.Client = new HubClient(hubAddress, "MyHub");
ConnectionHandler.Client.MyAlert += ConnectionHandler.ClientOnMyAlert;
ConnectionHandler.Client.ServerErrorEvent += ConnectionHandler.ClientOnServerErrorEvent;
await ConnectionHandler.Client.Connect(new List<string>() {
VehicleInfo.ThisVehicle.WarehouseCode,
VehicleInfo.ThisVehicle.VehicleName
});
}
My client is stored globally in my ConnectionHandler class where my event handlers are also kept. (I have breakpoints on these as I have not implemented them yet)
ConnectionHandler Class
public static class ConnectionHandler
{
public static HubClient Client { get; set; }
public static void ClientOnServerErrorEvent(string error)
{
throw new NotImplementedException(); //Currently not implemented
}
public static async Task ClientOnMyAlert(EnumMyAlertType alerttype, string message, Exception exception)
{
await Task.Yield(); //Currently not implemented
}
}
When I call the code to Invoke the procedure in my SignalR client, it returns a DataTable to me which is the intended result.
Call to SignalR
await ConnectHub();
DataTable dt = await ConnectionHandler.Client.Connection.InvokeCoreAsync<DataTable>(
"FetchStatuses",
new object[0]); //This call works as intended and returns a populated DataTable
StatusInfo = new CStatuses();
All the above code is currently done on the main form, however I wanted to move this call to SignalR into a constructor to try and tidy things up.
The problem comes when I try to move this call into another method, the program hangs as I don't think it has received the return value from SignalR, I have placed a breakpoint beneath it and it is not reached. A TryCatch reveals nothing as it hangs within the "Try" with no exception.
Calling from contructor
public CStatuses()
{
Statuses = new List<CStatus>();
var dataTable = ConnectionHandler.Client.Connection.InvokeCoreAsync<DataTable>("FetchStatuses",
new object[0])
.Result; //My program hangs on this line and proceeds no further
I am at a loss as to why it is doing this when I can get a value from the client from the form and when other members of my team have tried to do the same thing they can make a call to SignalR also from a different method.
Does anyone have any ideas as to how I can make this work?
I realize this has gotten quite long but if I can elaborate on things please let me know
FIXED CODE THANKS TO SOLUTION:
I have moved the code from my CStatuses constructor into a new async method within the same class and called it after initialization. This removes the need for .Result and appears to solve the problem for me.
public async Task PopulateStatuses()
{
var dataTable = await ConnectionHandler.Client.Connection.InvokeCoreAsync<DataTable>("FetchStatuses",
new object[0]);
Statuses = new List<CStatus>();
foreach (DataRow row in dataTable.Rows)
{
var status = new CStatus
{
StatusId = Common.Utility.GetInt16Value(row["StatusID"]),
StatusCode = Common.Utility.GetStringValue(row["StatusCode"]),
Description = Common.Utility.GetStringValue(row["Description"])
};
Statuses.Add(status);
}
}
You are running into a deadlock with the .Result call, I would suggest creating an async method in the CStatuses class and after you initialize your CStatuses class call the websocket for data.
I have created a chatbot in web channel and direct line.
When i tested in bot emulator i get the right response and when i try to test the same intent in localhost ( webchat) i got different response.
I will show you and example :
call an agent
give me your customer number
( after custemer number was sended ) are you sure ?
if you click Yes ...the data are stored in database ( sql server )
If you do the save in localhost you get : You cancelled the form ( in fact i havent cancell any form
Here is the luisdialog here i call the form :
[LuisIntent("human")]
public async Task human(IDialogContext context, LuisResult result)
{
var form = new FormDialog<Human>(
new Human(),
Human.BuildForm,
FormOptions.PromptInStart,
result.Entities);
context.Call<Human>(form, LossFormCompleted)
}
private async Task LossFormCompleted(IDialogContext context,
IAwaitable<Human> result)
{
HumanCall form = null;
try
{
form = await result;
}
catch (OperationCanceledException)
{
}
if (form == null)
{
await context.PostAsync("You cancelled the form.");
}
else
{
//call the LossForm service to complete the form fill
var message = $"Your data are stored in database";
await context.PostAsync(message);
}
context.Wait(this.MessageReceived);
}
The form model is :
[Serializable]
public class Human
{
[Prompt("What is your contract number?")]
public string contract;
public static IForm<Human> BuildForm()
{
OnCompletionAsyncDelegate<HumanCall> wrapUpRequest = async (context, state) =>
{
using (BotModelDataContext BotDb = new BotModelDataContext())
{
tblBot bot = new tblBot();
bot = BotDb.tblBots.SingleOrDefault(q => q.Reference == state.contract);
if (bot != null)
{
using (bbbserviceSoapClient cws = new bbbserviceSoapClient())
{
viewc a= new viewc();
a.Lastname = bot.Lastname;
}
}
}
};
return new FormBuilder<Human>().Message
("can you send us some info ?")
.Field(nameof(contract))
.OnCompletion(wrapUpRequest)
.Confirm("Are you sure: Yes or No. ")
.Build();
}
}
}
Can someone help me where i'm wrong ? What can i do to retrieve the same response ? It's about timeout problem or what do you thing ?
I do a test based on the code that you provided and make slight modifications, and I find that if some exceptions occur in wrapUpRequest method, it would show "You cancelled the form" instead of the message "Your data are stored in database".
So I suspect that exceptions occurring in wrapUpRequest method (perhaps database query issue or request sent by bbbserviceSoapClient is timeout etc) when you do test via web chat, which causes the issue.
To troubleshoot the issue, you can try to implement/write custom log to detect if any exception occurs within wrapUpRequest method when you test via web chat.