Archived
generated from imcompiling/discord-bot-template
Compare commits
48
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f79d63c78d | ||
|
|
5a8119f5e4 | ||
|
|
d5e6ffdedb | ||
|
|
661c864ba2 | ||
|
|
8d65d478cd | ||
|
|
10e561b0e3 | ||
|
|
842a385d7d | ||
|
|
e9c8336124 | ||
|
|
ef92902932 | ||
|
|
5c6fa4386e | ||
|
|
590cb9abe4 | ||
|
|
46f518c1c4 | ||
|
|
30ecdab8a5 | ||
|
|
565ea73792 | ||
|
|
bfef75870f | ||
|
|
56289e7038 | ||
|
|
54a011337a | ||
|
|
b3905fa15c | ||
|
|
a09845dfb8 | ||
|
|
a86b01292f | ||
|
|
1ad68f4014 | ||
|
|
3aa0bf291f | ||
|
|
b727a80e4f | ||
|
|
857b5f1669 | ||
|
|
34cd2f9975 | ||
|
|
6af2476339 | ||
|
|
4a3f356b23 | ||
|
|
4fe2339ac1 | ||
|
|
2069b2544f | ||
|
|
901bcf3c62 | ||
|
|
8e8f84a42e | ||
|
|
cd241e6f5e | ||
|
|
93d9b565c0 | ||
|
|
c816d3b5b8 | ||
|
|
91900f2b00 | ||
|
|
e1a7eae355 | ||
|
|
5b77abff13 | ||
|
|
467c76671b | ||
|
|
8499ae0a78 | ||
|
|
ea3204d890 | ||
|
|
e4881a74d4 | ||
|
|
892f873320 | ||
|
|
a5c1359fac | ||
|
|
bbacaa8bb9 | ||
|
|
eef85b424d | ||
|
|
3a4ac066a2 | ||
|
|
7fd3fb8458 | ||
|
|
dae0d205e5 |
@@ -0,0 +1,4 @@
|
||||
.*
|
||||
Dockerfile
|
||||
renovate.json
|
||||
README.md
|
||||
@@ -0,0 +1,16 @@
|
||||
# see https://editorconfig.org/
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Install Docker
|
||||
uses: https://github.com/papodaca/install-docker-action@v1
|
||||
- name: Log into container registry
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: gitea.cheezers.org
|
||||
username: imcompiling
|
||||
|
||||
@@ -17,11 +17,11 @@ jobs:
|
||||
- name: Install Docker
|
||||
uses: https://github.com/papodaca/install-docker-action@v1
|
||||
- name: Log into container registry
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: gitea.cheezers.org
|
||||
username: imcompiling
|
||||
password: $${{ secrets.PASSWORD }}
|
||||
password: ${{ secrets.PASSWORD }}
|
||||
- name: Build Docker Image
|
||||
run: docker build -t gitea.cheezers.org/imcompiling/off-cooldown-bot:latest .
|
||||
- name: Push Docker Image
|
||||
|
||||
@@ -23,8 +23,6 @@ jobs:
|
||||
- name: Set env
|
||||
run: echo "GIT_TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
- name: Build Docker Images
|
||||
run: docker build -t gitea.cheezers.org/imcompiling/off-cooldown-bot:latest -t gitea.cheezers.org/imcompiling/off-cooldown-bot:${{env.GIT_TAG}} .
|
||||
- name: Push Latest Image
|
||||
run: docker push gitea.cheezers.org/imcompiling/off-cooldown-bot:latest
|
||||
run: docker build -t gitea.cheezers.org/imcompiling/off-cooldown-bot:${{env.GIT_TAG}} .
|
||||
- name: Push Tagged Image
|
||||
run: docker push gitea.cheezers.org/imcompiling/off-cooldown-bot:${{env.GIT_TAG}}
|
||||
@@ -128,3 +128,5 @@ dmypy.json
|
||||
# logging
|
||||
nextcord.log
|
||||
outputs/
|
||||
|
||||
/database
|
||||
|
||||
Vendored
+2
-1
@@ -3,6 +3,7 @@
|
||||
"ms-python.vscode-pylance",
|
||||
"ms-python.python",
|
||||
"ms-python.debugpy",
|
||||
"charliermarsh.ruff"
|
||||
"charliermarsh.ruff",
|
||||
"editorconfig.editorconfig"
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+2
-2
@@ -8,8 +8,8 @@
|
||||
"name": "Python: Module",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "main",
|
||||
"justMyCode": true
|
||||
"module": "off_cooldown_bot",
|
||||
"justMyCode": true,
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+8
-1
@@ -3,5 +3,12 @@
|
||||
"reportOptionalMemberAccess": "warning",
|
||||
"reportArgumentType": "information",
|
||||
"reportAttributeAccessIssue": "warning"
|
||||
}
|
||||
},
|
||||
"cSpell.words": [
|
||||
"blurple",
|
||||
"cakeday",
|
||||
"cakedays",
|
||||
"cooldown",
|
||||
"nextcord"
|
||||
]
|
||||
}
|
||||
+4
-8
@@ -3,14 +3,10 @@ FROM python:3.12.3-slim
|
||||
# install uv
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.6.5 /uv /uvx /bin/
|
||||
|
||||
COPY . /home
|
||||
|
||||
# Sync the project into a new environment, using the frozen lockfile
|
||||
WORKDIR /home
|
||||
|
||||
COPY config.py config.py
|
||||
COPY main.py main.py
|
||||
COPY pyproject.toml pyproject.toml
|
||||
|
||||
COPY cogs cogs/
|
||||
|
||||
RUN uv sync --frozen --no-dev
|
||||
|
||||
CMD ["uv", "run", "./main.py"]
|
||||
CMD ["uv", "run", "--frozen", "--no-dev", "-m", "off_cooldown_bot"]
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
"""Example command file."""
|
||||
|
||||
import nextcord
|
||||
from nextcord.ext import commands
|
||||
|
||||
|
||||
class HiCog(commands.Cog, name="Test commands."):
|
||||
"""Class holds commands for testing."""
|
||||
|
||||
def __init__(self, bot: nextcord.Bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
@nextcord.slash_command()
|
||||
async def hi(self, interaction: nextcord.Interaction) -> None:
|
||||
"""
|
||||
Says hi.
|
||||
|
||||
Args:
|
||||
interaction (nextcord.Interaction): Interaction to reply to.
|
||||
|
||||
"""
|
||||
await interaction.response.send_message("Hi!", ephemeral=True)
|
||||
@@ -1,22 +0,0 @@
|
||||
"""
|
||||
File contains classes defining the Hydra configuraton structure.
|
||||
"""
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Params:
|
||||
"""
|
||||
Class defines the various configuration parameters.
|
||||
"""
|
||||
|
||||
discord_token: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class BotConfig:
|
||||
"""
|
||||
Class defines all configs required for Dakiny Bot.
|
||||
"""
|
||||
|
||||
params: Params
|
||||
@@ -0,0 +1 @@
|
||||
"""Empty init file for off_cooldown_bot module."""
|
||||
@@ -0,0 +1,14 @@
|
||||
"""Starts the app for off_cooldown_bot."""
|
||||
|
||||
import logging
|
||||
|
||||
from .app import run
|
||||
|
||||
nextcord_logger = logging.getLogger("nextcord")
|
||||
nextcord_logger.setLevel(logging.DEBUG)
|
||||
nextcord_handler = logging.FileHandler(filename="nextcord.log", encoding="utf-8", mode="w")
|
||||
nextcord_handler.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s"))
|
||||
nextcord_logger.addHandler(nextcord_handler)
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
@@ -1,21 +1,21 @@
|
||||
"""Main file for bot."""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from __future__ import annotations
|
||||
|
||||
import pathlib
|
||||
|
||||
import nextcord
|
||||
from nextcord.ext import commands
|
||||
|
||||
from settings import settings
|
||||
from off_cooldown_bot.open_raid import OpenRaid
|
||||
|
||||
nextcord_logger = logging.getLogger("nextcord")
|
||||
nextcord_logger.setLevel(logging.DEBUG)
|
||||
nextcord_handler = logging.FileHandler(filename="nextcord.log", encoding="utf-8", mode="w")
|
||||
nextcord_handler.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s"))
|
||||
nextcord_logger.addHandler(nextcord_handler)
|
||||
from .logger import ocd_logger
|
||||
from .settings import settings
|
||||
|
||||
birthday_list: list[int] = []
|
||||
|
||||
|
||||
def main() -> None:
|
||||
def run() -> None:
|
||||
"""Set config, intents, then connects the bot to discord."""
|
||||
# Allows privileged intents for monitoring members joining, roles editing, and role assignments
|
||||
# These need to be enabled in the developer portal as well
|
||||
@@ -25,28 +25,25 @@ def main() -> None:
|
||||
intents.message_content = True
|
||||
|
||||
# To enable the guilds privileged intent:
|
||||
# intents.guilds = True
|
||||
# intents.guilds = True # noqa: ERA001
|
||||
|
||||
# To enable the members privileged intent:
|
||||
# intents.members = True
|
||||
intents.members = True
|
||||
|
||||
bot = commands.Bot(intents=intents)
|
||||
|
||||
# Get the modules of all cogs whose directory structure is ./cogs/<module_name>
|
||||
for folder in os.listdir("cogs"):
|
||||
bot.load_extension(f"cogs.{folder}")
|
||||
for folder in pathlib.Path("./off_cooldown_bot/cogs").iterdir():
|
||||
bot.load_extension(f"off_cooldown_bot.cogs.{folder.name}")
|
||||
|
||||
@bot.listen()
|
||||
async def on_ready() -> None:
|
||||
"""When Discord is connected."""
|
||||
if bot.user is None:
|
||||
print("Discord connection failed.")
|
||||
ocd_logger.critical("Discord connection failed.")
|
||||
return
|
||||
print(f"{bot.user.name} has connected to Discord!")
|
||||
bot.add_view(OpenRaid())
|
||||
ocd_logger.info("%s has connected to Discord!", bot.user.name)
|
||||
|
||||
# Run Discord bot
|
||||
bot.run(settings.params.discord_token)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -2,11 +2,14 @@
|
||||
|
||||
from nextcord.ext import commands
|
||||
|
||||
from cogs.commands.hi import HiCog
|
||||
from off_cooldown_bot.cogs.commands.test import TestCog
|
||||
|
||||
from .birthday import BirthdayCog
|
||||
|
||||
|
||||
# This function will be called when this extension is loaded.
|
||||
# It is necessary to add these functions to the bot.
|
||||
def setup(bot: commands.Bot) -> None:
|
||||
"""Add all command classes to bot."""
|
||||
bot.add_cog(HiCog(bot))
|
||||
bot.add_cog(BirthdayCog())
|
||||
bot.add_cog(TestCog(bot))
|
||||
@@ -0,0 +1,95 @@
|
||||
"""Contains commands for birthday feature."""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import nextcord
|
||||
from nextcord.ext import commands
|
||||
from pytz import UTC
|
||||
from sqlmodel import Session, col, select
|
||||
|
||||
from off_cooldown_bot.app import birthday_list
|
||||
from off_cooldown_bot.database.db import engine
|
||||
from off_cooldown_bot.database.models import UserBirthday
|
||||
from off_cooldown_bot.utils import GUILDS
|
||||
|
||||
|
||||
class BirthdayCog(commands.Cog, name="Birthday Commands"):
|
||||
"""Class holds birthday commands."""
|
||||
|
||||
@nextcord.slash_command(guild_ids=GUILDS)
|
||||
async def birthday(self, interaction: nextcord.Interaction) -> None:
|
||||
"""Parent slash command for birthday. Never called."""
|
||||
|
||||
@birthday.subcommand(description="Set your birthday month and day for birthday fun!")
|
||||
async def set(
|
||||
self,
|
||||
interaction: nextcord.Interaction,
|
||||
month: int = nextcord.SlashOption(required=True, min_value=1, max_value=12, description="Your birth month."),
|
||||
day: int = nextcord.SlashOption(required=True, min_value=1, max_value=31, description="Your birth day."),
|
||||
) -> None:
|
||||
"""
|
||||
Set a user's birthday in the database. If birthday is today, adds to the birthday_list.
|
||||
|
||||
Args:
|
||||
interaction (nextcord.Interaction): Interaction that invoked the command.
|
||||
month (int, optional): The month a user was born. Is SlashOption.
|
||||
day (int, optional): The day a user was born. Is SlashOption.
|
||||
|
||||
"""
|
||||
# Ensure that a person used the command.
|
||||
if interaction.user is None:
|
||||
return
|
||||
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
|
||||
# check if valid day
|
||||
today = datetime.now(tz=UTC)
|
||||
try:
|
||||
birthday = datetime(today.year, month, day, tzinfo=UTC)
|
||||
except ValueError:
|
||||
await interaction.followup.send(f"Invalid date: {month}/{day}.", ephemeral=True)
|
||||
return
|
||||
|
||||
# If the birthday is today, add to birthday_list
|
||||
if today.date() == birthday.date():
|
||||
birthday_list.append(interaction.user.id)
|
||||
|
||||
new_birthday = UserBirthday(user_id=interaction.user.id, month=month, day=day)
|
||||
with Session(engine) as session:
|
||||
stmt = select(UserBirthday).where(UserBirthday.user_id == interaction.user.id)
|
||||
if (new_birthday := session.scalar(stmt)) is None:
|
||||
new_birthday = UserBirthday(user_id=interaction.user.id, month=month, day=day)
|
||||
session.add(new_birthday)
|
||||
else:
|
||||
new_birthday.month = month
|
||||
new_birthday.day = day
|
||||
session.commit()
|
||||
|
||||
await interaction.followup.send(f"Birthday set to {month}/{day}.", ephemeral=True)
|
||||
|
||||
@birthday.subcommand(description="Remove yourself from the birthday list.")
|
||||
async def clear(self, interaction: nextcord.Interaction) -> None:
|
||||
"""
|
||||
Remove the user from the birthday database.
|
||||
|
||||
Args:
|
||||
interaction (nextcord.Interaction): Interaction that invoked the command.
|
||||
|
||||
"""
|
||||
# Ensure that a person used the command.
|
||||
if interaction.user is None:
|
||||
return
|
||||
|
||||
if interaction.user.id in birthday_list:
|
||||
birthday_list.remove(interaction.user.id)
|
||||
|
||||
with Session(engine) as session:
|
||||
stmt = select(UserBirthday).where(col(UserBirthday.user_id) == interaction.user.id)
|
||||
if (user := session.scalar(stmt)) is None:
|
||||
await interaction.response.send_message("No birthday to clear.", ephemeral=True)
|
||||
return
|
||||
|
||||
session.delete(user)
|
||||
session.commit()
|
||||
|
||||
await interaction.response.send_message("Birthday cleared.", ephemeral=True)
|
||||
@@ -0,0 +1,43 @@
|
||||
"""Commands for testing."""
|
||||
|
||||
import nextcord
|
||||
from nextcord.ext import application_checks, commands
|
||||
|
||||
from off_cooldown_bot.open_raid import OpenRaid, build_raid_embed
|
||||
from off_cooldown_bot.utils import GUILDS
|
||||
|
||||
ME = 206602713511886849
|
||||
|
||||
|
||||
def is_me(): # noqa: ANN201
|
||||
"""Predicate that makes sure only I can run the test commands."""
|
||||
|
||||
def predicate(interaction: nextcord.Interaction) -> bool:
|
||||
if interaction.user is None:
|
||||
return False
|
||||
return interaction.user.id == ME
|
||||
|
||||
return application_checks.check(predicate)
|
||||
|
||||
|
||||
class TestCog(commands.Cog, name="Test commands."):
|
||||
"""Class holds commands for testing."""
|
||||
|
||||
def __init__(self, bot: commands.Bot) -> None:
|
||||
self.bot: commands.Bot = bot
|
||||
|
||||
@nextcord.slash_command(guild_ids=GUILDS)
|
||||
async def test(self, interaction: nextcord.Interaction) -> None:
|
||||
"""Parent slash command for help. Never called."""
|
||||
|
||||
@test.subcommand()
|
||||
@is_me()
|
||||
async def open_raid(self, interaction: nextcord.Interaction) -> None:
|
||||
"""
|
||||
Force starts election.
|
||||
|
||||
Args:
|
||||
interaction (nextcord.Interaction): Interaction with the message.
|
||||
|
||||
"""
|
||||
await interaction.response.send_message("Starting an open raid.", embed=build_raid_embed(), view=OpenRaid())
|
||||
@@ -0,0 +1,12 @@
|
||||
"""Initializing code for all loops."""
|
||||
|
||||
from nextcord.ext import commands
|
||||
|
||||
from .bot_presence import BotPresence
|
||||
from .daily_loop import DailyLoop
|
||||
|
||||
|
||||
def setup(bot: commands.Bot) -> None:
|
||||
"""Add functions to bot."""
|
||||
bot.add_cog(DailyLoop(bot))
|
||||
bot.add_cog(BotPresence(bot))
|
||||
@@ -0,0 +1,44 @@
|
||||
"""Functions for controlling the bot's presence. Also contains the loop."""
|
||||
|
||||
import random
|
||||
|
||||
import nextcord
|
||||
from nextcord.ext import commands, tasks
|
||||
|
||||
|
||||
class BotPresence(commands.Cog, name="Bot Presence"):
|
||||
"""Class for bot presence functions."""
|
||||
|
||||
def __init__(self, bot: commands.Bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_ready(self) -> None:
|
||||
"""Start the loop when bot is ready."""
|
||||
await self.bot.wait_until_ready()
|
||||
self.periodic_change_presence.start()
|
||||
|
||||
@tasks.loop(minutes=30)
|
||||
async def periodic_change_presence(self) -> None:
|
||||
"""Loops every 30 minutes and changes presence."""
|
||||
await self.change_presence()
|
||||
|
||||
async def change_presence(self) -> None:
|
||||
"""Randomly chooses a presence and updates bot's presence."""
|
||||
dungeons = [
|
||||
"Cinderbrew Meadery",
|
||||
"Darkflame Cleft",
|
||||
"The Rookery",
|
||||
"Priory of the Sacred Flame",
|
||||
"Operation: Floodgate",
|
||||
"The MOTHERLODE!!",
|
||||
"Theater of Pain",
|
||||
"Operation: Mechagon - Workshop",
|
||||
]
|
||||
|
||||
dungeon = random.choice(dungeons) # noqa: S311
|
||||
plus = random.randint(2, 12) # noqa: S311
|
||||
|
||||
presence = f"Running +{plus} {dungeon}"
|
||||
|
||||
await self.bot.change_presence(activity=nextcord.CustomActivity(presence))
|
||||
@@ -0,0 +1,103 @@
|
||||
"""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 off_cooldown_bot.app import birthday_list
|
||||
from off_cooldown_bot.database.db import engine
|
||||
from off_cooldown_bot.database.models import UserBirthday
|
||||
from off_cooldown_bot.logger import ocd_logger
|
||||
from off_cooldown_bot.open_raid import OpenRaid, build_raid_embed
|
||||
from off_cooldown_bot.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=UTC))
|
||||
async def daily_loop(self) -> None:
|
||||
"""Run daily repeated functions."""
|
||||
self.update_birthdays()
|
||||
|
||||
await self.cakedays()
|
||||
|
||||
@tasks.loop(time=time(hour=20, tzinfo=timezone("CST")))
|
||||
async def open_raid(self) -> None:
|
||||
"""
|
||||
Define the open raid loop.
|
||||
|
||||
Checks daily if it is Thursday and then posts the open raid sign up.
|
||||
"""
|
||||
today = datetime.now(timezone("CST"))
|
||||
if today.day + 2 == 5:
|
||||
# open raid is on Saturday, post 2 days before
|
||||
if (guild := self.bot.get_guild(settings.params.guild_id)) is None:
|
||||
ocd_logger.error("Guild id not correct.")
|
||||
return
|
||||
if (channel := guild.get_channel(settings.channel_ids.raid)) is None or not isinstance(
|
||||
channel, nextcord.TextChannel
|
||||
):
|
||||
ocd_logger.error("Raid channel is not defined or is not a text channel.")
|
||||
return
|
||||
await channel.send(embed=build_raid_embed(), view=OpenRaid())
|
||||
|
||||
@daily_loop.before_loop
|
||||
@open_raid.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,41 @@
|
||||
"""Defines cog for on message listener."""
|
||||
|
||||
import nextcord
|
||||
from nextcord.ext import commands
|
||||
|
||||
from off_cooldown_bot.app import birthday_list
|
||||
|
||||
|
||||
class SubmissionListener(commands.Cog, name="Message Listener"):
|
||||
"""Sets up message listener."""
|
||||
|
||||
def __init__(self, bot: commands.Bot) -> None:
|
||||
self.bot = bot
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_message(self, message: nextcord.Message) -> None:
|
||||
"""Listen for message creation. Adds score and attachments to submission."""
|
||||
# don't want to process any messages from bot.
|
||||
if message.author.id == self.bot.user.id: # type: ignore reportOptionalMemberAccess
|
||||
return
|
||||
|
||||
await birthday_action(message)
|
||||
|
||||
|
||||
# This function will be called when this extension is loaded.
|
||||
# It is necessary to add these functions to the bot.
|
||||
def setup(bot: commands.Bot) -> None:
|
||||
"""Add function to bot."""
|
||||
bot.add_cog(SubmissionListener(bot))
|
||||
|
||||
|
||||
async def birthday_action(message: nextcord.Message) -> None:
|
||||
"""
|
||||
Check a message's author and mentions to see if they are in the birthday list.
|
||||
|
||||
Args:
|
||||
message (nextcord.Message): The message to check for a birthday person.
|
||||
|
||||
"""
|
||||
if message.author.id in birthday_list or bool(set(message.raw_mentions) & set(birthday_list)):
|
||||
await message.add_reaction("🎂")
|
||||
@@ -0,0 +1 @@
|
||||
"""Package for database interaction."""
|
||||
@@ -0,0 +1,10 @@
|
||||
"""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/database.db"
|
||||
engine = create_engine(url)
|
||||
|
||||
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 Off Cooldown 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)
|
||||
@@ -0,0 +1,151 @@
|
||||
"""Contains functions for open raid sign-ups."""
|
||||
|
||||
import datetime
|
||||
|
||||
import nextcord
|
||||
|
||||
from off_cooldown_bot.logger import ocd_logger
|
||||
|
||||
MAX_TANKS = 2
|
||||
MAX_HEALERS = 2
|
||||
MAX_DPS = 25
|
||||
|
||||
|
||||
class OpenRaid(nextcord.ui.View):
|
||||
"""Class defines view for buttons on an open raid schedule message."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(timeout=None, auto_defer=True)
|
||||
|
||||
@nextcord.ui.button(label="Tank", style=nextcord.ButtonStyle.blurple, emoji="🛡️", custom_id="open_raid_tank")
|
||||
async def tank(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002
|
||||
"""
|
||||
Signs a user up as a tank.
|
||||
|
||||
Removes them if they are already signed up.
|
||||
|
||||
Args:
|
||||
button (nextcord.ui.Button): Un-used.
|
||||
interaction (nextcord.Interaction): Interaction with command.
|
||||
|
||||
"""
|
||||
if interaction.message is None or interaction.user is None:
|
||||
ocd_logger.warning("Command was called without a message or user.")
|
||||
return
|
||||
|
||||
embed = interaction.message.embeds[0]
|
||||
user_str = f"<@{interaction.user.id}>"
|
||||
|
||||
for index, field in enumerate(embed.fields):
|
||||
if field.name == "Tanks":
|
||||
values = [] if field.value is None else field.value.split("\n")
|
||||
if user_str in values:
|
||||
values.remove(user_str)
|
||||
else:
|
||||
if len(values) >= MAX_TANKS:
|
||||
await interaction.response.send_message(
|
||||
"Enough tanks have signed up, please choose another role.", ephemeral=True
|
||||
)
|
||||
return
|
||||
values.append(user_str)
|
||||
embed.set_field_at(index, name=field.name, value="\n".join(values), inline=False)
|
||||
break
|
||||
|
||||
await interaction.edit(embed=embed)
|
||||
|
||||
@nextcord.ui.button(label="Healer", style=nextcord.ButtonStyle.green, emoji="❤️", custom_id="open_raid_healer")
|
||||
async def healer(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002
|
||||
"""
|
||||
Signs a user up as a healer.
|
||||
|
||||
Removes them if they are already signed up.
|
||||
|
||||
Args:
|
||||
button (nextcord.ui.Button): Un-used.
|
||||
interaction (nextcord.Interaction): Interaction with command.
|
||||
|
||||
"""
|
||||
if interaction.message is None or interaction.user is None:
|
||||
ocd_logger.warning("Command was called without a message or user.")
|
||||
return
|
||||
|
||||
embed = interaction.message.embeds[0]
|
||||
user_str = f"<@{interaction.user.id}>"
|
||||
|
||||
for index, field in enumerate(embed.fields):
|
||||
if field.name == "Healers":
|
||||
values = [] if field.value is None else field.value.split("\n")
|
||||
if user_str in values:
|
||||
values.remove(user_str)
|
||||
else:
|
||||
if len(values) >= MAX_HEALERS:
|
||||
await interaction.response.send_message(
|
||||
"Enough healers have signed up, please choose another role.", ephemeral=True
|
||||
)
|
||||
return
|
||||
values.append(user_str)
|
||||
embed.set_field_at(index, name=field.name, value="\n".join(values), inline=False)
|
||||
break
|
||||
|
||||
await interaction.edit(embed=embed)
|
||||
|
||||
@nextcord.ui.button(label="DPS", style=nextcord.ButtonStyle.red, emoji="⚔️", custom_id="open_raid_dps")
|
||||
async def dps(self, button: nextcord.ui.Button, interaction: nextcord.Interaction) -> None: # noqa: ARG002
|
||||
"""
|
||||
Signs a user up as dps.
|
||||
|
||||
Removes them if they are already signed up.
|
||||
|
||||
Args:
|
||||
button (nextcord.ui.Button): Un-used.
|
||||
interaction (nextcord.Interaction): Interaction with command.
|
||||
|
||||
"""
|
||||
if interaction.message is None or interaction.user is None:
|
||||
ocd_logger.warning("Command was called without a message or user.")
|
||||
return
|
||||
|
||||
embed = interaction.message.embeds[0]
|
||||
user_str = f"<@{interaction.user.id}>"
|
||||
|
||||
for index, field in enumerate(embed.fields):
|
||||
if field.name == "DPS":
|
||||
values = [] if field.value is None else field.value.split("\n")
|
||||
if user_str in values:
|
||||
values.remove(user_str)
|
||||
else:
|
||||
if len(values) >= MAX_DPS:
|
||||
await interaction.response.send_message(
|
||||
"Enough dps have signed up, please choose another role.", ephemeral=True
|
||||
)
|
||||
return
|
||||
values.append(user_str)
|
||||
embed.set_field_at(index, name=field.name, value="\n".join(values), inline=False)
|
||||
break
|
||||
|
||||
await interaction.edit(embed=embed)
|
||||
|
||||
|
||||
def build_raid_embed() -> nextcord.Embed:
|
||||
"""
|
||||
Build the open raid embed.
|
||||
|
||||
Returns:
|
||||
nextcord.Embed: Embed that shows the raid's sign-ups.
|
||||
|
||||
"""
|
||||
today = datetime.datetime.now(tz=datetime.UTC).replace(hour=20)
|
||||
saturday_idx = 5
|
||||
days_delta = saturday_idx - today.weekday()
|
||||
if days_delta < 0:
|
||||
days_delta += 7
|
||||
raid_time = today + datetime.timedelta(days_delta)
|
||||
|
||||
embed = nextcord.Embed(
|
||||
title="Off Cooldown Open Raid", description=f"Raid time: t:{int(raid_time.timestamp())}:F", timestamp=raid_time
|
||||
)
|
||||
embed.add_field(name="Tanks", value="", inline=False)
|
||||
embed.add_field(name="Healers", value="", inline=False)
|
||||
embed.add_field(name="DPS", value="", inline=False)
|
||||
|
||||
return embed
|
||||
@@ -7,17 +7,27 @@ from dataclass_wizard import YAMLWizard
|
||||
CONFIG_LOCATION = "conf/config.yaml"
|
||||
|
||||
|
||||
@dataclass
|
||||
class ChannelIds:
|
||||
"""Class holds all channel ids needed by bot."""
|
||||
|
||||
general: int
|
||||
raid: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class Params:
|
||||
"""Class defines start parameters that shouldn't be changed."""
|
||||
|
||||
discord_token: str
|
||||
guild_id: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class Settings(YAMLWizard):
|
||||
"""Class defines all configs required for off-cooldown-bot."""
|
||||
|
||||
channel_ids: ChannelIds
|
||||
params: Params
|
||||
|
||||
def save(self) -> None:
|
||||
@@ -26,3 +36,7 @@ class Settings(YAMLWizard):
|
||||
|
||||
|
||||
settings = Settings.from_yaml_file(CONFIG_LOCATION)
|
||||
|
||||
# I'm not expecting a list to be returned.
|
||||
if isinstance(settings, list):
|
||||
settings = settings[0]
|
||||
@@ -0,0 +1,3 @@
|
||||
"""File contains functions and variables used widely."""
|
||||
|
||||
GUILDS = [1000247361639030804, 822199658306076672]
|
||||
+1
-1
@@ -6,9 +6,9 @@ authors = [{ name = "imcompiling", email = "[email protected]" }]
|
||||
readme = "README.md"
|
||||
dependencies = [
|
||||
"nextcord>=2.6.0",
|
||||
"python-dotenv>=1.0.1",
|
||||
"sqlmodel>=0.0.22",
|
||||
"dataclass-wizard[yaml]>=0.22.3,<1.0.0",
|
||||
"pytz>=2025.1",
|
||||
]
|
||||
requires-python = ">=3.12"
|
||||
|
||||
|
||||
@@ -306,7 +306,7 @@ source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "dataclass-wizard", extra = ["yaml"] },
|
||||
{ name = "nextcord" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "pytz" },
|
||||
{ name = "sqlmodel" },
|
||||
]
|
||||
|
||||
@@ -319,7 +319,7 @@ dev = [
|
||||
requires-dist = [
|
||||
{ name = "dataclass-wizard", extras = ["yaml"], specifier = ">=0.22.3,<1.0.0" },
|
||||
{ name = "nextcord", specifier = ">=2.6.0" },
|
||||
{ name = "python-dotenv", specifier = ">=1.0.1" },
|
||||
{ name = "pytz", specifier = ">=2025.1" },
|
||||
{ name = "sqlmodel", specifier = ">=0.0.22" },
|
||||
]
|
||||
|
||||
@@ -437,12 +437,12 @@ wheels = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.0.1"
|
||||
name = "pytz"
|
||||
version = "2025.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 }
|
||||
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/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/38/ac33370d784287baa1c3d538978b5e2ea064d4c1b93ffbd12826c190dd10/pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57", size = 507930 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user