Discord.py running an async function on demand - python-3.x

I want to run the following async function at a certain time.
async def test(ctx):
channel = bot.get_channel(730099302130516058)
await channel.send('hello')
I am testing it with
asyncio.run(test(bot.get_context)). But when I run it I get 'NoneType' object has no attribute 'send' And I have tested this and it means channel is equal to none so it cant send the message as channel = "None".
Now when I do the following it works. But of course I have to run the command test
#bot.command()
async def test(ctx):
channel = bot.get_channel(730099302130516058)
await channel.send('hello')
I plan to use schedule to run it at the times I required but will still call the function in a similar way.
Is there a way to call an async function and pass ctx correctly?
Entire Code:
import discord
from discord.ext import commands
import asyncio
TOKEN = "Token Would Be Here"
bot = commands.Bot(command_prefix='+')
async def test(ctx):
channel = bot.get_channel(730099302130516058)
await channel.send('hello')
asyncio.run(test(bot.get_context))
bot.run(TOKEN)

bot.get_channel() is returning None because the bot has not yet connected, meaning it cannot see any channels. You need to add await bot.wait_until_ready(), which will force the bot to wait until it is connected before continuing.
You also don't need to pass ctx as you never use it.
discord.py also already has it's own event loop that you can use. You can add the coroutine to the loop using bot.loop.create_task().
from discord.ext import commands
TOKEN = "Token Would Be Here"
bot = commands.Bot(command_prefix='+')
async def test():
await bot.wait_until_ready()
channel = bot.get_channel(370935329353367568)
await channel.send('hello')
bot.loop.create_task(test())
bot.run(TOKEN)

Related

I want to change the discord bot status dynamically

