How to use slash command in channel with JDA? - discord-jda

public class HelloWorldBot extends ListenerAdapter
{
public static void main(String[] args) throws LoginException
{
if (args.length < 1) {
System.out.println("You have to provide a token as first argument!");
System.exit(1);
}
// args[0] should be the token
// We don't need any intents for this bot. Slash commands work without any intents!
JDA jda = JDABuilder.createLight(args[0], Collections.emptyList())
.addEventListeners(new HelloWorldBot())
.setActivity(Activity.playing("Type /ping"))
.build();
jda.upsertCommand("ping", "Calculate ping of the bot").queue(); // This can take up to 1 hour to show up in the client
}
#Override
public void onSlashCommand(SlashCommandEvent event)
{
if (!event.getName().equals("ping")) return; // make sure we handle the right command
long time = System.currentTimeMillis();
event.reply("Pong!").setEphemeral(true) // reply or acknowledge
.flatMap(v ->
event.getHook().editOriginalFormat("Pong: %d ms", System.currentTimeMillis() - time) // then edit original
).queue(); // Queue both reply and edit
}
}
With the code above, I can make use slash command by DM the bot. However, how do I use slash command in a channel?
It says in the discord developer docs that:
In order to make Slash Commands work within a guild, the guild must
authorize your application with the applications.commands scope. The
bot scope is not enough.
How exactly do one do that with JDA?

To generate such an authorization URL you follow these steps:
Open your application dashboard
Open the tab OAuth2 after you selected your application
Generate an authorization URL with the scopes bot and applications.commands ticked
Copy that URL and open it in a new tab
Invite your bot to the guild with that link
Note that, as the comment here correctly points out, the global commands take up to 1 hour to propagate. If you want to test your bot, you can use guild commands which show up instantly.
To create a guild command use jda.getGuildById(guildId) to get the guild, then use the same methods for creating commands on that Guild instance instead of the JDA instance. Note that getting a guild requries for JDA to be ready, so make sure you call awaitReady() first after you build your JDA instance.

Related

What is the botframework security model?

I am exploring the Microsoft Bot Builder SDK to create a chat bot that integrates with MS Teams. Most of the provided samples do not have any authentication mechanisms and the samples that reference OAuth seem to do so for allowing the bot to access a resource using the on-behalf-of flow. Is correct way to think of the security model is that the bot should be considered public and any non-public information accessed is done from the context of the calling user?
The Bot Framework has three kinds of authentication/authorization to consider:
Bot auth - Microsoft app ID and password
Client auth - Direct Line secret/token, or various mechanisms for other channels
User auth - OAuth cards/prompts/tokens
Unfortunately there's some inconsistency in the documentation about which is which, but I've just raised an issue about that here: https://github.com/MicrosoftDocs/bot-docs/issues/1745
In any case, there's no need to think of all bots as "public." The Bot Builder SDK authenticates both incoming messages and outgoing messages using its app ID and password. This means any unauthorized messages sent to the bot's endpoint will be rejected, and no other bot can impersonate yours.
In general you should have the user sign in if you want the bot to access secure information on the user's behalf. But since you mentioned wanting to restrict bot access to specific tenants, I can briefly explain how to do that. You can find middleware here that does it in C#, and here's a modified version of the code that I think improves on it by using a hash set instead of a dictionary:
public class TeamsTenantFilteringMiddleware : IMiddleware
{
private readonly HashSet<string> tenantMap;
public TeamsTenantFilteringMiddleware(IEnumerable<string> allowedTenantIds)
{
if (allowedTenantIds == null)
{
throw new ArgumentNullException(nameof(allowedTenantIds));
}
this.tenantMap = new HashSet<string>(allowedTenantIds);
}
public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default(CancellationToken))
{
if (!turnContext.Activity.ChannelId.Equals(Channels.Msteams, StringComparison.OrdinalIgnoreCase))
{
await next(cancellationToken).ConfigureAwait(false);
return;
}
TeamsChannelData teamsChannelData = turnContext.Activity.GetChannelData<TeamsChannelData>();
string tenantId = teamsChannelData?.Tenant?.Id;
if (string.IsNullOrEmpty(tenantId))
{
throw new UnauthorizedAccessException("Tenant Id is missing.");
}
if (!this.tenantMap.Contains(tenantId))
{
throw new UnauthorizedAccessException("Tenant Id '" + tenantId + "' is not allowed access.");
}
await next(cancellationToken).ConfigureAwait(false);
}
}

