Mass update to latest discord.py

This commit is contained in:
quentin 2025-06-01 12:49:52 -05:00
parent ace8f0524f
commit 0b530493a7
32 changed files with 533 additions and 593 deletions

6
.gitignore vendored
View File

@ -1,4 +1,2 @@
/sqlPass.txt
/token.txt
/scr/Modules/TMC/ChangeServer/text.txt
/rcon.txt
/scr/modules/TMC/ChangeServer/text.txt
/settings.json

3
.idea/Discord Bot.iml generated
View File

@ -5,8 +5,9 @@
<sourceFolder url="file://$MODULE_DIR$/scr" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/venv" />
<excludeFolder url="file://$MODULE_DIR$/venv-linux" />
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.8 (discord_bot)" jdkType="Python SDK" />
<orderEntry type="jdk" jdkName="Python 3.13 (discord_bot)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="R User Library" level="project" />
<orderEntry type="library" name="R Skeletons" level="application" />

5
.idea/misc.xml generated
View File

@ -1,4 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8 (discord_bot)" project-jdk-type="Python SDK" />
<component name="Black">
<option name="sdkName" value="Python 3.12 (discord_bot)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (discord_bot)" project-jdk-type="Python SDK" />
</project>

6
.idea/sqldialects.xml generated
View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="PROJECT" dialect="MySQL" />
</component>
</project>

View File

@ -1,4 +1,5 @@
# Discord_Bot
bot.py in root is the main bot file
bot.py in /OLD is the bot file before the rewrite
/modules is the folder for all the modules

View File

View File

@ -1,121 +0,0 @@
import asyncio
import discord
import pymysql
from discord.ext import commands
password = open("../../../../sqlPass.txt", 'r')
con = pymysql.connect(host='localhost',
user='Bot',
password=f'{password}',
db='serverIDs',
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor)
class AddServer(commands.Cog):
def __init__(self, client):
self.client = client
@commands.Cog.listener()
async def on_message(self, ctx):
if isinstance(ctx.channel, discord.DMChannel) and (ctx.content.lower() == "add server"):
try:
await ctx.channel.send("Send me an invite to the server. Say ``cancel`` anytime to cancel.")
invite = await self.client.wait_for("message", check=lambda message: message.author == ctx.author
and message.channel == ctx.channel,
timeout=20)
invite = await self.client.fetch_invite(invite.content)
if invite == "cancel":
await ctx.channel.send("Process canceled")
return
categorys = self.client.get_channel(656998225076551680)
print(categorys.channels)
category_names = ""
for channel in categorys.channels:
category_names = category_names + '`' + channel.name + '` | '
await ctx.channel.send("Please slect a category from the following. This will change how the server"
"is shown in the Hub server \n" + category_names)
category = await self.client.wait_for("message", check=lambda message: message.author == ctx.author
and message.channel == ctx.channel, timeout=20)
category = category.content.lower
if category == "cancel":
await ctx.channel.send("Process canceled")
return
for channel in categorys.channels:
if category == channel.name:
pass
else:
category = "other"
await ctx.channel.send("Please send a list of tags separated by a "
"comma(,) no spaces")
tags = await self.client.wait_for("message", check=lambda message: message.author == ctx.author
and message.channel == ctx.channel,
timeout=20)
if tags == "cancel":
await ctx.channel.send("Process canceled")
return
await ctx.channel.send("Please send a description of the server")
description = await self.client.wait_for("message", check=lambda message: message.author == ctx.author
and message.channel == ctx.channel,
timeout=20)
if description == "cancel":
await ctx.channel.send("Process canceled")
return
nsfw = 0
for channel in invite.guild.channels:
try:
if channel.is_nsfw():
nsfw = 1
except AttributeError:
pass
try:
with con:
cur = con.cursor()
cur.execute(f"INSERT INTO servers "
f"(ID, name, category, tags, description, nsfw, invite, submitter) "
f"VALUES ({invite.guild.id},'"
f"{invite.guild.name}','"
f"{category}','"
f"{tags.content}','"
f"{description.content}','"
f"{nsfw}','"
f"{invite.code}','"
f"{ctx.author.id}')")
await ctx.channel.send("Server added successfully")
embed = discord.Embed(
colour=discord.Colour.green()
)
embed.set_author(name="Server Added")
embed.add_field(name="ID", value=f"{invite.guild.id}", inline=False)
embed.add_field(name="Name", value=f"{invite.guild.name}", inline=False)
embed.add_field(name="Tags", value=f"{tags.content}", inline=False)
embed.add_field(name="Code", value=f"{invite.code}", inline=False)
embed.add_field(name="Description", value=f"{description.content}", inline=False)
await self.client.get_channel(658442038475358238).send(embed=embed)
except Exception as e:
await ctx.channel.send("An error has occurred and will be fixed"
"shortly.")
print(e)
await self.client.get_channel(656997125627969546).send(e)
await self.client. \
get_channel(656997125627969546). \
send("<@305589587215122432>")
except asyncio.TimeoutError:
await ctx.channel.send("Process timeout")
return
await self.client.process_commands(ctx)
def setup(client):
client.add_cog(AddServer(client))

