I would like to display via bot how many times a command has been executed.
For this I have already inquired here on the platform and have tried different things. About my code:
e = discord.Embed(color=discord.Colour.green())
user = ctx.author
e.title = f "New suggestion!"
e.set_footer(text=f"{user} | {user.id}")
e.description = f "**__Submitter:__**\n {ctx.author}"
e.add_field(name="__Suggestion:__", value=f"{text}")
e.set_thumbnail(url=ctx.message.author.avatar_url)
e.timestamp = datetime.utcnow()
await channel.send(embed=e)
await ctx.message.add_reaction("✅")
The bot should add Suggestion no. #number in the title, which will go up one at a time when the command is executed. I have already tried the following:
def __init__(self, bot):
self.bot = bot
self.counter = 0
#Shortened
e.title = f "Suggestion no. {self.counter}"
Or just as well:
e.title = f "Suggestion no. {self.counter + 1}
But that didn't help, so how do I make sure that a correct number is displayed and that in case of a bot restart this number continues to rise and doesn't start again from the beginning.
Note that a global event did not work for me somehow!
You actually need to update the counter variable
self.counter += 1
e.title = f"Suggestion no. {self.counter}"
You can save it in a JSON or text file if you want the variable to continue after the bot restarts
Related
So I'm still making a warning system but in the process I am having this issue with the embed showing the warning multiple times. Shown in the image provided.
https://i.stack.imgur.com/ks4Gm.png I'm not sure what could be causing this but I think it might be the for loop I made?
#client.hybrid_command(name = "warnings", with_app_command=True, description="View the warnings of a member", aliases=["punishments"])
async def warnings(ctx, member: discord.Member = None):
if member == None:
await ctx.reply("A Member is required")
else:
check = warndb.warn_logs.find_one({"user_id": member.id})
if check is None:
await ctx.reply("This user has no warns")
else:
reason = check["reason"]
moderator_id = check["moderator_id"]
embed = discord.Embed(color=embedcolor, title=f"{member.name}'s warnings")
for w in check:
embed.add_field(name=f"{reason}", value=f"<#{moderator_id}>", inline=False)
await ctx.send(embed=embed)
There is no error and it works fine it just shows the warning(s) multiple times
You query with find_one so there is no point in looping over check.
I would try to just remove the line for w in check:.
If it's suppose to be an array(change to find_all), maybe you manually adding data to the table when testing and it's displaying all previous warns you've added.
I figured it out finally. The issue is you need to turn the check = warndb.warn_logs.find_one({"user_id": member.id}) into a list by doing check = list(warndb.warn_logs.find_one({"user_id": member.id})). Once you do that you should use the .get() function to get the values of the keys
In this case:
for w in check:
reason = w.get('reason')
moderatorid = w.get('moderator_id')
guildid = w.get('guild_id')
caseid = w.get('_id')
embed.add_field(name=f"Case ID: {caseid}—Reason: {reason}", value=f"<#{moderatorid}>", inline=False)
I'm trying to edit an embed by adding the user's name into the embed field. It works, however if there are more than one field it edits all of them. User can add and remove embeds using a command so I don't know what names these fields have.
I want it so that users can react by emojis and depending on which emoji they use, it adds their name to a field.
#bot.event
async def on_raw_reaction_add(payload):
msg = await bot.get_channel(payload.channel_id).fetch_message(payload.message_id)
if not msg.author.bot:
return
if not msg.embeds:
return
embed1 = msg.embeds[0]
embed_dict = embed1.to_dict()
for field in embed_dict["fields"]:
field["value"] += f" {payload.member.name},"
embed2 = discord.Embed.from_dict(embed_dict)
await msg.edit(embed=embed2)
You are iterating through every field, but only one of them needs to be changed.
You can check the PartialEmoji, which can be both standard or custom, used in the reaction through payload.emoji.
embed_dict = embed1.to_dict()
to_modify = {
discord.PartialEmoji.from_str('😂'): 0,
discord.PartialEmoji.from_str('😢'): 1
# etc
}
emoji_used = payload.emoji # returns PartialEmoji containing the emoji
number = to_modify[emoji_used] # select field to modify
embed_dict[number]["value"] += f" {payload.member.name},"
embed2 = discord.Embed.from_dict(embed_dict)
Pretty stupid situation, I have made a discord bot that webscrapes from a specific website and should print out a joke after the command "!anekdot".
Bot itself works, it prints out the first joke. But after printing the first joke bot must pop it. However it doesn't do that (type of anekdots and its length have been used to understand if anything changes and if I can apply list's functions to anekdots list):
import discord
from discord.ext import commands
from script import anekdot_script
bot = commands.Bot(command_prefix= '!')
anekdots = []
n_of_page = 1
#bot.command()
async def anekdot(ctx, anekdots = anekdots, n_of_page = n_of_page):
anekdots = anekdot_script(anekdots, n_of_page)
if anekdots:
await ctx.send(type(anekdots))
await ctx.send(anekdots[0].text)
anekdots.pop(0)
await ctx.send(len(anekdots))
else:
await ctx.send('Nothing left! Find new web-site!')
bot.run('ID')
I thought that making anekdots and n_of_page variables global, the problem doesn't even have to appear.
Output in discord (doesn't change at all):
<class 'list'>
The joke in russian...
24
Script just checks if the list is empty, if it's empty - the script webscrapes the page and create a list of jokes and return it:
def anekdot_script(anekdots, n_of_page): #return list of anekdots of a specific page
url = 'https://humornet.ru/anekdot/evrei/page/{}/'
if anekdots == []:
response = requests.get(url.format(n_of_page)) #getting page info
response.encoding = 'utf-8'
soup = BeautifulSoup(response.text, 'lxml') #parses the page to html format
anekdots = soup.select('.text') #anekdots have class 'text' on this page
n_of_page += 1
return list(anekdots)
I have tried slicing as well, didn't help at all.
I don't understand hot to make the bot update the list of jokes by removing the printed out joke.
There is problem with two (or maybe even three) different variables with the same name anekdots
There is global variable
anekdots = []
local variable
anekdots = anekdot_script(...)
which not really is local variable but rather parameter variable created in
async def anekdot(..., anekdots = ...)
and all this makes mess because you think that you assign to global variable but code assign it to local variable and next time it uses again anekdots = [] when it runs anekdot_script() and it reads again all jokes.
This works for me
anekdots = []
n_of_page = 1
#bot.command()
async def anekdot(ctx):
global anekdots # inform function that `anekdots = ...` has to assign to global variable instead of creating local one
global n_of_page # inform function that `n_of_page = ...` has to assign to global variable instead of creating local one
anekdots, n_of_page = anekdot_script(anekdots, n_of_page)
with
def anekdot_script(anekdots, n_of_page):
# ... code ...
return anekdots, n_of_page # need to return also `n_of_page`
Full working code but it doesn't get extra arguments in command !anekdot
import discord
from discord.ext import commands
import requests
from bs4 import BeautifulSoup
def anekdot_script(anekdots, n_of_page): #return list of anekdots of a specific page
url = 'https://humornet.ru/anekdot/evrei/page/{}/'
if not anekdots:
response = requests.get(url.format(n_of_page)) #getting page info
response.encoding = 'utf-8'
soup = BeautifulSoup(response.text, 'lxml') #parses the page to html format
anekdots = soup.select('.text') #anekdots have class 'text' on this page
n_of_page += 1
return anekdots, n_of_page # need to return also `n_of_page`
bot = commands.Bot(command_prefix= '!')
anekdots = []
n_of_page = 1
#bot.command()
async def anekdot(ctx):
global anekdots # inform function that `anekdots = ...` has to assign to global variable instead of creating local one
global n_of_page # inform function that `n_of_page = ...` has to assign to global variable instead of creating local one
anekdots, n_of_page = anekdot_script(anekdots, n_of_page)
if anekdots:
await ctx.send(type(anekdots))
await ctx.send(anekdots[0].text)
anekdots.pop(0)
await ctx.send(len(anekdots))
else:
await ctx.send('Nothing left! Find new web-site!')
import os
TOKEN = os.getenv('DISCORD_TOKEN')
bot.run(TOKEN)
Note: I am not familiar with Discord Bots. This is just a common way to solve the problem
You can try storing all the printed jokes in a list and check everytime if it was printed earlier.
jokes = [] #Create a empty list
def get_and_print(): #Get your jokes
while True:
#get joke as myJoke
if myJoke in jokes:
#get new joke
else:
break
print(myJoke)
jokes.append(myJoke) #Insert it in jokes list
You can also delete any joke if the list size exceeds, say 100
if len(jokes) > 100:
jokes.pop(-1)
I want the cooldown of one of my commands to start only if a condition in the function is met, like so:
#bot.command
async def move(ctx, destination):
destinations=["d1", "d2", "d3"] # List of valid arguments for the command
if destination in destinations:
movement(destination) # Function to actually move, not important for the question
# Start cooldown only here
else:
await ctx.send("This is not a valid destination")
This way, if the user mistypes the destination, they won't be penalized with the cooldown. How can i achieve that?
EDIT1: one would normally use discord.py's built-in #commands.cooldown decorator, here is the source:
def cooldown(rate, per, type=BucketType.default):
def decorator(func):
if isinstance(func, Command):
func._buckets = CooldownMapping(Cooldown(rate, per, type))
else:
func.__commands_cooldown__ = Cooldown(rate, per, type)
return func
return decorator
However this applies to the whole command.(It is normally placed after the #bot.command decorator)
There could be a lots of ways to craft your own cooldowns, here is a simple one that can do the trick. The idea behind it is for the bot to "remember" the last time someone used this specific command and to check this time before allowing the player to move.
from datetime import datetime, timedelta
on_cooldown = {} # Dictionary with user IDs as keys and datetime as values
destinations=["d1", "d2", "d3"] # List of valid arguments for the command
move_cooldown = 5 # cooldown of the move command in seconds
#bot.command()
async def move(ctx, destination):
if destination in destinations:
author = ctx.author.id
try:
# calculate the amount of time since the last (successful) use of the command
last_move = datetime.now() - on_cooldown[author]
except KeyError:
# the key doesn't exist, the player used the command for the first time
# or the bot has been shut down since
last_move = None
on_cooldown[author] = datetime.now()
if last_move is None or last_move.seconds > move_cooldown:
# move(...)
on_cooldown[author] = datetime.now() # the player successfully moved so we start his cooldown again
await ctx.send("You moved!")
else:
await ctx.send("You're still on cooldown.")
else:
await ctx.send("This is not a valid destination")
Note : you may or may not need to remove the parentheses after the #bot.command decorator.
Idk if this is what you're looking for, but there's a way to make the cooldown only activate after the code parses properly with this bit of code:
#bot.command(cooldown_after_parsing=True)
#commands.cooldown(rate, per, type=<BucketType.default: 0>)
you can find the document for commands.cooldown here
and the doc for cooldown_after_parsing here
Context:
I am using PyTelegramBotAPi or Python Telegram Bot
I have a code I am running when a user starts the conversation.
When the user starts the conversation I need to send him the first picture and a question if He saw something in the picture, the function needs to wait for the user input and return whether he saw it or not.
After that, I will need to keep sending the picture in a loop and wait for the answer and run a bisection algorithm on it.
What I have tried so far:
I tried to use reply markup that waits for a response or an inline keyboard with handlers but I am stuck because my code is running without waiting for the user input.
The code:
#bot.message_handler(func=lambda msg: msg in ['Yes', 'No'])
#bot.message_handler(commands=['start', 'help'])
def main(message):
"""
This is my main function
"""
chat_id = message.chat.id
try:
reply_answer = message.reply_to_message.text
except AttributeError:
reply_answer = '0'
# TODO : should wait for the answer asynchnonossly
def tester(n, reply_answer):
"""
Displays the current candidate to the user and asks them to
check if they see wildfire damages.
"""
print('call......')
bisector.index = n
bot.send_photo(
chat_id=chat_id,
photo=bisector.image.save_image(),
caption=f"Did you see it Yes or No {bisector.date}",
reply_markup=types.ForceReply(selective=True))
# I SHOUL WAIT FOR THE INPUT HERE AND RETURN THE USER INPUT
return eval(reply_answer)
culprit = bisect(bisector.count, lambda x: x, partial(tester, reply_answer=reply_answer) )
bisector.index = culprit
bot.send_message(chat_id, f"Found! First apparition = {bisector.date}")
bot.polling(none_stop=True)
The algorithm I am running on the user input is something like this :
def bisect(n, mapper, tester):
"""
Runs a bisection.
- `n` is the number of elements to be bisected
- `mapper` is a callable that will transform an integer from "0" to "n"
into a value that can be tested
- `tester` returns true if the value is within the "right" range
"""
if n < 1:
raise ValueError('Cannot bissect an empty array')
left = 0
right = n - 1
while left + 1 < right:
mid = int((left + right) / 2)
val = mapper(mid)
tester_values = tester(val) # Here is where I am using the ouput from Telegram bot
if tester_values:
right = mid
else:
left = mid
return mapper(right)
I hope I was clear explaining the problem, feel free to ask any clarification.
If you know something that can point me in the right direction in order to solve this problem, let me know.
I have tried a similar question but I am not getting answers.
You should save your user info in a database. Basic fields would be:
(id, first_name, last_name, username, menu)
What is menu?
Menu keeps user's current state. When a user sends a message to your bot, you check the database to find out about user's current sate.
So if the user doesn't exist, you add them to your users table with menu set to MainMenu or WelcomeMenu or in your case PictureMenu.
Now you're going to have a listener for update function, let's assume each of these a menu.
#bot.message_handler(commands=['start', 'help'])
so when the user sends start you're going to check user's menu field inside the function.
#bot.message_handler(commands=['start', 'help'])
def main(message):
user = fetch_user_from_db(chat_id)
if user.menu == "PictureMenu":
if message.photo is Not None:
photo = message.photo[0].file_id
photo_file = download_photo_from_telegram(photo)
do_other_things()
user.menu = "Picture2Menu";
user.save();
else:
send_message("Please send a photo")
if user.menu == "Picture2Menu":
if message.photo is Not None:
photo = message.photo[0].file_id
photo_file = download_photo_from_telegram(photo)
do_other_things()
user.menu = "Picture3Menu";
user.save();
else:
send_message("Please send a photo")
...
I hope you got it.
I have found the answer:
the trick was to use next_step_handler, and message_handler_function to handle command starting with start and help
Then as suggested by #ALi in his answer, I will be saving the user input answer as well as the question id he replied to in a dictionary where keys are questions and id are the answer.
Once the user has answered all questions, I can run the algorithms on his answer
Here is how it looks like in the code :
user_dict = {}
# Handle '/start' and '/help'
#bot.message_handler(commands=['help', 'start'])
def send_welcome(message):
# initialise the the bisector and
bisector = LandsatBisector(LON, LAT)
indice = 0
message = send_current_candidate(bot, message, bisector, indice)
bot.register_next_step_handler(
message, partial(
process_step, indice, bisector))
def process_step(indice, bisector, message):
# this run a while loop and will that send picture and will stop when the count is reached
response = message.text
user = User.create_get_user(message, bisector=bisector)
if indice < bisector.count - 1:
indice += 1
try:
# get or create
user.responses[bisector.date] = response # save the response
message = send_current_candidate(bot, message, bisector, indice)
bot.register_next_step_handler(
message, partial(
process_step, indice, bisector))
except Exception as e:
print(e)
bot.reply_to(message, 'oooops')
else:
culprit = bisect(bisector.count,
lambda x: x,
partial(
tester_function,
responses=list(user.responses.values())))
bisector.index = culprit
bot.reply_to(message, f"Found! First apparition = {bisector.date}")