discord.py asyncio coroutine - function partially running - python

I am writing a discord bot with discord.py and am trying to implement a system to buy stocks in, on python 3.6 as python3.7+ has asyncio.run(func()).
import discord
import asyncio
import os
import random
import time
import math
client = discord.Client()
# contains coin information including balance, stock price, number of stocks, etc
with open('coins.conf', 'r') as f:
for line in f.readlines():
exec(line)
def save_all():
with open('coins.conf', 'w') as f:
f.write('coins = '+repr(coins))
random.seed(os.urandom(32))
searchusers = []
bank_cooldown = {}
bans['global'] = False
#client.event
async def on_ready():
'''Notification on ready.'''
print('Logged in! Bot running.')
await client.change_presence(activity=discord.Game(name='/help'))
#client.event
async def on_member_join(user):
'''Direct message the rules on member join.'''
await user.create_dm()
await user.dm_channel.send(f'Hi **{user.name}**, welcome to the server! Be sure to read the rules to stay out of trouble. Have a great time!')
def getcoins(uid):
'''Get the amount of coins, if nonexistent set to 0.'''
try:
return coins[uid][0]
except Exception:
coins[uid] = [0, time.time()+1, time.time()+1, 0, 320, time.time()+600, 0]
return 0
def mention_to_uid(mention):
'''Extract the UID from a mention (exclude first two char and last char)'''
uid = mention[2:-1]
if uid[0] == '!':
uid = uid[1:]
return uid
def setcoins(uid, value):
'''Set the amount of coins someone has.'''
try:
coins[uid][0] = value
except Exception:
coins[uid] = [value, time.time()+1, time.time()+1, 0, 320, time.time()+600, 0]
#client.event
async def on_message(message):
'''Main bot code running on message.'''
#############################
# some initialization stuff #
#############################
if message.content.startswith('/'):
user = message.author.id
name = message.author.display_name
text = message.content[1:].strip()
command = text.split(' ')[0]
subcommand = text.split(' ')[1:]
######################
# other bot commands #
######################
if command == 'stocks':
if len(subcommand) < 2:
await message.channel.send('Missing arguments! `/stocks [buy | sell] <amount>` or `/stocks help [info | price]`')
elif subcommand[0] == 'help':
if subcommand[1] == 'info':
msg = 'Invest in bot stocks! Use `/stocks help price` to find the current price per share.'
msg += '\nEvery 7-15 minutes, the stock price will change. It will decrease or increase by a little bit.'
msg += '\nStocks will more than likely output profit, but it is very random.'
msg += '\nEvery share bought increases the price by 1 and every share sold decreases the price by 1.'
await message.channel.send(msg)
elif subcommand[1] in ('price', 'amount', 'cost'):
await message.channel.send(f"The current stock price is **{getcoins('stock')}**. Get them while you can!")
elif subcommand[0] == 'buy':
amount = int(subcommand[1])
if amount < 0:
await message.channel.send('lol dummy. Positive number dude.')
else:
cost = (amount * getcoins('stock')) + (amount**2 - amount)//2 # price including increases
if getcoins(user) < cost:
await message.channel.send(f"You don't have enough coins to buy that many shares! Try `/balance`.\n[Debug] {cost}")
else:
coins[user][6] += amount # coins[user][6] => number of stocks user has
setcoins(user, getcoins(user)-cost)
setcoins('stock', getcoins('stock')+amount)
await message.channel.send(f'You bought {amount} shares for {cost} coins.')
elif subcommand[0] == 'sell':
amount = int(subcommand[1])
if amount < 0:
await message.channel.send('lol dummy. Positive number dude.')
else:
sell = (amount * getcoins('stock')) - (amount**2 - amount)//2 # price including decreases
if coins[user][6] < amount:
await message.channel.send(f"You don't have enough shares!")
else:
coins[user][6] -= amount
setcoins(user, getcoins(user)+sell)
setcoins('stock', getcoins('stock')-amount)
await message.channel.send(f'You sold {amount} shares for {sell} coins.')
else:
await message.channel.send('Invalid arguments! `/stocks [buy | sell] <amount>` or `/stocks help [info | price]`')
######################
# other bot commands #
######################
##################################
# this stuff is the main problem #
##################################
async def main():
'''Bot code.'''
await client.start('Nj*********************************************************')
while True:
await asyncio.sleep(random.randint(420,900))
## I'm certain anything below this doesn't run ##
change = random.choice([ *1*[-50], *4*[-8], *5*[-7], *6*[-6], *7*[-5], *8*[-4], *9*[-3], *10*[-2], *10*[-1],
*10*[0], *10*[1], *10*[2], *10*[3], *9*[4], *8*[5], *7*[6], *6*[7], *5*[8], *4*[9], *3*[10], *2*[12], *1*[15]
])
setcoins('stock', getcoins('stock')+change)
if getcoins('stock') < 15:
setcoins('stock', 15)
setcoins('jackpot', getcoins('jackpot')+random.randint(10, 20))
asyncio.get_event_loop().run_until_complete(main())
Only half of the function is running.
The line await client.start() is not supposed to be a blocking call, and anyway is awaited. Even after waiting 20 minutes (should maximum 15 minutes) the second part of the loop - the change = random.choice line and everything below it does not run.
I think the problem is the client.start call is blocking, but from the documentation (https://discordpy.readthedocs.io/en/latest/api.html#discord.Client.start)
await start(*args, **kwargs)
This function is a coroutine.
A shorthand coroutine for login() + connect().
Raises
TypeError – An unexpected keyword argument was received.
the start command is a coroutine.
I have tried lowering the asyncio.sleep time down to 10 seconds, and even removing it entirely. The stock price and ticket jackpot amount which is what is supposed to be modified does not change.

Related

Send winnings from a specific account using python and json in python

`Hi Guys,
I need some help.
So.. ive been developing a discord bot over the last year and trying to make some improvements to the games i have made.. it is basically an economy system that allows users to transfer funds to one another, place bets, and recieve winnings etc.
I have created a slots game that automatically generates winnings and economy but my idea is to modify an exisiting heads or tails game with the same functions to basically:
Allow a user to deposit a bet into an account that holds the winnings/profits/bets etc.
The user will then either select heads or tails to win the challenge.
The winnings will then be transferred directly from the specific account.
My current code is:
mport discord
import random
from discord.ext import commands
from discord.ext import commands, tasks
from discord.ui import select, View
from discord.embeds import Embed
from discord import colour
import asyncio
from itertools import cycle
import os
import json
import datetime
from random import choice
intents = discord.Intents.default()
intents.message_content = True
client = commands.Bot(command_prefix='!', intents=intents) #Command prefix
#Notifies vs code that the bot is running on VS code#
#client.event
async def on_ready():
print("Bot is ready")
#Slot machine command when user enters !slots 50#
#client.command(pass_context=True)
async def slots(ctx, amount=None):
if amount == None:
em = discord.Embed(title=f'❓Please enter an amount❓',color = discord.Color.red())
em.timestamp = datetime.datetime.utcnow()
await ctx.send(embed= em)
return
bal = await update_bank(ctx.author)
amount = int(amount)
if amount > bal[0]:
em = discord.Embed(title=f'❌You do not have sufficient balance❌')
em.timestamp = datetime.datetime.utcnow()
await ctx.send(embed= em)
return
if amount < 0:
em = discord.Embed(title=f'❗Amount must be positive❗')
em.timestamp = datetime.datetime.utcnow()
await ctx.send(embed= em)
return
slots = ['🧝‍♂️', '🔥']
slot1 = slots[random.randint(0, 1)]
slotOutput = '| :{}: |\n'.format(slot1)
ok = discord.Embed(title = "RTC slot machine", color = discord.Color(0xFFEC))
ok.add_field(name = "{}\nWon".format(slotOutput), value = f'You won {1.9*amount} coins')
won = discord.Embed(title = "Slots Machine", color = discord.Color(0xFFEC))
won.add_field(name = "{}\nWon".format(slotOutput), value = f'You won {1.9*amount} coins')
if slot1:
await update_bank(ctx.author, 1.9 * amount)
await ctx.send(embed = won)
return
The deposit bet command for the agreed bet when user enters !db #hostname 500 for example#
#client.command()
async def db(ctx,member : discord.Member,amount = None):
await open_account(ctx.author)
await open_account(member)
if amount == None:
em = discord.Embed(title=f'❓Please enter an amount you wish to bet❓',color = discord.Color.red())
em.timestamp = datetime.datetime.utcnow()
await ctx.send(embed= em)
return
bal = await update_bank(ctx.author)
if amount == 'all':
amount = bal[0]
amount = int(amount)
if amount > bal[0]:
em = discord.Embed(title=f'❌You do not have sufficient balance❌',color = discord.Color.red())
em.timestamp = datetime.datetime.utcnow()
await ctx.send(embed= em)
return
if amount < 0:
em = discord.Embed(title =f'❗Amount must be positive❗',color = discord.Color.red())
em.timestamp = datetime.datetime.utcnow()
await ctx.send(embed= em)
return
await update_bank(ctx.author,-1*amount,'wallet')
await update_bank(member,amount,'bank')
em = discord.Embed(title=f'⚔{ctx.author.name} sent {amount}M RS3 to {member}', description=f'Please select the game/challenge !coinflip !elements or !yesandno⚔',color = discord.Color.green())
em.timestamp = datetime.datetime.utcnow()
await ctx.send(embed= em)
#Creates account details for member of the channel#
async def open_account(user):
users = await get_bank_data()
if str(user.id) in users:
return False
else:
users[str(user.id)] = {}
users[str(user.id)]["wallet"] = 0
users[str(user.id)]["bank"] = 0
with open('bank.json','w') as f:
json.dump(users,f)
return True
#Stores the user wallet amount in the bank.json file#
async def get_bank_data():
with open('bank.json','r') as f:
users = json.load(f)
return users
#Updates the users bank details held within the json file.
async def update_bank(user,change=0,mode = 'wallet'):
users = await get_bank_data()
users[str(user.id)][mode] += change
with open('bank.json','w') as f:
json.dump(users,f)
bal = users[str(user.id)]['wallet'],users[str(user.id)]['bank']
return bal
client.run("'''''")
Im looking for a way to modify the code so it takes the winnings from a specific account by implimenting the account ID or username.
Any ideas?`

How to nullify python asyncio global variables?

I am writing a personal Telegram Bot to collect statistics on my accounts market.csgo.com. The main task of the bot is to send async requests to the API and display information via Telegram. Everything works as it should, but the problem is with global variables, or rather, their incorrect counting. An example of one of my functions:
...
import asyncio
import aiohttp
sale_total_sum = 0
amount_total_items = 0
async def get_on_sale(session, dictt, message):
global sale_total_sum
global amount_total_items
async with session.get(f'https://market.csgo.com/api/v2/items?key={dictt[1][1]}') as resp:
html = await resp.json()
if html['items'] is None:
pass
else:
each_sale_sum = 0
each_amount_items = 0
for i in html['items']:
sale_total_sum += i['price']
each_sale_sum += i['price']
each_amount_items += 1
amount_total_items += 1
try:
await bot.send_message(message.from_user.id,
f'{dictt[0]} : <b>{each_sale_sum} $</b>\nItems: <i>{each_amount_items}</i>',
disable_web_page_preview=True, parse_mode=types.ParseMode.HTML)
except exceptions.RetryAfter as e:
await asyncio.sleep(e.timeout)
#dp.message_handler(content_types=['text'])
async def Main(message):
profiles = users()
async with aiohttp.ClientSession(trust_env=True) as session:
tasks = []
if message.text == 'On Sale 💰':
await bot.send_message(message.from_user.id, 'Information request. Wait..')
for i in profiles.items():
task = asyncio.ensure_future(get_on_sale(session, i, message))
tasks.append(task)
await asyncio.gather(*tasks)
await bot.send_message(message.from_user.id,
f'<b>Total on sale: {sale_total_sum} $\nTotal items: {amount_total_items}\nBot start at: {start}</b>',
reply_markup=kb_client, parse_mode=types.ParseMode.HTML)
executor.start_polling(dp, skip_updates=True)
Function result:
Account_1: 100 $
Items: 1
Account_2: 200 $
Items: 2
Account_3: 300 $
Items: 3
Total on sale: 600 $
Total items: 6
Bot works in polling mode executor.start_polling(dp, skip_updates=True). If the function async def get_on_sale is called for the first time after enabling, then its final count Total on sale: 600 ₽ Total items: 6 will be correct, but subsequent calls will double this amount, which is actually not like this:
Account_1: 100 $
Items: 1
Account_2: 200 $
Items: 2
Account_3: 300 $
Items: 3
Total on sale: 1200 $
Total items: 12
I know that the problem is in the global variables global sale_total_sum and global amount_total_items. But if you use simple variables instead, they will simply be overwritten, and not summarized as I need. Therefore, I want to ask - is there a way to somehow reset or reassign these global variables to 0 after the function ends? So that the data would be correct on the next call. Thank you.
The solution was to create an object with two fields where get_on_sale is the argument
from dataclasses import dataclass
#dataclass
class AggregatedStats:
sum: int = 0
items: int = 0
async def get_on_sale(session, dictt, message, stats):
...
for i in html['items']:
stats.sum += i['price']
each_sale_sum += i['price']
each_amount_items += 1
stats.items += 1
...
#dp.message_handler(content_types=['text'])
async def Main(message):
profiles = users()
async with aiohttp.ClientSession(trust_env=True) as session:
tasks = []
if message.text == 'On Sale 💰':
await bot.send_message(message.from_user.id, 'Information request. Wait..')
stats = AggregatedStats()
for i in profiles.items():
task = asyncio.ensure_future(get_on_sale(session, i, message, stats))
tasks.append(task)
await asyncio.gather(*tasks)
await bot.send_message(message.from_user.id,
f'<b>Total on sale: {stats.sum} ₽\nTotal items: {stats.items}\nBot start at: {start}</b>',
reply_markup=kb_client, parse_mode=types.ParseMode.HTML)

Is there a way that i can pause a function but not effect other functions in Python?

So basically i am coding a discord bot in python. It is a bot where you can buy/sell things from a shop, and go out farming or mining for resources. The main bit i need help with is making the function rest for around 5 minutes. This bot will probably be used multiple times in 5 minutes, so i can't pause the whole code. obviously time.sleep will not work since it pauses the whole script. I have tried a few different things but they didn't work well and i couldn't find much else on the internet. My code is:
#client.command()
async def farm(ctx, item):
fakelocation = 11
await get_bank_data()
users = await get_bank_data()
if users[str(ctx.author.id)]["location"] == "Farm":
item = item.lower()
returnholder = 6
product = "none"
user = ctx.author
users = await get_bank_data()
if item == "potato":
product = "potato"
amount = random.randrange(2, 10)
await ctx.send("Your crops will be given to you at the end of the session!")
returnholder = 5
if item == "carrot":
product = "carrot"
amount = random.randrange(4, 12)
await ctx.send("Your crops will be given to you at the end of the session!")
returnholder = 5
if item == "wheat":
product = "wheat"
amount = random.randrange(7, 15)
await ctx.send("Your crops will be given to you at the end of the session!")
returnholder = 5
if item == "apple":
product = "apple"
amount = random.randrange(2, 13)
await ctx.send("Your crops will be given to you at the end of the session!")
returnholder = 5
if item == "banana":
product = "banana"
amount = random.randrange(4, 10)
await ctx.send("Your crops will be given to you at the end of the session!")
returnholder = 5
if item == "orange":
product = "orange"
amount = random.randrange(3, 8)
await ctx.send("Your crops will be given to you at the end of the session!")
returnholder = 5
if returnholder == 6:
await ctx.send("That crop does not exist here!")
return
try:
index = 0
t = None
for thing in users[str(user.id)]["bag"]:
n = thing["item"]
if n == item:
old_amt = thing["amount"]
new_amt = old_amt + amount
users[str(user.id)]["bag"][index]["amount"] = new_amt
t = 1
break
index += 1
if t == None:
obj = {"item": product, "amount": amount}
users[str(user.id)]["bag"].append(obj)
except:
obj = {"item": product, "amount": amount}
users[str(user.id)]["bag"] = [obj]
with open("mainbank.json", "w") as f:
json.dump(users, f)
fakelocation = 4
if fakelocation == 11:
await ctx.send("Please move to the Farm to farm!")
return
This is the code and it is relatively similar to the Mine function. it checks what the item is and then if it exists it will give you a random amount of items.I want to make it stop first thing in try: since otherwise it will give you the items before the time is up. Thanks for any help!!
import asyncio
await asyncio.sleep(SECONDS)
This was the answer for me. Thanks #TinNguyen for answering my question simply.

Running two loops at the same time on my discord bot

So my discord bot in python has the task to send an embed as soon as the user sends the message "$start". After this the code is starting a while loop and checking if a user has reacted. Simultaneously I want to edit the message of the bot every second so I can display some kind of timer to show the users how much time they have left to react but I dont know how to implement a timer running at the same time. Would it be useful to use multiprocessing for this one?
Here is my very specific code if anyone needs it to answer my question :)
#client.command()
async def start(ctx):
timer = 120
remTime = timer
seedCapital = 10000
sessionEmpty = True
players[ctx.author] = seedCapital
em = discord.Embed(title = "New Poker Session", description = "Waiting for players to join ...\nreact with 💸 to join or ▶️ to start (only host)", color = discord.Color.green())
#em.add_field(name = "2 minutes remaining from now on!", value = "")
botMsg = await ctx.send(embed = em)
await botMsg.add_reaction("💸")
await botMsg.add_reaction("▶️")
while True:
mom0 = time.perf_counter()
try:
reaction, user = await client.wait_for('reaction_add', timeout = remTime, check = lambda reaction, user: reaction.emoji in ["💸", "▶️"])
#msg = await client.wait_for('message', timeout = remTime, check = lambda m: m.channel == ctx.channel)
#print(f"receiving message: {msg.content}")
except asyncio.TimeoutError:
break
if reaction.emoji == "💸":
sessionEmpty = False
await ctx.send(f"{user.mention} joined the session!")
players[user] = seedCapital
elif user == ctx.author and reaction.emoji == "▶️":
break
mom1 = time.perf_counter()
neededTime = mom1 - mom0
remTime -= neededTime
print(remTime)
if not sessionEmpty:
await ctx.send("starting session ...")
#start session
else:
await ctx.send("Noone joined your session :(")
We can use asyncio.gather to run coroutines concurrently.
#after command
from datetime import datetime, timedelta
end_time = datetime.now() + timedelta(seconds=30) #30 seconds to react
async def coro():
for i in range(30, 0, 5):
await botMsg.edit(f'time remaining: {i}') #send another message if you don't want old content to be erased
await asyncio.sleep(5)
async def coro2():
while datetime.now() <= endtime:
#do stuff
await asyncio.gather(coro(), coro2())
Note: There might be a small delay between the completion of the two coroutines.
References:
asyncio.gather
Edit: Here is the small delay I mentioned

