Skip to content

Commit

Permalink
Make config an instance attribute of DTbot class
Browse files Browse the repository at this point in the history
This change makes it easier to do runtime config changes, such as version & last updated changes for hot reload updates, by tying the config to the DTbot instance and letting it be overwritten at runtime.
It also does away with the awful and barely understandable chain of imports of the DTbot.py file's config constant across files, sometimes directly, sometimes via other modules.

(backported from v3)
  • Loading branch information
MajorTanya committed Sep 20, 2022
1 parent 3b6dca7 commit 1d6ae19
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 172 deletions.
29 changes: 13 additions & 16 deletions DTbot.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import datetime
import logging
from configparser import ConfigParser
Expand All @@ -8,27 +10,22 @@
intents = discord.Intents.default()
intents.members = True

config = ConfigParser()
config.read('./config/config.ini')
TOKEN = config.get('General', 'TOKEN')

extensions = config.items('Extensions')
startup_extensions = []
for key, ext in extensions:
startup_extensions.append(ext)

startup_time = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0)
log_startup_time = startup_time.strftime('%Y-%m-%d (%H-%M-%S %Z)')


class DTbot(commands.Bot):
def __init__(self):
def __init__(self, bot_config: ConfigParser | None = None):
super().__init__(case_insensitive=True, command_prefix=commands.when_mentioned, intents=intents)
self.bot_startup = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0)
if bot_config:
self.bot_config = bot_config
else:
self.bot_config = ConfigParser()
self.bot_config.read('./config/config.ini')
self.dtbot_colour = discord.Colour(0x5e51a8)
self.remove_command('help')
# set up logging and bind to instance
self.log = logging.getLogger('discord')
self.log.setLevel(logging.WARNING)
log_startup_time = self.bot_startup.strftime('%Y-%m-%d (%H-%M-%S %Z)')
filehandler = logging.FileHandler(filename=f'./logs/{log_startup_time}.log', encoding='utf-8', mode='w')
streamhandler = logging.StreamHandler()
filehandler.setFormatter(logging.Formatter('%(asctime)s: %(levelname)s: %(name)s: %(message)s'))
Expand All @@ -37,7 +34,7 @@ def __init__(self):
self.log.addHandler(streamhandler)

async def setup_hook(self):
for extension in startup_extensions:
for _, extension in self.bot_config.items('Extensions'):
try:
await self.load_extension(extension)
print(f'Successfully loaded extension {extension}.')
Expand All @@ -52,5 +49,5 @@ async def on_ready(self):
print(self.user.id)
print('------')

def run(self, token: str = TOKEN, **kwargs):
super().run(token, log_handler=None)
def run(self, **kwargs):
super().run(self.bot_config.get('General', 'TOKEN'), log_handler=None)
8 changes: 5 additions & 3 deletions database_management.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import random
import time
from configparser import ConfigParser

import discord
import mysql.connector as mariadb
from discord.ext import commands
from mysql.connector import pooling

from DTbot import DTbot, config
from DTbot import DTbot

db_config = dict(config.items('Database'))
db_config = ConfigParser()
db_config.read('./config/config.ini')
# open the pooled connection used for all stored procedure calls
cnx = mariadb.pooling.MySQLConnectionPool(pool_size=10, pool_reset_session=True, **db_config)
cnx = mariadb.pooling.MySQLConnectionPool(pool_size=10, pool_reset_session=True, **(dict(db_config.items('Database'))))


def dbcallprocedure(procedure, *, returns: bool = False, params: tuple):
Expand Down
48 changes: 24 additions & 24 deletions dev.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import datetime
import sys
from configparser import ConfigParser
Expand All @@ -6,21 +8,20 @@
from discord import Game
from discord.ext import commands, tasks

from DTbot import DTbot, config, startup_time

dtbot_version = config.get('Info', 'dtbot_version')
last_updated = config.get('Info', 'last_updated')
h_code = config.get('Developers', 'h_code')
hb_freq = config.getint('Heartbeat', 'hb_freq')
sdb_code = config.get('Developers', 'sdb_code')
from DTbot import DTbot


class Dev(commands.Cog, command_attrs=dict(hidden=True)):
"""Developer Commands and DTbot Management"""

HB_FREQ: float = 60

