How to add AdaptiveCard to QnA Bot in Python - python

I wish to use some AdaptiveCard with my QnA python bot.
I use sample code from botbuilder repo:
from botbuilder.ai.qna import QnAMaker, QnAMakerEndpoint
from botbuilder.core import ActivityHandler, MessageFactory, TurnContext
from botbuilder.schema import ChannelAccount
from config import DefaultConfig
class QnABot(ActivityHandler):
def __init__(self, config: DefaultConfig):
self.qna_maker = QnAMaker(
QnAMakerEndpoint(
knowledge_base_id=config.QNA_KNOWLEDGEBASE_ID,
endpoint_key=config.QNA_ENDPOINT_KEY,
host=config.QNA_ENDPOINT_HOST,
)
)
async def on_members_added_activity(
self, members_added: [ChannelAccount], turn_context: TurnContext
):
for member in members_added:
if member.id != turn_context.activity.recipient.id:
await turn_context.send_activity(
"Hallo!"
)
async def on_message_activity(self, turn_context: TurnContext):
# The actual call to the QnA Maker service.
response = await self.qna_maker.get_answers(turn_context)
if response and len(response) > 0:
await turn_context.send_activity(MessageFactory.text(response[0].answer))
else:
await turn_context.send_activity("Ich kann nicht")
So how I have to configure qna_maker.get_answers() and rows in QnA Knowledge Base, for using AdaptiveCards like an answer for a question?
UPD
What I wish to return answer inside card:
card = HeroCard(
title="Answer Title here",
subtitle="short answer",
buttons=[task_module_action], #task_module_action - button which have to show long variant of answer
)

Related

How to get a Nextcord Python Bot to react to button clicks?