View File

@ -1,16 +0,0 @@
import pymysql.cursors
password = open("../../../../sqlPass.txt", 'r')
con = pymysql.connect(host='localhost',
user='Bot',
password=f'{password}',
db='serverIDs',
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor)
with con:
cur = con.cursor()
cur.execute("INSERT INTO servers (name, tags, invite, description) VALUES ('Not Very Official Changed Server','furry, rp', 'invitelink.com','N/A')")

View File

@ -1,14 +0,0 @@
import pymysql
password = open("../../../../sqlPass.txt", 'r')
con = pymysql.connect(host='localhost',
user='Bot',
password=f'{password}',
db='serverIDs',
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor)
with con:
cur = con.cursor()
print(cur.execute("SELECT ID FROM servers WHERE ID = 1"))

View File

@ -1 +0,0 @@
SELECT ID FROM servers WHERE ID = 1;

View File

@ -1,59 +0,0 @@
import discord
import pymysql
from discord.ext import commands
password = open("../../../../sqlPass.txt", 'r')
con = pymysql.connect(host='localhost',
user='Bot',
password=f'{password}',
db='serverIDs',
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor)
class RemoveServer(commands.Cog):
def __init__(self, client):
self.client = client
@commands.Cog.listener()
async def on_message(self, ctx):
if isinstance(ctx.channel, discord.DMChannel):
if ctx.content.lower() == "remove server":
await ctx.channel.send("Send the server ID.")
server_id = await self.client.wait_for("message", check=lambda message: message.author == ctx.author)
try:
server_id = int(server_id.content)
except ValueError:
await ctx.channel.send("Invalid server ID")
try:
member = self.client.get_guild(server_id).get_member(ctx.author.id)
if member.guild_permissions.manage_guild:
with con:
cur = con.cursor()
server_exists = cur.execute(f"SELECT ID FROM servers WHERE ID = {server_id}")
if server_exists == 0:
with con:
cur = con.cursor()
cur.execute(f"DELETE FROM servers WHERE ID={server_id}")
else:
await ctx.channel.send("That server could not be found in our databases")
except AttributeError:
await ctx.channel.send("Cannot verify server ownership. "
"Either you are not the guild owner or "
"the bot has not been added to verify ownership.\n"
"Add the bot here : https://discordapp.com/api/oauth2"
"/authorize?client_id=501485906801459200&permissions=1024"
"&scope=bot\n"
"Once the bot is added restart the verification process "
"using ``remove server`` in this DM\n"
"After ownership is verified the bot can be removed.")
await self.client.process_commands(ctx)
# todo
def setup(client):
client.add_cog(RemoveServer(client))

View File

@ -1 +0,0 @@

View File