def __init__(self, bot: DTbot):
self.bot = bot
self.hb_chamber = None
Dev.HB_FREQ = self.bot.bot_config.getint('Heartbeat', 'hb_freq')
self.H_CODE = self.bot.bot_config.get('Developers', 'h_code')
self.SDB_CODE = self.bot.bot_config.get('Developers', 'sdb_code')
self.hb_chamber: discord.TextChannel | None = None
if len(sys.argv) < 2 or sys.argv[1] == '1':
# run "python launcher.py 1" to start DTbot with a heartbeat message, 0 if not
# if no parameter is provided, defaults to run with a heartbeat
Expand All @@ -35,33 +36,36 @@ def cog_unload(self):
async def cog_check(self, ctx: commands.Context):
return await self.bot.is_owner(ctx.message.author)

@tasks.loop(seconds=hb_freq)
@tasks.loop(seconds=HB_FREQ)
async def heartbeat(self):
if not self.bot.is_closed():
now_dt = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0)
now_ts = int(now_dt.timestamp())
startup_ts = int(startup_time.timestamp())
uptime = now_dt - startup_time
startup_ts = int(self.bot.bot_startup.timestamp())
uptime = now_dt - self.bot.bot_startup
dtbot_version = self.bot.bot_config.get('Info', 'dtbot_version')
beat_embed = discord.Embed(colour=self.bot.dtbot_colour, title=f"{self.bot.user.name}'s Heartbeat",
description=f"{self.bot.user.name} is still alive and running!")
beat_embed.add_field(name="Startup time:", value=f"<t:{startup_ts}:D> - <t:{startup_ts}:T>")
beat_embed.add_field(name="Time now:", value=f"<t:{now_ts}:D> - <t:{now_ts}:T>", inline=False)
beat_embed.add_field(name="Uptime:", value=uptime)
beat_embed.set_footer(text=f"DTbot v. {dtbot_version}")
await self.hb_chamber.send(embed=beat_embed, delete_after=hb_freq)
await self.hb_chamber.send(embed=beat_embed, delete_after=Dev.HB_FREQ)

@heartbeat.before_loop
async def before_heartbeat(self):
await self.bot.wait_until_ready()
startup_ts = int(startup_time.timestamp())
self.hb_chamber = self.bot.get_channel(config.getint('Heartbeat', 'hb_chamber'))
self.heartbeat.change_interval(seconds=Dev.HB_FREQ) # apply the config value
startup_ts = int(self.bot.bot_startup.timestamp())
self.hb_chamber = self.bot.get_channel(self.bot.bot_config.getint('Heartbeat', 'hb_chamber'))
startup_embed = discord.Embed(colour=self.bot.dtbot_colour, title=f"{self.bot.user.name}'s Heartbeat",
description=f"{self.bot.user.name} is starting up!")
startup_embed.add_field(name="Startup time:", value=f"<t:{startup_ts}:D> - <t:{startup_ts}:T>")
await self.hb_chamber.send(embed=startup_embed)

@commands.Cog.listener()
async def on_ready(self):
dtbot_version = self.bot.bot_config.get('Info', 'dtbot_version')
await self.bot.change_presence(activity=Game(name=f"Do @\u200bDTbot help (v. {dtbot_version})"))

@commands.group(description="Manages the heartbeat of DTbot. Developers only.")
Expand All @@ -71,14 +75,14 @@ async def heart(self, ctx: commands.Context):

@heart.command(description="Stops the heartbeat of DTbot. Developers only.")
async def stop(self, ctx: commands.Context, code=None):
if code == h_code:
if code == self.H_CODE:
self.heartbeat.stop()
self.bot.log.info(f'Heartbeat stopped by user {ctx.author}.')
await ctx.send(f'Heartbeat stopped by user {ctx.author}.')

