Global variables that mix between servers, python discord - python

I have a game that is basically two commands, test and test2.
test makes you generate a word that you have to guess in test2, and if you miss the word 6 times you lose.
from collections import defaultdict
word = ""
guessesLeft = 6
blanks = []
guessedLetters = []
lettersFound = 0
bot = commands.Bot(command_prefix=("!"))
bot.gamex = defaultdict(bool)
#bot.command()
async def test(ctx, *, message):
await ctx.message.delete()
global word, guessesLeft, blanks, lettersFound, guessedLetters
if not bot.gamex[ctx.guild.id]:
word = message.lower()
blanks = []
guessedLetters = []
lettersFound = 0
guessesLeft = 6
bot.gamex[ctx.guild.id] = True
for i in range(0, len(word)):
blanks .append("-")
print(i)
await ctx.send(embed=discord.Embed(title="hangman: " + " ".join(blanks)))
#bot.command()
async def test2(ctx, *, guess):
global word, guessesLeft, blanks, lettersFound, guessedLetters
if bot.gamex[ctx.guild.id]:
if str.isalpha(guess) and len(guess) is 1 and str.lower(guess) not in guessedLetters:
if str.lower(guess) in word:
await ctx.send(guess + " is in the word. Good job!")
for i in range(0, len(word)):
if word[i] == str.lower(guess):
blanks[i] = str.lower(guess)
lettersFound += 1
else:
await ctx.send(guess + " is NOT in the word.")
guessesLeft -= 1
guessedLetters.append(str.lower(guess))
await ctx.send(" ".join(blanks))
await ctx.send("Guessed letters: " + " ".join(guessedLetters))
await ctx.send("Guesses left: " + str(guessesLeft))
if guessesLeft == 0:
await ctx.send("No guesses left. You lose!")
bot.gamex[ctx.guild.id] = False
if lettersFound == len(word)-1:
await ctx.send("You've won! The word was: " + word)
bot.gamex[ctx.guild.id] = False
It's a hangman game, but the game variables are mixing on every server the bot is on, if I guess a word on one server, it appears on another server, I want each server to be individual and have commands individual.
Only those in the global are mixing.
What would the command look like so that the variables don't get mixed up between the servers?

Make a class that represents the state of the game, and replace your gamex mapping with a mapping of guild ids to games:
games = {}
class Game:
def __init__(self, word, guesses=6):
self.word = word.lower()
self.blanks = ["-"]*len(word)
self.guessedLetters = []
self.lettersFound = 0
self.guessesLeft = guesses
Then in your commands, you get the Game storing the state for that guild.
#bot.command()
async def create_game(ctx, *, word):
await ctx.message.delete()
if ctx.guild.id in games:
await ctx.send("Game already in progress")
else:
games[ctx.guild.id] = Game(word)
await ctx.send(embed=discord.Embed(title="hangman: " + " ".join(games[ctx.guild.id].blanks)))
And you would call del games[ctx.guild.id] to remove entries when the game is complete.
A well-designed Game object would mean that the logic about how the game is played would be pulled out of your commands and into the Game object. Ideally, you would be able to take the Game class from this code and use it to implement the same game in a browser or other interface with minimal changes.

Related

Telegram bot written using Telebot stucks when is used by 2+ people

I wrote a simple TicTacToe game: user sends cell's index and bot places character('X' or 'O') into that cell and plays back. It works fine as long as it's being used by one person. However, when a few are concurrently playing, bot refuses to process requests and sometimes sends the similar board(which I store in dict for each player individually)
I think that I messed up with async function 'MakeMove'. I haven't used python much and a bit of advise would be highly appreciated. Thank you!
import telebot
import asyncio
#Consists of objects of class Player
players = {}
class Player:
def __init__(self, userName):
self.userName = userName
self.winsAmount = 0
self.loses = 0
self.pickedChar = 'X'
self.isGameRunning = False
self.isTurn = True
self.currentBoard = Board(self.pickedChar)
...
#bot.message_handler(commands=['game'])
def StartGame(message):
chatId = message.chat.id
if chatId not in players:
players[chatId] = Player(message.from_user.username)
if not isGameRunning(chatId):
player = players[chatId]
player.isGameRunning = True
bot.send_message(chatId, f'Hello, *{player.userName}*!\nYour wins amount is _{player.winsAmount}_\nYou\'ve lost _{player.loses}_ times', parse_mode='Markdown')
keyboard = telebot.types.InlineKeyboardMarkup()
keyboard.row(telebot.types.InlineKeyboardButton('X', callback_data='charX'),
telebot.types.InlineKeyboardButton('O', callback_data='charO'))
bot.send_message(chatId, 'Choose your side: X | O', reply_markup=keyboard)
else:
keyboard = telebot.types.InlineKeyboardMarkup()
keyboard.row(telebot.types.InlineKeyboardButton('End the current game', callback_data='endGame'))
bot.send_message(chatId, 'The game is still running!', reply_markup=keyboard)
#bot.message_handler(content_types=['text'])
def HandleMoves(message):
player = players[message.chat.id]
board = player.currentBoard
for i in range(9):
if message.text == (str)(i + 1) and player.isGameRunning and player.isTurn:
if board.desk[i] == '_':
board.desk[i] = board.playerChar
player.isTurn = False
else:
bot.send_message(message.chat.id, 'The cell is already occupied')
.........................................................
# I suppouse these two functions cause the problem
async def CheckForTurn(chatId):
while True:
if not players[chatId].isTurn:
return
await asyncio.sleep(0.2)
async def MakeMove(chatId):
board = players[chatId].currentBoard
player = players[chatId]
while True:
DrawBoard(chatId)
if board.GetState() != States.IN_GAME:
EndGame(chatId, board.GetState())
break
if player.isTurn:
bot.send_message(chatId, 'Make a move (from 1 to 9)')
await CheckForTurn(chatId)
if not player.isTurn:
index = GetBestMove(board)
char = ' '
if player.pickedChar == 'O':
char = 'X'
else:
char = 'O'
board.desk[index] = char
player.isTurn = True