I'm trying to write a Discord Bot including a command for creating a poll like this: (The amount of options can be different)
/poll Is this bot working? <> Yes No Maybe
Then it already looks good:
(https://i.ibb.co/p3bmK5K/botbuttons.png)
However, I can't find out how to react on a button click (call the onpollbuttonclick(button) function.
Here's the relevant part of my code:
import nextcord
from nextcord.ext import commands
bot = commands.Bot(command_prefix="/")
#bot.command(name="poll")
async def poll(ctx, *, all):
separator = all.find("<>")
text = all[0:separator]
newtext = ""
for i in text:
newtext += i
options = all[separator+2:len(all)].split()
pollbuttons = nextcord.ui.View()
async def onpollbuttonclick(button):
votestatus[button] += 1
await ctx.send("Vote accepted!")
votestatus = {}
for i in options:
pollbuttons.add_item(nextcord.ui.Button(style=nextcord.ButtonStyle.red, label=i))
votestatus[i] = 0
myembed = nextcord.Embed(title="Poll")
myembed.add_field(name="\u200b", value=newtext, inline=False)
await ctx.send(embed=myembed, view=pollbuttons)
bot.run("mytoken")
if you want to create a Button with a callback function try this. This is a little bit more work than with decorators, but you can change the labels on runtime.
I hope it does the job because I didn't try it by myself.
import nextcord
from nextcord.ext import commands
bot = commands.Bot(command_prefix="/")
class Poll_Button(nextcord.ui.Button):
def __init__(self,label):
self.super.init(custom_id="yourcustomid",
label=label,
style=nextcor.ButtonStlyle.red)
async def callback(self,interaction:nextcord.Interaction):
await interaction.send("Vote accepted")
class Poll_View(nextcord.ui.View):
def __init__(self,options:List[str]):
self.super.init()
for option in options:
self.add_item(Poll_Button(option))
#bot.command(name="poll")
async def poll(ctx,*,all):
separator = all.find("<>")
text = all[0:separator]
newtext = ""
for i in text:
newtext += i
options = all[separator+2:len(all)].split()
myembed = nextcord.Embed(title="Poll")
myembed.add_field(name="\u200b", value=newtext, inline=False)
await ctx.send(embed=myembed, view=Poll_View(options)
bot.run("mytoken")
With a decorator, it looks something like this
#nextcor.ui.button(parameter):
async def funcname(button,interaction):
await interaction.send("Vote accepted")

discord.py - edit the interaction message after a timeout in discord.ui.Select

How can I access the interaction message and edit it?
discord.ui.Select
class SearchMenu(discord.ui.Select):
def __init__(self, ctx, bot, data):
self.ctx = ctx
self.bot = bot
self.data = data
self.player = Player
values = []
for index, track in enumerate(self.data[:9]):
values.append(
discord.SelectOption(
label=track.title,
value=index + 1,
description=track.author,
emoji=f"{index + 1}\U0000fe0f\U000020e3"
)
)
values.append(discord.SelectOption(label='Cancel', description='Exit the search menu.', emoji="🔒"))
super().__init__(placeholder='Click on the Dropdown.', min_values=1, max_values=1, options=values)
async def callback(self, interaction: discord.Interaction):
if self.values[0] == "Cancel":
embed = Embed(emoji=self.ctx.emoji.whitecheck, description="This interaction has been deleted.")
return await interaction.message.edit(embed=embed, view=None)
discord.ui.View
class SearchMenuView(discord.ui.View):
def __init__(self, options, ctx, bot):
super().__init__(timeout=60.0)
self.ctx = ctx
self.add_item(SearchMenu(ctx, bot, options))
async def interaction_check(self, interaction: discord.Interaction):
if interaction.user != self.ctx.author:
embed = Embed(description=f"Sorry, but this interaction can only be used by {self.ctx.author.name}.")
await interaction.response.send_message(embed=embed, ephemeral=True)
return False
else:
return True
async def on_timeout(self):
embed = Embed(emoji=self.ctx.emoji.whitecross, description="Interaction has timed out. Please try again.")
await self.message.edit(embed=embed, view=None)
If I try to edit the interaction like this I am getting
-> AttributeError: 'SearchMenuView' object has no attribute 'message'
After 60 seconds the original message should be replaced with the embed in the timeout.
You're trying to ask the View to send a message, which is not a method in discord.ui.View.
You could defer the response and don't let it timeout and allow the user to try again?
async def interaction_check(self, interaction: discord.Interaction):
if interaction.user != self.ctx.author:
embed = Embed(description=f"Sorry, but this interaction can only be used by {self.ctx.author.name}.")
await interaction.channel.send(embed=embed, delete_after=60)
await interaction.response.defer()
return True
view = MyView()
view.message = await channel.send('...', view=view)
After that you can use self.message in on_timeout (or somewhere else you don't have access to interaction.message) to edit it.
Source: https://discord.com/channels/336642139381301249/669155775700271126/860883838657495040

Discord py - menu with variables from a dictionary

I'm trying to create a command that outputs five different memes with the help of a menu. You should then be able to go back and forth with the arrow keys.
Like this, but with arrow buttons below.
So far I have saved the variables for creating the meme in a dictionary. But I don't know how to call it up so that the menu understands it correctly.
My Code:
import discord
import aiohttp
from discord.ext import commands, menus
class Test:
def __init__(self, key, value):
self.key = key
self.value = value
class Source(menus.GroupByPageSource):
async def format_page(self, menu, entry):
offset = ((menu.current_page) * 5) + 1
joined = '\n'.join(f'• ``{v.value}``' for i, v in enumerate(entry.items, offset))
embed = discord.Embed(title=entry.key, description=joined, color=0xf7fcfd)
embed.set_author(name=f"Page: {menu.current_page + 1}/{self.get_max_pages()}")
return embed
class Reddit(commands.Cog):
def __init__(self, client):
self.client = client
#commands.command()
async def info(self, ctx):
async with aiohttp.ClientSession() as session:
async with session.get("https://meme-api.herokuapp.com/gimme/5") as resp:
memes = await resp.json()
all_memes = {}
count = 0
for meme in memes["memes"]:
count += 1
await ctx.trigger_typing()
if meme["nsfw"] == True:
return True
all_memes[count] = {
"link": meme["postLink"],
"reddit": meme["subreddit"],
"title": meme["title"],
"author": meme["author"],
"upvotes": meme["ups"],
"nsfw": meme["nsfw"],
"image": meme["preview"][-1]
}
data = [
Test(key=key, value=value)
for key in ["NONE"]
for value in all_memes.values()
]
pages = menus.MenuPages(
source=Source(data, key=lambda t: t.key, per_page=1),
clear_reactions_after=True
)
return await pages.start(ctx)
def setup(client):
client.add_cog(Reddit(client))
If I call the command now, the output looks like this:
It should create an embed like this:
embed = discord.Embed(description=f"[{title}]({link})", color=0xf7fcfd)
embed.set_author(name=f'u/{reddit}')
embed.add_field(name="Author:", value=author)
embed.add_field(name="Upvotes:", value=upvotes)
embed.set_image(url=image)
embed.set_footer(
text=f"Requested by {ctx.author} | Response time : {round(self.client.latency * 1000)} ms",
icon_url=ctx.author.avatar_url,
)
await ctx.send(embed=embed)
Thank you for your time :)
class DisplayPages(Menu):
def __init__(self, pages, timeout: int = 180):
super().__init__(timeout=timeout, clear_reactions_after=True)
self.current = 0
self.pages = pages
async def send_initial_message(self, _, destination):
current = self.pages[self.current]
if isinstance(current, discord.Embed):
return await destination.send(embed=current)
return await destination.send(current)
async def update_index(self, payload, index_change):
self.current = max(0, min(len(self.pages) - 1, self.current + index_change))
async def on_reaction(self, payload, index_change):
await self.update_index(payload, index_change)
current = self.pages[self.current]
if isinstance(current, discord.Embed):
return await self.message.edit(content=None, embed=current)
await self.message.edit(content=current, embed=None)
#menus.button(Reacts.REWIND)
async def go_first(self, payload):
await self.on_reaction(payload, -self.current)
#menus.button(Reacts.ARROW_LEFT)
async def go_prev(self, payload):
await self.on_reaction(payload, -1)
#menus.button(Reacts.ARROW_RIGHT)
async def go_next(self, payload):
await self.on_reaction(payload, 1)
#menus.button(Reacts.FAST_FORWARD)
async def go_last(self, payload):
await self.on_reaction(payload, len(self.pages) - self.current - 1)
This is my old base class for menus with 4 buttons for what you want. Works quite simply by editing the message after each button press with a new embed/message.
You can use the AutoEmbedPaginator or CustomEmbedPaginator class of DiscordUtils

Toggling command access - Discord.py

im pretty new to python so i thought a good learning project would be to create a discord bot for a personal server, i have a few commands that only i as the owner of the bot can access but i would like to be able to toggle that access with a command so that my friends can also use them but i have a problem, this is the piece of the code giving me an error:
MyID = '<#xxxxxxxxxxxxxxxx>'
FFA = False
class TestCommands(commands.Cog):
def __init__(self, client):
self.client = client
#commands.Cog.listener()
async def on_ready(self):
def IDCheck(ctx):
return ctx.message.author.id == xxxxxxxxxxxxxxxxxxxxxx
#commands.command()
async def ToggleFFA(ctx):
if FFA == False:
FFA = True
print (FFA)
await message.channel.send('All user can now use owner locked commands')
if FFA == True:
FFA = False
print (FFA)
await message.channel.send('All user can no longer use owner locked commands')
###########################################################################
#commands.command()
if FFA == False:
#commands.check(IDCheck)
async def FFATest(self, ctx, *, question):
loopnumber = 0
while spamnumber < int(question):
await ctx.send('Test' + MyID);
await asyncio.sleep(1)
loopnumber += 1
print ({loopnumber})
if FFA == True:
async def LoopTest(self, ctx, *, question):
loopnumber = 0
while loopnumber < int(question):
await ctx.send('Test' + MyID);
await asyncio.sleep(1)
loopnumber+= 1
print ({loopnumber})
###########################################################################
i get an invalid syntax error within the highlighted piece of code. If anyone knows a simpler way of toggling the access or a way that i can correct the error i would really appreciate it.
thanks in advance.
You can specify a bot_check_once method that will be used as a check on all commands from the bot. We can then have an attribute of the cog that controls which mode we are in:
class MyCog(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.owner_only = True
async def bot_check_once(self, ctx):
app = await self.bot.application_info()
if self.owner_only:
return ctx.author == app.owner
else:
return True
#commands.command()
async def all_users(self, ctx):
self.owner_only = False
#commands.command()
async def just_owner(self, ctx):
self.owner_only = True
Well you could add a check method outside. Here is an example.
def FFA_Enabled(ctx):
global FFA
if commands.is_owner():
return True
else:
return FFA
#commands.check(FFA_enabled):
async def SomeCommand(self, ctx:Context):
ctx.send("Message")
This Should work
if you don't know what ctx:Context means
It derives ctx from the type Context(i use it for autofill) if you wanna you is i suggest you type this:
from discord.ext.commands import Context
You can use a LIST for this, Inside that you can store the USER ID and Status ID.
Note: This is a just snippet to give you an idea, The ID Will reset when the script is restarted, I recommend you to save it in a file and load it from there.
You can also use a function to return True/False based on the USER ID instead of writing a bunch of code in each command.
users = []
status = 'Global'
#commands.is_owner()
#commands.command()
async def add_user(self,ctx,user:discord.User):
global users
id = user.id
await ctx.send(f'{user.name} has been added into the mod list.')
return users.append(id)
#commands.is_owner()
#commands.command()
async def change_status(self,ctx):
global status
if status == 'Global':
status = 'Local'
elif status == 'Local':
status = 'Global'
await ctx.send(f'Status has been changed to {status}')
return status
#commands.command()
async def test_command(self,ctx):
global status
global users
#IF Status is Local
if status == 'Local':
if ctx.user.id in users:
#ALLOW THE USERS' WHOS' ID IS IN THE LIST
pass
else:
#EXIT THE FUNCTION IF USER NOT IN THE LIST
return
#IF The status is Global
else:
#ALLOW THE COMMAND IF IT'S GLOBAL
pass

BotBuilder Python - Handling multiple dialog and intent

I have the following code to handle multiple intents,
Code
async def on_message_activity(self, turn_context: TurnContext):
recognizer_result = await self.luis.recognize(self.recognizer, turn_context)
intent = self.luis.get_top_intent(recognizer_result)
await self.process_intent(turn_context, recognizer_result, intent)
async def process_intent(self, turn_context: TurnContext, recognizer_result, intent):
if intent == 'Greeting_Wishes':
await greeting_wishes(turn_context, user_info)
elif intent == 'Greeting_Question':
await greeting_question(turn_context)
elif intent == 'Movement':
dialog = Movement(recognizer_result)
await DialogHelper.run_dialog(
dialog,
turn_context,
self.dialog_state
)
Problem
Greeting intent is working fine
Movement intent is properly taking to the configured dialog but after asking a couple of inputs to the user and when the user enters their value it is either going back to greeting intent or going nowhere since the intent is None
Can someone help how to handle multiple intents with dialogs?
Any help would be appreciated!
I ended up having one main dialog and extended the other dialogs depending upon the other intent. Look at the code sample below,
async def on_message_activity(self, turn_context: TurnContext):
recognizer_result = await self.luis.recognize(self.recognizer, turn_context)
intent = self.luis.get_top_intent(recognizer_result)
await self.process_intent(turn_context, recognizer_result, intent)
async def process_intent(self, turn_context: TurnContext, recognizer_result, intent):
if intent == "Greeting_Wishes" and not entity:
await greeting_wishes(turn_context, user_info)
return
if intent == "Greeting_Question" and not entity:
await greeting_question(turn_context)
return
dialog = MainDialog(self.luis, intent, recognizer_result)
await DialogHelper.run_dialog(
dialog,
turn_context,
self.conversation_state.create_property("DialogState")
)
main_dialog.py
Within the main dialog, I'll check for the intent and begin the appropriate dialog.
class MainDialog(ComponentDialog):
def __init__(self, luis, intent, recognizer_result):
super(MainDialog, self).__init__(MainDialog.__name__)
self.luis = luis
self.intent = intent
self.recognizer_result = recognizer_result
self.add_dialog(SampleDialog1())
self.add_dialog(SampleDialog2())
self.add_dialog(
WaterfallDialog(
"main_dialog_id", [self.main_step]
)
)
self.initial_dialog_id = "main_dialog_id"
async def main_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
dialog_detail = self.luis.get_entities(self.intent, self.recognizer_result)
if self.intent == "one":
return await step_context.begin_dialog(SampleDialog1.__name__, dialog_detail)
elif self.intent == "two":
return await step_context.begin_dialog(SampleDialog2.__name__, dialog_detail)

Categories

Resources