How do I run multiple instances of a command and be able to cancel a certain instance in discord.py

I have a simple discord.py bot that pretty much just counts to whatever number a user wants. So far, the command looks like this:
#bot.command()
async def count(ctx, startNum, endNum):
startNum = int(startNum)
endNum = int(endNum)
currentNum = startNum
if startNum > endNum:
await ctx.send("nice try, i'm not counting backwards")
while currentNum < (endNum + 1) or startNum < endNum:
await ctx.send(ctx.message.author.name + ": " + str(currentNum))
currentNum += 1
await ctx.send(ctx.message.author.mention + " I've finished counting to " + str(endNum))
Lets say you run count 10, it will display
username: 1
username: 2
username: 3
...
username: 10
I would like to create a command that pretty much allows a user to cancel one specific counter and not any others.
Preferably, each counter would display a seperate counter ID, which you can then cancel using a command like cancel ID. It would sort of look like:
> count 1 50
CounterID: 1
CounterID: 2
CounterID: 3
> cancel CounterID
CounterID has been cancelled
How could this be done?
In the while loop, you could add a wait_for event with a timeout of 1 second:
import asyncio # for the exception
#bot.command()
async def count(...):
# code
# make sure the person cancelling the timer is the original requester
# and they are cancelling it from the same channel
def check(msg):
return msg.author == ctx.author and msg.channel == ctx.channel and \
msg.content.lower().startswith("cancel")
while currentNum < endNum:
try:
msg = await bot.wait_for(message, check=check, timeout=1)
await ctx.send("Your counter has been cancelled!")
break # break the loop if they send a message starting with "cancel"
except asyncio.TimeoutError:
await ctx.send(ctx.author.name + ": " + str(currentNum))
currentNum += 1
Though that example won't allow for specific counters to be cancelled (apart from a user's own counter). If you had some database (such as json, sqlite, mongodb etc.), you could perhaps store current counter IDs for each user.
References:
Client.wait_for()
asyncio.TimeoutError

Categories