1 Commits

8 changed files with 174 additions and 11 deletions

4
.dockerignore Normal file
View File

@@ -0,0 +1,4 @@
.gitea/
__pycache__/
.vscode
tests/

4
.gitignore vendored
View File

@@ -164,4 +164,6 @@ data/
*.disabled *.disabled
config/ config/
tests/

View File

@@ -1,11 +1,11 @@
import discord, json, os, random, re import discord, json, os, random, re
import collections import collections
from discord.ext import commands from discord.ext import commands, bridge
from setup.logger import LOGGER from setup.logger import LOGGER
from setup.config import Config from setup.config import Config
class InsultsCog(commands.Cog): class InsultsCog(commands.Cog):
def __init__(self, bot:discord.Bot): def __init__(self, bot:bridge.Bot):
self.bot = bot self.bot = bot
with open(os.path.join(Config.CONFIG_PATH, "insults.txt")) as f: with open(os.path.join(Config.CONFIG_PATH, "insults.txt")) as f:

149
cogs/intern.py Normal file
View File

@@ -0,0 +1,149 @@
import discord, json, os, random, re, datetime
import collections
from discord.ext import commands, bridge, tasks
from setup.logger import LOGGER
from setup.config import Config
class InternCog(commands.Cog):
def __init__(self, bot:commands.Bot) -> None:
self.bot = bot
self.in_voice_channel = False
self.connected_guild_ids = set()
def _get_guild_data(self, guild_id):
return next((data for data in self.data if data["guild_id"] == guild_id), None)
def _get_guild_voice_client(self, guild_id):
return next(
(
voice_client
for voice_client in self.bot.voice_clients
if getattr(getattr(voice_client, "guild", None), "id", None) == guild_id
),
None,
)
def _resolve_voice_action(self, before_channel, after_channel, guild_voice_client):
if after_channel is None and before_channel is not None:
return "disconnect"
if before_channel is not None and after_channel is not None:
if before_channel.id != after_channel.id:
return "move"
return None
if after_channel is not None and before_channel is None:
if guild_voice_client is not None:
return "move"
return "connect" if random.random() <= 0.25 else None
return None
def load_from_file(self):
try:
with open(Config.INTERN_MEMORY_PATH) as f:
self.data = json.load(f)
if len(self.data) == 0: raise FileNotFoundError
for guild_data in self.data:
guild = self.bot.get_guild(guild_data["guild_id"])
if guild:
mentor = guild.get_member(guild_data["mentor_id"])
LOGGER.debug(f"Loaded mentor: {mentor.name} for guild {guild.name}")
except FileNotFoundError:
today = datetime.datetime.now()
next_roll = datetime.datetime(year=today.year,month=today.month,day=today.day) + datetime.timedelta(days=7)
self.data = [
{
"mentor_id": random.choice(guild.members).id,
"guild_id": guild.id,
"next_roll": next_roll.isoformat()
}
for guild in self.bot.guilds
]
def load_from_database(self):
pass
def save_to_file(self):
with open(Config.INTERN_MEMORY_PATH, 'w') as f:
json.dump(self.data,f)
def save_to_database(self):
pass
@tasks.loop(hours=24)
async def update(self):
today = datetime.datetime.now()
for guild_data in self.data:
next_roll = datetime.datetime.fromisoformat(guild_data["next_roll"])
if today > next_roll:
guild = self.bot.get_guild(guild_data["guild_id"])
if guild:
members = [
member
for member in guild.get_role(1516232292576788530).members
if member not in guild.get_role(1526911990495445143).members
and member.id != guild_data["mentor_id"]
]
new_mentor = random.choice(members)
new_mentor_id = new_mentor.id
guild_data["mentor_id"] = new_mentor_id
new_next_roll = datetime.datetime(year=today.year,month=today.month,day=today.day) + datetime.timedelta(days=7)
guild_data["next_roll"] = new_next_roll.isoformat()
LOGGER.debug(f"New mentor: {new_mentor.name} for guild {guild.name}")
self.save_to_file()
@commands.Cog.listener()
async def on_ready(self):
self.load_from_file()
self.update.start()
@commands.Cog.listener()
async def on_voice_state_update(self, member:discord.Member, before:discord.VoiceState, after:discord.VoiceState):
if not self.data:
return
guild_id = None
if before and before.channel and before.channel.guild:
guild_id = before.channel.guild.id
elif after and after.channel and after.channel.guild:
guild_id = after.channel.guild.id
if guild_id is None:
return
guild_data = self._get_guild_data(guild_id)
if not guild_data or member.id != guild_data["mentor_id"]:
return
if before and before.channel:
channel_name = getattr(before.channel, "name", "unknown")
LOGGER.debug(f"Mentor left channel {channel_name}")
if after and after.channel:
channel_name = getattr(after.channel, "name", "unknown")
LOGGER.debug(f"Mentor joined channel {channel_name}")
guild_voice_client = self._get_guild_voice_client(guild_id)
action = self._resolve_voice_action(before.channel if before else None, after.channel if after else None, guild_voice_client)
if action == "connect":
try:
await after.channel.connect()
self.connected_guild_ids.add(guild_id)
self.in_voice_channel = True
except discord.ClientException as exc:
LOGGER.warning(f"Could not connect to voice channel: {exc}")
elif action == "move":
if guild_voice_client:
await guild_voice_client.move_to(after.channel)
else:
await after.channel.connect()
self.connected_guild_ids.add(guild_id)
self.in_voice_channel = True
elif action == "disconnect":
if guild_voice_client:
await guild_voice_client.disconnect(force=True)
self.connected_guild_ids.discard(guild_id)
self.in_voice_channel = False

