Discord.py - How to retain argument for use with view? - discord

Using discord.py, I've got a command that takes an argument and returns data from a .yml file entry of the same name. It's a lot of data per entry, so what I'm trying to do is split it up into multiple pages, which should be able to be switched between with buttons.
However, I can't find a way to make a button view retain the argument that the user inputted (it's a slash command). I kind of need this to access the relevant data.
Here is the command and the view which I've got right now (I plan to add more buttons for the view):
#client.tree.command()
#app_commands.describe(
entry="The enemy ID (0 - 230)"
)
async def enemy(interaction: discord.Interaction, entry: app_commands.Range[int, 0, 230]):
"""Look up enemy data from enemy_configuration_table.yml"""
file = discord.File(f"BattleSprites/{entry}.png", filename = "battle_sprite.png")
embed=discord.Embed(title=enemy_configuration_table[entry]['Name'], color=0xdb0000)
embed.set_author(name=f"Enemy {entry}")
embed.set_thumbnail(url="attachment://battle_sprite.png")
embed.add_field(name="HP", value=enemy_configuration_table[entry]['HP'], inline=True)
embed.add_field(name="PP", value=enemy_configuration_table[entry]['PP'], inline=True)
embed.add_field(name="Offense", value=enemy_configuration_table[entry]['Offense'], inline=True)
embed.add_field(name="Defense", value=enemy_configuration_table[entry]['Defense'], inline=True)
embed.add_field(name="Luck", value=enemy_configuration_table[entry]['Luck'], inline=True)
embed.add_field(name="Guts", value=enemy_configuration_table[entry]['Guts'], inline=True)
embed.add_field(name="Speed", value=enemy_configuration_table[entry]['Speed'], inline=True)
embed.add_field(name="Exp.", value=enemy_configuration_table[entry]['Experience points'], inline=True)
embed.add_field(name="Money", value=f"${enemy_configuration_table[entry]['Money']}", inline=True)
embed.add_field(name="Level", value=enemy_configuration_table[entry]['Level'], inline=True)
await interaction.response.send_message(file=file, embed=embed, view=enemyButtons())
class enemyButtons(discord.ui.View):
def __init__(self, *, timeout=180):
super().__init__(timeout=timeout)
#discord.ui.button(label="Sprites",style=discord.ButtonStyle.gray)
async def gray_button(self,button:discord.ui.Button, interaction: discord.Interaction):#, entry: app_commands.Range[int, 0, 230]): <- I've tried including this but it doesn't work.
file = discord.File(f"SpriteGroups/{enemy_configuration_table[entry]['Overworld Sprite']}.png", filename = "overworld_sprite.png")
embed=discord.Embed(title=enemy_configuration_table[entry]['Name'], color=0xdb0000)
embed.set_author(name=f"Enemy {entry}")
embed.set_image(url="attatchment://overworld_sprite.png")
await interaction.response.edit_message(file=file, embed=embed, view=enemyButtons())

You can always pass the data you want to keep to the View class and store it as a class variable.
So:
class EnemyButtons(discord.ui.View):
def __init__(self, some_data, timeout=180):
super().__init__(timeout=timeout)
self.some_data = some_data
#discord.ui.button(label="Sprites", style=discord.ButtonStyle.gray)
async def gray_button(self, button: discord.ui.Button, interaction: discord.Interaction):
# use that data here
print(f"Look at my data: {self.some_data}")
Then, when you instantiate your View within the slash command:
#client.tree.command()
#app_commands.describe(entry="The enemy ID (0 - 230)")
async def enemy(interaction: discord.Interaction, entry: app_commands.Range[int, 0, 230]):
# give the view the entry number or whatever else you want
view = EnemyButtons(entry)
await interaction.response.edit_message(view=enemyButtons())
I've obviously trimmed down the functions/class to make it easier for this example. But you can have as many parameters in __init__ that you need.

Related

making discord bot command to store message content (python)