@ -1,195 +0,0 @@
from typing import Union
import discord
from discord.ext import commands
import math
from datetime import datetime, timedelta
import pymysql
from apscheduler.schedulers.asyncio import AsyncIOScheduler
password = open("../sqlPass.txt", 'r').read()
scheduler = AsyncIOScheduler({
'apscheduler.jobstores.default': {
'type': 'sqlalchemy',
'url': f'mysql+pymysql://quentin:{password}@192.168.1.52:5618/discord_bot?charset=utf8mb4',
},
'apscheduler.job_defaults.coalesce': 'True',
'apscheduler.timezone': 'America/Chicago'
})
scheduler.start()
mcSelf = None
def get_con() -> pymysql.Connection:
return pymysql.connect(host='192.168.1.52',
port=5618,
user='quentin',
password=f'{password}',
db='minecraft',
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor)
async def get_poll_results(choices: dict, pollMessageId: int, channelId: int):
print("run")
getPollResultsReactions = (await McRoll.get_message(mcSelf, pollMessageId, channelId)).reactions
for reaction in getPollResultsReactions:
async for user in reaction.users():
serverIP = reaction_to_serverip(reaction)
if not user.bot:
choices[serverIP]['votes'] = choices[serverIP]['votes'] + 1
choices[serverIP]['score'] = choices[serverIP]['votes'] * round(
2 * math.log10(choices[serverIP]['lastActivated'] + 1) + 1, 2)
prune_results(choices)
if len(choices) >= 3:
await McRoll.main_poll_recursion(mcSelf, choices, channelId)
else:
await McRoll.declare_winner(mcSelf, choices, channelId)
def reaction_to_serverip(reaction: Union[discord.Reaction, str]) -> int:
con = get_con()
reaction = str(reaction.emoji.id) \
if isinstance(reaction.emoji, discord.Emoji) else reaction.emoji.encode('unicode_escape').decode('utf-8')
with con.cursor() as cursor:
cursor.execute("SELECT serverIP FROM minecraft.server_list WHERE reaction=%s;", reaction)
serverIp = cursor.fetchone()
con.close()
return serverIp['serverIP']
def get_choices() -> dict:
con = get_con()
choices = {}
with con.cursor() as cursor:
cursor.execute("SELECT serverIP, serverName, lastActivated, reaction, getEmoji, onlyServer "
"FROM minecraft.server_list WHERE lastActivated is not null and permanent=0")
row = cursor.fetchone()
while row is not None:
row['votes'] = 0
row['score'] = 0
choices[row['serverIP']] = row
row = cursor.fetchone()
con.close()
return choices
def pop_lowest(choices: dict):
lowest = {'score': 10}
pop = False
for choice in list(choices):
if choices[choice]['score'] < lowest['score']:
lowest = choices[choice]
pop = True
if pop:
choices.pop(lowest['serverIP'])
def prune_results(choices: dict):
for serverIp, choice in list(choices.items()):
if choice['votes'] <= 0:
del choices[serverIp]
if len(choices) >= 4:
pop_lowest(choices)
pop_lowest(choices)
elif len(choices) == 3:
pop_lowest(choices)
class McRoll(commands.Cog):
def __init__(self, client):
self.client = client
self.embedInfo = """Each server has a multiplier based on how long it has been sense that server has been
rolled. When this poll is over all servers with 0 votes will be disregarded. If there are 4 or more servers
left the two with the lowest votes will be disregarded. Poll is rerun until there are only 2 servers,
these two are the winners. Ties reroll the poll. You can only vote for one server per poll. """
self.pollMessageId = None
global mcSelf
mcSelf = self # This is a bad way of doing things but it works
@commands.command()
async def mc_roll(self, ctx):
if ctx.message.author.id != 305589587215122432:
return
choices = get_choices()
await self.main_poll_recursion(choices, ctx.channel.id)
async def main_poll_recursion(self, choices, channelId):
self.pollMessageId = (await self.poll(choices, channelId)).id
scheduler.add_job(get_poll_results, 'date',
run_date=(datetime.now() + timedelta(seconds=6)),
args=[choices, self.pollMessageId, channelId],
coalesce=True)
@commands.Cog.listener()
async def on_reaction_add(self, reaction, user):
if self.pollMessageId is None:
return
reactedUsers = {}
if reaction.message.id == self.pollMessageId and user.id != 533427166193385494:
for iReaction in reaction.message.reactions:
async for iUser in iReaction.users():
if iUser.id != 533427166193385494:
if iUser.id not in reactedUsers:
reactedUsers[iUser.id] = False
if reactedUsers[iUser.id]:
await reaction.remove(user)
self.pollMessageId = reaction.message.id
await user.send(
"You can only vote for one option, please remove your previous vote to change it.")
return
self.pollMessageId = reaction.message.id
reactedUsers[iUser.id] = True
async def poll(self, choices: dict, channel: int) -> discord.Message:
channel = await self.client.fetch_channel(channel)
embed = discord.Embed(title="Poll", description=self.embedInfo,
timestamp=(datetime.utcnow() + timedelta(days=1)))
for choice in choices.values():
embed.add_field(
name=(choice['serverName'] + ' ' +
(str(self.client.get_emoji(int(choice['reaction'])))
if choice['getEmoji']
else bytes(choice['reaction'], "utf-8").decode("unicode_escape"))),
value=("Multiplier : " + str(round(2 * math.log10(choice['lastActivated'] + 1) + 1, 2)) +
'\n' + "Last Rolled : " + str(choice['lastActivated'] * 2) + " weeks ago." +
("\nThis is an only server, meaning if it wins it will be the only winner" if choice['onlyServer']
else " ")))
pollMessage = await channel.send(embed=embed)
await channel.send('@everyone')
for choice in choices.values():
await pollMessage.add_reaction(self.client.get_emoji(int(choice['reaction']))
if choice['getEmoji']
else bytes(choice['reaction'], "utf-8").decode("unicode_escape"))
return pollMessage
async def declare_winner(self, choices, channelId):
embed = discord.Embed(title="Winner", description="Congratulations")
for item in list(choices):
winner = choices[item]
embed.add_field(
name=winner['serverName'],
value="Multiplier : " + str(round(2 * math.log10(winner['lastActivated'] + 1) + 1, 2)) + '\n' +
"Last Rolled : " + str(winner['lastActivated'] * 2) + " weeks ago." + '\n' +
"Votes : " + str(winner['votes']) + '\n' +
"Calculated Votes: " + str(
winner['votes'] * round(2 * math.log10(winner['lastActivated'] + 1) + 1, 2))
)
channel = await self.client.fetch_channel(channelId)
await channel.send(embed=embed)
await channel.send("@everyone")
async def get_message(self, messageId, channelId):
channel = await self.client.fetch_channel(channelId)
return await channel.fetch_message(messageId)
def setup(client):
client.add_cog(McRoll(client))

