Compare commits
1 Commits
60e95791a9
...
feature/in
| Author | SHA1 | Date | |
|---|---|---|---|
| 6692c43102 |
4
.dockerignore
Normal file
4
.dockerignore
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
.gitea/
|
||||||
|
__pycache__/
|
||||||
|
.vscode
|
||||||
|
tests/
|
||||||
@@ -2,49 +2,15 @@ name: CD
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main, v2]
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: cd-${{ github.ref_name }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-push:
|
|
||||||
name: Build and push Docker image
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v4
|
|
||||||
- name: Log in to Docker Hub
|
|
||||||
uses: docker/login-action@v4
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_TOKEN }}
|
|
||||||
- name: Restore Docker build cache
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: /tmp/.buildx-cache
|
|
||||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-buildx-
|
|
||||||
- name: Build and push image
|
|
||||||
uses: docker/build-push-action@v7
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
${{ secrets.DOCKER_USERNAME }}/botmafieux:latest
|
|
||||||
${{ secrets.DOCKER_USERNAME }}/botmafieux:${{ github.sha }}
|
|
||||||
cache-from: type=local,src=/tmp/.buildx-cache
|
|
||||||
cache-to: type=local,dest=/tmp/.buildx-cache,mode=max
|
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
name: Deploy to VPS
|
name: Deploy to VPS
|
||||||
needs: build-and-push
|
needs: build-and-push
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Copy compose file to VPS
|
- name: Copy files to VPS
|
||||||
uses: appleboy/scp-action@v1
|
uses: appleboy/scp-action@v1
|
||||||
with:
|
with:
|
||||||
host: ${{ secrets.VPS_HOST }}
|
host: ${{ secrets.VPS_HOST }}
|
||||||
@@ -53,6 +19,7 @@ jobs:
|
|||||||
port: ${{ secrets.VPS_SSH_PORT }}
|
port: ${{ secrets.VPS_SSH_PORT }}
|
||||||
source: "compose.yaml"
|
source: "compose.yaml"
|
||||||
target: "/opt/docker/botmafieux"
|
target: "/opt/docker/botmafieux"
|
||||||
|
|
||||||
- name: Deploy on VPS
|
- name: Deploy on VPS
|
||||||
uses: appleboy/ssh-action@v1
|
uses: appleboy/ssh-action@v1
|
||||||
with:
|
with:
|
||||||
@@ -61,7 +28,12 @@ jobs:
|
|||||||
key: ${{ secrets.VPS_SSH_KEY }}
|
key: ${{ secrets.VPS_SSH_KEY }}
|
||||||
port: ${{ secrets.VPS_SSH_PORT }}
|
port: ${{ secrets.VPS_SSH_PORT }}
|
||||||
script: |
|
script: |
|
||||||
set -euo pipefail
|
|
||||||
cd /opt/docker/botmafieux
|
cd /opt/docker/botmafieux
|
||||||
docker compose -f compose.yaml --env-file .env pull
|
|
||||||
docker compose -f compose.yaml --env-file .env up -d --remove-orphans
|
docker compose -f compose.yaml --env-file .env down
|
||||||
|
|
||||||
|
docker compose -f compose.yaml --env-file .env build
|
||||||
|
|
||||||
|
docker compose -f compose.yaml --env-file .env up -d
|
||||||
|
|
||||||
|
docker compose -f compose.yaml --env-file .env.prod ps
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
name: CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
name: Run tests
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v7
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: 3.13
|
|
||||||
- name: Install dependencies
|
|
||||||
run: python -m pip install --no-cache-dir -r requirements.txt
|
|
||||||
- name: Run pytest
|
|
||||||
run: pytest -q
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -164,4 +164,6 @@ data/
|
|||||||
|
|
||||||
*.disabled
|
*.disabled
|
||||||
|
|
||||||
config/
|
config/
|
||||||
|
|
||||||
|
tests/
|
||||||
@@ -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
149
cogs/intern.py
Normal 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
|
||||||
25
compose.yaml
25
compose.yaml
@@ -1,22 +1,9 @@
|
|||||||
networks:
|
|
||||||
internal:
|
|
||||||
internal: true
|
|
||||||
outbound:
|
|
||||||
external: true
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
botmafieux:
|
botmafieux:
|
||||||
image: nebulo9/botmafieux:latest
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./Dockerfile
|
||||||
|
tags:
|
||||||
|
- nebulo9/botmafieux:v2
|
||||||
container_name: botmafieux
|
container_name: botmafieux
|
||||||
restart: unless-stopped
|
env_file: ./.env
|
||||||
networks:
|
|
||||||
- internal
|
|
||||||
- outbound
|
|
||||||
env_file: ./.env
|
|
||||||
volumes:
|
|
||||||
- ./config:/config
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
cpus: '0.25'
|
|
||||||
memory: '256M'
|
|
||||||
6778
config/hltv.html
6778
config/hltv.html
File diff suppressed because one or more lines are too long
@@ -1,27 +0,0 @@
|
|||||||
Ta gueule.
|
|
||||||
Qui a demandé ?
|
|
||||||
Mais tais-toi!
|
|
||||||
zZzZzZzZ......
|
|
||||||
Tout ça ?
|
|
||||||
On peut se passer du message je pense.
|
|
||||||
C'est chiant le message là.
|
|
||||||
Mdr on s'en fout.
|
|
||||||
Pas lu.
|
|
||||||
Trop long j'ai décroché.
|
|
||||||
Il faut quitter le serveur, vous gênez l'espace s'il vous plaît.
|
|
||||||
Gênant...
|
|
||||||
Si je devais noter la pertinence du message ce serait genre 1/10.
|
|
||||||
Tu es ridicule, arrête d'écrire.
|
|
||||||
Ça a intéréssé quelqu'un ?
|
|
||||||
Franchement c'était nécessaire d'écrire ça ?
|
|
||||||
Honestly the most useless thing I read today.
|
|
||||||
Dommage que les hommes soient cons…
|
|
||||||
Ta mère mérite la prison pour avoir mis au monde un HOMME.
|
|
||||||
T'as pas beaucoup de neurones.
|
|
||||||
Arrêtons de normaliser les hommes svp.
|
|
||||||
🚮🚮
|
|
||||||
Ton message me donne envie de vomir.
|
|
||||||
J'ai pas ri perso.
|
|
||||||
Tiens tiens encore une raison de détester les hommes !
|
|
||||||
Y'a moyen ton père c'est Macron.
|
|
||||||
Tu préfères devenir un être humain sympa ou rester un homme ?
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
[{"mentor_id": 264189554582355969, "guild_id": 897157935006900254, "next_roll": "2026-07-22T00:00:00"}]
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
target;weight;take
|
|
||||||
any;1.0;Tu as besoin d'un café ?
|
|
||||||
any;1.0;Je peux te faire un café si tu veux ?
|
|
||||||
any;1.0;Tu as besoin d'un thé ?
|
|
||||||
any;1.0;Je peux te faire un thé si tu veux ?
|
|
||||||
any;1.0;Tu as besoin d'un matcha latte?
|
|
||||||
any;1.0;Je peux te faire un matcha latte si tu veux ?
|
|
||||||
any;1.0;Tu peux m'aider sur un truc ?
|
|
||||||
any;1.0;Désolé, j'ai pas compris.
|
|
||||||
any;1.0;Attends, tu peux réexpliquer ?
|
|
||||||
any;1.0;J'ai fait une bêtise je crois...
|
|
||||||
nebulo;2.0;J'ai un bug dans mon code tu peux regarder ?
|
|
||||||
bluebro;2.0;J'ai un bug dans mon code tu peux regarder ?
|
|
||||||
viebah;2.0;J'ai un bug dans mon code tu peux regarder ?
|
|
||||||
merdix;2.0;Mon modèle 3D est cassé, je fais quoi ?
|
|
||||||
goupzy;2.0;Mon modèle 3D est cassé, je fais quoi ?
|
|
||||||
viebah;2.0;Mon modèle 3D est cassé, je fais quoi ?
|
|
||||||
goupzy;2.0;Mon modèle 3D est cassé, je fais quoi ?
|
|
||||||
nino274;2.0;Mon modèle 3D est cassé, je fais quoi ?
|
|
||||||
cemesah;2.0;J'ai fait une erreur, il faut réimprimer les cartes...
|
|
||||||
cemesah;2.0;J'ai pas sauvegardé et InDesign a planté...
|
|
||||||
cemesah;2.0;Tu veux du bouillon ?
|
|
||||||
|
1
main.py
1
main.py
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
11
setup/bot.py
11
setup/bot.py
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
import unittest
|
|
||||||
from types import SimpleNamespace
|
|
||||||
from unittest.mock import AsyncMock, patch
|
|
||||||
|
|
||||||
from cogs.intern import InternCog
|
|
||||||
|
|
||||||
|
|
||||||
class InternVoiceBehaviorTests(unittest.TestCase):
|
|
||||||
def make_cog(self, voice_client=None):
|
|
||||||
cog = InternCog.__new__(InternCog)
|
|
||||||
cog.bot = SimpleNamespace(voice_clients=[voice_client] if voice_client else [])
|
|
||||||
cog.data = [{"guild_id": 123, "mentor_id": 456}]
|
|
||||||
cog.connected_guild_ids = set()
|
|
||||||
cog.in_voice_channel = False
|
|
||||||
return cog
|
|
||||||
|
|
||||||
def make_channel(self, channel_id, guild, connect_mock=None, name="test-channel"):
|
|
||||||
return SimpleNamespace(id=channel_id, guild=guild, connect=connect_mock or AsyncMock(), name=name)
|
|
||||||
|
|
||||||
def make_member(self, member_id):
|
|
||||||
return SimpleNamespace(id=member_id)
|
|
||||||
|
|
||||||
async def _run_handler(self, cog, before_channel, after_channel, voice_client):
|
|
||||||
before = SimpleNamespace(channel=before_channel)
|
|
||||||
after = SimpleNamespace(channel=after_channel)
|
|
||||||
member = self.make_member(456)
|
|
||||||
await cog.on_voice_state_update(member, before, after)
|
|
||||||
|
|
||||||
def test_initial_join_only_connects_with_25_percent_chance(self):
|
|
||||||
cog = self.make_cog()
|
|
||||||
connect_mock = AsyncMock()
|
|
||||||
guild = SimpleNamespace(id=123, voice_client=None)
|
|
||||||
channel = self.make_channel(1, guild, connect_mock=connect_mock)
|
|
||||||
before = None
|
|
||||||
after = SimpleNamespace(channel=channel)
|
|
||||||
member = self.make_member(456)
|
|
||||||
|
|
||||||
with patch("cogs.intern.random.random", return_value=0.1):
|
|
||||||
import asyncio
|
|
||||||
asyncio.run(cog.on_voice_state_update(member, before, after))
|
|
||||||
connect_mock.assert_awaited_once()
|
|
||||||
|
|
||||||
cog = self.make_cog()
|
|
||||||
connect_mock = AsyncMock()
|
|
||||||
guild = SimpleNamespace(id=123, voice_client=None)
|
|
||||||
channel = self.make_channel(1, guild, connect_mock=connect_mock)
|
|
||||||
before = None
|
|
||||||
after = SimpleNamespace(channel=channel)
|
|
||||||
member = self.make_member(456)
|
|
||||||
|
|
||||||
with patch("cogs.intern.random.random", return_value=0.9):
|
|
||||||
import asyncio
|
|
||||||
asyncio.run(cog.on_voice_state_update(member, before, after))
|
|
||||||
connect_mock.assert_not_awaited()
|
|
||||||
|
|
||||||
def test_move_always_moves_the_bot_when_connected(self):
|
|
||||||
guild = SimpleNamespace(id=123, voice_client=None)
|
|
||||||
voice_client = SimpleNamespace(move_to=AsyncMock(), disconnect=AsyncMock(), guild=guild)
|
|
||||||
cog = self.make_cog(voice_client=voice_client)
|
|
||||||
before_channel = self.make_channel(1, guild)
|
|
||||||
after_channel = self.make_channel(2, guild)
|
|
||||||
import asyncio
|
|
||||||
asyncio.run(self._run_handler(cog, before_channel, after_channel, voice_client))
|
|
||||||
voice_client.move_to.assert_awaited_once_with(after_channel)
|
|
||||||
|
|
||||||
def test_disconnect_always_disconnects_when_mentor_leaves(self):
|
|
||||||
guild = SimpleNamespace(id=123, voice_client=None)
|
|
||||||
voice_client = SimpleNamespace(move_to=AsyncMock(), disconnect=AsyncMock(), guild=guild)
|
|
||||||
cog = self.make_cog(voice_client=voice_client)
|
|
||||||
before_channel = self.make_channel(1, guild)
|
|
||||||
after_channel = None
|
|
||||||
import asyncio
|
|
||||||
asyncio.run(self._run_handler(cog, before_channel, after_channel, voice_client))
|
|
||||||
voice_client.disconnect.assert_awaited_once_with(force=True)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
unittest.main()
|
|
||||||
Reference in New Issue
Block a user