So this is my first time coding an actual project that isn't a small coding task. I've got a bot that runs and responds to a message if it says "hello". I've read the API documentation up and down and really only have a vague understanding of it and I'm not sure how to implement it.
My question right now is how would I go about creating a command that takes informationn from a message the command is replying to (sender's name, message content) and stores it as an object. Also, what would be the best way to store that information?
I want to learn while doing this and not just have the answers handed to me ofc, but I feel very lost. Not sure where to even begin.
I tried to find tutorials on coding discord bots that would have similar functions to what I want to do, but can't find anything.
Intro :
Hi NyssaDuke !
First of all, prefer to paste your code instead of a picture. It's easier for us to take your code and try to produce what you wish.
In second, I see an "issue" in your code since you declare twice the bot. You can specify the intents when you declare your bot as bot = commands.Bot(command_prefix="!", intents=intents)
Finally, as stated by #stijndcl , it's against TOS, but I will try to answer you at my best.
filesystem
My bot needs to store data, like users ID, language, and contents relative to a game, to get contacted further. Since we can have a quite big load of requests in a small time, I prefered to use a file to store instead of a list that would disappear on crash, and file allow us to make statistics later. So I decided to use pytables that you can get via pip install pytables. It looks like a small DB in a single file. The file format is HDF5.
Let say we want to create a table containing user name and user id in a file :
import tables
class CUsers (tables.IsDescription) :
user_name = StringCol(32)
user_id = IntCol()
with tables.open_file("UsersTable.h5", mode="w") as h5file :
groupUser = h5file.create_group("/", "Users", "users entries")
tableUser = h5file.create_table(groupUser, "Users", CUsers, "users table")
We have now a file UsersTable.h5 that has an internal table located in root/Users/Users that is accepting CUsers objects, where, therefore, user_name and user_id are the columns of that table.
getting user info and storing it
Let's now code a function that will register user infos, and i'll call it register. We will get the required data from the Context that is passed with the command, and we'll store it in file.
#bot.command(name='register')
async def FuncRegister (ctx) :
with tables.open_file("UsersTable.h5", mode="a") as h5file :
tableUser = h5file.root.Users.Users
particle = tableUser.row
particle['user_name'] = str(ctx.author)
particle['user_id'] = ctx.author.id
particle.append()
tableUser.flush()
The last two lines are sending the particle, that is the active row, so that is an object CUsers, into the file.
An issue I got here is that special characters in a nickname can make the code bug. It's true for "é", "ü", etc, but also cyrillic characters. What I did to counter is to encode the user name into bytes, you can do it by :
particle['user_name'] = str(ctx.author).encode()
reading file
It is where it starts to be interesting. The HFS5 file allows you to use kind of sql statements. Something you have to take in mind is that strings in the file are UTF-8 encoded, so when you extract them, you have to call for .decode(utf-8). Let's now code a function where a user can remove its entry, based on its id :
#bot.command(name="remove")
async def FuncRemove(ctx) :
with tables.open_file("UsersTable.h5", mode="a") as h5file :
tableUser = h5file.root.Users.Users
positions = tableUser.get_where_list("(user_id == '%d')" % ctx.author.id)
nameuser = tableUser[positions[0]]['user_name'].decode('utf-8')
tableUser.remove_row(positions[0])
.get_where_list() returns a list of positions in the file, that I later address to find the right position in the table.
bot.fetch_user(id)
If possible, prefer saving ID over name, as it complicates the code with encode() and decode(), and that bots have access to a wonderful function that is fetch_user(). Let's code a last function that will get you the last entry in your table, and with the id, print the username with the fetch method :
#bot.command(name="last")
async def FuncLast(ctx) :
with tables.open_file("UsersTable.h5", mode="r") as h5file :
tableUser = h5file.root.Users.Users
lastUserIndex = len(tableUser) - 1
iduser = tableUser[lastUserIndex]['user_id']
member = await bot.fetch_user(iduser)
await ctx.send(member.display_name)
For further documentation, check the manual of discord.py, this link to context in particular.

Wagtail - Add data to streamfield CharBlock on save()

I'm trying to automatically fill in a specific charblock on a streamfield I have created on save and I can get it to change but not save because when the page refreshes the new data is not there.
def save(self, *args, **kwargs):
if kwargs.get('update_fields'):
if self.auto_update:
for product in self.product_details.raw_data:
product_name = product['value']['product_name']
product_url = product['value']['product_url']
# I'm trying to set the field data here
product['value']['product_snippet_url'] = 'Test'
# when I print, I see the 'Test' value in the dict
print(product)
else:
# Do stuff on publish()
pass
return super().save(*args, **kwargs)
# Streamfield Block
class ProductNameAndUrlBlock(blocks.StructBlock):
product_name = blocks.CharBlock(required=True, help_text='Add product name')
product_url = blocks.URLBlock(required=True, help_text='Add Product URL')
product_snippet_url = blocks.CharBlock(required=False, help_text='Link to snippet')
The above code is for testing purposes, my end goal is to insert the url of the related snippet there and then use JS to turn it into a button so when someone clicks on it, it links to the snippet page.
Writing back to the stream's raw_data property will not reliably save the data. raw_data is a "backup" copy of the data as retrieved from the database, in a simplified format - for example, a PageChooserBlock value is stored there as a plain ID rather than a Page object. Once data is read or written through the stream's standard methods (e.g. looping over for block in self.product_details or writing self.product_details[0] = ...), the raw_data entry is treated as stale and not looked at again.
The correct approach is to read and write the stream itself as a list, rather than going through raw_data:
for product in self.product_details:
product_name = product.value['product_name']
product_url = product.value['product_url']
# I'm trying to set the field data here
product.value['product_snippet_url'] = 'Test'

How do you run behave (with python code) steps implementation with gherkin scenarios data input?

I start python and TDD, I would like to know how to run behave steps with python and scenarios table for the two scenarios below:
The program ask a user to enter data (humididity level and temperature) and it prints those data.
For the first scenario, user fill out data and the program prints those data (normal case). I just want to check if there is data input
For the second scenario, if user fill out "text" the program return a syntax error.I would like to check the data type in #then steps
The problem is that when I run steps with behave command it asks me to enter data but I want the program uses data table in gherkin scenarios. Can you help please?
gherkin scenario below:
Feature: As a user I want fill out humidex data to visualize it
Scenario: user fill out humidex data correctly
Given a user
When user fill out humidexdata
|humidity|temperature|
|50% |28C° |
Then user visualize
|humidity|temperature|
|50% |28C° |
Scenario: user fill out humidex data with text
Given a user
When user fill out humidexdata
|humidity |temperature|
|lorem ipsum|lorem ipsum|
Then user visualize a syntax error "data syntax is wrong retry"
step implementations with behave:
from behave import *
from fillouthumidexdata import *
#given(u'a user')
def step_impl(context):
context.user = User()
#when(u'user fill out humidexdata')
def step_impl(context):
context.user.fillout_humidexData()
#then(u'user visualize')
def step_impl(context):
context.user.visualize_humidexData()
python code:
class User():
def __init__(self):
self.humidity = []
self.temperature =[]
def fillout_humidexData(self):
print("Enter humidity level (%)")
input(self.humidity)
print("Enter temperature (C°)")
input(self.temperature)
def visualize_humidexData(self):
print(self.humidity)
print(self.temperature)
I am not sure why you need actually separate User class as all of that can be done in simple steps, but following this approach I would change first a bit the User class at least to:
class User():
def __init__(self):
self.humidity = 0
self.temperature = 0
def fillout_humidexData(self, humidity, temperature):
self.humidity = humidity
self.temperature = temperature
def visualize_humidexData(self):
print(f'humidity: {self.humidity}')
print(f'temperature: {self.temperature}')
And then use the following steps:
from behave import step, given, when, then
from fillouthumidexdata import *
#given(u'a user')
def step_impl(context):
context.user = User()
#when(u'user fill out humidexdata')
def step_impl(context):
humidity, temperature = context.table.rows[0]
context.user.fillout_humidexData(humidity, temperature)
#then(u'user visualize')
def step_impl(context):
context.user.visualize_humidexData()
rows[0] should give you values from first row in your setup table(2 values from 2 columns).
I think this should work, but I am not sure if this is what you want.

wxPython: get data in variables from Google Spreadsheet and also from user, work with all variables, return results to user

I hope I didn't miss any topic that could answer my problem. I'm here now because I'm terribly frustrated and tired with the following task:
- I have a Spreasheet with Drive.Google with lots of data in it
- I would like to create an application with wxPython that would pull data from this spreeadsheet (in the most easy way possible)
- Would also like to get multiple data from a user who will access this application through a nice interface (panel aka window)
- The multiple data introduced by the user should be able to work with the data pulled out from the Spreasheet. For example to see if the data introduced by the user is in the Spreadsheet or not and also some other operations with the next data introduced by the user.
- Finally and most importantly show the results to the user (later I would also like to add some functions to save somehow the results)
I hope I managed to express clearly what I would like to do. Now I'm new to Google API's, Python adn wxPython, but I have experience with C++ , php, html .
I've spent 2 weeks now with discovering Drive.Google and learning Python and wxPython. I did follow all tuturials on these, made my notes, read tones of stackoverflow questions-answers, wiki.wxpython.org etc. I learn every single day and I can do now many things separately but to have all functions like I described above I just couldn't work out how to do. At least please orient me in the direction. Awfel lot of times I spend hours doing examples and getting nowhere. I have Python, wxPython extention, GoogleAppEngine Launcher and even pyCharm demo. Please be kind. This is my first question ever.
here's the mess I made so far combining relevant examples:
import wx
import gdata.docs
import gdata.docs.service
import gdata.spreadsheet.service
import re, os
class Form(wx.Panel):
def __init__(self, *args, **kwargs):
super(Form, self).__init__(*args, **kwargs)
self.createControls()
self.bindEvents()
self.doLayout()
self.spreasht()
def createControls(self):
self.logger = wx.TextCtrl(self, style=wx.TE_MULTILINE|wx.TE_READONLY)
self.saveButton = wx.Button(self, label="Elvegzes")
self.nameLabel = wx.StaticText(self, label="type Name1:")
self.nameTextCtrl = wx.TextCtrl(self, value="type here")
self.name2Label = wx.StaticText(self, label="type Name2:")
self.name2TextCtrl = wx.TextCtrl(self, value="type here")
def bindEvents(self):
for control, event, handler in \
[(self.saveButton, wx.EVT_BUTTON, self.onSave),
(self.nameTextCtrl, wx.EVT_TEXT, self.onNameEntered),
(self.nameTextCtrl, wx.EVT_CHAR, self.onNameChanged)]:
control.Bind(event, handler)
def doLayout(self):
raise NotImplementedError
def spreadsht(self):
gd_client = gdata.spreadsheet.service.SpreadsheetsService()
gd_client.email = 'my email address'
gd_client.password = 'my password to it'
gd_client.source = 'payne.org-example-1'
gd_client.ProgrammaticLogin()
q = gdata.spreadsheet.service.DocumentQuery()
q['title'] = 'stationcenter'
q['title-exact'] = 'true'
feed = gd_client.GetSpreadsheetsFeed(query=q)
spreadsheet_id = feed.entry[0].id.text.rsplit('/',1)[1]
feed = gd_client.GetWorksheetsFeed(spreadsheet_id)
worksheet_id = feed.entry[0].id.text.rsplit('/',1)[1]
al1 = raw_input('Name1: ')
print al1
al2 = raw_input('Name2: ')
print al2
rows = gd_client.GetListFeed(spreadsheet_id, worksheet_id).entry
for row in rows:
for key in row.custom:
if al1 == row.custom[key].text:
print ' %s: %s' % (key, row.custom[key].text)
def onColorchanged(self, event):
self.__log('User wants color: %s'%self.colors[event.GetInt()])
def onReferrerEntered(self, event):
self.__log('User entered referrer: %s'%event.GetString())
def onSave(self,event):
self.__log('User clicked on button with id %d'%event.GetId())
def onNameEntered(self, event):
self.__log('User entered name: %s'%event.GetString())
def onNameChanged(self, event):
self.__log('User typed character: %d'%event.GetKeyCode())
event.Skip()
def onInsuranceChanged(self, event):
self.__log('User wants insurance: %s'%bool(event.Checked()))
# Helper method(s):
def __log(self, message):
''' Private method to append a string to the logger text
control. '''
self.logger.AppendText('%s\n'%message)
class FormWithSizer(Form):
def doLayout(self):
''' Layout the controls by means of sizers. '''
boxSizer = wx.BoxSizer(orient=wx.HORIZONTAL)
gridSizer = wx.FlexGridSizer(rows=5, cols=2, vgap=10, hgap=10)
# Prepare some reusable arguments for calling sizer.Add():
expandOption = dict(flag=wx.EXPAND)
noOptions = dict()
emptySpace = ((0, 0), noOptions)
# Add the controls to the sizers:
for control, options in \
[(self.nameLabel, noOptions),
(self.nameTextCtrl, expandOption),
(self.allomas2Label, noOptions),
(self.allomas2TextCtrl, expandOption),
emptySpace,
(self.saveButton, dict(flag=wx.ALIGN_CENTER))]:
gridSizer.Add(control, **options)
for control, options in \
[(gridSizer, dict(border=5, flag=wx.ALL)),
(self.logger, dict(border=5, flag=wx.ALL|wx.EXPAND,
proportion=1))]:
boxSizer.Add(control, **options)
self.SetSizerAndFit(boxSizer)
class FrameWithForms(wx.Frame):
def __init__(self, *args, **kwargs):
super(FrameWithForms, self).__init__(*args, **kwargs)
notebook = wx.Notebook(self)
form2 = FormWithSizer(notebook)
notebook.AddPage(form2, 'CALTH')
self.SetClientSize(notebook.GetBestSize())
if __name__ == '__main__':
app = wx.App(0)
frame = FrameWithForms(None, title='Relevant title˝')
frame.Show()
app.MainLoop()
THANK YOU AGAIN!!!!!!!!!!!
First, make sure you can download the data you want with just Python. Then create a wxPython GUI with a single button. In that button's handler, have it call the script that can download the data you want.
If that causes your GUI to become unresponsive, then you'll need to use a thread to do the downloading. I recommend the following articles if that's the case:
http://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/
http://wiki.wxpython.org/LongRunningTasks
Okay, so now you have the data downloading appropriately. Now you add a grid widget or a listctrl / object list view widget. Pick one of those. I prefer object list view, which you can read about here. Then in your button handler you can call your downloader script or thread and when that's done, you can load the widget with that data. If you're using a thread, then the thread will have to call the next step (i.e. the widget loading bit).
Now you should have your data displayed. All that's left is making it look pretty and maybe putting the downloading part into a menu item.

How to pass parameters dynamically to map function on GAE mapreduce?

I need to run a mapreduce job that is dynamic in the sense that parameters need to be passed to the map and reduce functions each time the mapreduce job is run (e.g., in response to a user request).
How do I accomplish this? I could not see anywhere in the documentation how to do dynamic processing at runtime for map and reduce.
class MatchProcessing(webapp2.RequestHandler):
def get(self):
requestKeyID=int(self.request.get('riderbeeRequestID'))
userKey=self.request.get('userKey')
pipeline = MatchingPipeline(requestKeyID, userKey)
pipeline.start()
self.redirect(pipeline.base_path + "/status?root=" + pipeline.pipeline_id)
class MatchingPipeline(base_handler.PipelineBase):
def run(self, requestKeyID, userKey):
yield mapreduce_pipeline.MapreducePipeline(
"riderbee_matching",
"tasks.matchingMR.riderbee_map",
"tasks.matchingMR.riderbee_reduce",
"mapreduce.input_readers.DatastoreInputReader",
"mapreduce.output_writers.BlobstoreOutputWriter",
mapper_params={
"entity_kind": "models.rides.RiderbeeRequest",
"requestKeyID": requestKeyID,
"userKey": userKey,
},
reducer_params={
"mime_type": "text/plain",
},
shards=16)
def riderbee_map(riderbeeRequest):
# would like to access the requestKeyID and userKey parameters that were passed in mapper_params
# so that we can do some processing based on that
yield (riderbeeRequest.user.email, riderbeeRequest.key().id())
def riderbee_reduce(key, values):
# would like to access the requestKeyID and userKey parameters that were passed earlier, perhaps through reducer_params
# so that we can do some processing based on that
yield "%s: %s\n" % (key, len(values))
Help please?
I'm pretty sure you can just specify parameters in mapper_parameters, and read them from the context module. See http://code.google.com/p/appengine-mapreduce/wiki/UserGuidePython#Mapper_parameters for more details.
This is how to access the mapper parameters from the mapper function, using the context module:
from mapreduce import context
def riderbee_map(riderbeeRequest):
ctx = context.get()
params = ctx.mapreduce_spec.mapper.params
requestKeyID = params["requestKeyID"]

Resources