View File

@ -1,23 +0,0 @@
import re
from discord.ext import commands
class ParseForIssues(commands.Cog):
def __init__(self, client):
self.client = client
@commands.Cog.listener()
async def on_message(self, message):
if message.author.id == 533427166193385494:
return
content = message.content.lower()
matches = re.findall("ttg-[0-9]+", content)
for match in matches:
await message.channel.send("https://youtrack.themissingcrowbar.com:8942/issue/"+match)
def setup(client):
client.add_cog(ParseForIssues(client))

View File

@ -1,38 +0,0 @@
import asyncio
import time
import logging
from datetime import datetime, timedelta, date
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.schedulers.blocking import BlockingScheduler
scheduler = AsyncIOScheduler({
'apscheduler.jobstores.default': {
'type': 'sqlalchemy',
'url': f'mysql+pymysql://Quentin:kaPl0wskii@192.168.1.52:5618/discord_bot?charset=utf8mb4'
},
'apscheduler.executors.default': {
'class': 'apscheduler.executors.pool:ThreadPoolExecutor',
'max_workers': '20'
},
'apscheduler.executors.processpool': {
'type': 'processpool',
'max_workers': '5'
},
'apscheduler.job_defaults.coalesce': 'true',
'apscheduler.job_defaults.max_instances': '3',
'apscheduler.timezone': 'America/Chicago',
})
scheduler.start()
async def hello():
print("hello world")
if __name__ == '__main__':
# scheduler.add_job(hello, 'date', run_date=datetime.now()+timedelta(seconds=20))
try:
asyncio.get_event_loop().run_forever()
except (KeyboardInterrupt, SystemExit):
pass

View File

@ -1,36 +0,0 @@
import random
from discord.ext import commands
content = [", was just chosen to be the one.",
", has joined!",
", looks like someone took the slow train from Philly.",
", just bought the house!",
", has a new home.",
", if in ten years i haven't had a baby and you haven't had a baby?",
" is hear to play minecraft and get killed by my sword!",
", is most definitely a furry.",
", did you bring your sword?",
", welcome!"]
class Join(commands.Cog):
def __init__(self, client):
self.client = client
@commands.Cog.listener()
async def on_member_join(self, member):
if member.guild.id == 605932352736067585:
if member.id == 234703033924059138:
await self.client.get_channel(605932352736067589).send(
member.mention +
", is most definitely both a furry and a weeb. We should all bully him because he stinks."
)
else:
await self.client.get_channel(605932352736067589).send(member.mention +
random.choice(content) + "[Join]")
await member.add_roles(self.client.get_guild(605932352736067585).get_role(605934866676056070))
def setup(client):
client.add_cog(Join(client))