Azure AD authentication with Bot Framework SDK V4

I am using Bot Framework SDK V4 (.Net) for building my Bot Service. I would like to enable authentication using Azure AD.
I found these steps - https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-tutorial-authentication?view=azure-bot-service-3.0
But this is for SDK V3, which is not working for V4
Can someone help on how to enable Azure AD Authentication for bots built using V4 framework?
I know this is a bit late answer but it might help someone. You need to create your bot service in Azure and get Microsoft App Id and App Password.
Then you can prompt the user to sign in.
private static OAuthPrompt Prompt(string connectionName)
{
return new OAuthPrompt(
LoginPromptName,
new OAuthPromptSettings
{
ConnectionName = connectionName,
Text = "Please Sign In",
Title = "Sign In",
Timeout = 300000, // User has 5 minutes to login (1000 * 60 * 5)
});
}
Create a WaterfallStep to login Prompt.
private static async Task<DialogTurnResult> PromptStepAsync(WaterfallStepContext step, CancellationToken cancellationToken)
{
return await step.BeginDialogAsync(LoginPromptName, cancellationToken: cancellationToken);
}
Next you can can continue to do what ever you want with the token.
private static async Task<DialogTurnResult> LoginStepAsync(WaterfallStepContext step, CancellationToken cancellationToken)
{
// Get the token from the previous step. Note that we could also have gotten the
// token directly from the prompt itself. There is an example of this in the next method.
var tokenResponse = (TokenResponse)step.Result;
if (tokenResponse != null)
{
// use the token to do exciting things!
}
else
{
// If Bot Service does not have a token, send an OAuth card to sign in
}
await step.Context.SendActivityAsync("Login was not successful please try again.", cancellationToken: cancellationToken);
return Dialog.EndOfTurn;
}
Follow this guide for more information.
You can even set other OAuth providers for Azure like Github, Facebook. To do this go to Settings of your Bot Channels Registration and find add new connection option.
I'm using the botframework community's AzureAdAuthMiddleware to inject the Azure AD authentication functionality into my v4 chatbot.
You can check it out here: https://github.com/BotBuilderCommunity/botbuilder-community-dotnet

Microsoft Bot Framework: Exception: The data is changed

I have a bot with the following conversation scenario:
Send text to LUIS
LUIS intent calls context.Call(...) to launch a Dialog
This dialog terminates, save some info in the userData:
private static async Task storeBotData(IDialogContext context, BotData userData)
{
Activity activity = (Activity)context.Activity;
StateClient sc = activity.GetStateClient();
await sc.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, userData);
}
And after it call another dialog, again with context.Call(...).
Then the last dialog runs and terminates.
My problem is that when updating the user data at the end of the first dialog (step 3), I have the following exception in the Bot Framework Channel Emulator:
`Exception: The data is changed [File of type 'text/plain']`...
What happens here ? I think that when a dialog terminates, it call setUserData by itself, but I don't understand why I can't update userData anywhere in the code...
I have tried to catch the exception, but nothing is catched.. But I know that the userData is updated, because when I try to retrieve it back, it is updated...
Any help is welcome :)
Thanks
Botframework restores/saves state of conversation after each act of activity, so under the covers typical flow looks like the following:
[23:15:40] <- GET 200 getUserData
[23:15:47] <- GET 200 getConversationData
[23:15:47] <- GET 200 getPrivateConversationData
...
[23:16:42] <- POST 200 setConversationData
[23:16:42] <- POST 200 setUserData
[23:16:42] <- POST 200 setPrivateConversationData
As it is mentioned here : These botData objects will fail to be stored if another instance of your bot has changed the object already. So in your case the exception occurs at the termination of dialog, when framework calls setUserData by himself and figures out that the BotData has been changed already (by your explicit call of BotState.SetUserDataAsync). I suppose that's why you were not able to catch the exception.
Solution:
I used the following code and it fixed the issue:
private static void storeBotData(IDialogContext context, BotData userData)
{
var data = context.UserData;
data.SetValue("field_name", false);
}
The reason it works is that we modify the object of UserData but allow botFramework to "commit" it himself, so there is no conflict
I agree with #Artem (this solved my issue too, thanks!). I would just add the following guideline.
Use
var data = context.UserData;
data.SetValue("field_name", false);
whenever you have a IDialogContext object available, so you let the Bot Framework commit changes.
Use instead
StateClient sc = activity.GetStateClient();
await sc.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, userData);
when you don't have an IDialogContext object, e.g. in the MessageController class.

