python – Discord Bot queries an external API

I'm posting here to ask for help to improve with python help since I'm pretty new and I come from C #.

The main feature of this bot is to scan the Wargaming API for information about the drives.

Project structure:

project
| main.py
└───ENV
│
└───data
| classes.py
| dbcontext.py
| getStats.py

My principal:

import asyncio
import registration
bone import
import system
import re
import json

import discord
from the discord.ext import commands

from data import dbcontext, log, secret, getStats as GetStats
from data.translations import en, de, pl, tr
from data.classes import Config, Ship, Player, Stats, ReturnVal, ErrorType

sys.path.append (os.path.join (os.path.dirname (__ file__), "data"))


"" "
WoWs-Stats Bot.
Creator Fuyune

Use the Discord.py frame by Rapptz
"" "

####### Bot basic configuration #######

bot = commands.Bot (command_prefix = "!")
logger = logging.getLogger (& # 39; discord & # 39;)
logger.setLevel (logging.ERROR)
manager = logging.FileHandler (filename = err.log, encoding = utf-8, mode = a)
handler.setFormatter (logging.Formatter (
& # 39;% (asctime) s:% (levelname) s:% (name) s:% (message) s & # 39;))
logger.addHandler (manager)
bot.remove_command (& # 39; help & # 39;)
configs = []
regions = ["eu","ru","na","asia"]
langs = ["de","en","pl","tr"] #remember add to imports too
amounts = {}

####### Events Bot #######

@ bot.event
async def on_ready ():
log.writeLog ("init", "Ready")
print ("Bot Started")
configs = []
    for x in bot.guilds:
configs.append (dbcontext.getConfig (x.id))
print ("Connected to:" + x.name)
log.writeLog ("Connected", x.name)


@ bot.command ()
async def stats (ctx, * args):
if ctx.message.guild is None:
wait ctx.send ("** I am not allowed to reply to private messages. **")
other:
config = dbcontext.getConfig (ctx.guild.id)
if (len (args) == 0) or (len (args) == 1 and args[0].lower () == "help"):
wait writeHelp (ctx, config)
elif len (args) == 1 and args[0] ! = "help":
playerObject = GetStats.getPlayer (config, arguments[0])
if playerObject == None or playerObject.id == 0:
wait writeError (ctx, config, ErrorType.UNKNOWN_PLAYER)
other:
playerStats = GetStats.getPlayerStats (config, playerObject)
if playerStats.hidden:
wait writeError (ctx, config, ErrorType.HIDDEN_STATS)
elif playerStats.damage == 0:
wait writeError (ctx, config, ErrorType.UNKNOWN_STATS)
other:
wait writeAnswer (ctx, config, playerObject, playerStats)
elif len (args)> = 2:
playerObject = GetStats.getPlayer (config, arguments[0])
if playerObject == None or playerObject.id == 0:
wait writeError (ctx, config, ErrorType.UNKNOWN_PLAYER)
other:
ship = dbcontext.getShip ("" .join (arguments)[1:]))
if ship.id == 0:
wait writeError (ctx, config, ErrorType.UNKNOWN_SHIP)
other:
shipStats = GetStats.getShipStats (config, playerObject, ship)
if shipStats.hidden:
wait writeError (ctx, config, ErrorType.HIDDEN_STATS)
elif shipStats.damage == 0:
wait writeError (ctx, config, ErrorType.UNKNOWN_STATS)
other:
wait writeAnswer (ctx, config, playerObject, ship, shipStats)


@ bot.command ()
async def statsr (ctx, * args):
if ctx.message.guild is None:
wait ctx.send ("** I am not allowed to reply to private messages. **")
other:
config = dbcontext.getConfig (ctx.guild.id)
regex = re.compile ("^ (![1-9][1]|![1-9]) | (! s[1-4]) $ ", re.IGNORECASE) # regex to check the valid season
if (len (args) == 0) or (len (args) == 1 and args[0].lower () == "help"):
wait writeHelp (ctx, config)
elif regex.match (args[0]):
wait writeError (ctx, config, ErrorType.UNKNOWN_SEASON)
elif len (args) == 2 and args[0] ! = "help":
season = convertSeason (args[0])
playerObject = GetStats.getPlayer (config, arguments[1])
if playerObject == None or playerObject.id == 0:
wait writeError (ctx, config, ErrorType.UNKNOWN_PLAYER)
other:
playerStats = GetStats.getRankedStats (config, playerObject, season)
if playerStats.hidden:
wait writeError (ctx, config, ErrorType.HIDDEN_STATS)
elif playerStats.damage == 0:
wait writeError (ctx, config, ErrorType.UNKNOWN_STATS)
other:
wait writeAnswer (ctx, config, playerObject, playerStats)
elif len (args)> 2:
season = convertSeason (args[0])
playerObject = GetStats.getPlayer (config, arguments[1])
if playerObject == None or playerObject.id == 0:
wait writeError (ctx, config, ErrorType.UNKNOWN_PLAYER)
other:
ship = dbcontext.getShip ("" .join (arguments)[2:]))
if ship.id == 0:
wait writeError (ctx, config, ErrorType.UNKNOWN_SHIP)
other:
shipStats = GetStats.getRankedStats (config, playerObject, season, ship)
if shipStats.hidden:
wait writeError (ctx, config, ErrorType.HIDDEN_STATS)
elif shipStats.damage == 0:
wait writeError (ctx, config, ErrorType.UNKNOWN_STATS)
other:
wait writeAnswer (ctx, config, playerObject, ship, shipStats)
other:
wait writeHelp (ctx, config)

####### Support Features #######

async def writeAnswer (ctx, config, * args):
player = none
ship = None
stats = None
embed = None
translation = globals ()[config.language]
    for arg in args:
if isinstance (argument, player):
player = arg
elif isinstance (argument, ship):
ship = arg
elif isinstance (arg, statistics):
stats = arg
if player! = None and stats! = None and ship! = None:
color = getColor (stats.avgWins)
embed = discord.Embed (title = translation.title.format (user name = player.name), url = GetStats.getPlayerLink (configuration, player), description = translation.description.format (shipname = ship.name ), color = color)
embed.set_author (name = "WoWs-Stats-Bot", url = "https://github.com/De-Wohli")
embed.set_thumbnail (url = ship.url)
embed.add_field (name = translation.battles, value = stats.battles, inline = True)
embed.add_field (name = translation.avgDamage, value = stats.avgDamage, inline = True)
embed.add_field (name = translation.winrate, value = "{:. 2f}%". format (stats.avgWins), inline = True)
embed.set_footer (text = translation.footer)
elif player! = None and stats! = None and ship == None:
color = getColor (stats.avgWins)
embed = discord.Embed (title = translation.title.format (user name = player.name), url = GetStats.getPlayerLink (config, player), description = translation.general, color = color)
embed.set_author (name = "WoWs-Stats-Bot", url = "https://github.com/De-Wohli")
embed.add_field (name = translation.battles, value = stats.battles, inline = True)
embed.add_field (name = translation.avgDamage, value = stats.avgDamage, inline = True)
embed.add_field (name = translation.winrate, value = "{:. 2f}%". format (stats.avgWins), inline = True)
embed.set_footer (text = translation.footer)
if integrated! = None:
wait ctx.send (embed = embed)


async def writeHelp (ctx, config):
color = discord.Color.teal ()
translation = globals ()[config.language]
    embed = discord.Embed (title = translation.helpHeader, description = translation.helpDescription, color = color)
embed.set_author (name = "WoWs-Stats-Bot")
embed.add_field (name = "! stats [player]", value = translation.helpPlayer, inline = False)
embed.add_field (name = "! stats [player] [shipname]", value = translation.helpShip, inline = False)
embed.add_field (name = "! statsr [season] [player]", value = translation.helpRanked, inline = False)
embed.add_field (name = "! statsr [season] [player] [shipname]", value = translation.helpSRanked, inline = False)
embed.set_footer (text = "This bot was created by Fuyu_Kitsune")
wait ctx.send (embed = embed)


async def writeError (ctx, config, errorType):
color = discord.Color.dark_teal ()
translation = globals ()[config.language]
    errorText = translation.error[errorType.value]
    embed = discord.Embed (title = "Error", description = errorText, color = color)
embed.set_author (name = "WoWs-Stats-Bot")
embed.set_footer (text = translation.footer)
wait ctx.send (embed = embed)


def getColor (value):
if value <= 40:
        return discord.Colour.red()
    elif value > 40 and value <= 45:
        return discord.Colour.orange()
    elif value > 45 and value <= 50:
        return discord.Colour.gold()
    elif value > 50 and value <= 53:
        return discord.Colour.green()
    elif value > 53 and value <= 56:
        return discord.Color.dark_green()
    elif value > 56 and value <= 60:
        return discord.Color.teal()
    elif value > 60 and value <= 66:
        return discord.Color.purple()
    elif value > 66:
return discord.Colour.dark_purple ()


def convertSeason (value):
season = value
if season == "s1":
season = "101"
season elif == "s2":
season = "102"
elif season == "s3":
season = "103"
elif season == "s4":
season = "104"
return season

This being the main method, here is where the analysis of the order takes place. The usual order is !Statistics [playername] [shipname] My main concerns here are the async def stats (ctx, * args): and async def statsr (ctx, * args): the functions.

data / classes.py:

from enum import Enum


Ship class:
def __init __ (self, id = 0, name = "", url = ""):
self.id = id
self.name = name
self.url = url

def __eq __ (self, other):
returns self.name == other


class player:
def __init __ (self, id = 0, name = "", code = "404"):
self.id = id
self.name = name
self.code = code


Class statistics:
def __init __ (auto, fights = 0, frags = 0, damage_dealt = 0, wins = 0, hidden = false, code = 404):
self.hidden = hidden
self.battles = battles
self.frags = float (frags)
self.damage = float (damage_dealt)
self.wins = wins
self.code = code

@property
def avgFrags (self):
if self.battles == 0:
returns 0
round back (self.frags / self.battles, 2)

@property
def avgDamage (self):
if self.battles == 0:
returns 0
round trip (self.damage / self.battles, 2)

@property
def avgWins (self):
if self.battles == 0:
returns 0
round trip (float (self.wins / self.battles), 4) * 100


Config class:
def __init __ (self, serverId = 0, region = "eu", language = "en"):
self.serverId = serverId
self.region = region
self.language = language


ReturnVal class (Enum):
SUCCESS = 0
FAILED = 1
DOUBLE = 2

ErrorType class (Enum):
def __str __ (auto):
return str (self.value)

UNKNOWN_PLAYER = 0
UNKNOWN_SHIP = 1
UNKNOWN_STATS = 2
HIDDEN_STATS = 3
UNKNOWN_SEASON = 4

these are my model classes, since I come from C #, I'm not sure if it would be an acceptable way to handle this in python.

data / dbcontext.py:

import system
bone import
import mysql.connector

import data.log as a log
from data.classes import Config, ReturnVal, Ship
from data.secret import Secret


def connect ():
mydb = mysql.connector.connect (host = Secret.dbAddr, user = Secret.dbUser, passwd = Secret.dbPwd, database = Secret.dbName)
return mydb


def getShip (name):
try:
con = connect ()
cursor = con.cursor ()
sql = SELECT id, Name, url FROM SHIPS WHERE name LIKE% s & # 39;
val = (name,)
cursor.execute (sql, val)
lines = cursor.fetchone ()
if the lines are null:
sql = SELECT id, Name, url FROM ships WHERE id = (SELECT id FROM Asn WHERE name LIKE% s) & # 39;
val = (name,)
cursor.execute (sql, val)
lines = cursor.fetchone ()
if the lines are null:
return boat ()
other:
return the boat (id = row[0], name = lines[1], url = rows[2])
other:
return the boat (id = row[0], name = lines[1], url = rows[2])
with the exception of e:
log.writeLog ("getShip", str (e))
con.rollback ()
return boat ()
finally:
con.commit ()
con.close ()



def addAsn (name, asn):
try:
con = connect ()
cursor = con.cursor ()
sql = SELECT id, Name, url FROM SHIPS WHERE name LIKE% s & # 39;
val = (name,)
cursor.execute (sql, val)
lines = cursor.fetchone ()
if the lines are null:
return ReturnVal.FAILED
other:
sql = INSERT INTO Asn (name, id, url) VALUES (% s,% s,% s) & # 39;
val = (asn, lines[0]rows[2])
cursor.execute (sql, val)
return ReturnVal.SUCCESS
except mysql.connector.IntegrityError as e:
return ReturnVal.DOUBLE
with the exception of e:
log.writeLog ("getShip", str (e))
con.rollback ()
return ReturnVal.FAILED
finally:
con.commit ()
con.close ()

def getConfig (id):
try:
con = connect ()
cursor = con.cursor ()
sql = SELECT region, Config language WHERE ServerId =% s & # 39;
val = (id,)
cursor.execute (sql, val)
lines = cursor.fetchone ()
if the lines are null:
config = Config (serverId = id)
sql = INSERT INTO Config (ServerID, Region, Language) VALUES (% s,% s,% s) & # 39;
val = (config.serverId, config.region, config.language)
cursor.execute (sql, val)
back config
other:
return Config (serverId = id, region = rows[0], language = lines[1])
with the exception of e:
log.writeLog ("getConfig", str (e))
con.rollback ()
finally:
con.commit ()
con.close ()

def addConfig (id):
try:
con = connect ()
cursor = con.cursor ()
sql = INSERT INTO Config (ServerId, Region, Language) VALUES (% s,% s,% s) & # 39;
val = (id, "eu", "en")
cursor.execute (sql, val)
return ReturnVal.SUCCESS
with the exception of e:
log.writeLog ("addConfig", str (e))
con.rollback ()
return ReturnVal.FAILED
finally:
con.commit ()
con.close ()

def updateConfig (config):
try:
con = connect ()
cursor = con.cursor ()
sql = UPDATE Config SET region =% s, language =% s WHERE ServerID =% s & # 39;
val = (config.region, config.language, config.serverId)
cursor.execute (sql, val)
return ReturnVal.SUCCESS
with the exception of e:
log.writeLog ("updateConfig", str (e))
con.rollback ()
return ReturnVal.FAILED
finally:
con.commit ()
con.close ()

In this file, I manage all access to the database. The database mainly stores ship names and identifiers. Is this way of managing the connection to the database secure or vulnerable?

data / getStats.py:

bone import
import system
import requests
import json

sys.path.append (os.path.join (os.path.dirname (__ file__), "lib"))
sys.path.append (os.path.join (os.path.dirname (__ file __), "../ data"))
from data.classes, import Ship, Player, Stats
from data.secret import Secret
from data.api import api
from the data import log


def getPlayer (config, playerName):
try:
url = api.psearch.format (reg = config.region, wgapi = Secret.api, playerName = playerName)
response = requests.get (url)
statuscode = response.status_code
response = response.json ()
if answer["status"] == "ok":
if answer["meta"]["count"]    == 0:
return player (code = 200)
other:
pseudo = answer["data"][0]["nickname"]
                
                
                
                pid = answer["data"][0]["account_id"]
                
                
                
                newPlayer = Player (name = username, id = pid, code = status code)
return newPlayer
other:
return of the player (code = statuscode)
with the exception of e:
print (str (e))
log.writeLog ("getPlayer", str (e))
return player (code = 200)


def getPlayerLink (config, player):
link = str.format ("{} {} - {}", str (api.plink) .format (reg = config.region), player.id, player.name)
return link


def getPlayerStats (config, player):
try:
url = api.pstats.format (reg = config.region, wgapi = Secret.api, accountID = player.id)
response = requests.get (url)
statuscode = response.status_code
response = response.json ()
if (answer["status"] == "ok"):
if bool (answer["data"]):
if bool (answer["meta"]["hidden"]):
st = Stats (1,1,1,1, True, 200)
other:
battles = answer["data"][str(player.id)]["statistics"]['pvp']['battles']
                    
                    
                    
                    wins = answer["data"][str(player.id)]["statistics"]['pvp']['wins']
                    
                    
                    
                    frags = answer["data"][str(player.id)]["statistics"]['pvp']['frags']
                    
                    
                    
                    damage_dealt = answer["data"][str(player.id)]["statistics"]['pvp']['damage_dealt']
                    
                    
                    
                    st = Stats (battles, frags, damage_dealt, wins, False, statuscode)
other:
returns the statistics (code = 200)
back st
other:
returns the statistics (code = statuscode)
with the exception of e:
print (str (e))
log.writeLog ("getPlayerStats", str (e))
returns the statistics (code = 200)


def getShipStats (configuration, drive, ship):
try:
url = api.sstats.format (reg = config.region, wgapi = Secret.api, accountID = player.id, shipID = ship.id)
response = requests.get (url)
statuscode = response.status_code
response = response.json ()
if (answer["status"] == "ok"):
if bool (answer["meta"]["hidden"]):
st = Stats (1,1,1,1, True, 200)
elif bool (answer["data"]) and no answer["data"][str(player.id)]    == None:
battles = answer["data"][str(player.id)][0]['pvp']['battles']
                
                
                
                wins = answer["data"][str(player.id)][0]['pvp']['wins']
                
                
                
                frags = answer["data"][str(player.id)][0]['pvp']['frags']
                
                
                
                damage_dealt = answer["data"][str(player.id)][0]['pvp']['damage_dealt']
                
                
                
                st = Stats (battles, frags, damage_dealt, wins, False, statuscode)
other:
returns the statistics (code = 200)
back st
other:
returns the statistics (code = statuscode)
with the exception of e:
print (str (e))
log.writeLog ("getShipStats", str (e))
returns the statistics (code = 200)


def getRankedStats (config, player, season, ship = none):
try:
If the ship is not None:
url = api.rsstats.format (reg = config.region, wgapi = Secret.api, accountID = player.id, shipID = ship.id)
other:
url = api.rpstats.format (reg = config.region, wgapi = Secret.api, accountID = player.id)
response = requests.get (url)
statuscode = response.status_code
response = response.json ()
stats = Stats ()
if (answer["status"] == "ok"):
if bool (answer["meta"]["hidden"]):
stats = Stats (1,1,1,1, True, 200)
elif bool (answer["data"]) and no answer["data"][str(player.id)]    == None:
If the ship is not None:
seasons = answer["data"][str(player.id)][0]["seasons"]
                    
                    
                    
                    if season in season:
currentSeason = answer["data"][str(player.id)][0]["seasons"][season]
                    
                    
                    
                    other:
returns the statistics (code = 200)
other:
seasons = answer["data"][str(player.id)]["seasons"]
                    
                    
                    
                    if season in season:
currentSeason = answer["data"][str(player.id)]["seasons"][season]
                    
                    
                    
                    other:
returns the statistics (code = 200)
r = []
                r.append (currentSeason["rank_solo"])
r.append (currentSeason["rank_div2"])
r.append (currentSeason["rank_div3"])
for x in r:
if x is not None:
stats.wins + = x["wins"]
                        stats.damage + = x["damage_dealt"]
                        stats.battles + = x["battles"]
                        stats.frags + = x["frags"]
                stats.code = 200
other:
returns the statistics (code = 200)
return statistics
other:
returns the statistics (code = statuscode)
pass
with the exception of e:
log.writeLog ("getRankedStats", str (e))
pass

This is the communication class for querying the endpoints of the API. Endpoints are stored in a different file data / api.py containing a class with the stored variables. that is to say.

API class:
psearch = "https: //api.worldofwarsars. {reg} / wows / account / list /? application_id = {wgapi} & search = {playerName}"

The code presented here runs and is hosted on a linux server without much problems for now, but I'm curious to know about it, probably a lot of things that I could improve on.
If necessary, I can provide the answers of the API if the code that I have published is of interest.

In general, I am looking for help to improve my python programming and improve the performance / stability of my code. Any information as well as criticism is welcome.

Thank you.