@heart.command(description="Starts the heartbeat of DTbot. Developers only.")
async def start(self, ctx: commands.Context, code=None):
if code == h_code:
if code == self.H_CODE:
self.heartbeat.restart() if self.heartbeat.is_running() else self.heartbeat.start()
self.bot.log.info(f'Heartbeat started by user {ctx.author}.')
await ctx.send(f'Heartbeat started by user {ctx.author}.')
Expand Down Expand Up @@ -107,6 +111,7 @@ async def reload(self, ctx: commands.Context, extension_name: str):
@commands.command(description="Update / Refresh DTbot's Rich Presence. Developers only.",
brief="Update DTbot's Rich Presence. Developers only.")
async def updaterp(self, ctx: commands.Context, *caption: str):
dtbot_version = self.bot.bot_config.get('Info', 'dtbot_version')
if caption:
caption = " ".join(caption)
caption = caption.replace("DTbot", "@\u200bDTbot")
Expand All @@ -123,13 +128,8 @@ async def updaterp(self, ctx: commands.Context, *caption: str):
'and date in the `info` and `changelog` commands.\nDevelopers only.',
brief='Refresh the version and date of the last update. Developers only.')
async def updatever(self, ctx: commands.Context):
global dtbot_version, last_updated
refreshed_config = ConfigParser()
refreshed_config.read('./config/config.ini')
dtbot_version = refreshed_config.get('Info', 'dtbot_version')
last_updated = refreshed_config.get('Info', 'last_updated')
config.set('Info', 'dtbot_version', dtbot_version)
config.set('Info', 'last_updated', last_updated)
self.bot.bot_config = ConfigParser()
self.bot.bot_config.read('./config/config.ini')
self.bot.log.info(f"{ctx.author} refreshed dtbot_version and last_update.")
self.bot.log.info("Updating Rich Presence and reloading General...")
await ctx.invoke(self.bot.get_command('updaterp'), 'Do DTbot help (v. dtbot_version)') # type: ignore
Expand All @@ -138,7 +138,7 @@ async def updatever(self, ctx: commands.Context):
@commands.command(description='Shutdown command for the bot. Developers only.',
brief='Shut the bot down. Developers only.')
async def shutdownbot(self, ctx: commands.Context, passcode: str):
if passcode == sdb_code:
if passcode == self.SDB_CODE:
try:
self.heartbeat.stop()
except:
Expand Down
58 changes: 30 additions & 28 deletions general.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,30 @@
from discord.ext import commands
from discord.ext.commands import cooldown

from DTbot import DTbot, config, startup_time
from DTbot import DTbot
from database_management import dbcallprocedure
from dev import dtbot_version, last_updated
from linklist import changelog_link
from util.PaginatorSession import PaginatorSession

main_dev_id = config.getint('Developers', 'main dev id')
main_dev = config.get('Developers', 'main')
secondary_dev = config.get('Developers', 'secondary')
dtbot_devs = f'{main_dev}\n{secondary_dev}'
INVITE = config.get('General', 'INVITE')
PERMSEXPL = config.get('General', 'PERMSEXPL')
REQHALL = config.getint('General', 'REQHALL')

AVATAR_ARTIST = config.get('About', 'AVATAR ARTIST')
GH_LINK = config.get('About', 'GH LINK')
SUPPORT_LINK = config.get('About', 'SUPPORT LINK')
TWITTER_LINK = config.get('About', 'TWITTER LINK')


class General(commands.Cog):
"""General utility commands, like user info and uptime, among others"""

def __init__(self, bot: DTbot):
self.bot = bot
main_dev = self.bot.bot_config.get('Developers', 'main')
secondary_dev = self.bot.bot_config.get('Developers', 'secondary')
self.MAIN_DEV_ID = self.bot.bot_config.getint('Developers', 'main dev id')
self.DTBOT_DEVS = f'{main_dev}\n{secondary_dev}'
self.INVITE = self.bot.bot_config.get('General', 'INVITE')
self.PERMSEXPL = self.bot.bot_config.get('General', 'PERMSEXPL')
self.REQHALL = self.bot.bot_config.getint('General', 'REQHALL')
self.AVATAR_ARTIST = self.bot.bot_config.get('About', 'AVATAR ARTIST')
self.GH_LINK = self.bot.bot_config.get('About', 'GH LINK')
self.SUPPORT_LINK = self.bot.bot_config.get('About', 'SUPPORT LINK')
self.TWITTER_LINK = self.bot.bot_config.get('About', 'TWITTER LINK')
self.ANNOUNCEMENT_LINK = self.bot.bot_config.get('About', 'ANNOUNCEMENT LINK')
self.ANNOUNCEMENT_MSG = self.bot.bot_config.get('About', 'ANNOUNCEMENT MSG')

@commands.command(description="Displays a user's avatar. Defaults to command user's avatar when "
"no user is mentioned.",
Expand All @@ -53,15 +52,17 @@ async def avatar(self, ctx: commands.Context, *, user: discord.Member = None):
brief='DTbot announcements')
async def announcements(self, ctx: commands.Context):
embed = discord.Embed(colour=self.bot.dtbot_colour, title='Announcement',
url=config.get('About', 'ANNOUNCEMENT LINK'),
description=config.get('About', 'ANNOUNCEMENT MSG'))
url=self.ANNOUNCEMENT_LINK,
description=self.ANNOUNCEMENT_MSG)
await ctx.send(embed=embed)

