Archived
generated from imcompiling/discord-bot-template
Compare commits
5
Commits
4d023e35f1
...
bbacaa8bb9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbacaa8bb9 | ||
|
|
eef85b424d | ||
|
|
3a4ac066a2 | ||
|
|
7fd3fb8458 | ||
|
|
dae0d205e5 |
Vendored
+5
-1
@@ -3,5 +3,9 @@
|
|||||||
"reportOptionalMemberAccess": "warning",
|
"reportOptionalMemberAccess": "warning",
|
||||||
"reportArgumentType": "information",
|
"reportArgumentType": "information",
|
||||||
"reportAttributeAccessIssue": "warning"
|
"reportAttributeAccessIssue": "warning"
|
||||||
}
|
},
|
||||||
|
"cSpell.words": [
|
||||||
|
"cakeday",
|
||||||
|
"cakedays"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
"""Initializing code for all loops."""
|
||||||
|
|
||||||
|
from nextcord.ext import commands
|
||||||
|
|
||||||
|
from cogs.loops.daily_loop import DailyLoop
|
||||||
|
|
||||||
|
|
||||||
|
def setup(bot: commands.Bot) -> None:
|
||||||
|
"""Add functions to bot."""
|
||||||
|
bot.add_cog(DailyLoop(bot))
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
"""Defines the daily loop that runs the Off Cooldown bot functions."""
|
||||||
|
|
||||||
|
from datetime import UTC, datetime, time
|
||||||
|
|
||||||
|
import nextcord
|
||||||
|
from nextcord.ext import commands, tasks
|
||||||
|
from pytz import timezone
|
||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from sqlmodel import col
|
||||||
|
|
||||||
|
from database.db import engine
|
||||||
|
from database.models import UserBirthday
|
||||||
|
from logger import ocd_logger
|
||||||
|
from main import birthday_list
|
||||||
|
from settings import settings
|
||||||
|
|
||||||
|
|
||||||
|
class DailyLoop(commands.Cog, name="Daily Loop"):
|
||||||
|
"""Class for performing daily functions."""
|
||||||
|
|
||||||
|
def __init__(self, bot: commands.Bot) -> None:
|
||||||
|
self.bot = bot
|
||||||
|
self.current_election = None
|
||||||
|
self.update_birthdays()
|
||||||
|
self.daily_loop.start()
|
||||||
|
|
||||||
|
@tasks.loop(time=time(hour=0, tzinfo=timezone("UTC")))
|
||||||
|
async def daily_loop(self) -> None:
|
||||||
|
"""Run daily repeated functions."""
|
||||||
|
self.update_birthdays()
|
||||||
|
|
||||||
|
await self.cakedays()
|
||||||
|
|
||||||
|
@daily_loop.before_loop
|
||||||
|
async def before_loop(self) -> None:
|
||||||
|
"""Actions run before the loop starts. Want to wait for bot to be ready and get the current cycle."""
|
||||||
|
await self.bot.wait_until_ready()
|
||||||
|
|
||||||
|
def update_birthdays(self) -> None:
|
||||||
|
"""Goes through database to collect all birthdays that are today. Adds user's id to birthday list."""
|
||||||
|
birthday_list.clear()
|
||||||
|
|
||||||
|
today = datetime.now(tz=UTC).date()
|
||||||
|
|
||||||
|
stmt = (
|
||||||
|
select(col(UserBirthday.user_id))
|
||||||
|
.where(col(UserBirthday.month) == today.month)
|
||||||
|
.where(col(UserBirthday.day) == today.day)
|
||||||
|
)
|
||||||
|
with Session(engine) as session:
|
||||||
|
to_add = session.scalars(stmt)
|
||||||
|
birthday_list.extend(to_add.all())
|
||||||
|
|
||||||
|
async def cakedays(self) -> None:
|
||||||
|
"""
|
||||||
|
Loops through the guild's humans checking for joined_at dates that match today's date.
|
||||||
|
|
||||||
|
Sends a message saying happy cakeday.
|
||||||
|
"""
|
||||||
|
today = datetime.now(tz=UTC).date()
|
||||||
|
if (guild := self.bot.get_guild(settings.params.guild_id)) is None:
|
||||||
|
ocd_logger.warning("Guild not found in bot.")
|
||||||
|
return
|
||||||
|
if (announce_channel := guild.get_channel(settings.channel_ids.general)) is None or not isinstance(
|
||||||
|
announce_channel, nextcord.TextChannel
|
||||||
|
):
|
||||||
|
ocd_logger.warning("Set general channel isn't found or isn't a text channel.")
|
||||||
|
return
|
||||||
|
|
||||||
|
cakedays: list[nextcord.Member] = [
|
||||||
|
human
|
||||||
|
for human in guild.humans
|
||||||
|
if human.joined_at is not None and human.joined_at.month == today.month and human.joined_at.day == today.day
|
||||||
|
]
|
||||||
|
|
||||||
|
for member in cakedays:
|
||||||
|
years = today.year - member.joined_at.year # type: ignore reportOptionalMemberAccess
|
||||||
|
year_str = "years" if years > 1 else "year"
|
||||||
|
msg = f"Happy Cake day! <@{member.id}> joined the discord {years} {year_str} ago!"
|
||||||
|
await announce_channel.send(msg)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""Package for database interaction."""
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
"""Simple file to set up the sqlmodel connection to database."""
|
||||||
|
|
||||||
|
from sqlmodel import SQLModel, create_engine
|
||||||
|
|
||||||
|
from . import models # noqa: F401 - Need this import for creating the models.
|
||||||
|
|
||||||
|
url = "sqlite:///database.db"
|
||||||
|
engine = create_engine(url)
|
||||||
|
|
||||||
|
# Don't need to create all when connected to prod.
|
||||||
|
SQLModel.metadata.create_all(engine)
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
"""File holds all model definitions of SQL tables."""
|
||||||
|
|
||||||
|
from sqlalchemy.dialects.mysql import BIGINT, SMALLINT
|
||||||
|
from sqlmodel import Column, Field, SQLModel
|
||||||
|
|
||||||
|
|
||||||
|
class UserBirthday(SQLModel, table=True):
|
||||||
|
"""
|
||||||
|
Represents a user's birthday.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
user_id: The user's discord id. Primary key.
|
||||||
|
month: The month the user was born.
|
||||||
|
day: The day the user was born.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
user_id: int = Field(sa_column=Column(BIGINT(unsigned=True), primary_key=True, nullable=False))
|
||||||
|
month: int = Field(sa_column=Column(SMALLINT(unsigned=True), nullable=False))
|
||||||
|
day: int = Field(sa_column=Column(SMALLINT(unsigned=True), nullable=False))
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
"""Creates logger for Agon bot."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
ocd_logger = logging.getLogger("default")
|
||||||
|
ocd_logger.setLevel(logging.INFO)
|
||||||
|
handler = logging.StreamHandler(sys.stdout)
|
||||||
|
handler.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s"))
|
||||||
|
ocd_logger.addHandler(handler)
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
"""Main file for bot."""
|
"""Main file for bot."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import pathlib
|
||||||
|
|
||||||
import nextcord
|
import nextcord
|
||||||
from nextcord.ext import commands
|
from nextcord.ext import commands
|
||||||
|
|
||||||
|
from logger import ocd_logger
|
||||||
from settings import settings
|
from settings import settings
|
||||||
|
|
||||||
nextcord_logger = logging.getLogger("nextcord")
|
nextcord_logger = logging.getLogger("nextcord")
|
||||||
@@ -14,6 +17,8 @@ nextcord_handler = logging.FileHandler(filename="nextcord.log", encoding="utf-8"
|
|||||||
nextcord_handler.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s"))
|
nextcord_handler.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s"))
|
||||||
nextcord_logger.addHandler(nextcord_handler)
|
nextcord_logger.addHandler(nextcord_handler)
|
||||||
|
|
||||||
|
birthday_list: list[int] = []
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Set config, intents, then connects the bot to discord."""
|
"""Set config, intents, then connects the bot to discord."""
|
||||||
@@ -28,21 +33,21 @@ def main() -> None:
|
|||||||
# intents.guilds = True
|
# intents.guilds = True
|
||||||
|
|
||||||
# To enable the members privileged intent:
|
# To enable the members privileged intent:
|
||||||
# intents.members = True
|
intents.members = True
|
||||||
|
|
||||||
bot = commands.Bot(intents=intents)
|
bot = commands.Bot(intents=intents)
|
||||||
|
|
||||||
# Get the modules of all cogs whose directory structure is ./cogs/<module_name>
|
# Get the modules of all cogs whose directory structure is ./cogs/<module_name>
|
||||||
for folder in os.listdir("cogs"):
|
for folder in pathlib.Path("cogs").iterdir():
|
||||||
bot.load_extension(f"cogs.{folder}")
|
bot.load_extension(f"cogs.{folder}")
|
||||||
|
|
||||||
@bot.listen()
|
@bot.listen()
|
||||||
async def on_ready() -> None:
|
async def on_ready() -> None:
|
||||||
"""When Discord is connected."""
|
"""When Discord is connected."""
|
||||||
if bot.user is None:
|
if bot.user is None:
|
||||||
print("Discord connection failed.")
|
ocd_logger.critical("Discord connection failed.")
|
||||||
return
|
return
|
||||||
print(f"{bot.user.name} has connected to Discord!")
|
ocd_logger.info(f"{bot.user.name} has connected to Discord!")
|
||||||
|
|
||||||
# Run Discord bot
|
# Run Discord bot
|
||||||
bot.run(settings.params.discord_token)
|
bot.run(settings.params.discord_token)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ dependencies = [
|
|||||||
"python-dotenv>=1.0.1",
|
"python-dotenv>=1.0.1",
|
||||||
"sqlmodel>=0.0.22",
|
"sqlmodel>=0.0.22",
|
||||||
"dataclass-wizard[yaml]>=0.22.3,<1.0.0",
|
"dataclass-wizard[yaml]>=0.22.3,<1.0.0",
|
||||||
|
"pytz>=2025.1",
|
||||||
]
|
]
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
|
|||||||
@@ -7,17 +7,26 @@ from dataclass_wizard import YAMLWizard
|
|||||||
CONFIG_LOCATION = "conf/config.yaml"
|
CONFIG_LOCATION = "conf/config.yaml"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ChannelIds:
|
||||||
|
"""Class holds all channel ids needed by bot."""
|
||||||
|
|
||||||
|
general: int
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Params:
|
class Params:
|
||||||
"""Class defines start parameters that shouldn't be changed."""
|
"""Class defines start parameters that shouldn't be changed."""
|
||||||
|
|
||||||
discord_token: str
|
discord_token: str
|
||||||
|
guild_id: int
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Settings(YAMLWizard):
|
class Settings(YAMLWizard):
|
||||||
"""Class defines all configs required for off-cooldown-bot."""
|
"""Class defines all configs required for off-cooldown-bot."""
|
||||||
|
|
||||||
|
channel_ids: ChannelIds
|
||||||
params: Params
|
params: Params
|
||||||
|
|
||||||
def save(self) -> None:
|
def save(self) -> None:
|
||||||
|
|||||||
@@ -307,6 +307,7 @@ dependencies = [
|
|||||||
{ name = "dataclass-wizard", extra = ["yaml"] },
|
{ name = "dataclass-wizard", extra = ["yaml"] },
|
||||||
{ name = "nextcord" },
|
{ name = "nextcord" },
|
||||||
{ name = "python-dotenv" },
|
{ name = "python-dotenv" },
|
||||||
|
{ name = "pytz" },
|
||||||
{ name = "sqlmodel" },
|
{ name = "sqlmodel" },
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -320,6 +321,7 @@ requires-dist = [
|
|||||||
{ name = "dataclass-wizard", extras = ["yaml"], specifier = ">=0.22.3,<1.0.0" },
|
{ name = "dataclass-wizard", extras = ["yaml"], specifier = ">=0.22.3,<1.0.0" },
|
||||||
{ name = "nextcord", specifier = ">=2.6.0" },
|
{ name = "nextcord", specifier = ">=2.6.0" },
|
||||||
{ name = "python-dotenv", specifier = ">=1.0.1" },
|
{ name = "python-dotenv", specifier = ">=1.0.1" },
|
||||||
|
{ name = "pytz", specifier = ">=2025.1" },
|
||||||
{ name = "sqlmodel", specifier = ">=0.0.22" },
|
{ name = "sqlmodel", specifier = ">=0.0.22" },
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -445,6 +447,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 },
|
{ url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytz"
|
||||||
|
version = "2025.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/5f/57/df1c9157c8d5a05117e455d66fd7cf6dbc46974f832b1058ed4856785d8a/pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e", size = 319617 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/eb/38/ac33370d784287baa1c3d538978b5e2ea064d4c1b93ffbd12826c190dd10/pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57", size = 507930 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyyaml"
|
name = "pyyaml"
|
||||||
version = "6.0.2"
|
version = "6.0.2"
|
||||||
|
|||||||
Reference in New Issue
Block a user