Merge branch '6-add-clan-rules'
This commit is contained in:
+2
-1
@@ -130,7 +130,7 @@ dmypy.json
|
|||||||
# logging
|
# logging
|
||||||
nextcord.log
|
nextcord.log
|
||||||
|
|
||||||
# data file for bot
|
# bot files
|
||||||
highscores_data.json
|
highscores_data.json
|
||||||
highscores_config.json
|
highscores_config.json
|
||||||
deleted_backups/
|
deleted_backups/
|
||||||
@@ -140,3 +140,4 @@ sql_tester.py
|
|||||||
conf
|
conf
|
||||||
outputs
|
outputs
|
||||||
database
|
database
|
||||||
|
dakiny-messages/
|
||||||
+2
-5
@@ -1,7 +1,7 @@
|
|||||||
FROM python:3.10.6-slim
|
FROM python:3.10.6-slim
|
||||||
|
|
||||||
# install dependencies for mysqlclient
|
# install dependencies
|
||||||
# RUN apt-get update && apt-get -y upgrade && apt-get -y install python3-dev default-libmysqlclient-dev build-essential
|
RUN apt-get update && apt-get -y upgrade && apt-get -y install git
|
||||||
|
|
||||||
RUN pip install --upgrade pip
|
RUN pip install --upgrade pip
|
||||||
|
|
||||||
@@ -20,7 +20,4 @@ COPY database_setup database_setup/
|
|||||||
|
|
||||||
RUN pip install -r requirements.txt
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
# remove build deps that aren't needed to run
|
|
||||||
# RUN apt-get -y remove python3-dev build-essential && apt-get -y autoremove
|
|
||||||
|
|
||||||
CMD ["python3", "-u", "./main.py"]
|
CMD ["python3", "-u", "./main.py"]
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
"""
|
||||||
|
File contains logic for creating a webhook listener, pulling from git, and updating all messages.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import nextcord
|
||||||
|
import uvicorn
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from git import Repo
|
||||||
|
from nextcord.ext import commands
|
||||||
|
|
||||||
|
from config import DakinyConfig
|
||||||
|
from utils import REPO_DIR, update_clan_messages
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
REPO_URL = "https://gitea.cheezers.org/imcompiling/dakiny-messages.git"
|
||||||
|
|
||||||
|
|
||||||
|
class ClanMessages(commands.Cog, name="Clan Messages"):
|
||||||
|
"""
|
||||||
|
Cog to be loaded into the bot. Used to be able to have a fastapi post listener and access to the bot at
|
||||||
|
the same time.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, bot: commands.Bot):
|
||||||
|
self.bot = bot
|
||||||
|
self.config: DakinyConfig = self.bot.config
|
||||||
|
self.repo_setup()
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_ready(self):
|
||||||
|
"""
|
||||||
|
Triggers when the bot is in ready state. Adds the webhook listener to FastAPI and serves.
|
||||||
|
"""
|
||||||
|
app.add_api_route("/messages/", self.update_messages, methods=["POST"])
|
||||||
|
config = uvicorn.Config(app=app, host="0.0.0.0", port=5000, log_level="info")
|
||||||
|
server = uvicorn.Server(config)
|
||||||
|
await server.serve()
|
||||||
|
|
||||||
|
async def update_messages(self):
|
||||||
|
"""
|
||||||
|
Pulls from git and then calls method to update all the discord messages as described by the
|
||||||
|
files in the git repo.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.origin.pull()
|
||||||
|
guild: nextcord.Guild = self.bot.get_guild(self.config.params.guild_id)
|
||||||
|
|
||||||
|
await update_clan_messages(guild)
|
||||||
|
|
||||||
|
return {"message": "Successfully updated all discord messages."}
|
||||||
|
|
||||||
|
def repo_setup(self):
|
||||||
|
"""
|
||||||
|
Checks if the repo exists and creates repo object, clones if it does not.
|
||||||
|
"""
|
||||||
|
# check if repo has been cloned. open repo object
|
||||||
|
if os.path.isdir(REPO_DIR):
|
||||||
|
self.repo = Repo(REPO_DIR)
|
||||||
|
else:
|
||||||
|
self.repo = Repo.clone_from(REPO_URL, REPO_DIR)
|
||||||
|
|
||||||
|
self.origin = self.repo.remote("origin")
|
||||||
|
|
||||||
|
if self.config.params.git_branch == "test":
|
||||||
|
self.repo.create_head("test", self.origin.refs.test).set_tracking_branch(
|
||||||
|
self.origin.refs.test
|
||||||
|
).checkout()
|
||||||
|
else:
|
||||||
|
self.repo.create_head("main", self.origin.refs.main).set_tracking_branch(
|
||||||
|
self.origin.refs.main
|
||||||
|
).checkout()
|
||||||
|
|
||||||
|
self.origin.pull()
|
||||||
|
|
||||||
|
|
||||||
|
# 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):
|
||||||
|
"""Adds function to bot"""
|
||||||
|
bot.add_cog(ClanMessages(bot))
|
||||||
@@ -5,6 +5,7 @@ Cog loader. Adds all command cogs to the bot.
|
|||||||
from nextcord.ext import commands
|
from nextcord.ext import commands
|
||||||
|
|
||||||
from cogs.commands.record import RecordCog
|
from cogs.commands.record import RecordCog
|
||||||
|
from cogs.commands.refresh import RefreshCog
|
||||||
from cogs.commands.remove import RemoveCog
|
from cogs.commands.remove import RemoveCog
|
||||||
from cogs.commands.request import RequestCog
|
from cogs.commands.request import RequestCog
|
||||||
from cogs.commands.set import setCog
|
from cogs.commands.set import setCog
|
||||||
@@ -20,6 +21,7 @@ def setup(bot: commands.Bot):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
bot.add_cog(RecordCog(bot))
|
bot.add_cog(RecordCog(bot))
|
||||||
|
bot.add_cog(RefreshCog())
|
||||||
bot.add_cog(RemoveCog())
|
bot.add_cog(RemoveCog())
|
||||||
bot.add_cog(RequestCog(bot))
|
bot.add_cog(RequestCog(bot))
|
||||||
bot.add_cog(TestCog(bot))
|
bot.add_cog(TestCog(bot))
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
"""
|
||||||
|
Contained commands and views for refresh commands.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import nextcord
|
||||||
|
from nextcord.ext import commands
|
||||||
|
|
||||||
|
from utils import update_clan_messages
|
||||||
|
|
||||||
|
|
||||||
|
class RefreshCog(commands.Cog, name="Refresh Commands"):
|
||||||
|
"""
|
||||||
|
Class holds refresh commands.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@nextcord.slash_command(
|
||||||
|
default_member_permissions=nextcord.Permissions(administrator=True)
|
||||||
|
)
|
||||||
|
async def refresh(self, interaction: nextcord.Interaction):
|
||||||
|
"""
|
||||||
|
Parent slash command for refresh. Never called.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@refresh.subcommand(description="Force refresh all of the bot managed messages.")
|
||||||
|
async def messages(self, interaction: nextcord.Interaction):
|
||||||
|
"""
|
||||||
|
Calls update_clan_messages() to update all messages.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
interaction (nextcord.Interaction): Interaction with the command.
|
||||||
|
"""
|
||||||
|
await interaction.response.defer()
|
||||||
|
await update_clan_messages(interaction.guild)
|
||||||
|
await interaction.followup.send("Messages have been refreshed.", ephemeral=True)
|
||||||
@@ -23,6 +23,7 @@ class Params:
|
|||||||
event_participate_points: int
|
event_participate_points: int
|
||||||
event_post_channel_id: int
|
event_post_channel_id: int
|
||||||
general_channel_id: int
|
general_channel_id: int
|
||||||
|
git_branch: str
|
||||||
guild_id: int
|
guild_id: int
|
||||||
high_council_role_id: int
|
high_council_role_id: int
|
||||||
voting_parent_id: int
|
voting_parent_id: int
|
||||||
|
|||||||
@@ -47,3 +47,8 @@ CREATE TABLE ParticipationPoints (
|
|||||||
Points int unsigned DEFAULT 0,
|
Points int unsigned DEFAULT 0,
|
||||||
PRIMARY KEY (Cycle, UserId)
|
PRIMARY KEY (Cycle, UserId)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE Messages (
|
||||||
|
Path varchar(100) PRIMARY KEY,
|
||||||
|
Id bigint unsigned not null
|
||||||
|
);
|
||||||
@@ -12,9 +12,10 @@ Base = automap_base()
|
|||||||
Base.prepare(autoload_with=engine)
|
Base.prepare(autoload_with=engine)
|
||||||
|
|
||||||
# define the classes
|
# define the classes
|
||||||
ElectionInformation = Base.classes.ElectionInformation
|
|
||||||
CouncilMembers = Base.classes.CouncilMembers
|
CouncilMembers = Base.classes.CouncilMembers
|
||||||
Votes = Base.classes.Votes
|
ElectionInformation = Base.classes.ElectionInformation
|
||||||
UserBirthday = Base.classes.UserBirthday
|
Messages = Base.classes.Messages
|
||||||
Presences = Base.classes.Presences
|
|
||||||
ParticipationPoints = Base.classes.ParticipationPoints
|
ParticipationPoints = Base.classes.ParticipationPoints
|
||||||
|
Presences = Base.classes.Presences
|
||||||
|
UserBirthday = Base.classes.UserBirthday
|
||||||
|
Votes = Base.classes.Votes
|
||||||
|
|||||||
@@ -4,3 +4,5 @@ pytz==2022.7.1
|
|||||||
hydra-core==1.3.2
|
hydra-core==1.3.2
|
||||||
sqlalchemy<2.0
|
sqlalchemy<2.0
|
||||||
pandas==1.5.2
|
pandas==1.5.2
|
||||||
|
fastapi==0.111.0
|
||||||
|
GitPython==3.1.43
|
||||||
@@ -2,21 +2,25 @@
|
|||||||
File contains util functions.
|
File contains util functions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import nextcord
|
import nextcord
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select, update
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from config import DakinyConfig
|
from config import DakinyConfig
|
||||||
from database_setup.sql_data_classes import (
|
from database_setup.sql_data_classes import (
|
||||||
CouncilMembers,
|
CouncilMembers,
|
||||||
|
Messages,
|
||||||
ParticipationPoints,
|
ParticipationPoints,
|
||||||
Votes,
|
Votes,
|
||||||
engine,
|
engine,
|
||||||
)
|
)
|
||||||
|
|
||||||
NOMINEE_SPLITTER = "========================================\n\n"
|
NOMINEE_SPLITTER = "========================================\n\n"
|
||||||
|
REPO_DIR = "./dakiny-messages"
|
||||||
|
MESSAGES_DIR = f"{REPO_DIR}/messages"
|
||||||
|
|
||||||
|
|
||||||
def get_datetime(day: str) -> datetime:
|
def get_datetime(day: str) -> datetime:
|
||||||
@@ -386,3 +390,121 @@ def record_event_host(config: DakinyConfig, user: int):
|
|||||||
points.Points += host_point_value
|
points.Points += host_point_value
|
||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
async def update_clan_messages(guild: nextcord.Guild):
|
||||||
|
"""
|
||||||
|
Updates all clan messages to contents in MESSAGES_DIR.
|
||||||
|
Does not to any git work. Is expected to be called after git is pulled.
|
||||||
|
|
||||||
|
for ever directory, sort the files by number then post
|
||||||
|
posting involves checking if there is a message for that particular file then editing it or creating a
|
||||||
|
new one. the message could be deleted, which would cause a new message to be sent and database updated.
|
||||||
|
|
||||||
|
Channel fetching/management is handled by another function.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
guild (nextcord.Guild): Guild object that all channels and messages belong to.
|
||||||
|
"""
|
||||||
|
|
||||||
|
dirs = [
|
||||||
|
dir_name
|
||||||
|
for dir_name in os.listdir(MESSAGES_DIR)
|
||||||
|
if os.path.isdir(f"{MESSAGES_DIR}/{dir_name}")
|
||||||
|
]
|
||||||
|
|
||||||
|
with Session(engine) as session:
|
||||||
|
for dir_name in dirs:
|
||||||
|
dir_path = f"{MESSAGES_DIR}/{dir_name}"
|
||||||
|
channel = await get_clan_message_channel(session, dir_name, guild)
|
||||||
|
sorted_files = sorted(
|
||||||
|
os.listdir(dir_path),
|
||||||
|
key=lambda filename: int(filename.split(".")[0]),
|
||||||
|
)
|
||||||
|
|
||||||
|
for file_name in sorted_files:
|
||||||
|
message_name = f"{dir_name}/{file_name}"
|
||||||
|
file_path = f"{dir_path}/{file_name}"
|
||||||
|
with open(file_path, "r", encoding="utf8") as reader:
|
||||||
|
stmt = select(Messages.Id).where(Messages.Path == message_name)
|
||||||
|
msg_id = session.scalar(stmt)
|
||||||
|
if msg_id is None:
|
||||||
|
# don't have a message for this file
|
||||||
|
msg = await channel.send(reader.read(2000))
|
||||||
|
new_message = Messages(Path=message_name, Id=msg.id)
|
||||||
|
session.add(new_message)
|
||||||
|
session.commit()
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
msg: nextcord.Message = await channel.fetch_message(msg_id)
|
||||||
|
await msg.edit(reader.read(2000))
|
||||||
|
except nextcord.NotFound:
|
||||||
|
msg = await channel.send(reader.read(2000))
|
||||||
|
stmt = (
|
||||||
|
update(Messages)
|
||||||
|
.where(Messages.Path == message_name)
|
||||||
|
.values(Id=msg.id)
|
||||||
|
)
|
||||||
|
session.execute(stmt)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
async def get_clan_message_channel(
|
||||||
|
session: Session, channel_name: str, guild: nextcord.Guild
|
||||||
|
) -> nextcord.TextChannel:
|
||||||
|
"""
|
||||||
|
Used to obtain a channel from the guild object. Searches database for an id to get the channel. If that is not
|
||||||
|
found, then searches through all channels in the guild to find one with a matching name. If that also doesn't
|
||||||
|
turn up a channel, then creates it. Adds an entry into database for last two paths.
|
||||||
|
Assumes that there is an open session to the database.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
session (Session): Open connection to database.
|
||||||
|
channel_name (str): Name of channel to obtain.
|
||||||
|
guild (nextcord.Guild): Guild object to get channel from.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
nextcord.TextChannel: Channel object with name channel_name.
|
||||||
|
"""
|
||||||
|
stmt = select(Messages.Id).where(Messages.Path == channel_name)
|
||||||
|
channel_id = session.scalar(stmt)
|
||||||
|
if channel_id is None:
|
||||||
|
ret_channel = None
|
||||||
|
# channel is not tracked. see if it exists
|
||||||
|
channels = await guild.fetch_channels()
|
||||||
|
for channel in channels:
|
||||||
|
if (
|
||||||
|
channel.name == channel_name
|
||||||
|
and channel.__class__ is nextcord.TextChannel
|
||||||
|
):
|
||||||
|
ret_channel = channel
|
||||||
|
|
||||||
|
if ret_channel is None:
|
||||||
|
# channel doesn't exist, we must create it
|
||||||
|
ret_channel = await guild.create_text_channel(
|
||||||
|
channel_name,
|
||||||
|
reason="Creating channel as required for by design of Dakiny-Messages repo.",
|
||||||
|
)
|
||||||
|
|
||||||
|
# record channel information
|
||||||
|
new_item = Messages(Path=channel_name, Id=ret_channel.id)
|
||||||
|
session.add(new_item)
|
||||||
|
session.commit()
|
||||||
|
return ret_channel
|
||||||
|
|
||||||
|
try:
|
||||||
|
return await guild.fetch_channel(channel_id)
|
||||||
|
except nextcord.NotFound:
|
||||||
|
# the channel was deleted for some reason
|
||||||
|
ret_channel = await guild.create_text_channel(
|
||||||
|
channel_name,
|
||||||
|
reason="Creating channel as required for by design of Dakiny-Messages repo.",
|
||||||
|
)
|
||||||
|
stmt = (
|
||||||
|
update(Messages)
|
||||||
|
.where(Messages.Path == channel_name)
|
||||||
|
.values(Id=ret_channel.id)
|
||||||
|
)
|
||||||
|
session.execute(stmt)
|
||||||
|
session.commit()
|
||||||
|
return ret_channel
|
||||||
|
|||||||
Reference in New Issue
Block a user