View File

@ -1,26 +0,0 @@
import random
from discord.ext import commands
content = [" just bit the dust.",
" gave up on life.",
" couldn't take the heat anymore.",
" boarded the train.",
" was pricked to death.",
" just left.",
" ragequit.",
" doesn't like me anymore"]
class Join(commands.Cog):
def __init__(self, client):
self.client = client
@commands.Cog.listener()
async def on_member_remove(self, member):
if member.guild.id == 605932352736067585:
await self.client.get_channel(605932352736067589).send(member.mention + random.choice(content) + "[Leave")
def setup(client):
client.add_cog(Join(client))

View File

@ -1,31 +0,0 @@
from discord.ext import commands
class Spam(commands.Cog):
def __init__(self, client):
self.client = client
@commands.Cog.listener()
async def on_message(self, message):
dump = []
print(len(dump))
print((dump[len(dump)-1].created_at - dump[1].created_at).total_seconds())
async for temp in message.channel.history(limit=5):
if temp.author == message.author:
dump.append(temp)
if (dump[len(dump)-1].created_at - dump[1].created_at).total_seconds() <= 2:
await message.delete()
await self.client.process_commands(message)
def setup(client):
client.add_cog(Spam(client))
""" async for tempMessage in message.channel.history(limit=5):
if tempMessage.id == message.id:
pass
elif tempMessage.author == message.author:
difference = message.created_at - tempMessage.created_at
if difference.total_seconds() <= 2:
await message.delete()"""

View File

@ -1,20 +0,0 @@
import asyncio
import math
from datetime import datetime, timedelta
import discord
import pymysql
from discord.ext import commands
class doym(commands.Cog):
def __init__(self, client):
self.client = client
@commands.command()
async def doymCMD(self, ctx):
await ctx.channel.send("https://www.youtube.com/watch?v=5t53TcKIlMc")
def setup(client):
client.add_cog(doym(client))

View File

100
scr/config.py Normal file
View File

@ -0,0 +1,100 @@
from __future__ import annotations
import json
from dataclasses import dataclass
@dataclass
class MysqlSettings:
user: str
password: str
host: str
port: int
db: str
@dataclass
class BotSettings:
token: str
guildId: int
prefix: str
@dataclass
class RoleSettings:
member: str
roles: BotSettings.RoleSettings
@dataclass
class ChannelSettings:
joinLog: int
leaveLog: int
channels: BotSettings.ChannelSettings
class CommandSettings:
def load(self, data):
pass
commands: dict[str, CommandSettings]
@dataclass
class YoutrackSettings:
url: str
class Config:
def __init__(self, configFile: str = "./settings.json"):
self.configFile = configFile
with open(self.configFile, 'r') as file:
self.configData = json.loads(file.read())
self.__load_mysql()
self.__load_bot()
self.__load_youtrack()
def register_command_settings(self, settingJson: str):
def decorator(cls: type[BotSettings.CommandSettings]):
currentSettings = self.BOT.commands.get(settingJson)
if currentSettings is not None:
raise Exception(f"Setting {settingJson} already loaded.")
instance = cls()
instance.load(self.commandData[settingJson])
self.BOT.commands[settingJson] = instance
return decorator
def __load_mysql(self):
mysqlData = self.configData["mysql"]
self.MYSQL = MysqlSettings(
user=mysqlData["user"],
password=mysqlData["password"],
host=mysqlData["host"],
port=mysqlData["port"],
db=mysqlData["db"]
)
def __load_bot(self):
botData = self.configData["bot"]
self.BOT = BotSettings(
token=botData["token"],
guildId=botData["guild"],
prefix=botData["prefix"],
roles=BotSettings.RoleSettings(
member=botData["roles"]["member"]
),
channels=BotSettings.ChannelSettings(
joinLog=botData["channels"]["joinLog"],
leaveLog=botData["channels"]["leaveLog"]
),
commands={}
)
self.commandData = botData["commands"]
def __load_youtrack(self):
youtrackData = self.configData["youtrack"]
self.YOUTRACK = YoutrackSettings(
url=youtrackData["url"]
)

15
scr/main.py Normal file
View File

