import os
import discord
import requests
import json
import asyncio
import aiohttp
import logging
import sys
from discord . ext import commands , tasks
from discord . ext . commands import has_permissions , MissingPermissions
from dotenv import load_dotenv
bot = commands . Bot ( command_prefix = " . " , intents = discord . Intents . all ( ) , chunk_guilds_at_startup = True )
load_dotenv ( )
token = os . getenv ( ' token ' )
crabwings_role_id = int ( os . getenv ( ' crabwings_role_id ' ) )
duckfeet_role_id = int ( os . getenv ( ' duckfeet_role_id ' ) )
client_role_id = int ( os . getenv ( ' client_role_id ' ) )
subuser_role_id = int ( os . getenv ( ' subuser_role_id ' ) )
verified_role_id = int ( os . getenv ( ' verified_role_id ' ) )
guild_id = int ( os . getenv ( ' guild_id ' ) )
verification_channel = int ( os . getenv ( ' verification_channel ' ) )
verification_message = int ( os . getenv ( ' verification_message ' ) )
logging . basicConfig ( filename = ' console.log ' ,
level = logging . INFO ,
format = ' [ %(asctime)s %(levelname)s ] %(message)s ' ,
datefmt = ' % Y- % m- %d % H: % M: % S ' ,
)
logging . getLogger ( ) . addHandler ( logging . StreamHandler ( sys . stdout ) )
@bot . event
async def on_ready ( ) :
# Marks bot as running
logging . info ( ' I am running. ' )
@bot . event
async def on_message ( message ) :
if message . author != bot . user and message . guild == None :
channel = message . channel
# Potential API key, so tries it out
if len ( message . content ) == 48 :
url = " https://panel.birdflop.com/api/client/account "
cookies = {
' pterodactyl_session ' : ' eyJpdiI6InhIVXp5ZE43WlMxUU1NQ1pyNWRFa1E9PSIsInZhbHVlIjoiQTNpcE9JV3FlcmZ6Ym9vS0dBTmxXMGtST2xyTFJvVEM5NWVWbVFJSnV6S1dwcTVGWHBhZzdjMHpkN0RNdDVkQiIsIm1hYyI6IjAxYTI5NDY1OWMzNDJlZWU2OTc3ZDYxYzIyMzlhZTFiYWY1ZjgwMjAwZjY3MDU4ZDYwMzhjOTRmYjMzNDliN2YifQ % 3D % 3D ' ,
}
headers = {
' Accept ' : ' application/json ' ,
' Content-Type ' : ' application/json ' ,
' Authorization ' : ' Bearer ' + message . content ,
}
response = requests . get ( url , headers = headers , cookies = cookies )
# If API token is verified to be correct:
if str ( response ) == " <Response [200]> " :
# Formats response of account in JSON format
json_response = response . json ( )
# Loads contents of users.json
file = open ( ' users.json ' , ' r ' )
data = json . load ( file )
file . close ( )
# Checks if user exists. If so, skips adding them to users.json
client_id_already_exists = False
discord_id_already_exists = False
for user in data [ ' users ' ] :
if user [ ' client_id ' ] == json_response [ ' attributes ' ] [ ' id ' ] :
client_id_already_exists = True
logging . info ( " User already exists " )
if user [ ' discord_id ' ] == message . author . id :
discord_id_already_exists = True
logging . info ( " User already exists " )
if client_id_already_exists == False and discord_id_already_exists == False :
data [ ' users ' ] . append ( {
' discord_id ' : message . author . id ,
' client_id ' : json_response [ ' attributes ' ] [ ' id ' ] ,
' client_api_key ' : message . content
} )
json_dumps = json . dumps ( data , indent = 2 )
# Adds user to users.json
file = open ( ' users.json ' , ' w ' )
file . write ( json_dumps )
file . close ( )
guild = bot . get_guild ( guild_id )
member = guild . get_member ( message . author . id )
if member :
url = " https://panel.birdflop.com/api/client "
cookies = {
' pterodactyl_session ' : ' eyJpdiI6InhIVXp5ZE43WlMxUU1NQ1pyNWRFa1E9PSIsInZhbHVlIjoiQTNpcE9JV3FlcmZ6Ym9vS0dBTmxXMGtST2xyTFJvVEM5NWVWbVFJSnV6S1dwcTVGWHBhZzdjMHpkN0RNdDVkQiIsIm1hYyI6IjAxYTI5NDY1OWMzNDJlZWU2OTc3ZDYxYzIyMzlhZTFiYWY1ZjgwMjAwZjY3MDU4ZDYwMzhjOTRmYjMzNDliN2YifQ % 3D % 3D ' ,
}
headers = {
' Accept ' : ' application/json ' ,
' Authorization ' : ' Bearer ' + message . content ,
}
response = requests . get ( url , headers = headers , cookies = cookies )
# If API token is verified to be correct, continues
if str ( response ) == " <Response [200]> " :
# Formats response for servers in JSON format
servers_json_response = response . json ( )
user_client = False
user_subuser = False
user_crabwings = False
user_duckfeet = False
for server in servers_json_response [ ' data ' ] :
server_owner = server [ ' attributes ' ] [ ' server_owner ' ]
if server_owner == True :
user_client = True
elif server_owner == False :
user_subuser = True
server_node = server [ ' attributes ' ] [ ' node ' ]
if server_node == " Crabwings - NYC " :
user_crabwings = True
elif server_node == " Duckfeet - EU " :
user_duckfeet = True
if user_client == True :
role = discord . utils . get ( guild . roles , id = client_role_id )
await member . add_roles ( role )
if user_subuser == True :
role = discord . utils . get ( guild . roles , id = subuser_role_id )
await member . add_roles ( role )
if user_crabwings == True :
role = discord . utils . get ( guild . roles , id = crabwings_role_id )
await member . add_roles ( role )
if user_duckfeet == True :
role = discord . utils . get ( guild . roles , id = duckfeet_role_id )
await member . add_roles ( role )
role = discord . utils . get ( guild . roles , id = verified_role_id )
await member . add_roles ( role )
await channel . send ( ' Your Discord account has been linked to your panel account! You may unlink your Discord and panel accounts by reacting in the #verification channel or by deleting your Verification API key. ' )
logging . info ( " Success message sent to " + message . author . name + " # " + str ( message . author . discriminator ) + " ( " + str ( message . author . id ) + " ) " + " . User linked to API key " + message . content + " and client_id " + str ( json_response [ ' attributes ' ] [ ' id ' ] ) )
elif client_id_already_exists :
await channel . send ( ' Sorry, your panel account is already linked to a Discord account. If you would like to link your panel account to a different Discord account, please unlink your panel account first by deleting its Verification API key and waiting up to 10 minutes. ' )
logging . info ( " Duplicate panel message sent to " + message . author . name + " # " + str ( message . author . discriminator ) + " ( " + str ( message . author . id ) + " ) " + " for using API key " + message . content + " linked to client_id " + str ( json_response [ ' attributes ' ] [ ' id ' ] ) )
elif discord_id_already_exists :
await channel . send ( ' Sorry, your Discord account is already linked to a panel account. If you would like to link your Discord account to a different panel account, please unlink your Discord account first by reacting in the #verification channel. ' )
logging . info ( " Duplicate Discord message sent to " + message . author . name + " # " + str ( message . author . discriminator ) + " ( " + str ( message . author . id ) + " ) " + " for using API key " + message . content + " linked to client_id " + str ( json_response [ ' attributes ' ] [ ' id ' ] ) )
# Makes json pretty with indentations and stuff, then writes to file
# json_dumps = json.dumps(json_response, indent = 2)
# file = open("data.json", "w")
# file.write(json_dumps)
# file.close()
# linked_servers = {}
# linked_servers['server'] = []
# for server in json_response['data']:
# server_owner = server['attributes']['server_owner']
# server_uuid = server['attributes']['uuid']
# server_name = server['attributes']['name']
# server_node = server['attributes']['node']
# message_sender = message.author.id
# info = str(server_owner) + server_uuid + server_name + server_node + str(message_sender)
# logging.info(info)
# file = open('/home/container/data/' + str(message.author.id) + '.json', "a")
# file.write(info + '\n')
# file.close()
else :
#Says if API key is the corect # of characters but invalid
await channel . send ( " Sorry, that appears to be an invalid API key. " )
logging . info ( ' invalid sent to ' + message . author . name + " # " + str ( message . author . discriminator ) + " ( " + str ( message . author . id ) + " ) " )
else :
#Says this if API key is incorrect # of characters
await channel . send ( ' Sorry, that doesn \' t appear to be an API token. An API token should be a long string resembling this: ```yQSB12ik6YRcmE4d8tIEj5gkQqDs6jQuZwVOo4ZjSGl28d46``` ' )
logging . info ( " obvious incorrect sent to " + message . author . name + " # " + str ( message . author . discriminator ) + " ( " + str ( message . author . id ) + " ) " )
elif len ( message . attachments ) > 0 :
if message . attachments [ 0 ] . url . endswith ( ( ' .png ' , ' .jpg ' , ' .jpeg ' , ' .mp4 ' , ' .mov ' , ' .avi ' , ' .gif ' , ' .image ' ) ) == False :
download = message . attachments [ 0 ] . url
r = requests . get ( download , allow_redirects = True )
text = r . text
text = " \n " . join ( text . splitlines ( ) )
if ' � ' not in text : # If it's not an image/gif
truncated = False
if len ( text ) > 100000 :
text = text [ : 99999 ]
truncated = True
req = requests . post ( ' https://bin.birdflop.com/documents ' , data = text )
key = json . loads ( req . content ) [ ' key ' ]
response = " "
response = response + " https://bin.birdflop.com/ " + key
response = response + " \n Requested by " + message . author . mention
if truncated :
response = response + " \n (file was truncated because it was too long.) "
embed_var = discord . Embed ( title = " Please use a paste service " , color = 0x1D83D4 )
embed_var . description = response
await message . channel . send ( embed = embed_var )
await bot . process_commands ( message )
@bot . event
async def on_raw_reaction_add ( payload ) :
global verification_message
global verification_channel
if payload . message_id != verification_message :
return
if payload . user_id == bot . user . id :
return
# Remove the reaction
guild = discord . utils . get ( bot . guilds , id = guild_id )
verification_channel_obj = await bot . fetch_channel ( verification_channel )
verification_message_obj = await verification_channel_obj . fetch_message ( verification_message )
member = guild . get_member ( payload . user_id )
await verification_message_obj . remove_reaction ( payload . emoji , member )
if str ( payload . emoji ) == " ✅ " :
await member . send ( " Hey there! It looks like you ' d like to verify your account. I ' m here to help you with that! \n \n If you ' re confused at any point, see https://birdflop.com/verification for a tutorial including images. \n \n With that said, let ' s get started! You ' ll want to start by grabbing some API credentials for your account by signing into https://panel.birdflop.com. Head over to the **Account** section in the top right, then click on the **API Credentials tab**. You ' ll want to create an API key with description `Verification` and `172.18.0.2` in the **Allowed IPs section**. \n \n When you finish entering the necessary information, hit the blue **Create **button. \n \n Next, you ' ll want to copy your API credentials. After clicking **Create**, you ' ll receive a long string. Copy it with `ctrl+c` (`cmnd+c` on Mac) or by right-clicking it and selecting **Copy**. \n \n If you click on the **Close **button before copying the API key, no worries! Delete your API key and create a new one with the same information. \n \n Finally, direct message your API key to Botflop: that ' s me! \n \n To verify that you are messaging the key to the correct user, please ensure that the my ID is `Botflop#2403` and that my username is marked with a blue **BOT** badge. Additionally, the only server under the **Mutual Servers** tab should be Birdflop Hosting. \n \n After messaging me your API key, you should receive a success message. If you do not receive a success message, please create a ticket in the Birdflop Discord ' s #support channel. " )
logging . info ( " sent verification challenge to " + member . name + " # " + str ( member . discriminator ) + " ( " + str ( member . id ) + " ) " )
else :
file = open ( ' users.json ' , ' r ' )
data = json . load ( file )
file . close ( )
i = 0
j = - 1
for client in data [ ' users ' ] :
j + = 1
if client [ ' discord_id ' ] == member . id :
data [ ' users ' ] . pop ( j )
i = 1
if i == 1 :
json_dumps = json . dumps ( data , indent = 2 )
file = open ( ' users.json ' , ' w ' )
file . write ( json_dumps )
file . close ( )
await member . edit ( roles = [ ] )
await member . send ( " Your Discord account has successfully been unlinked from your Panel account! " )
logging . info ( ' successfully unlinked ' + member . name + " # " + str ( member . discriminator ) + " ( " + str ( member . id ) + " ) " )
@bot . command ( )
async def ping ( ctx ) :
await ctx . send ( f ' Your ping is { round ( bot . latency * 1000 ) } ms ' )
@bot . command ( name = " react " , pass_context = True )
@has_permissions ( administrator = True )
async def react ( ctx , url , reaction ) :
channel = await bot . fetch_channel ( int ( url . split ( " / " ) [ 5 ] ) )
message = await channel . fetch_message ( int ( url . split ( " / " ) [ 6 ] ) )
await message . add_reaction ( reaction )
logging . info ( ' reacted to ' + url + ' with ' + reaction )
@tasks . loop ( minutes = 10 )
async def update_servers ( ) :
logging . info ( " synchronizing roles " )
file = open ( ' users.json ' , ' r ' )
data = json . load ( file )
file . close ( )
guild = bot . get_guild ( guild_id )
i = - 1
for client in data [ ' users ' ] :
i + = 1
member = guild . get_member ( client [ ' discord_id ' ] )
if member :
api_key = client [ ' client_api_key ' ]
url = " https://panel.birdflop.com/api/client "
cookies = {
' pterodactyl_session ' : ' eyJpdiI6InhIVXp5ZE43WlMxUU1NQ1pyNWRFa1E9PSIsInZhbHVlIjoiQTNpcE9JV3FlcmZ6Ym9vS0dBTmxXMGtST2xyTFJvVEM5NWVWbVFJSnV6S1dwcTVGWHBhZzdjMHpkN0RNdDVkQiIsIm1hYyI6IjAxYTI5NDY1OWMzNDJlZWU2OTc3ZDYxYzIyMzlhZTFiYWY1ZjgwMjAwZjY3MDU4ZDYwMzhjOTRmYjMzNDliN2YifQ % 3D % 3D ' ,
}
headers = {
' Accept ' : ' application/json ' ,
' Authorization ' : ' Bearer ' + api_key ,
}
response = requests . get ( url , headers = headers , cookies = cookies )
# If API token is verified to be correct, continues
if str ( response ) == " <Response [200]> " :
# Formats response for servers in JSON format
servers_json_response = response . json ( )
user_client = False
user_subuser = False
user_crabwings = False
user_duckfeet = False
for server in servers_json_response [ ' data ' ] :
server_owner = server [ ' attributes ' ] [ ' server_owner ' ]
if server_owner == True :
user_client = True
elif server_owner == False :
user_subuser = True
server_node = server [ ' attributes ' ] [ ' node ' ]
if server_node == " Crabwings - NYC " :
user_crabwings = True
elif server_node == " Duckfeet - EU " :
user_duckfeet = True
role = discord . utils . get ( guild . roles , id = client_role_id )
if user_client == True :
await member . add_roles ( role )
else :
await member . remove_roles ( role )
role = discord . utils . get ( guild . roles , id = subuser_role_id )
if user_subuser == True :
await member . add_roles ( role )
else :
await member . remove_roles ( role )
role = discord . utils . get ( guild . roles , id = crabwings_role_id )
if user_crabwings == True :
await member . add_roles ( role )
else :
await member . remove_roles ( role )
role = discord . utils . get ( guild . roles , id = duckfeet_role_id )
if user_duckfeet == True :
await member . add_roles ( role )
else :
await member . remove_roles ( role )
else :
data [ ' users ' ] . pop ( i )
json_dumps = json . dumps ( data , indent = 2 )
file = open ( ' users.json ' , ' w ' )
file . write ( json_dumps )
file . close ( )
await member . edit ( roles = [ ] )
logging . info ( " removed discord_id " + str ( client [ ' discord_id ' ] ) + " with client_id " + str ( client [ ' client_id ' ] ) + " and INVALID client_api_key " + client [ ' client_api_key ' ] )
else :
# file = open('oldusers.json', 'r')
# olddata = json.load(file)
# file.close()
# Checks if user exists. If so, skips adding them to users.json
# already_exists = False
# for olduser in olddata['users']:
# if olduser['discord_id'] == json_response['attributes']['id']:
#
# if already_exists == False:
# olddata['users'].append({
# 'discord_id': message.author.id,
# 'client_id': json_response['attributes']['id'],
# 'client_api_key': message.content
# })
# json_dumps = json.dumps(olddata, indent = 2)
# file = open('oldusers.json', 'w')
# file.write(json_dumps)
# file.close()
data [ ' users ' ] . pop ( i )
json_dumps = json . dumps ( data , indent = 2 )
file = open ( ' users.json ' , ' w ' )
file . write ( json_dumps )
file . close ( )
logging . info ( " removed discord_id " + str ( client [ ' discord_id ' ] ) + " with client_id " + str ( client [ ' client_id ' ] ) + " and client_api_key " + client [ ' client_api_key ' ] )
@update_servers . before_loop
async def before_update_servers ( ) :
logging . info ( ' waiting to enter loop ' )
await bot . wait_until_ready ( )
update_servers . start ( )
bot . run ( token )
# full name: message.author.name + "#" + str(message.author.discriminator) + " (" + str(message.author.id) + ")"