This is the code im using
async def on_ready():
print(f'bots up')
await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name='.help'))
like every minute the status changes ,
I tried but it doesnt allow me to use other commands , I put this code but it would give a error saying i cant put message argument in on_ready()
await bot.process_commands(message)
This is how I change the bots presence
async def status_task():
while True:
await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name=f"listining status"))
await asyncio.sleep(10)# 10 as in 10seconds
await bot.change_presence(activity=discord.Game(name="playing status"))
await asyncio.sleep(10)
await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.competing, name=f"competing status"))
await asyncio.sleep(10)
await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.streaming, name=f"streaming status"))
await asyncio.sleep(10)
and then on_ready
#bot.event
async def on_ready():
print(f"bots up")
bot.loop.create_task(status_task())
hopefully this works for you,(for the status_task) change the 10 seconds in between each to lets say 60 , then every minute it will change
To periodically change your bot's presence, you can use a discord.ext.tasks.Loop. A basic blueprint could look something like this:
import discord
from discord.ext import commands
from discord.ext import tasks
bot = commands.Bot('.')
#tasks.loop(seconds=30)
async def switch_presence():
# list of all activities to switch between
activities = [
discord.Activity(type=discord.ActivityType.listening, name='First activity'),
discord.Activity(type=discord.ActivityType.listening, name='Another activity')
]
curr_activity = bot.activity
# default to the first activity if not set or invalid
if curr_activity not in activities:
await bot.change_presence(activity=activities[0])
return
# use modulo to start from the beginning once the list is exhausted
next_activity_index = (activities.index(curr_activity) + 1) % len(activities)
await bot.change_presence(activity=activities[next_activity_index])
#bot.event
async def on_ready():
switch_presence.start()
With some modifications, you can put this into a Cog as well. That would allow you to keep one static activity list instead of dynamically creating it (simply create it in the Cog's __init__ and store it as a class variable) and also store the index of the next activity centrally, eliminating the need to compute it every single time the loop is called

Simplest way to check the users in channel with discord.py

I am making a simple bot, all I want it to do is wait for me to type a command with an argument (a vc), so for example when I type !channel general, the bot will return a list of the members in that channel. So if Bob and Jeff are in General the bot will return member_list = ['Bob', 'Jeff'] Any easy way to do that?
Update:
import discord
import os
from discord.ext import commands
client = discord.Client()
bot = commands.Bot(command_prefix='$')
#client.event
async def on_ready():
print('We have logged in as {0.user}'.format(client))
#bot.command()
async def members(ctx, channel: discord.VoiceChannel):
member_list = [i.name for i in channel.members]
print(member_list)
await ctx.send(member_list) # or return member_list whatever you want
client.run(os.getenv('TOKEN'))
Here's my code up above, when I run bot it does not do anything when I type $members general, anyone know what I'm doing wrong?
Use VoiceChannel.members
#bot.command()
async def members(ctx, channel: discord.VoiceChannel):
member_list = [i.name for i in channel.members]
await ctx.send(member_list) # or return member_list whatever you want

editing messages with discord.py

I'm trying to edit my bots sent messages, but I'm getting an error
#client.command()
async def edit(ctx):
message = await ctx.send('testing')
time.sleep(0.3)
message.edit(content='v2')
Error:
RuntimeWarning: coroutine 'Message.edit' was never awaited
message.edit(content='v2')
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
And by the way, is there any way of editing a message by simply having the message ID?
time.sleep() is a blocking call, meaning that it pretty much screws up your script. What you'll instead want to use is await asyncio.sleep().
Also, edit() is a coroutine, so it needs to be awaited. Here is what your command should look like:
import asyncio # if you haven't already
#client.command()
async def edit(ctx):
message = await ctx.send('testing')
await asyncio.sleep(0.3)
await message.edit(content='v2')
To edit a message via ID, you'll need the channel that it came from:
#client.command()
async def edit(ctx, msg_id: int = None, channel: discord.TextChannel = None):
if not msg_id:
channel = client.get_channel(112233445566778899) # the message's channel
msg_id = 998877665544332211 # the message's id
elif not channel:
channel = ctx.channel
msg = await channel.fetch_message(msg_id)
await msg.edit(content="Some content!")
The usage for this command would be !edit 112233445566778899 #message-channel-origin assuming that the prefix is !, and don't bother using the channel argument if the message is in the channel you're executing the command in.
References:
Message.edit()
asyncio.sleep()
Messageable.fetch_message()

Keeeping the loop going until input (discord.py)

I'm running a discord.py bot and I want to be able to send messages through the IDLE console. How can I do this without stopping the bot's other actions? I've checked out asyncio and found no way through.
I'm looking for something like this:
async def some_command():
#actions
if input is given to the console:
#another action
I've already tried pygame with no results but I can also try any other suggestions with pygame.
You can use aioconsole. You can then create a background task that asynchronously waits for input from console.
Example for async version:
from discord.ext import commands
import aioconsole
client = commands.Bot(command_prefix='!')
#client.command()
async def ping():
await client.say('Pong')
async def background_task():
await client.wait_until_ready()
channel = client.get_channel('123456') # channel ID to send goes here
while not client.is_closed:
console_input = await aioconsole.ainput("Input to send to channel: ")
await client.send_message(channel, console_input)
client.loop.create_task(background_task())
client.run('token')
Example for rewrite version:
from discord.ext import commands
import aioconsole
client = commands.Bot(command_prefix='!')
#client.command()
async def ping(ctx):
await ctx.send('Pong')
async def background_task():
await client.wait_until_ready()
channel = client.get_channel(123456) # channel ID to send goes here
while not client.is_closed():
console_input = await aioconsole.ainput("Input to send to channel: ")
await channel.send(console_input)
client.loop.create_task(background_task())
client.run('token')

How to send a message with discord.py from outside the event loop (i.e. from python-telegram-bot thread)?

I want to use make a bot that communicates between discord and telegram by using the libraries python-telegram-bot and discord.py (version 1.0.0). However the problem is that discord.py uses async functions and python-telegram-bot threading. With the code below, everything works fine for messages being posted in discord (the bot sends them correctly to telegram), however the other way around does not work (bot gets messages from telegram and sends it to discord). I previously had issues with syntax/runtime errors as I tried to run the discords channel.send function in a sync function (thus either returning only a generator object or complaining that I cannot use await in a sync function). However, at the same time the python-telegram-bot's MessageHandler needs a sync function so when giving him a async function Python complains that "await" was never called for the async function.
I now tried to use the async_to_sync method from asgiref library to run my async broadcastMsg from the MessageHandler, however the code still does not send the message to discord! It seems to call the function correctly but only until line print('I get to here'). No error is displayed and no message is poping up in discord. I guess it has something to do with the fact that I have to register the function as a task in the discord.py event loop, however registering is only working when it happens before botDiscord.run(TOKENDISCORD) has been executed which of course has to happen before.
So to boil my problem down to one question:
How am I able to interact with the discord.py event loop from another thread (which is from the telegram MessageHandler). Or if this is not possible: How can I send a message with discord.py without being within the discord.py event loop?
Thank you for your help
import asyncio
from asgiref.sync import async_to_sync
from telegram import Message as TMessage
from telegram.ext import (Updater,Filters,MessageHandler)
from discord.ext import commands
import discord
TChannelID = 'someTelegramChannelID'
DChannel = 'someDiscordChannelObject'
#%% define functions / commands
prefix = "?"
botDiscord = commands.Bot(command_prefix=prefix)
discordChannels = {}
async def broadcastMsg(medium,channel,message):
'''
Function to broadcast a message to all linked channels.
'''
if isinstance(message,TMessage):
fromMedium = 'Telegram'
author = message.from_user.username
channel = message.chat.title
content = message.text
elif isinstance(message,discord.Message):
fromMedium = 'Discord'
author = message.author
channel = message.channel.name
content = message.content
# check where message comes from
textToSend = '%s wrote on %s %s:\n%s'%(author,fromMedium,channel,content)
# go through channels and send the message
if 'telegram' in medium:
# transform channel to telegram chatID and send
updaterTelegram.bot.send_message(channel,textToSend)
elif 'discord' in medium:
print('I get to here')
await channel.send(textToSend)
print("I do not get there")
#botDiscord.event
async def on_message(message):
await broadcastMsg('telegram',TChannelID,message)
def on_TMessage(bot,update):
# check if this chat is already known, else save it
# get channels to send to and send message
async_to_sync(broadcastMsg)('discord',DChannel,update.message)
#%% initialize telegram and discord bot and run them
messageHandler = MessageHandler(Filters.text, on_TMessage)
updaterTelegram = Updater(token = TOKENTELEGRAM, request_kwargs={'read_timeout': 10, 'connect_timeout': 10})
updaterTelegram.dispatcher.add_handler(messageHandler)
updaterTelegram.start_polling()
botDiscord.run(TOKENDISCORD)
How can I send a message with discord.py without being within the discord.py event loop?
To safely schedule a coroutine from outside the event loop thread, use asyncio.run_coroutine_threadsafe:
_loop = asyncio.get_event_loop()
def on_TMessage(bot, update):
asyncio.run_coroutine_threadsafe(
broadcastMsg('discord', DChannel, update.message), _loop)
you can try split them to 2 separates *.py files.
t2d.py #telegram to discor
import subprocess
from telethon import TelegramClient, events, sync
api_id = '...'
api_hash = '...'
with TelegramClient('name', api_id, api_hash) as client:
#client.on(events.NewMessage()) #inside .NewMessage() you can put specific channel like: chats="test_channel"
async def handler(event):
print('test_channel raw text: ', event.raw_text) #this row is not necessary
msg = event.raw_text
subprocess.call(["python", "s2d.py", msg])
client.run_until_disconnected()
s2d.py #send to discord
import discord, sys
my_secret = '...'
clientdiscord = discord.Client()
#clientdiscord.event
async def on_ready():
#print('We have logged in as {0.user}'.format(clientdiscord)) #this row is not necessary
channel = clientdiscord.get_channel(123456789) # num of channel where you want to write message
msg = sys.argv[1] #grab message
msg = 's2d: ' + msg #only for test, you can delete this row
await channel.send(msg)
quit() # very important quit this bot
clientdiscord.run(my_secret)
It will be a little bit slower (subprocess will make delay), but very easy solution

Resources