I am trying to run this code in a way that runs the function background() at startup while also running client.run() to start a discord bot. How can I achieve this? In its current state, depending on which order I call the functions mentioned above, it only runs the first one, so in this case it only runs the discord bot because i called the function first...
from hashlib import new
from re import I
from web3 import Web3
import sys, json, time, asyncio, threading
from logging import exception
import json
import time
import urllib3
import discord
from discord.ext.commands import Bot
from discord.ext import commands
# set rpc
web3 = Web3(Web3.HTTPProvider("rpc goes here (i have hidden for this post)"))
# use https://github.com/Cog-Creators/Red-DiscordBot/issues/581 to solve SSL problems on Mac OS
# use "token" not "client secret" for discord bot
discord_token = ('discord secret here i have hidden for this post')
# init discord stuff
client = discord.Client()
# set bot command prefix
client = commands.Bot(command_prefix = '$') #put your own prefix here
# take block data input, parse for new contract, then call discord function to broadcast
async def contractTx(txhash):
# call when new block is found to get contract addy and send it to discord
new_contract = web3.eth.get_transaction_receipt(txhash)
global contract_address
contract_address = new_contract['contractAddress']
print(f"New contract deployed: {contract_address}")
await new_contract_discord(contract_address=contract_address)
# runs on program startup
async def background():
# set block index
global block_index
block_index = web3.eth.get_block_number()
# start looping through blocks
while True:
if block_index != web3.eth.get_block_number():
# new block, do the things
# set current block to new block
block_index = web3.eth.get_block_number()
# check if transactions in new block contain new contract creation
# if yes, send to main function
print(f"NEW BLOCK: {block_index}")
global transactions
current_block = web3.eth.get_block(block_index, full_transactions=True)
transactions = current_block.transactions
for tx in transactions:
if tx['to'] != None:
# is a contract creation tx, send to export function
task1 = asyncio.create_task(contractTx(tx['hash'].hex()))
else:
# still the same block, so wait a bit
print("no new blocks")
await asyncio.sleep(1)
#client.event
async def on_ready():
print("bot online") #will print "bot online" in the console when the bot is online
#client.event
async def new_contract_discord(ctx, contract_address):
channel = client.get_channel(949889447938367531)
await channel.send(f"new contract: {contract_address}")
# test command so you know how it works
# do $foo "string" and it responds with "string"
#client.command()
async def foo(ctx, arg):
await ctx.send(arg)
# run the discord bot with the token
client.run(discord_token)
# run the background func
asyncio.run(background())```
import threading
def A():
while True:
print("A")
def B():
while True:
print("B")
threading.Thread(target=A).start()
threading.Thread(target=B).start()
This will output ABABAB... So, the functions are running simultaneously.
Related
I'm trying to build a discord bot that sends new twitter posts into chat (in realtime). Not all that familiar to async yet, and with minimal examples available I'm still kind of lost.
I have two classes, twitterClient and discordClient, and I pass the message sending function and the event loop to the twitterClient. I then call asyncio.run_coroutine_threadsafe to run the message sending function when the twitterClient receives a tweet.
When I run this and post a tweet, the program seems to receive the tweet (as shown by the print statement) but the program doesn't get any further. Since ctrl-c doesn't work after this step, it seems to me that the program freezes there. What should I do? Am I even using asyncio correctly?
import discord
from discord.ext import commands
import tweepy
import asyncio
import os
from dotenv import load_dotenv
load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
GUILD = os.getenv('DISCORD_GUILD')
BEARER = os.getenv('TWITTER_BEARER_TOKEN')
rule = {'fromknowgnod':'from:knowgnod'}
class twitterClient(tweepy.StreamingClient):
def __init__(self, send, loop, *args, **kwargs):
super().__init__(*args, **kwargs)
self.loop = loop
self.send = send
def on_connect(self):
print('connected to stream')
# check if rule already uploaded
api_rule_tags = [i.tag for i in self.get_rules().data]
if list(rule.keys())[0] not in api_rule_tags:
self.add_rule(value=rule[list(rule.keys())[0]], tag=list(rule.keys())[0])
def on_tweet(self, tweet):
print("received tweet: "+str(tweet.id))
a = asyncio.run_coroutine_threadsafe(self.send(tweet.id), self.loop)
a.result()
def on_errors(self, errors):
print(f"Received error code {errors}")
self.disconnect()
return False
class discordClient(discord.Client):
async def on_ready(self):
print(f'Logged on as {self.user}!')
myStream = twitterClient(self.send_message, asyncio.get_event_loop(), BEARER)
myStream.filter()
async def send_message(self, msg):
c = self.get_channel(1003516355003289694)
c.send(f'https://twitter.com/knowgnod/status/{msg}')
intents = discord.Intents.default()
intents.messages = True
client = discordClient(intents=intents)
client.run(TOKEN)
I am trying to run two different Discord Bots using a single python script using cogs. But when I try to run the 2nd bot it throws an ImportError even-though I didn't use that specific Library. The reaction roles bot works fine without the anti spam bot. Here's my code. FYI I am working inside a Virtual Env.
main.py
if __name__ == "__main__":
try:
reaction_role_bot = commands.Bot(command_prefix=config["reaction_role_bot"]["bot_prefix"], intents=discord.Intents.all())
reaction_slash = SlashCommand(reaction_role_bot, sync_commands=True)
reaction_role_bot.load_extension(f"cogs.{str(os.path.basename('cogs/reaction_roles.py')[:-3])}")
anti_spam_bot = commands.Bot(command_prefix=config["anti_spam_bot"]["bot_prefix"], intents=discord.Intents.default())
spam_slash = SlashCommand(anti_spam_bot, sync_commands=True)
anti_spam_bot.load_extension(f"cogs.{str(os.path.basename('cogs/anti_spam.py')[:-3])}")
event_loop = asyncio.get_event_loop()
event_loop.create_task(reaction_role_bot.run(config["reaction_role_bot"]["token"]))
event_loop.create_task(anti_spam_bot.run(config["anti_spam_bot"]["token"]))
event_loop.run_forever()
except Exception as e:
print(e)
anti_spam.py
import platform
import os
import discord
from discord.ext import commands
from antispam import AntiSpamHandler
from antispam.plugins import AntiSpamTracker, Options
class AntiSpamBot(commands.Cog):
def __init__(self, client):
self.client = client
# Initialize the AntiSpamHandler
self.client.handler = AntiSpamHandler(self.client, options=Options(no_punish=True))
# 3 Being how many 'punishment requests' before is_spamming returns True
self.client.tracker = AntiSpamTracker(self.client.handler, 3)
self.client.handler.register_extension(self.client.tracker)
#commands.Cog.listener()
async def on_ready(self):
print("---------------------------------")
print(f"Logged in as {str(self.client.user)}")
print(f"Discord.py API version: {discord.__version__}")
print(f"Python version: {platform.python_version()}")
print(f"Running on: {platform.system()} {platform.release()} ({os.name})")
await self.client.change_presence(status=discord.Status.idle, activity=discord.Game(name="Head of Security"))
print("---------------------------------\n")
# The code in this event is executed every time a valid commands catches an error
#commands.Cog.listener()
async def on_command_error(context, error):
raise error
#commands.Cog.listener()
async def on_message(self, message):
await self.client.handler.propagate(message)
if self.client.tracker.is_spamming(message):
await message.delete()
await message.channel.send(f"{message.author.mention} has been automatically kicked for spamming.")
await message.author.kick()
await self.client.process_commands(message)
def setup(client):
client.add_cog(AntiSpamBot(client))
Error
Extension 'cogs.anti_spam' raised an error: ImportError: cannot import name 'AsyncMock' from 'unittest.mock' (/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/mock.py)
I've no experiences using cogs and its bit confusing me. Any kind of help would help me to sort this out! Thanks in advance!
I do not believe this is a cog registration issue. I believe this is an import error from some of the dependencies in your cog file. I googled your error and found something similar, I recommend checking it out here for some more information.
As a blanket statement, I would double check that you have mock installed, and that you're installing it on the version of Python that you think you're installing it on. It can get wonky if you have multiple python versions insealled.
Also, on an unrelated note:: It is best to avoid running multiple bot instances in one python file, but I can help you do it the best way possible.
For starters, you have to realize that Client.run is an abstraction of a couple of more lower level concepts.
There is Client.login which logs in the client and then Client.connect which actually runs the processing. These are coroutines.
asyncio provides the capability of putting things in the event loop for it to work whenever it has time to.
Something like this e.g.
loop = asyncio.get_event_loop()
async def foo():
await asyncio.sleep(10)
loop.close()
loop.create_task(foo())
loop.run_forever()
If we want to wait for something to happen from another coroutine, asyncio provides us with this functionality as well through the means of synchronisation via asyncio.Event. You can consider this as a boolean that you are waiting for:
e = asyncio.Event()
loop = asyncio.get_event_loop()
async def foo():
await e.wait()
print('we are done waiting...')
loop.stop()
async def bar():
await asyncio.sleep(20)
e.set()
loop.create_task(bar())
loop.create_task(foo())
loop.run_forever() # foo will stop this event loop when 'e' is set to true
loop.close()
Using this concept we can apply it to the discord bots themselves.
import asyncio
import discord
from collections import namedtuple
# First, we must attach an event signalling when the bot has been
# closed to the client itself so we know when to fully close the event loop.
Entry = namedtuple('Entry', 'client event')
entries = [
Entry(client=discord.Client(), event=asyncio.Event()),
Entry(client=discord.Client(), event=asyncio.Event())
]
# Then, we should login to all our clients and wrap the connect call
# so it knows when to do the actual full closure
loop = asyncio.get_event_loop()
async def login():
for e in entries:
await e.client.login()
async def wrapped_connect(entry):
try:
await entry.client.connect()
except Exception as e:
await entry.client.close()
print('We got an exception: ', e.__class__.__name__, e)
entry.event.set()
# actually check if we should close the event loop:
async def check_close():
futures = [e.event.wait() for e in entries]
await asyncio.wait(futures)
# here is when we actually login
loop.run_until_complete(login())
# now we connect to every client
for entry in entries:
loop.create_task(wrapped_connect(entry))
# now we're waiting for all the clients to close
loop.run_until_complete(check_close())
# finally, we close the event loop
loop.close()
I have a cog which is ment to handel events and loops. I am using aioschedule to run ping every minute as a test if the aioschedule works. But now the problem is that when I put it inside the class it asks for self but when i give self it gives an error.pls help with this
import discord
import os
import asyncio
import time
from discord.ext import commands,tasks
import aioschedule as schedule
class Events(commands.Cog):
def __init__(self, client):
self.client = client
async def bot_test_clear(self):
channel_bot_test = self.client.get_channel(os.getenv('bot-test-text'))
messages = await channel_bot_test.history(limit=100).flatten()
if not messages:
return
embed = discord.Embed(description='It has been 1 hour, clearing chats...', color=0xff0000)
await channel_bot_test.send(embed=embed)
await asyncio.sleep(10)
await channel_bot_test.purge(limit=None)
async def ping(self):
self.client.send("pong")
schedule.every(5).seconds.do(ping)
#commands.Cog.listener()
async def on_ready(self):
self.bot_text_clear.start()
print(f'{self.client.user} is now online')
def setup(client):
client.add_cog(Events(client))
loop = asyncio.get_event_loop()
while True:
loop.run_until_complete(schedule.run_pending())
time.sleep(0.1)
img of the error
So I answered your earlier question and suggested you use aioschedule so I will show you how I use it.
def _run_scheduler(self):
""" Create the scheduler task. """
async def _run_scheduler():
self._scheduler_running = True
print("Created 'Scheduler' task")
while self._scheduler_running:
await aioschedule.run_pending()
await asyncio.sleep(0.25)
if not self._scheduler_running:
asyncio.create_task(_run_scheduler())
The above method is attached to my bot, and is called once when the bot is created. The above code will process all the jobs you schedule. I use create_task so I can call the method from a non-async method.
#commands.Cog.listener()
async def on_startup(self):
aioschedule.every().monday.at("21:15").do(self.announce_winners)
aioschedule.every().day.at("21:30").do(self.start_quiz)
I add jobs, which are then processed in that event loop in the earlier code block. I use on_startup (a custom event) which is called after on_ready since on_ready can be called multiple times if the bot disconnects (stop duplicate jobs)
Your error (which should be text, not an image) is due to schedule.every(5).seconds.do(ping) being called and it is expecting self which you do not give it.
I am writing a bot and I need to implement the following functionality: the bot once every 10 minutes(for example) parse a certain URL and if there were changes from the previous call, writes to the chat.
Since the bot is also engaged in other things, I decided to loop the parsing in the function with sleep at the end. If there are changes, I try to send a message to the chat, but then a problem happens.
Since a successful combination of circumstances does not arise from an event in the chat, I can't pull the "entity" from the "event" for the "send_message" function. therefore, we have to get through the "get_entity" function and links to the chat as a parameter, but for some reason this does not work from another stream. below is a simplified code:
import threading, queue
from time import sleep
import asyncio
from telethon.sync import TelegramClient, events
import config as cfg
bot = TelegramClient('Bot', cfg.api_id, cfg.api_hash)
#bot.on(events.NewMessage(pattern=r'^(?i)(idchat){1}$'))
async def echoidchat(event):
channelaa = await bot.get_entity('https://t.me/elvistest')
await bot.send_message(channelaa, 'ответ')
def parseurls():
for x in range(10):
q.put(x)
pass
async def pre_sendmsg():
while True:
try:
msg = q.get_nowait()
except Exception as e:
await asyncio.sleep(1.0)
else:
await sendmsg(msg)
q.task_done()
async def sendmsg(msg):
channel = await bot.get_entity('https://t.me/elvistest')
await bot.send_message(channel, f'ответ из другого потока {msg}')
if __name__ == '__main__':
q = queue.Queue()
parseurls()
bot.start(bot_token=cfg.bot_token)
threading.Thread(target=asyncio.run, daemon=True, args=(pre_sendmsg(),)).start()
bot.run_until_disconnected()
The thing is that on the line " boot.get_entity" nothing happens. The script execution is lost somewhere and does not go further, that is, the next line with "bot. send_message" is simply not executed. however, "def echoidchat" is working at this time.
Well done!
This is work like I want.
import random
import threading, queue
from time import sleep
import asyncio
from telethon import TelegramClient, events
import config as cfg
bot = TelegramClient('Bot', cfg.api_id, cfg.api_hash)
#bot.on(events.NewMessage(pattern=r'^(?i)(idchat){1}$'))
async def echoidchat(event):
await bot.send_message(event.chat, 'ответ')
async def parseurls():
while True:
ts = abs(int(random.random()*10))
print(f'parseurls({ts})')
await sendmsg(ts)
await asyncio.sleep(ts)
async def sendmsg(msg):
print(f'sendmsg({msg}) - start')
channel = await bot.get_entity('https://t.me/elvistest')
await bot.send_message(channel, f'ответ из другого потока {msg}')
print(f'sendmsg({msg}) - done')
def main():
bot.start(bot_token=cfg.bot_token)
loop = asyncio.get_event_loop()
tasks = [
loop.create_task(parseurls()),
loop.create_task(bot.run_until_disconnected()),
]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
if __name__ == '__main__':
main()
Main goal:
Send a message to a channel every Wednesday at 08:00
This is my current code:
import schedule
import time
import discord
import asyncio
from discord.ext import commands, tasks
from discord.ext.tasks import loop
client = discord.Client()
# IMPORTANT
channel =
botToken = ""
async def sendloop():
while True:
schedule.run_pending()
await asyncio.sleep(1)
#client.event
async def on_ready():
general_channel = client.get_channel(channel)
print('ready')
schedule.every(2).seconds.do(lambda:loop.create_task(general_channel.send('TEST')))
client.run(botToken)
So far, there are no errors, just stuck on "ready". I am a beginner using VS Code.
I wouldn't recommend using scheduler, since discord the extension task which can do the same as what you are trying to implement in your code:
from discord.ext import commands, tasks
ch = id_channel
client = commands.Bot(command_prefix='.')
TOKEN = "Token"
#every week at this time, you can change this to seconds=2
#to replicate what you have been testing
#tasks.loop(hours=168)
async def every_wednesday():
message_channel = client.get_channel(ch)
await message_channel.send("Your message")
#every_wednesday.before_loop
async def before():
await client.wait_until_ready()
every_wednesday.start()
client.run(TOKEN)
If you were to run this code on the time you want it to be repeated, it will accomplish what you want. Another approach is checking on the before() function to check if it is the right time to run if you don't want to wait and run the code on the day you want this to run.
But if you really want to use the schedule module, this post my help you: Discord.py Time Schedule