How to stop multiple of the same command from one user? (discord bots)

I am running something similar to a guessing game where you are given boundaries and my bot will tell you if your guess is higher or lower. I'm wondering how I would stop the user from opening multiple games? (as in, it would overlap messages and get real ugly and I want to avoid that.) Here is my code:
aliases=['gg', 'guessinggame'])
async def guessing_game(ctx, num):
number = random.randint(0, int(num))
await ctx.send("Game started!")
num_of_tries = 0
user = ctx.author.id
await ctx.send('Take a guess.')
while True:
response = await bot.wait_for('message')
currentsender = response.author.id
if currentsender != user or currentsender == bot.user.id:
continue
# if not response.content.lower().startswith('b&'):
# continue
if response.content == "stop bot pls":
await ctx.send(f'Stopped game, {response.author.mention}')
return
if not response.content.isdigit():
continue
num_of_tries += 1
guess = int(response.content)
if guess < number:
await ctx.send(f'My number is larger, {response.author.mention}.')
await ctx.send('Take a guess.')
elif guess > number:
await ctx.send(f'My number is smaller, {response.author.mention}.')
await ctx.send('Take a guess.')
else:
await ctx.send(f'Hey, {response.author.mention}, you got it! (with {num_of_tries} tries)')
return

Discord Bot Won't # People

I have been making a bot that adds people who react to a message to an array, and then #'s everyone on the array once a goal is met. I have gotten it to send out the message that should # everyone but it just shows the text instead of actually notifying the people. "Names" is the list that it gathers and "send_out" is supposed to format it.
#client.event
async def on_raw_reaction_add(payload):
if payload.message_id == messageid and payload.member.bot == False:
if str(payload.emoji.name) == "🦍":
name = str(payload.member.name + "#" + payload.member.discriminator)
global count
count += 1
global names
names.append(name)
print (names)
if (count == goal_actual):
print("Goal has been reached.")
channel = client.get_channel(channel_id)
await channel.send("We now have " + str(goal_actual) + " for " + game_actual + "!")
print(channel)
global send_out
for x in names:
send_out += ("#" + x +" ")
await channel.send(send_out)
send_out = []
else:
print("Detected reaction from " + name + ". There are is now " , count , " people ready.")
It's best to use the member object that payload gives you
Here (discord.py docs) we can see that discord.on_raw_reaction_add(payload) gives a RawReactionActionEvent object.
RawReactionActionEvent also has a member object, which is a normal member object, this has an attribute member.mention
You could use this instead to tag people
#client.event
async def on_raw_reaction_add(payload):
if payload.message_id == messageid and payload.member.bot == False:
if str(payload.emoji.name) == "🦍":
global count
count += 1
global names
names.append(payload.member)
print (names)
if (count == goal_actual):
print("Goal has been reached.")
channel = client.get_channel(channel_id)
await channel.send("We now have " + str(goal_actual) + " for " + game_actual + "!")
print(channel)
global send_out
for member in names:
send_out += (member.mention)
await channel.send(send_out)
send_out = []
else:
print("Detected reaction from " + name + ". There are is now " , count , " people ready.")

How to make my discord bot delete an nsfw word and not another sfw word that contains the nsfw word? (discord.py)

