I am building a Discord bot in Python and would like to receive HTTP requests from Twitch.tv's API (See Webhooks Guide & Webhooks Reference) (To subscribe to events like; X streamer has gone live) and based on the content of the HTTP (POST or GET) request received from Twitch, do something on the Discord bot, e.g: Output a message on a text channel.
I am using the discord.py Python Discord API/Library.
I've looked into the matter and found that Flask seemed like a good minimalist choice for a webserver to receive these requests on.
I should preface this by saying I'm very new to Python and I've never used Flask before.
Now. The problem is I can't seem to figure out a way to run the Flask server inside of my discord bot.
I've tried adding this simple code into my discord.py script:
from flask import Flask, request
app = Flask(__name__)
#app.route('/posts', methods=['POST'])
def result():
print(request.form['sched'])
# Send a message to a discord text channel etc...
return 'Received !'
When I run my discord.py script which looks something like this:
(Stripped away some commands and features for the sake of keeping this shorter)
import discord
import asyncio
from flask import Flask, request
app = Flask(__name__)
#app.route('/posts', methods=['POST'])
def result():
print(request.form['sched'])
# Send a message to a discord text channel etc...
return 'Received !'
client = discord.Client()
#client.event
async def on_ready():
print('Logged in as')
print(client.user.name)
print(client.user.id)
print('------')
#client.event
async def on_message(message):
if message.author == client.user:
return
content = message.content
fullUser = message.author.name+'#'+message.author.discriminator
print(str(message.timestamp)+" #"+message.channel.name+" "+fullUser+": "+str(content.encode('ascii', 'ignore').decode('ascii')))
if content.startswith('!'):
content = content[1:]
if content.startswith('test'):
counter = 0
tmp = await client.send_message(message.channel, 'Calculating messages...')
async for log in client.logs_from(message.channel, limit=100):
if log.author == message.author:
counter += 1
await client.edit_message(tmp, 'You have {} messages.'.format(counter))
client.run('MyTokenHere')
It seems like if I point flask to discord.py (the above) and run it, it'll start the code, get to the "client.run('MyTokenHere')" part for discord, and just stop at that and run the discord bot. It's not until I exit out of the bot by doing Ctrl+C that the actual Flask server starts, but now the discord bot is disconnected and no longer does any processing.
The same problem persists if I were to for example add "app.run()" somewhere in my code (before calling "client.run()" which starts the Discord bot part) to launch the Flask server; It'll just run the flask, get stuck on that until I Ctrl+C out of the Flask server, then it'll proceed to start the Discord bot.
Ultimately, I need to use the Discord API and I need to be connected to the Discord API gateway and all that good jazz to actually send messages to a channel with the bot, so I don't really know what to do here.
So. I think I've tried my best to explain what I'm ultimately trying to achieve here, and hopefully someone can help me find a way to either make this work with Flask, or if there's a better and easier way, provide a different solution.
This is cog example in discord.py
I made this thing for dbl (Discord Bot Lists) you can implement, the thing you need.
Note: run your heroku app with webprocess
example :
web: python main.py
Then go on https://uptimerobot.com/
and setup to ping your web app after every 5 minutes
from aiohttp import web
from discord.ext import commands, tasks
import discord
import os
import aiohttp
app = web.Application()
routes = web.RouteTableDef()
def setup(bot):
bot.add_cog(Webserver(bot))
class Webserver(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.web_server.start()
#routes.get('/')
async def welcome(request):
return web.Response(text="Hello, world")
#routes.post('/dbl')
async def dblwebhook(request):
if request.headers.get('authorization') == '3mErTJMYFt':
data = await request.json()
user = self.bot.get_user(data['user']) or await self.bot.fetch_user(data['user'])
if user is None:
return
_type = f'Tested!' if data['type'] == 'test' else f'Voted!'
upvoted_bot = f'<#{data["bot"]}>'
embed = discord.Embed(title=_type, colour=discord.Color.blurple())
embed.description = f'**Upvoter :** {user.mention} Just {_type}' + f'\n**Upvoted Bot :** {upvoted_bot}'
embed.set_thumbnail(url=user.avatar_url)
channel = self.bot.get_channel(5645646545142312312)
await channel.send(embed=embed)
return 200
self.webserver_port = os.environ.get('PORT', 5000)
app.add_routes(routes)
#tasks.loop()
async def web_server(self):
runner = web.AppRunner(app)
await runner.setup()
site = web.TCPSite(runner, host='0.0.0.0', port=self.webserver_port)
await site.start()
#web_server.before_loop
async def web_server_before_loop(self):
await self.bot.wait_until_ready()
As the kind commenters informed me; threading seems like the way to go.
Thanks guys!
Another cool way if you aren't going to use flask extensions, use quart instead of flask, then it will super easy for you.
Note: Run your heroku app with webprocess
example :
web: python main.py
Then go on https://uptimerobot.com/ and setup to ping your web app after every 5 minutes
# Code Example
from discord.ext import commands
from quart import Quart
import os
app = Quart(__name__)
bot = commands.Bot('!')
#bot.command()
async def something(ctx):
...
"""
Note: On Heroku you cant bind your webserver with 5000 port as they aren't static.
To fix above problem you will have to get dynamic port from the environment variable and you are good to go.
"""
PORT = os.environ.get('PORT')
bot.loop.create_task(app.run_task('0.0.0.0', PORT))
bot.run('Token')
Or you can use the Terminal Multiplexer, tmux to run them independently!.
If you are running on a Linux platform, tmux python3 flaskapp.py would run the flask app, while you can independently run the discord bot.
You can use asyncio with flask to perform this
import discord
import asyncio
class PrintDiscordChannelsClient(discord.Client):
def __init__(self, *args, **kwargs):
self.guild_id = kwargs.pop('guild_id')
super().__init__(*args, **kwargs)
async def on_ready(self):
try:
await self.wait_until_ready()
guild = self.get_guild(int(self.guild_id))
print(f'{guild.name} is ready!')
channels = guild.text_channels
print(f'{(channels)} channels found')
await self.close()
except Exception as e:
print(e)
await self.close()
async def print_discord_channels(guild_id):
client = PrintDiscordChannelsClient(guild_id=guild_id)
await client.start('DISCORD_BOT_TOKEN')
#application.route("/discord-api", methods=["GET"])
def discord_api():
guild_id = request.args.get('guild_id')
asyncio.run(print_discord_channels(guild_id=guild_id))
return make_response("Success", 200)
Related
I am trying to have a server that will be able to give us the status on discord.
I did achieve this by having an extra socketio client that run discord.py aswell.
I would like discord to run on the server instead of on a client. I am unable to make this work at the same time, any insight or help would be appreciated.
I tried launching discord before socketio, also tried launching discord in a different thread.
--Update--
The orginal post had a emit onConenct. this is apparently a known issue and will cause a namespace error. . please find trhe edited code below
as a minimal reproductable example here is the code:
server:
import asyncio
from time import sleep
from flask import Flask
from aiohttp import web
import socketio
from discord.ext import commands
sio = socketio.AsyncServer(async_mode='aiohttp',logger=True)
app = web.Application()
sio.attach(app)
bot = commands.Bot(command_prefix='+')
#bot.command(help="testing")
async def test(ctx):
await ctx.send("Hello world!")
await sio.emit('marco')
#sio.on('polo')
async def message(sid):
print("a wild client responded")
async def start_discord():
print('starting discord')
await bot.start('token')
if __name__ == '__main__':
asyncio.run(asyncio.gather(web.run_app(app),start_discord()))
client :
import asyncio
from click import prompt
import socketio
from discord.ext import commands
sio = socketio.Client(logger=True)
#sio.on("marco")
def polo():
sio.emit('polo')
print("wow i found a server")
if __name__ == "__main__":
sio.connect("http://localhost:8080")
sio.wait()
Ther server and client are connecting but not discord.
Your issue is that the socket server only starts when the discord bot exists. This will never happen.
If you want to run 2 functions concurrently use asyncio.gather in combination with client.start
Like this:
if __name__ == '__main__':
asyncio.run(
asyncio.gather(
bot.start('SuperSecret Token')
sio.start(app, host="localhost", port=8080)
)
)
This will ensure they start the the same time.
Note your code to create a custom event loop is probably unnecessary.
Having multiple calls to asyncio.run in your code is a mistake 99% of the time. You normally need only one async context.
I'm hosting a python discord bot on Replit.com 24/7 with a hacker plan.
Is there a way to make the bot DM me when the code stops running by any reason?
I've tried the atexit and signal function but it's not doing anything when I stop the code. Here's the code:
import atexit
import signal
async def handle_exit():
user = await getUserByID(my_ID)
await user.send(f"{bot.user} is offline! :x:")
atexit.register(handle_exit)
signal.signal(signal.SIGTERM, handle_exit)
signal.signal(signal.SIGINT, handle_exit)
#discord code here
Any other way than DM on discord is okay too, as long as I get notified when the bot goes offline.
Thank you in advance.
ok, so u have to add an file with flask server :
from threading import Thread
from itertools import cycle
from flask import Flask
app = Flask('')
#app.route('/test')
def main():
return "Your Bot Is Ready"
#app.route('/')
def lol():
return "Your Bot Is Ready"
def run():
app.run(host="0.0.0.0", port=8080)
def keep_alive():
server = Thread(target=run)
server.start()
and then import that file in ur main.py or bot file and then add file_name.keep_alive()
and then make a new bot and use following code:
from discord.ext import commands
import requests
bot = commands.Bot(prefix="!")
#client.event
async def on_ready():
channel = bot.get_channel()
while True:
r = requests.get("url that u get on bar of replit")
if r.status_code != 200:
await channel.send("bot is offline")
bot.run("token of another bot")
and to get url
I'm trying to combine a Twitch API package (twitchio) with a webserver (sanic) with the intent of serving chat commands to a game running locally to the python script. I don't have to use Sanic or twitchio but those are the best results I've found for my project.
I think I understand why what I have so far isn't working but I'm at a total loss of how to resolve the problem. Most of the answers I've found so far deal with scripts you've written to use asyncio and not packages that make use of event loops.
I can only get the webserver OR the chat bot to function. I can't get them to both run concurrently.
This is my first attempt at using Python so any guidance is greatly appreciated.
#!/usr/bin/python
import os
from twitchio.ext import commands
from sanic import Sanic
from sanic.response import json
app = Sanic(name='localserver')
bot = commands.Bot(
token=os.environ['TMI_TOKEN'],
client_id=os.environ['CLIENT_ID'],
nick=os.environ['BOT_NICK'],
prefix=os.environ['BOT_PREFIX'],
initial_channels=[os.environ['CHANNEL']]
)
#app.route("/")
async def query(request):
return json(comcache)
#bot.event
async def event_ready():
'Called once when the bot goes online.'
print(f"{os.environ['BOT_NICK']} is online!")
ws = bot._ws # this is only needed to send messages within event_ready
await ws.send_privmsg(os.environ['CHANNEL'], f"/me has landed!")
#bot.event
async def event_message(ctx):
'Runs every time a message is sent in chat.'
# make sure the bot ignores itself and the streamer
if ctx.author.name.lower() == os.environ['BOT_NICK'].lower():
return
await bot.handle_commands(ctx)
# await ctx.channel.send(ctx.content)
if 'hello' in ctx.content.lower():
await ctx.channel.send(f"Hi, #{ctx.author.name}!")
#bot.command(name='test')
async def test(ctx):
await ctx.send('test passed!')
if __name__ == "__main__":
app.run(host="127.0.0.1", port=8080)
bot.run()
I built a bot that updates the name of the bot to a price in Python. Likewise, the status of the bot updates to a different price as well. The bot works as intended for a bit of time, but then I receive the following message from Discord:
It appears your bot, "MY BOT NAME", has connected to Discord more than 1000 times within a short time period. Since this kind of behavior is usually a result of a bug we have gone ahead and reset your bot's token.
My bot runs every 15 seconds, from a shell script, on a linux server, that also kills the last process which was run before it. I kill the preceding process so they don't eat up my memory and crash my server.
Here is my code:
#!/usr/bin/python
import discord
from discord.ext import commands
import requests
import json
import emoji
import sys
import asyncio
client = commands.Bot(command_prefix = '.')
url = 'URL FOR THE API'
price = requests.get(url)
rapid_gprice = price.json()['data']['price1']
standardp = price.json()['data']['price2']
#client.event
async def on_ready():
guild = client.get_guild(MY GUILD ID)
me = guild.me
await me.edit(nick=standardp)
activity = discord.Game(name=rapid_gprice)
await client.change_presence(status=discord.Status.online, activity=activity)
client.run('MY TOKEN')
I'm fairly certain I need to use some sort of function inside my Python script that loops through the price api and updates the Discord bot accordingly, only having to run the Python script once.
I'm happy to provide any additional information you may need. Thanks in advance.
Running your script every 15 seconds is a really bad idea, there's an build-in discord.py extension called tasks, it lets you run background tasks, hence the name.
from discord.ext import tasks
#tasks.loop(seconds=15)
async def change_nick(guild):
nick = # Get the nick here
await guild.me.edit(nick=nick)
#client.event
async def on_ready():
await client.wait_until_ready()
guild = client.get_guild(MY GUILD ID)
change_nick.start(guild)
Also I see that you're using requests, which is a blocking library, I'd suggest you using aiohttp instead
Take a look at the tasks introduction
This is how I ended up solving my issue. It's probably code overkill but it works. Feel free to help me alter it to make it more efficient:
#!/usr/bin/python
from discord.ext import tasks, commands
import aiohttp
import json
import asyncio
import discord
client = commands.Bot(command_prefix = '.')
url = 'THE API LINK I''M USING'
async def getPrice1():
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
response = await resp.json()
price1 = response['data']['priceZYX']
return price1
async def getPrice2():
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
response = await resp.json()
price2 = response['data']['priceXYZ']
return price2
#tasks.loop(seconds=7)
async def change_nick(guild):
nick = await getPrice1()
await guild.me.edit(nick=nick)
activity = discord.Game(name=await getPrice2())
await client.change_presence(status=discord.Status.online, activity=activity)
#client.event
async def on_ready():
await client.wait_until_ready()
guild = client.get_guild(MY GUILD ID)
change_nick.start(guild)
I got an error trying to implement the pricedata (from url) to my bot's username.
The error: discord.errors.HTTPException: 400 Bad Request (error code: 50035): Invalid Form Body
In nick: Could not interpret "(the live price)" as string.
If you have the same problem (and using discord rewrite), make sure to label (nick=nick) as a string:
#tasks.loop(seconds=15)
async def change_nick(guild):
nick = await getPrice1()
await guild.me.edit(nick=str(nick))
PS: Im fairly new to coding so please correct me if im wrong, it worked for me. Also thanks for this posts it helped me alot making my bot.
import discord
from discord.ext import commands
import random
print("Starting bot...")
TOKEN = (the token)
client = commands.Bot(command_prefix='?')
# answers with the ms latency
#client.command(name='ping', pass_context = True)
async def ping(ctx):
await ctx.send(f'Pong! {round(client.latency * 1000)}ms ')
client.run(TOKEN)
it runs any on_message commands but not anything else.
I've looked at many tutorial sites and pages in S-O but none seem to work.
Check TeilaRei's last post, you need to add a line to to on_message().