How to terminate a Bot conversation (and get client details)?

I have a simple Bot as below:
[Serializable]
[Template(TemplateUsage.NotUnderstood, "I do not understand \"{0}\".", "Try again, I don't get \"{0}\".")]
class MyOrder
{
public string Subject;
public string Description;
public static IForm<MyOrder> BuildForm()
{
return new FormBuilder<MyOrder>()
.Field(nameof(MyOrder.Subject), "What Subject should I use?")
.Field(nameof(MyOrder.Description), "And what Description?")
.AddRemainingFields()
.OnCompletionAsync(MyFormComplete)
.Build();
}
private static async Task MyFormComplete(IDialogContext context, MyOrder order)
{
if (order != null)
{
await context.PostAsync($"Created. Number is 9833");
}
else
{
await context.PostAsync("Form returned empty response!");
}
}
Once the form is completed the MyFormComplete callback is made.
First question - How do I get access to the client details in that function? I need to know the Skype handle so that I can map it to a internal user.
Secondly - After completing the form I can't start a new one. No matter what I enter on the client it just keeps triggering the callback function. There must be a way to terminate the session/conversation so that the next text from the Skype client will start a new conversation/form. Yeah?
Worked out how to get the Skype caller id inside the Dialog's CompletionDelegate. Simply add the message's From details to the message.BotUserData inside the MessageController before building the dialog.
message.BotUserData = JObject.FromObject(message.From)
I can then access this on the context within that callback.

Wp7:Push notification channel URI is null

We are trying to test push notifications, using the latest code from the documentation How to: Set Up a Notification Channel for Windows Phone
public HttpNotificationChannel myChannel;
public void CreatingANotificationChannel()
{
myChannel = HttpNotificationChannel.Find("MyChannel");
if (myChannel == null)
{
myChannel = new HttpNotificationChannel("MyChannel","www.contoso.com");
// An application is expected to send its notification channel URI to its corresponding web service each time it launches.
// The notification channel URI is not guaranteed to be the same as the last time the application ran.
myChannel.ChannelUriUpdated += new EventHandler<NotificationChannelUriEventArgs>(myChannel_ChannelUriUpdated);
myChannel.Open();
}
else // Found an existing notification channel.
{
// The URI that the application sends to its web service.
Debug.WriteLine("Notification channel URI:" + myChannel.ChannelUri.ToString());
}
myChannel.HttpNotificationReceived += new EventHandler<HttpNotificationEventArgs>(myChannel_HttpNotificationReceived);
myChannel.ShellToastNotificationReceived += new EventHandler<NotificationEventArgs>(myChannel_ShellToastNotificationReceived);
myChannel.ErrorOccurred += new EventHandler<NotificationChannelErrorEventArgs>(myChannel_ErrorOccurred);
}
If HttpNotificationChannel.Find() returns null, it opens a new channel, but the ChannelUriUpdated event is never triggered.
If HttpNotificationChannel.Find() returns a channel, the ChannelUri property is null. The sample code crashes here because it assumes the ChannelUri property to be not null.
In neither case is the ErrorOccurred event triggered.
How can i solve this problem? This problem is because of microsoft server or any thing else?
Thnks in advance
EDIT
Waiting for replay,after ten days i am suffering of null uri problem
Can any one tell me how can i solve this problem some time MSPN server give chanalk uri ans some time not i mean some time it give null reference Exception.
What Microsoft doing?
If I don't go wrong, www.contoso.com it's a example URI to demonstrate that you need to put your own server URL address, but in my experience, I never use in that way. I prefer just to put
myChannel = new HttpNotificationChannel("MyChannel");
Look this example (it's in Spanish) but the codes are very clear of what you need to do to set the push notification client and service.
I hope I helped you.
You are testing in what mobile are Emulator,
Do you have developer account subscription for windows phone development,
Had you Developer unlocked your mobile,
Noorul.
I think the problem is that you are using the HttpNotificationChannel constructor of the authenticated web service, according to the documentation.
Instead, you should use the constructor that takes only one parameter, as you can check in this example
/// Holds the push channel that is created or found.
HttpNotificationChannel pushChannel;
// The name of our push channel.
string channelName = "ToastSampleChannel";
// Try to find the push channel.
pushChannel = HttpNotificationChannel.Find(channelName);
// If the channel was not found, then create a new connection to the push service.
if (pushChannel == null)
{
pushChannel = new HttpNotificationChannel(channelName);
...
}
Hope it helps

Resources