@commands.command(description="Get an overview over the recentmost update of DTbot",
brief="Recent updates to DTbot")
@commands.bot_has_permissions(embed_links=True)
async def changelog(self, ctx: commands.Context):
latest_commit = requests.get("https://api.github.com/repos/MajorTanya/DTbot/commits").json()[0]
dtbot_version = self.bot.bot_config.get('Info', 'dtbot_version')
last_updated = self.bot.bot_config.get('Info', 'last_updated')
embed = discord.Embed(colour=self.bot.dtbot_colour,
description=f'__Recent changes to DTbot:__\nNewest version: {dtbot_version} '
f'({last_updated})')
Expand All @@ -83,22 +84,23 @@ async def changeserverprefix(self, ctx: commands.Context, *_):
@commands.bot_has_permissions(embed_links=True)
async def info(self, ctx: commands.Context):
now_dt = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0)
uptime = now_dt - startup_time
uptime = now_dt - self.bot.bot_startup
dtbot_version = self.bot.bot_config.get('Info', 'dtbot_version')
embed = discord.Embed(title=f"{self.bot.user.name}'s info",
description=f"Hello, I'm {self.bot.user.name}, a multipurpose bot for your Discord "
f"server.\n\nIf you have any command requests, use the `request` "
f"command.\n\nThank you and have a good day.\n\n[__**"
f"{self.bot.user.name} Support Server**__]({SUPPORT_LINK})",
f"{self.bot.user.name} Support Server**__]({self.SUPPORT_LINK})",
colour=self.bot.dtbot_colour)
embed.add_field(name="Authors", value=dtbot_devs)
embed.add_field(name="GitHub repository", value=f"Find me [here]({GH_LINK})")
embed.add_field(name="Twitter", value=f"[Tweet @DTbotDiscord]({TWITTER_LINK})", inline=True)
embed.add_field(name="Authors", value=self.DTBOT_DEVS)
embed.add_field(name="GitHub repository", value=f"Find me [here]({self.GH_LINK})")
embed.add_field(name="Twitter", value=f"[Tweet @DTbotDiscord]({self.TWITTER_LINK})", inline=True)
embed.add_field(name="Stats", value=f"In {len(self.bot.guilds)} servers with {len(self.bot.users)} members")
embed.add_field(name="\u200b", value="\u200b")
embed.add_field(name="Uptime", value=uptime)
embed.add_field(name="Invite me", value=f"[Invite me]({INVITE}) to your server too.\n[Explanation]({PERMSEXPL})"
f" for DTbot's permissions")
embed.add_field(name="Avatar by", value=AVATAR_ARTIST, inline=False)
embed.add_field(name="Invite me", value=f"[Invite me]({self.INVITE}) to your server too.\n"
f"[Explanation]({self.PERMSEXPL}) for DTbot's permissions")
embed.add_field(name="Avatar by", value=self.AVATAR_ARTIST, inline=False)
embed.set_footer(text=f"DTbot v. {dtbot_version}")
await ctx.send(embed=embed)

Expand All @@ -120,8 +122,8 @@ async def ping(self, ctx: commands.Context):
aliases=['req'])
@cooldown(2, 86400, commands.BucketType.user)
async def request(self, ctx: commands.Context, command: str, *functionality: str):
reqhall = self.bot.get_channel(REQHALL)
dev_dm = self.bot.get_user(main_dev_id)
reqhall = self.bot.get_channel(self.REQHALL)
dev_dm = self.bot.get_user(self.MAIN_DEV_ID)
embed = discord.Embed(title=f"New request by {ctx.author.name}",
description=f'{ctx.author} (ID: {ctx.author.id}) requested the following command:',
colour=ctx.author.colour)
Expand All @@ -144,7 +146,7 @@ async def resetserverprefix(self, ctx: commands.Context):
brief="DTbot's uptime")
async def uptime(self, ctx: commands.Context):
now_dt = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0)
uptime = now_dt - startup_time
uptime = now_dt - self.bot.bot_startup
await ctx.send(f"{self.bot.user.name}'s uptime is: `{uptime}`")

@commands.command(description="Shows details on user, such as Name, Join Date, or Highest Role",
Expand Down
Loading

0 comments on commit 1d6ae19

Please sign in to comment.