0

I'm trying to post a dataframe to a Discord channel. However, I am having issue getting Discord.py to close the connection and move on the next task. I've tried using the event loop as suggested in this thread (How to run async function in Airflow?) as well as asyncio.run() function. Not really familiar with the async and hoping to get some pointers here. Below is my code in Python that I've tried importing in DAG and Task without success. Thanks in advance!

Airflow: 2.5.1

Python: 3.7

import discord
from tabulate import tabulate
import asyncio
import pandas as pd



async def post_to_discord(df, channel_id, bot_token, as_message=True, num_rows=5):
    intents = discord.Intents.default()
    intents.members = True
    client = discord.Client(intents=intents)
    try:
        @client.event
        async def on_ready():
            channel = client.get_channel(channel_id)
            if as_message:
                # Post the dataframe as a message, num_rows rows at a time
                for i in range(0, len(df), num_rows):
                    message = tabulate(df.iloc[i:i+num_rows,:], headers='keys', tablefmt='pipe', showindex=False)
                    await channel.send(message)

            else:
                # Send the dataframe as a CSV file
                df.to_csv("dataframe.csv", index=False)
                with open("dataframe.csv", "rb") as f:
                    await channel.send(file=discord.File(f))
        # client.run(bot_token)
        await client.start(bot_token)
        await client.wait_until_ready()
    finally:
        await client.close()

async def main(df, channel_id, bot_token, as_message=True, num_rows=5):
    # loop = asyncio.get_event_loop()
    # result = loop.run_until_complete(post_to_discord(df, channel_id, bot_token, as_message, num_rows))
    result = asyncio.run(post_to_discord(df, channel_id, bot_token, as_message, num_rows))
    await result
    return result

if __name__ =='__main__':
    main()


NinjaWarrior
  • 25
  • 1
  • 2
  • 9
  • Remove `await result` when you use `loop.run_until_complete()`/`asyncio.run()`. Also, change `async def main` to `def main`. – aaron Feb 07 '23 at 08:34
  • @aaron Thanks for the suggestions. Made those two changes (def main and remove await result), but the task kept on running (does not close the connection) after the message was posted in Discord. – NinjaWarrior Feb 07 '23 at 09:50
  • 1
    Was it stuck in `await channel.send(message)`, or did it finish executing that and still stuck in `await client.wait_until_ready()`? – aaron Feb 09 '23 at 15:20
  • @aaron. it's the later. it finishes executing ```await channel.send(message)``` (verified through printing a line after await channel.send(message)) and still stuck in ```await client.wait_until_ready()``` – NinjaWarrior Feb 09 '23 at 18:26

1 Answers1

-1

It seems like your script works but the server is blocking the open socket (and kudos - the discord server is good at that). So we will work by creating a ping function (adopted from another answer).

def ping(ip, port):
    try:
        s = socket.socket() # TCP - standard values are `socket.AF_INET, socket.SOCK_STREAM` so you don't have to write them
        s.settimeout(2)
        print('[DEBUG] connect')
        s.connect((ip, int(port)))
        #result = s.connect_ex((ip, int(port)))
        #print('result:', result)
        return True
    except socket.timeout as ex:
        print('[DEBUG] timeout')
        return True        
    except Exception as ex:
        print('[Exception]', ex)
        return False
    finally:
        print('[DEBUG] close')
        s.close()

Feel free to test your ID

id = ...channel number...
print(id, type(id))

and you should see

<built-in function id> <class 'builtin_function_or_method'>

Then let us move on to improving your code:

import discord
import asyncio
import time # you are not using this module 
import socket
import os
from tabulate import tabulate
import pandas as pd # as pd is not required

def ping(ip, port):
        try:
            s = socket.socket() # TCP - standard values are `socket.AF_INET, socket.SOCK_STREAM` so you don't have to write them
            s.settimeout(2)
            print('[DEBUG] connect')
            s.connect((ip, int(port)))
            #result = s.connect_ex((ip, int(port)))
            #print('result:', result)
            return True
        except socket.timeout as ex:
            print('[DEBUG] timeout')
            return True        
        except Exception as ex:
            print('[Exception]', ex)
            return False
        finally:
            print('[DEBUG] close')
            s.close()



TOKEN = os.getenv('DISCORD_TOKEN')
client = discord.Client()

async def post_to_discord(df, channel_id, bot_token, as_message=True, num_rows=5):
    intents = discord.Intents.default()
    intents.members = True
    client = discord.Client(intents=intents)
    try:
        @client.event
        async def on_ready():
            channel = client.get_channel(channel_id)
            if as_message:
                # Post the dataframe as a message, num_rows rows at a time
                for i in range(0, len(df), num_rows):
                    message = tabulate(df.iloc[i:i+num_rows,:], headers='keys', tablefmt='pipe', showindex=False)
                    await channel.send(message)

            else:
                # Send the dataframe as a CSV file
                df.to_csv("dataframe.csv", index=False)
                with open("dataframe.csv", "rb") as f:
                    await channel.send(file=discord.File(f))
        # client.run(bot_token)
        await client.start(bot_token)
        await client.wait_until_ready()
        while True:
            online = ping("26.51.174.109", "25565") #modify it as you see fit
            #online = ping("192.168.1.101", "8081") #same as above
            if online:
                print("server online")
                #await channel.edit(name="Server Status - Online")
            else:
                print("server offline")
                #await channel.edit(name="Server Status - Offline")
            await asyncio.sleep(5)
        # optional - client.run(TOKEN)
    finally:
        await client.close()

async def main(df, channel_id, bot_token, as_message=True, num_rows=5):
    # loop = asyncio.get_event_loop()
    # result = loop.run_until_complete(post_to_discord(df, channel_id, bot_token, as_message, num_rows))
    result = asyncio.run(post_to_discord(df, channel_id, bot_token, as_message, num_rows))
    await result
    return result

if __name__ =='__main__':
    main()
  • 1
    "Feel free to test your ID" you override the built-in function `id` but don't use it in the code? What is the point of printing that? – aaron Feb 13 '23 at 04:43
  • @aaron there is no information, and it's a validation step. Why not? – Paritosh Kulkarni Feb 13 '23 at 07:15
  • no information about ID – Paritosh Kulkarni Feb 13 '23 at 07:23
  • I also have the same question Aaron has on the ping function. I don't quite get how that solves the problem. I ended up not using the discord.py but Discord's webhook with requests method instead. `requests.post(webhook_url, json=payload)`Not the same as bot but serves the purpose I'm currently doing (sending files or messages to the channel) – NinjaWarrior Feb 13 '23 at 08:45