I made my discord bot delete nsfw words whenever it encounters them. But, I encountered a problem.
I had the word "turd" in my list. But whenever someone said "saturday", it got deleted too.
How do I make it so that the word alone gets deleted, and not an instance of it in another sfw word? Any help would be appreciated!
Here's my code:
def __init__(self, client):
self.client = client
self.client.attempts = {}
#commands.Cog.listener()
async def on_message(self, msg):
for word in file:
if word in msg.content.lower():
await msg.delete()
attempts = 0
try:
self.client.attempts[msg.author.id] += 1
except KeyError:
self.client.attempts[msg.author.id] = 1
#the person gets muted if they say the same nsfw word 3 times in a row.
if self.client.attempts[msg.author.id] == 3:
muted_role = msg.author.guild.get_role(783622936837226529)
await msg.author.add_roles(muted_role)
embed = discord.Embed(
title='',
description='',
colour=discord.Colour.red()
)
embed.add_field(name=f"✅ {msg.author.display_name} has been muted.", value='Reason: Toxicity')
await msg.channel.send(embed=embed)
await asyncio.sleep(1800)
await msg.author.remove_roles(muted_role)
break
else:
pass
You could use the following code. However, this is an inefficient method
for word in file:
if word in msg.content.lower():
if msg.content.lower() in ["saturday"]: return
You could use regex
import re
def string_found(word, content):
if re.search(r"\b" + re.escape(word) + r"\b", content):
return True
return False
for word in file:
if string_found(word, message.content.lower()) or string_found(string + "s", message.content.lower()):

Making embeds of more than one page using discord.py

I'm currently trying to make a bot using discord.py. One of the functions I want it to have is to show the users with X role. I've managed to create said function, and make the bot send the results with a nice embed. However, now I want to improve it and make it more similar to Nadeko's inrole function (i.e. only one embed and the user can react to it to change the page).
#client.command(name = "inrole", brief = "Users with a certain role",
description= "I'll give you a list with all the users with a certain role.", pass_context=True)
async def inrole(ctx, *args):
server = ctx.message.guild
role_name = (' '.join(args))
role_id = server.roles[0]
for role in server.roles:
if role_name.lower() == role.name.lower():
role_id = role
break
else:
await ctx.send(f"Oh noes! That role doesn't exist!")
return
n = 0
members = []
for member in server.members:
if role_id in member.roles:
n += 1
name = str(n) + ". " + member.name + " #" + member.discriminator + "\n"
members.append(name)
composite_members = [members[x:x + 20] for x in range(0, len(members), 20)]
for elements in composite_members:
string = ""
for element in elements:
string = string + element
embedVar = discord.Embed(title=f"List of users with {role} - {n}", colour=discord.Colour.blue())
embedVar.add_field(name = "Members", value = string)
await ctx.send(embed=embedVar)
I've read the documentation, but I'm still quite lost in how to make it like Nadeko's. Right now, if there are 200 users with that role, then it will print 10 embeds, which can be rather annoying.
Thanks in advance!
EDIT:
I've improved the code a bit, but now I have a different problem.
def predicate(message, l, r):
def check(reaction, user):
if reaction.message.id != message.id or user == client.user:
return False
if l and reaction.emoji == "⏪":
return True
if r and reaction.emoji == "⏩":
return True
return False
return check
#inrole function. To see all members with that role
#client.command(name = "inrole", brief = "Users with a certain role",
description= "I'll give you a list with all the users with a certain role.", pass_context=True)
async def inrole(ctx, *role):
server = ctx.message.guild
role_name = (' '.join(role))
role_id = server.roles[0]
for role in server.roles:
if role_name.lower() == role.name.lower():
role_id = role
break
else:
await ctx.send(f"Oh noes! That role doesn't exist!")
return
n = 0
members = []
for member in server.members:
if role_id in member.roles:
n += 1
name = str(n) + ". " + member.name + " #" + member.discriminator + "\n"
members.append(name)
composite_members = [members[x:x + 20] for x in range(0, len(members), 20)]
pages = []
for elements in composite_members:
string = ""
for element in elements:
string = string + element
embedVar = discord.Embed(title=f"List of users with {role} - {n}", colour=discord.Colour.blue())
embedVar.add_field(name = "Members", value = string)
pages.append(embedVar)
page = 0
left = "⏪"
right = "⏩"
while True:
msg = await ctx.send(embed = pages[(page)])
l = page != 0
r = page != len(pages) - 1
if l:
await msg.add_reaction(left)
if r:
await msg.add_reaction(right)
# bot.wait_for_reaction
react = await client.wait_for('reaction_add', check=predicate(msg, l, r))
if react[0] == left:
page -= 1
elif react[0] == right:
page += 1
await msg.delete()
In the last if/elif statement, it doesn't work. There isn't an error, but although react[0] is the same emoji as right or left (respectively), page never changes, and thus the embed doesn't change either. I changed the elif for an else, and it that case it does work, but it can only go forward.
Figured it out. Issue was that react[0] wasn't the same type as left and right. Here's the solution for that issue, if anyone is interested:
if str(react[0]) == left:
page -= 1
elif str(react[0]) == right:
page += 1
The rest of the code is exactly the same as the one I posted in the edit.
message_ = None
#client.command()
async def edit(ctx):
global message_
message_ = await ctx.send('EMBED PAGE[1]')
#client.event
async def on_reaction_add(reaction, user):
global message_
msg_ID = 487165969903517696
if int(reaction.message.id) != ChID:
return;
elif user.reaction.emoji == "🏃":
message_.edit(content='EMBED PAGE [2]')
this is just an idea! I'm not sure this code works but there is an option to edit our embed message! so use that to make your embed message better [documentation for edit]

Categories