@ -0,0 +1,15 @@
from config import Config
from myBot import MyBot
config = Config()
bot = MyBot(config=config)
@bot.event
async def on_ready():
print(f'Bot started as {bot.user}')
modules = ["modules.youtrackIntegration",
"modules.TMC"]
for module in modules:
await bot.load_extension(module)

View File

@ -0,0 +1,9 @@
from myBot import MyBot
cogs = [".autoReply",
".commands"]
async def setup(bot: MyBot):
for cog in cogs:
await bot.load_extension(cog, package="modules.TMC")

View File

@ -0,0 +1,9 @@
from myBot import MyBot
cogs = [".join",
".leave"]
async def setup(bot: MyBot):
for cog in cogs:
await bot.load_extension(cog, package=__package__)

View File

@ -0,0 +1,37 @@
import random
from discord.ext import commands
from myBot import MyBot
content = [", was just chosen to be the one.",
", has joined!",
", looks like someone took the slow train from Philly.",
", just bought the house!",
", has a new home.",
", if in ten years I haven't had a baby and you haven't had a baby?",
" is hear to play minecraft and get killed by my sword!",
", is most definitely a furry.",
", did you bring your sword?",
", welcome!",
", two trucks?",
", *Who needs atom clamps! I have a funny robot!!",
" DO NOT POST FNF MUCKBANG.",
" just joined, hmm. *sniffs*. Eww.",
", hold by pee, I've gotta beer."]
class Join(commands.Cog):
def __init__(self, bot: MyBot):
self.client = bot
@commands.Cog.listener()
async def on_member_join(self, member):
if member.guild.id == 605932352736067585:
await self.client.get_channel(605932352736067589).send("[Join] " + member.mention +
random.choice(content))
await member.add_roles(self.client.get_guild(605932352736067585).get_role(605934866676056070))
async def setup(bot: MyBot):
await bot.add_cog(Join(bot))

View File

@ -0,0 +1,36 @@
import random
from discord.ext import commands
from myBot import MyBot
content = [" just bit the dust.",
" gave up on life.",
" couldn't take the heat anymore.",
" boarded the train.",
" was pricked to death.",
" just left.",
" ragequit.",
" doesn't like me anymore.",
" chickened out.",
", you're*",
" held the pee.",
", Shit & Pants ~Jack Black",
" rolled off with the root beer.",
" doesn't feel like it anymore.",
" didn't install the microwave ovens.",
" was slower than an i7 Q 740m @ 1.73GHz!"]
class Leave(commands.Cog):
def __init__(self, bot: MyBot):
self.client = bot
@commands.Cog.listener()
async def on_member_remove(self, member):
if member.guild.id == 605932352736067585:
await self.client.get_channel(605932352736067589).send("[Leave] " + member.mention + random.choice(content))
async def setup(myBot: MyBot):
await myBot.add_cog(Leave(myBot))

View File

@ -0,0 +1,8 @@
from myBot import MyBot
cogs = [".mcRoll"]
async def setup(bot: MyBot):
for cog in cogs:
await bot.load_extension(cog, package=__package__)

View File