View File

@@ -2,7 +2,6 @@ import os, discord, json, sys, random
from argparse import ArgumentParser from argparse import ArgumentParser
from datetime import datetime from datetime import datetime
from asyncio import create_task from asyncio import create_task
from discord import option
from discord.ext import tasks, commands from discord.ext import tasks, commands
from setup.bot import bot, reload_feature, register_cogs from setup.bot import bot, reload_feature, register_cogs
from setup.logger import LOGGER from setup.logger import LOGGER

View File

@@ -1,10 +1,13 @@
aiohappyeyeballs==2.6.2 aiohappyeyeballs==2.7.1
aiohttp==3.14.1 aiohttp==3.14.1
aiosignal==1.4.0 aiosignal==1.4.0
asarPy==1.0.1 asarPy==1.0.1
async-timeout==5.0.1
attrs==26.1.0 attrs==26.1.0
audioop-lts==0.2.2
beautifulsoup4==4.15.0 beautifulsoup4==4.15.0
certifi==2026.5.20 certifi==2026.5.20
cffi==2.1.0
charset-normalizer==3.4.7 charset-normalizer==3.4.7
frozenlist==1.8.0 frozenlist==1.8.0
h11==0.16.0 h11==0.16.0
@@ -15,6 +18,8 @@ outcome==1.3.0.post0
propcache==0.5.2 propcache==0.5.2
psycopg2-binary==2.9.12 psycopg2-binary==2.9.12
py-cord==2.8.0 py-cord==2.8.0
pycparser==3.0
PyNaCl==1.6.2
PySocks==1.7.1 PySocks==1.7.1
python-dotenv==1.2.2 python-dotenv==1.2.2
requests==2.34.2 requests==2.34.2
@@ -24,8 +29,9 @@ sortedcontainers==2.4.0
soupsieve==2.8.4 soupsieve==2.8.4
trio==0.33.0 trio==0.33.0
trio-websocket==0.12.2 trio-websocket==0.12.2
typing_extensions==4.15.0 typing_extensions==4.16.0
urllib3==2.7.0 urllib3==2.7.0
wavelink==3.5.2
websocket-client==1.9.0 websocket-client==1.9.0
wsproto==1.3.2 wsproto==1.3.2
yarl==1.24.2 yarl==1.24.2

View File

@@ -1,10 +1,9 @@
import discord import discord
from discord.ext import bridge from discord.ext import commands
# from cogs.birthday import BirthdayCog
# from cogs.productivity import ProductivityCog
from cogs.cs import CSCog from cogs.cs import CSCog
from cogs.insult import InsultsCog from cogs.insult import InsultsCog
from cogs.roulette import RouletteCog from cogs.roulette import RouletteCog
from cogs.intern import InternCog
from setup.logger import LOGGER from setup.logger import LOGGER
bot_intents = discord.Intents.default() bot_intents = discord.Intents.default()
@@ -13,13 +12,15 @@ bot_intents.members = True
bot_intents.presences = True bot_intents.presences = True
bot_intents.guilds = True bot_intents.guilds = True
bot_intents.reactions = True bot_intents.reactions = True
bot_intents.voice_states = True
bot = bridge.Bot(command_prefix='$', intents=bot_intents) bot = commands.Bot(command_prefix='$', intents=bot_intents)
COG_REGISTRY = { COG_REGISTRY = {
'cs': CSCog, 'cs': CSCog,
'insult': InsultsCog, 'insult': InsultsCog,
'roulette': RouletteCog 'roulette': RouletteCog,
'intern': InternCog
} }

View File

@@ -7,6 +7,8 @@ class Config:
TOKEN = str(os.getenv("TOKEN", "")) TOKEN = str(os.getenv("TOKEN", ""))
CONFIG_PATH = str(os.getenv("CONFIG_PATH")) CONFIG_PATH = str(os.getenv("CONFIG_PATH"))
INTERN_MEMORY_PATH = os.path.join(CONFIG_PATH,"intern.json")
INTERN_TAKES_PATH = os.path.join(CONFIG_PATH,"intern_takes.csv")
COGS = os.getenv("COGS", "") COGS = os.getenv("COGS", "")
@classmethod @classmethod