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)
Related
Can't pass value from one function to another
i am using aiogram and FSM to pass
data['start_name2'] = message.text
from one function to another
who can tell me what is my mistake?
This function creates a state in the "Start" state group with the name "start_name2".
class Start(StatesGroup):
start_name2= State() #NAME
This is a telegram bot function that handles a callback query with the data 'NAME'. When triggered, the bot will send a message to the chat asking the user to write their name. The input name is then processed and sent to a GET API request, and the received information is then parsed and displayed in a limited form as messages in the chat. If the input name is invalid, the bot will send a message saying so. The function also adds a message with a link to a website at the end.
#dp.callback_query_handler(lambda c: c.data == 'NAME')
async def process_callback_button2(callback_query: types.CallbackQuery, state: FSMContext):
await bot.answer_callback_query(callback_query.id)
await bot.send_message(callback_query.message.chat.id, text='Please write name. Example : Alex Bortin',reply_markup=kb.inline_kb4)
await Start.start_name2.set()
try:
await callback_query.message.delete()
except Exception as exception:
print("someone click button")
#dp.message_handler(state=Start.start_name2)
async def namee(message: types.Message, state: FSMContext):
# Check if the input is valid
if not is_valid_input2(message.text):
await bot.send_message(message.from_user.id, "Invalid input. Please enter a valid name.")
return
name = message.text
async with state.proxy() as data:
data['start_name2'] = message.text
try:
msg = await bot.send_message(message.from_user.id, parse_mode="HTML" ,text=f'Entered name: <b>{name}</b>\n\nSearching...')#---
await bot.send_chat_action(message.from_user.id , ChatActions.TYPING)#---
for i in range(3):#---
random_emojis = random.sample(emojis, 4)#---
random_emojis_string = " ".join(random_emojis)#---
await asyncio.sleep(0.4)
await msg.edit_text(parse_mode="HTML" ,text=f'Entered name: <b>{name}</b>')#---
URL = "API"
URL2 = "JUST URL"
# GET
response_text = await fetch(URL)
soup = BeautifulSoup(response_text, "html.parser")
nameD = (soup.prettify())
print(nameD)
await msg.edit_text(parse_mode="HTML" ,text=f'Entered name: <b>{name}</b>') #---
pattern_value = r'"value"\s*:\s*(\d+),'
obj = json.loads(nameD)
data2 = ""
match_address = re.search(pattern_value, nameD)
if match_address:
value = match_address.group(1)
data2 += f'Data found: <b>{value}</b>\n'
else:
data2 += 'Data found: <b>0</b>\n'
count = 0
for index in obj['hits']['hits']:
if count >= 10:
break
count += 1
db_name = index['_index']
strings = set()
for a, b in index['_source'].items():
strings.add(f"<b>{a}</b> : {b}")
string = "\n".join(list(strings))
await bot.send_message(message.from_user.id, text=f"<b>data base name</b> : {db_name}\n{string}", parse_mode="HTML")
await message.reply('Page 0', reply_markup=get_keyboard(0))
text2 = hlink('website', URL2)
await bot.send_message(message.from_user.id, parse_mode="HTML" , text=f'\nThe report can be viewed on our WEB {text2}')
name = 0
except Exception as exception:
name = 0
text2 = hlink('website', URL2)
await bot.send_message(message.from_user.id, parse_mode="HTML" , text=f'\nThe report can be viewed on our WEB {text2}')
await state.finish()
This function is a callback handler for a callback query in the Telegram Bot API. It handles a vote "up" action, increasing the vote count by 1. It logs the callback data and retrieves a value from the state proxy (stored data). It also prints the retrieved value.
#dp.callback_query_handler(vote_cb.filter(action='up'))
async def vote_up_cb_handler(query: types.CallbackQuery, callback_data: dict, state: FSMContext):
logging.info(callback_data)
amount = int(callback_data['amount'])
amount += 1
async with state.proxy() as data:
start_name2 = data.get('start_name2')
print(start_name2)
#Here I try print start_name2
The maximum that I have achieved is that my last function outputs
"None"
I have been having issues with my queue feature as it would either delete two elements from the playlist dictionary at once when I skip a song using a command or it comes up with bugs when I let the songs finish- whenever I try to come up with a fix which would resolve both issues.
This is the relevant code -
def queueCheck(self, ctx, id, vc):
text = ""
if queues[id] != []: # if server player queue not empty
stream = queues[id].pop(0)
if self.skipping == True:
current[id] = tracks[id].pop(0)
if len(tracks[id]) != 0:
text = "Now playing **" + tracks[ctx.message.guild.id][0] + "**"
self.skipping = False
vc.play(stream, after=lambda e: self.queueCheck(ctx, id, vc))
else:
text = "Queue has finished playing!"
coro = ctx.send(text)
fut = asyncio.run_coroutine_threadsafe(coro, self.client.loop)
try:
fut.result()
except:
pass
async def playSong(self, ctx, url):
with youtube_dl.YoutubeDL(self.YDL_OPTIONS) as ydl:
info = ydl.extract_info(url, download=False)
if 'entries' in info: # if no url is input
url2 = info['entries'][0]['formats'][0]['url']
if ctx.message.guild.id not in tracks.keys():
tracks[ctx.message.guild.id] = [info['entries'][0]['title']]
else:
tracks[ctx.message.guild.id].append(info['entries'][0]['title'])
elif 'formats' in info: # if url is passed
url2 = info['formats'][0]['url']
if ctx.message.guild.id not in tracks.keys():
tracks[ctx.message.guild.id] = [info['title']]
else:
tracks[ctx.message.guild.id].append(info['title'])
#print(*(x for x in tracks[ctx.message.guild.id]), sep='\n')
stream = await discord.FFmpegOpusAudio.from_probe(url2, **self.FFMPEG_OPTIONS)
return stream
async def queue(self, ctx, url):
stream = await self.playSong(ctx, url)
guild = ctx.message.guild
if guild.id in queues:
queues[guild.id].append(stream)
await ctx.send('Added track ' + '**' + str(tracks[guild.id][len(tracks[guild.id]) - 1]) + '**')
#commands.command(name='play', help="Plays any song", aliases=['pl'])
async def play(self, ctx, *, url):
if ctx.author.voice is None:
return await ctx.send('You are not in a voice channel!')
if ctx.voice_client is None:
await ctx.author.voice.channel.connect()
vc = ctx.guild.voice_client
guild = ctx.message.guild
if not vc.is_playing() and not self.isPaused:# and not self.skipping:
stream = await self.playSong(ctx, url)
queues[guild.id] = [stream]
vc.play(stream, after=lambda e: self.queueCheck(ctx, guild.id, vc))
await ctx.send('Playing ' + '**' + str(tracks[ctx.message.guild.id][0]) + '**')
else:
await self.queue(ctx, url)
#commands.command(name='queue', help="Shows the current queue", aliases=['q','Q'])
async def showQueue(self, ctx):
if len(tracks[ctx.message.guild.id]) == 1:
queueEmbed = discord.Embed(title="Queue", description = "Your queue seems to be a tad bit empty :(", color=discord.Colour.random())
queueEmbed.add_field(name=':drum: Currently playing \n' + tracks[ctx.message.guild.id][0], value='\u200b', inline=False)
await ctx.send(embed=queueEmbed)
elif len(tracks[ctx.message.guild.id]) > 0 or current[ctx.message.guild.id] is not None:
queueEmbed = discord.Embed(title="Queue", color=discord.Colour.random())
queuee = ""
if len(tracks[ctx.message.guild.id]) != 0:
for i in range(1, len(tracks[ctx.message.guild.id])):
queuee += ":white_small_square:" + str(i) + ". " + tracks[ctx.message.guild.id][i] + "\n"
else:
queuee = "No songs here"
queueEmbed.add_field(name='Coming up next', value='**' + queuee + '**', inline=False)
queueEmbed.add_field(name=':drum: Currently playing \n' + tracks[ctx.message.guild.id][0], value='\u200b', inline=False)
await ctx.send(embed=queueEmbed)
else:
await ctx.send('No music in queue')
#commands.command(name='skip', help="Skips the current song")
async def skip(self, ctx):
if ctx.author.voice is None:
return await ctx.send('You are not in a voice channel!')
self.skipping = True
ctx.voice_client.stop()
self.isPaused = False
Cannot post the pic of the queue showing the wrong output but here is the output embed-
Queue
-Coming up next-
1. Song 2 # should be shown below
2. Song 3
-:drum: Currently playing-
Song 1 # has finished already
Basically, if I keep skipping my way through all the songs before they end, the queue will update, otherwise if I wait for them to finish, the queue never updates. I am aware that this has to do with the conditional check for self.skipping which only lets me update the queue if I am skipping songs, but how can I pop off elements from track the moment the song ends at the same time?
I'm not sure where you are getting the songs from as you haven't shown that section of your code, however I am going to assume you are using YoutubeDL. In the case of YoutubeDL, what you can do is extract the duration of the video in seconds, and then have a while loop counting up from 0 every second, once the counter reaches the same value as the duration of the video, just remove the track from the queue and carry on. For example:
def player():
with YoutubeDL(ydl_opts) as ydl:
info = ydl.extract_info(queues[id][0], download=False)
vduration = info['duration']
link = info['url']
voice.play(FFmpegPCMAudio(link, **ffmpeg_opts))
voice.is_playing()
#commands.command()
asnyc def play(ctx, url)
queues[id].append(url)
#do everything you do here
while True:
if 0 <= 0 < len(queues[id]):
player()
counter = 0
while not counter >= vduration:
await asyncio.sleep(1)
counter += 1
queues[id].pop(0)
This is just an extremely simple example because as I said before it's hard to answer if you do not show all the relevant code, however it should apply regardless. Essentially all you need is to extract the duration of the video into a variable and then just count up 1 every second for as long as the video is playing, and then when variables match, move on to the next video.
I made a discord bot that sends every 120 sec transactions from one ethereum addrs but i dont want to send same stuff over and over so if it send USDT token and Again in 120 sec try USDT to just skip it until it got new thing is that possble or not?
code:
import requests
import sys
import json
import discord
import time
btoken = "mytoken"
result=requests.get('https://api.ethplorer.io/getAddressHistory/0x3f5ce5fbfe3e9af3971dd833d26ba9b5c936f0be?apiKey=freekey&type=transfer')
result.status_code
result.text
result.json()
results = "soon:tm:"
def price_of_gas(inp):
def recursive_function(inp):
if type(inp) is list:
for i in inp:
ans = recursive_function(i)
if ans!=None: return ans
elif type(inp) is dict:
if 'name' in inp: return inp['name']
for i in inp:
ans = recursive_function(inp[i])
if ans!=None: return ans
else: return None
ans = recursive_function(inp)
return ans if ans else "Could NOT find the new token tx"
print (price_of_gas(result.json()))
class MyClient(discord.Client):
async def on_ready(self):
print('Logged on as', self.user)
async def on_message(self, message):
# don't respond to ourselves
if message.author == self.user:
return
if message.content == '.get':
await message.channel.send('Alert! Alert! Buy')
await message.channel.send(result.json()['operations'][0]['tokenInfo']['symbol'])
await message.channel.send(result.json()['operations'][0]['tokenInfo']['address'])
print ('get command was tryed')
else:
print ('comand not found')
if message.content == '.help':
await message.channel.send("try .get")
print ('help command was tryed')
if message.content == '.stop':
await message.channel.send('Bye...')
print('bye')
sys.exit()
if message.content == '.start':
while True:
# Code executed here
print ('done')
price_of_gas(result.json())
print (price_of_gas(result.json()))
await message.channel.send(price_of_gas(result.json()))
time.sleep(120)
#print(result.json()['operations'][0]['tokenInfo']['name'])
#print(result.json()['operations'][0]['tokenInfo']['symbol'])
#print(result.json()['operations'][0]['tokenInfo']['address'])
#print (result.json()['tokenSymbol'])
#print (result.text)
print ('done no errors')
print ('done no errors with check data')
client = MyClient()
client.run(btoken)
print ('done no errors 2')
so if user type .start bot will start while loop that sends newest tx from selected addrs but problem is it will send same stuff i just need some if statment in while loop or something.
You shoudn't use the requests module (it's blocking), you should use aiohttp instead
You also shoudn't use time.sleep as it also blocks the whole thread. You should use asyncio.sleep. (If you still stay with time.sleep you're not going to be able to use the bot when it's "sleeping")
Answering your question, you can simply have a variable with the value of the previous price/value and check if the new message is the same, if it's not - send it
while True:
price = price_of_gas(result.json())
# Checking if the `previous_price` var exists
if hasattr(self, "previous_price"):
# If yes, comparing the values
if self.previous_price != price:
# If they're not the same, send the message
await message.channel.send(f"Current price: {price}")
self.previous_price = price # Updating the variable
else:
# If the `previous_price` var doesn't exists, creating it
self.previous_price = price
await asyncio.sleep(120) # Remember to import asyncio
Making HTTP requests with aiohttp
import aiohttp
async def main():
async with aiohttp.ClientSession() as session:
async with session.get("URL") as resp:
data = await resp.json()
# Note: you should create ONE session per application
EDIT:
If you want to use blocking functions (like the price_of_gas) you can use the next method
await self.loop.run_in_executor(None, price_of_gas, result.json())
More info here
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
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.