@ -0,0 +1,226 @@
import math
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone, date
import discord
import pymysql
import pymysql.cursors
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from discord.ext import commands
from discord.ext.commands import Context
from tzlocal import get_localzone_name
from config import BotSettings
from myBot import MyBot
@dataclass
class PollChoice:
lastActivated: date
votes: int
score: float
reaction: str
serverName: str
getEmoji: int
bot_instance_registry = {}
async def poll_result_dispatcher(class_name, instance_id, *args):
instance = bot_instance_registry.get((class_name, instance_id))
if instance:
await instance.get_poll_results(*args)
else:
print(f"No instance found for {class_name}:{instance_id}")
def get_multiplier(choice: PollChoice):
activatedNWeeksAgo = (datetime.now().date() - choice.lastActivated).days / 7
return round(1.7 * math.log10(activatedNWeeksAgo + 1) + 1, 2)
def pop_lowest(choices: dict[int, PollChoice]):
if not choices:
return
pop = min(choices.items(), key=lambda item: item[1].score)[0]
del choices[pop]
def find_choice_by_reaction(choices: dict[int, PollChoice], reaction: discord.Reaction) -> PollChoice | None:
for choice in choices.values():
if choice.reaction == str(reaction):
return choice
return None
class McRoll(commands.Cog):
def __init__(self, bot: MyBot):
self.bot = bot
self.embedInfo = (f"Each server has a multiplier based on how long it has been sense that server has been rolled. "
f"When this poll is over all servers with 0 votes will be disregarded. "
f"1/3rd of the servers with the lowest votes will be disregarded. "
f"If there are 4 or more servers Poll is rerun until there are only {self.bot.config.BOT.commands["mc_roll"].pollRuntimeHours} servers, "
f"these are the winners. Ties reroll the poll. You can only vote for one server per poll. ")
self.pollMessageId = None
bot_instance_registry[('McRoll', id)] = self
mysqlConf = self.bot.config.MYSQL
self.scheduler = AsyncIOScheduler({
'apscheduler.jobstores.default': {
'type': 'sqlalchemy',
'url': f'mysql+pymysql://{mysqlConf.user}:{mysqlConf.password}@{mysqlConf.host}:{mysqlConf.port}/{mysqlConf.db}?charset=utf8mb4',
},
'apscheduler.job_defaults.coalesce': 'True',
'apscheduler.timezone': get_localzone_name()
})
self.scheduler.start()
def prune_results(self, choices: dict[int, PollChoice]):
for key, choice in list(choices.items()):
if choice.votes <= 0:
del choices[key]
amountToPop = round(len(choices) * .33)
if (len(choices) - amountToPop) <= self.bot.config.BOT.commands["mc_roll"].runningServers:
for i in range(len(choices) - self.bot.config.BOT.commands["mc_roll"].runningServers):
pop_lowest(choices)
return
for i in range(amountToPop):
pop_lowest(choices)
def get_con(self) -> pymysql.Connection:
mysqlConf = self.bot.config.MYSQL
return pymysql.connect(host=mysqlConf.host,
port=mysqlConf.port,
user=mysqlConf.user,
password=mysqlConf.password,
db=mysqlConf.db,
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor)
async def get_poll_results(self, choices: dict[int, PollChoice], pollMessageId: int, channelId: int):
reactions = (await self.get_message(pollMessageId, channelId)).reactions
for reaction in reactions:
async for user in reaction.users():
choice = find_choice_by_reaction(choices, reaction)
if choice is None:
continue
if not user.bot:
choice.votes = choice.votes + 1
choice.score = choice.votes * get_multiplier(choice)
self.prune_results(choices)
if len(choices) > self.bot.config.BOT.commands["mc_roll"].runningServers:
await self.main_poll_recursion(choices, channelId)
else:
await self.declare_winner(choices, channelId)
def get_choices(self) -> dict[int, PollChoice]:
with self.get_con() as con:
choices: dict[int, PollChoice] = {}
with con.cursor() as cursor:
cursor.execute("SELECT id, serverName, lastActivated, reaction, getEmoji FROM server_list WHERE disabled=false")
records = cursor.fetchall()
for row in records:
choice = PollChoice(
lastActivated=datetime.now().date() if row["lastActivated"] is None else row["lastActivated"],
votes=0,
score=0,
reaction=row["reaction"],
serverName=row["serverName"],
getEmoji=row["getEmoji"]
)
choices[row["id"]] = choice
return choices
@commands.command()
async def mc_roll(self, ctx: Context):
if not ctx.author.guild_permissions.administrator:
return
choices = self.get_choices()
await self.main_poll_recursion(choices, ctx.channel.id)
async def main_poll_recursion(self, choices, channelId):
self.pollMessageId = (await self.poll(choices, channelId)).id
self.scheduler.add_job(poll_result_dispatcher, 'date',
run_date=(datetime.now() + timedelta(self.bot.config.BOT.commands["mc_roll"].pollRuntimeHours)),
args=['McRoll', id, choices, self.pollMessageId, channelId],
coalesce=True)
@commands.Cog.listener()
async def on_reaction_add(self, reaction, user):
if self.pollMessageId is None:
return
reactedUsers = {}
if reaction.message.id == self.pollMessageId and user.id != self.bot.user.id:
for iReaction in reaction.message.reactions:
async for iUser in iReaction.users():
if iUser.id != self.bot.user.id:
if iUser.id not in reactedUsers:
reactedUsers[iUser.id] = False
if reactedUsers[iUser.id]:
await reaction.remove(user)
self.pollMessageId = reaction.message.id
await user.send(
"You can only vote for one option, please remove your previous vote to change it.")
return
self.pollMessageId = reaction.message.id
reactedUsers[iUser.id] = True
async def poll(self, choices: dict[int, PollChoice], channel: int) -> discord.Message:
channel = await self.bot.fetch_channel(channel)
embed = discord.Embed(title="Poll", description=self.embedInfo,
timestamp=(datetime.now(timezone.utc) + timedelta(days=1)))
for choice in choices.values():
embed.add_field(
name=(choice.serverName + ' ' +
(str(self.bot.get_emoji(int(choice.reaction)))
if choice.getEmoji
else choice.reaction)),
value=("Multiplier : " + str(get_multiplier(choice)) +
'\n' + "Last Rolled : " + str(
round((datetime.now().date() - choice.lastActivated).days)) + " days ago."))
pollMessage = await channel.send(embed=embed)
await channel.send('@everyone')
for choice in choices.values():
await pollMessage.add_reaction(self.bot.get_emoji(int(choice.reaction))
if choice.getEmoji
else choice.reaction)
return pollMessage
async def declare_winner(self, choices: dict[int, PollChoice], channelId):
embed = discord.Embed(title="Winner", description="Congratulations")
for choice in choices.values():
embed.add_field(
name=choice.serverName,
value=("Multiplier : " + str(get_multiplier(choice)) + '\n' +
"Last Rolled : " + str(
round((datetime.now().date() - choice.lastActivated).days)) + " days ago." + '\n' +
"Votes : " + str(choice.votes) + '\n' +
"Calculated Votes: " + str(choice.score))
)
channel = await self.bot.fetch_channel(channelId)
await channel.send(embed=embed)
await channel.send("@everyone")
async def get_message(self, messageId, channelId):
channel = await self.bot.fetch_channel(channelId)
return await channel.fetch_message(messageId)
async def setup(bot: MyBot):
@bot.config.register_command_settings("mc_roll")
class McRollSettings(BotSettings.CommandSettings):
def __init__(self):
self.runningServers: int = -1
self.pollRuntimeHours: int = -1
def load(self, data):
self.runningServers = data["runningServers"]
self.pollRuntimeHours = data["pollRuntimeHours"]
await bot.add_cog(McRoll(bot))

View File

@ -0,0 +1,8 @@
from myBot import MyBot
cogs = [".parseForIssues"]
async def setup(bot: MyBot):
for cog in cogs:
await bot.load_extension(cog, package=__package__)

View File

@ -0,0 +1,15 @@
import json
import requests
from config import YoutrackSettings
class Api:
def __init__(self, settings: YoutrackSettings):
self.URL = settings.url
def get_projects(self):
r = requests.get(self.URL + "api/admin/projects?fields=id,shortName")
data = json.loads(r.content)
return data

View File

@ -0,0 +1,32 @@
import re
from discord.ext import commands
from myBot import MyBot
from .api import Api
class ParseForIssues(commands.Cog):
def __init__(self, bot: MyBot):
self.bot = bot
self.shortProjectNames = None
self.api = Api(bot.config.YOUTRACK)
@commands.Cog.listener()
async def on_message(self, message):
if message.author.id == self.bot.user.id:
return
content = message.content.lower()
if self.shortProjectNames is None:
projects = self.api.get_projects()
self.shortProjectNames = []
for project in projects:
self.shortProjectNames.append(project["shortName"].lower())
matches = re.findall(rf"(?:{'|'.join(map(re.escape, self.shortProjectNames))})-[0-9]+", content)
for match in matches:
await message.channel.send(self.api.URL + "/issue/" + match)
async def setup(bot: MyBot):
await bot.add_cog(ParseForIssues(bot))

29
scr/myBot.py Normal file
View File

@ -0,0 +1,29 @@
import discord
from discord.ext import commands
from config import Config
class MyBot(commands.Bot):
def __init__(self, config: Config):
intents = discord.Intents.default()
intents.message_content = True
intents.members = True
super().__init__(command_prefix=config.BOT.prefix, intents=intents)
self.config: Config = config
super().run(self.config.BOT.token)
async def setup_hook(self) -> None:
modules = [
"modules.youtrackIntegration",
"modules.TMC"
]
for module in modules:
await self.load_extension(module)
return await super().setup_hook()
async def on_ready(self):
print(f'Bot started as {self.user}')