Compare commits

..
1 Commits
Author SHA1 Message Date
marc 93be37fc91 pipfile.lock update 2022-04-02 17:01:42 +02:00
141 changed files with 1816 additions and 3956 deletions
+3
View File
@@ -4,6 +4,9 @@
Makefile
Dockerfile
docker-compose.yml
README.md
Procfile
run.sh
runtime.txt
Pipfile
Pipfile.lock
-6
View File
@@ -1,7 +1 @@
liberapay: marcpartensky
patreon: marcpartensky
github: marcpartensky
custom:
# - monero:43J2XmRtjDPRLD35UsYGH2AZamFBQz7dt4VdvZXVm3SuadTrvcoXUXBRUNHqukFte23Q5TWXqhbwaLJJgoVQzzbe2AJDANQ
- https://paypal.me/marcpartensky
- https://buymeacoffee.com/marcpartensky
+3 -26
View File
@@ -13,7 +13,7 @@ on:
- master
env:
IMAGE_NAME: discord-bot
IMAGE_NAME: website
jobs:
build:
@@ -32,18 +32,12 @@ jobs:
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push with tag
id: tag-build
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
push: true
tags: marcpartensky/discord-bot:v${{ github.run_number }}.${{ steps.tag.outputs.tag }}
- name: Build and push latest
id: latest-build
uses: docker/build-push-action@v2
with:
push: true
tags: marcpartensky/discord-bot:latest
test-python-versions:
runs-on: ${{ matrix.os }}
@@ -67,20 +61,3 @@ jobs:
- name: Display Python version
run: python -c "import sys; print(sys.version)"
deploy:
name: Deploy
runs-on: ubuntu-latest
needs:
- build
steps:
- name: executing remote ssh commands using password
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.KEY}}
port: ${{ secrets.PORT }}
script: |
cd ~/git/docker/services
podman-compose -f discord.yml pull
podman-compose -f discord.yml up -d --force-recreate bot
-53
View File
@@ -1,53 +0,0 @@
name: Docker
on:
push:
branches:
- '*'
env:
IMAGE_NAME: discord-bot
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push latest
id: latest-build
uses: docker/build-push-action@v2
with:
push: false
tags: marcpartensky/discord-bot:latest
test-python-versions:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ['3.8', '3.9', '3.10']
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
cache: pip
- uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ hashFiles('setup.py') }}-${{ hashFiles('requirements.txt') }}
- name: Install requirements
run: pip install -r requirements.txt
- name: Display Python version
run: python -c "import sys; print(sys.version)"
-3
View File
@@ -8,6 +8,3 @@ venv/
.env
.env.dev
./config/marc_store.yml
.python-version
.ash_history
.cache
+20 -10
View File
@@ -1,16 +1,26 @@
FROM python:3.8.13-alpine AS alpine
LABEL maintainer="marc.partensky@proton.me"
FROM python:3.8.12-slim
LABEL maintainer="marc.partensky@gmail.com"
LABEL image="https://hub.docker.com/r/marcpartensky/discord-bot"
LABEL source="https://github.com/marcpartensky/discord-bot"
RUN apt-get update && apt-get upgrade -y && apt-get autoremove -y
RUN apt-get install -y ffmpeg git curl
COPY requirements.txt ./
RUN pip install -U pip
RUN pip install -r requirements.txt
WORKDIR /app
COPY README.md LICENSE pyproject.toml uv.lock build.sh ./
COPY ./discord_bot discord_bot
RUN ./build.sh
WORKDIR /app/discord_bot
COPY ./tts tts
COPY ./libs libs
COPY ./assets assets
COPY ./utils utils
COPY ./config config
COPY ./models models
COPY ./cogs cogs
COPY __main__.py LICENSE ./
ENV DISCORD_BOT_HOST=0.0.0.0
ENV DISCORD_BOT_PORT=8000
ENV DISCORD_BOT_HOST 0.0.0.0
ENV DISCORD_BOT_PORT 8000
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 CMD curl -f http://localhost:8000/live || exit 1
# ENTRYPOINT ["uv", "run", "--directory", "discord_bot", "python", "."]
ENTRYPOINT ["python", "."]
ENTRYPOINT ["python", "__main__.py"]
+10 -8
View File
@@ -1,10 +1,8 @@
run:
uv --directory discord_bot run python .
start:
pipenv run python .
update:
uv lock
uv pip compile requirements.txt \
--universal \
--output-file requirements.txt
pipenv lock --pre --clear
pipenv lock -r > requirements.txt
dev:
docker-compose -f docker-compose.yml up -d --build --force-recreate --remove-orphans dev
prod:
@@ -16,8 +14,12 @@ push: build
deploy: push
DOCKER_HOST=ssh://vps docker service update vps_esclave
setup:
uv pip install
uv shell
pip install pipenv
pipenv shell
pipenv install
recreate:
docker-compose -f docker-compose.yml up --force-recreate dev
fixwithbrew:
brew install python@3.8
sudo ln -sf /home/linuxbrew/.linuxbrew/Cellar/python@3.8/3.8.12_1/bin/python3.8 /usr/bin/python3.8
+46
View File
@@ -0,0 +1,46 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
beautifulsoup4 = "*"
"discord.py[voice]" = "*"
fbchat = "*"
# fbchat = {git = "git://github.com/carpedm20/fbchat.git"}
nightcore = "*"
profanity-check = "*"
progressbar2 = "*"
# pyicloud = "*"
pymongo = "*"
requests = "*"
rich = "*"
translate = "*"
wikipedia = "*"
wolframalpha = "*"
html2text = "*"
gTTS = "2.2.1"
Pillow = "*"
PyGithub = "*"
PyYAML = "*"
youtube_dl = "*"
python-dotenv = "*"
dpy-http-server = "*"
pexpect = "*"
# libs/sherlock
dnspython = "*"
requests-futures = "*"
torrequest = "*"
# libs/google
unidecode = '*'
future = '*'
selenium = "*"
fake-useragent = "*"
[dev-packages]
black = "*"
pylint = "*"
jedi = "*"
[requires]
python_version = "3.8.12"
Generated
+1445
View File
File diff suppressed because it is too large Load Diff
+55 -66
View File
@@ -4,22 +4,22 @@
import os
import random
import itertools
import asyncio
import warnings
from rich import print
import html
import progressbar
warnings.filterwarnings("ignore")
import progressbar
import discord
from discord.ext import commands, tasks
import html
from dotenv import load_dotenv
load_dotenv()
from config.config import prefix, masters, access
from config.config import prefix, masters, delete_after_time, status, access
from config.credentials import token, client_id
from discord.ext import commands, tasks
# TODO
@@ -39,65 +39,67 @@ from config.credentials import token, client_id
# # If we are in a guild, we allow for the user to mention us or use any of the prefixes in our list.
# return commands.when_mentioned_or(*prefixes)(bot, message)
client = commands.Bot(command_prefix=prefix, case_insensitive=True)
client.id = client_id
class Main(commands.Bot):
class Main(commands.Cog):
"""Main cog for commands."""
def __init__(self, *args, client_id, **kwargs):
def __init__(self, bot: commands.Bot):
"""Create the main cog using the bot as argument."""
super().__init__(*args, **kwargs)
self.id = client_id
self.bot = bot
self.invited = "invité"
self.good_invited = "bon invité"
self.bad_invited = "mauvais invité"
self.help_every = 5
async def setup_hook(self) -> None:
"""Automatically run by discord.py."""
self.load_cogs()
self.load_status()
await self.load_cogs()
self.load_icloud()
self.api: PycloudService = None
def load_icloud(self):
"""Charge l'api d'icloud."""
pass
def load_status(self):
"""Charge les statuts."""
self._status = []
for command in self.commands:
self.status = []
for command in self.bot.commands:
msg = prefix + command.name + " " + command.short_doc
self._status.append(msg)
random.shuffle(self._status)
self.status.append(msg)
random.shuffle(self.status)
i = 0
while i < len(self._status):
while i < len(self.status):
msg = prefix + "help Voir les commandes."
self._status.insert(i, msg)
self.status.insert(i, msg)
i += self.help_every
self._status = itertools.cycle(self._status)
async def load_cogs(self):
"""Charge tous les cogs."""
cogs = os.listdir("./cogs")
with progressbar.ProgressBar(max_value=len(cogs)) as bar:
for i, filename in enumerate(cogs):
bar.update(i)
if filename.endswith(".py"):
print(filename)
await self.load_extension(f"cogs.{filename[:-3]}")
self.status = itertools.cycle(self.status)
@commands.command()
async def environment(self, ctx: commands.Context):
"""Affiche l'environnement dans lequel le bot tourne."""
host = os.environ.get("HOST")
print(f"environment: {host}")
await ctx.send(f"> Environment: **{host}**")
@commands.command(name="charge-tous")
@access.admin
async def load_all(self, ctx: commands.Context):
"""Charge tous les cogs."""
await self.load_cogs()
self.load_cogs()
await ctx.send("Tous les cogs sont chargés.")
def load_cogs(self):
"""Charge tous les cogs."""
cogs = os.listdir("./cogs")
with progressbar.ProgressBar(max_value=len(cogs)) as bar:
for i, filename in enumerate(cogs):
bar.update(i)
if filename.endswith(".py"):
self.bot.load_extension(f"cogs.{filename[:-3]}")
@commands.command()
async def list_cogs(self, ctx: commands.Context):
async def cogs(self, ctx: commands.Context):
"""Liste tous les cogs."""
cog_list = []
for file in os.listdir("./cogs"):
@@ -112,40 +114,36 @@ class Main(commands.Bot):
@commands.command()
async def loaded_cogs(self, ctx: commands.Context):
"""Liste tous les cogs."""
loaded_cog_list = [f"- {s[5:]}" for s in self.extensions.keys()]
if len(loaded_cog_list) == 0:
await ctx.send("> Aucun cog chargé")
else:
list_text = "\n".join(loaded_cog_list)
text = "```md\n" + list_text + "\n```"
print(text)
await ctx.send(text)
loaded_cog_list = [f"- {s[5:]}" for s in self.bot.extensions.keys()]
list_text = "\n".join(loaded_cog_list)
text = "```md\n" + list_text + "\n```"
await ctx.send(text)
@commands.command(name="décharge-tous")
@access.admin
async def unload_all(self, ctx: commands.Context):
"""Décharge tous les cogs."""
await self.unload_cogs()
self.unload_cogs()
await ctx.send("Tous les cogs sont déchargés.")
async def unload_cogs(self):
def unload_cogs(self):
"""Décharge tous les cogs."""
for filename in os.listdir("./cogs"):
if filename.endswith(".py"):
await self.unload_extension(f"cogs.{filename[:-3]}")
self.bot.unload_extension(f"cogs.{filename[:-3]}")
@commands.command(name="charge", aliases=["load"])
@access.admin
async def load(self, ctx, extension):
"""Charge les extensions."""
await self.load_extension(f"cogs.{extension}")
self.bot.load_extension(f"cogs.{extension}")
await ctx.send(f"Le cog {extension} a été chargée.")
@commands.command(name="décharge", aliases=["unload"])
@access.admin
async def unload(self, ctx, extension):
"""Décharge les extensions."""
await self.unload_extension(f"cogs.{extension}")
self.bot.unload_extension(f"cogs.{extension}")
await ctx.send(f"Le cog {extension} a été déchargée.")
@tasks.loop(seconds=10)
@@ -159,9 +157,9 @@ class Main(commands.Bot):
- discord.ActivityType.watching,
- discord.ActivityType.competing
"""
await self.change_presence(
await self.bot.change_presence(
activity=discord.Activity(
type=discord.ActivityType.watching, name=next(self._status)
type=discord.ActivityType.watching, name=next(self.status)
)
)
@@ -169,7 +167,7 @@ class Main(commands.Bot):
async def on_ready(self):
"""Déclare être prêt."""
self.change_status.start()
print(f"{self.user} is connected to Discord!")
print(f"{self.bot.user} is connected to Discord!")
@commands.Cog.listener()
async def on_member_join(self, member: discord.Member):
@@ -205,7 +203,7 @@ class Main(commands.Bot):
return False
try:
msg = await self.wait_for(
msg = await self.bot.wait_for(
"message", check=check, timeout=timeout
)
await channel.send("T'es un bon toi. T'iras loin!")
@@ -233,7 +231,7 @@ class Main(commands.Bot):
def check(ctx):
return not ctx.author.bot
msg = await self.wait_for(
msg = await self.bot.wait_for(
"message", check=check, timeout=timeout
)
await channel.send(f"On a dit une minute de silence!")
@@ -275,21 +273,12 @@ class Main(commands.Bot):
# break
async def main():
def setup(bot: commands.Bot):
"""Setup the bot for the main cog."""
os.system("clear")
print(f"prefix: {prefix}")
# warnings.filterwarnings("ignore")
intents = discord.Intents.default()
intents.message_content = True
# client = commands.Bot(command_prefix=prefix, case_insensitive=True)
bot = Main(intents=intents, command_prefix=prefix, case_insensitive=False, client_id=client_id)
async with bot:
await bot.start(token)
bot.add_cog(Main(bot))
if __name__ == "__main__":
asyncio.run(main())
os.system("clear")
setup(client)
client.run(token)

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

-27
View File
@@ -1,27 +0,0 @@
import discord
import os
import dotenv
from discord.ext import commands
dotenv.load_dotenv()
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix=">", intents=intents)
# bot = discord.Bot()
@bot.command()
async def hello(ctx, name: str = None):
name = name or ctx.author.name
await ctx.respond(f"Hello {name}!")
@bot.command(name="Say Hello")
async def hi(ctx, user):
await ctx.respond(f"{ctx.author.mention} says hello to {user.name}!")
@bot.command()
async def ping(ctx):
await ctx.send("pong")
bot.run(os.environ["DISCORD_TOKEN"])
-20
View File
@@ -1,20 +0,0 @@
#!/bin/sh
apk update
apk add curl
curl -LsSf https://astral.sh/uv/install.sh | sh &
apk add --no-cache --virtual .build build-base libffi-dev &
wait
export PATH="$PATH:$HOME/.cargo/bin"
# uv sync -v &
uv pip compile pyproject.toml -o requirements.txt
# uv export > requirements.txt
# sed -i '1,2d' requirements.txt
pip install -r requirements.txt &
apk add ffmpeg postgresql p7zip &
wait
# apk del .build &
# rm -rf /var/cache/apk/* /tmp/* &
# wait
+2 -2
View File
@@ -363,5 +363,5 @@ class Admin(commands.Cog):
await ctx.send(msg)
async def setup(bot):
await bot.add_cog(Admin(bot))
def setup(bot):
bot.add_cog(Admin(bot))
@@ -70,5 +70,5 @@ class Advertisement(commands.Cog):
await ctx.send(f"Le prix de {name} est maintenant à {price}.")
async def setup(bot):
await bot.add_cog(Advertisement(bot))
def setup(bot):
bot.add_cog(Advertisement(bot))
+26 -10
View File
@@ -6,18 +6,16 @@ __author__ = "Marc Partensky"
# from utils.check import check
# from utils import tools
import os, time
import os
import typing
import uuid
import traceback
import discord
import aiofiles
from discord.ext import commands
import server
from aiohttp import web
MAX_CHUNK_SIZE = 8 * 1024 * 1024 # 8 MB
class API(commands.Cog):
"""HTTP web service to interact with the bot."""
@@ -29,18 +27,26 @@ class API(commands.Cog):
host=os.environ.get("DISCORD_BOT_HOST") or "localhost",
port=int(os.environ.get("DISCORD_BOT_PORT") or "8000"),
)
self.guild_id = int(os.environ.get(
"DISCORD_BOT_GUILD_ID"
) or "550332212340326428")
self.contexts: typing.Dict[str, commands.Context] = {}
self.channel_id = int(os.environ["CHANNEL_ID_NOTIF"])
self.guild_id = int(
os.environ.get("DISCORD_BOT_GUILD_ID") or "550332212340326428"
)
self.bot.loop.create_task(self._start_server())
self.contexts: typing.Dict[str, commands.Context] = {}
async def _start_server(self):
"""Task to run the HTTP server."""
await self.bot.wait_until_ready()
await self.server.start()
# async def checker(self, a, request):
# print(a)
# print(request)
# # return request.headers.get("authorization") == "password"
# return True
async def fail_handler(self, request):
return web.json_response(data={"message": "you are not authorized"}, status=401)
@server.add_route(path="/send/user", method="POST", cog="API")
async def send_user(self, request: web.Request):
"""API home path."""
@@ -62,6 +68,15 @@ class API(commands.Cog):
data={"text": "Successfully sent message."}, status=200
)
# def build_context(self, channel: discord.TextChannel, args: list) -> commands.Context:
# """Build a fake context to run bot commands."""
# # state = discord
# data = {}
# state = discord.message.ConnectionState({}, {}, {}, http, loop)
# message = discord.Message(state, channel, data)
# view = discord.channel.TextChannel
# return commands.Context(message, self.bot, view, args=args))
@server.add_route(path="/command/channel", method="POST", cog="API")
async def command_channel(self, request: web.Request):
"""API home path."""
@@ -119,6 +134,7 @@ class API(commands.Cog):
return web.json_response(data=self.bot, status=200)
@server.add_route(path="/live", method="GET", cog="API")
# @server.check(predicate=checker, fail_handler="fail_handler")
async def live(self, request: web.Request):
"""API home path."""
return web.json_response(text="OK", status=200)
@@ -138,5 +154,5 @@ class API(commands.Cog):
return
async def setup(bot: commands.Bot):
await bot.add_cog(API(bot))
def setup(bot: commands.Bot):
bot.add_cog(API(bot))
+2 -2
View File
@@ -541,5 +541,5 @@ class Bank(commands.Cog):
await ctx.send(msg)
async def setup(bot):
await bot.add_cog(Bank(bot))
def setup(bot):
bot.add_cog(Bank(bot))
+8 -5
View File
@@ -1,11 +1,14 @@
from config.config import access, delete_after_time, link
from config.config import access, status, delete_after_time, masters, link
from config import emoji
from utils.date import days, months
from utils import tools
from discord.ext import commands
from discord.ext import commands, tasks
from translate import Translator
from discord import Message, PartialEmoji
from discord.utils import get
from discord import Message, PartialEmoji, Emoji
from bs4 import BeautifulSoup
import requests
import datetime
import discord
@@ -371,5 +374,5 @@ class Basic(commands.Cog):
file = requests.get(url)
await self.bot.user.edit(avatar=file.content)
async def setup(bot):
await bot.add_cog(Basic(bot))
def setup(bot):
bot.add_cog(Basic(bot))
@@ -88,7 +88,7 @@ class BlackJack(commands.Cog):
room.embed_message = await ctx.send(embed=embed)
else:
if room.message:
await room.message.edit(content=content)
room.message.edit(content=content)
else:
room.message = await ctx.send(content)
@@ -103,7 +103,7 @@ class BlackJack(commands.Cog):
@commands.Cog.listener()
async def on_command_error(self, ctx: commands.Context, error):
"""Envoie l'erreur aux utilisateurs."""
await self.send(ctx, error)
self.send(ctx, error)
print("erreur capturée")
@blackjack.command(name="créer", aliases=["create", "c"])
@@ -235,7 +235,7 @@ class BlackJack(commands.Cog):
room = self.getRoom(ctx)
player = room.find(member)
player.drawing = False
await self.send(ctx, f"> {member.name} se couche.")
self.send(ctx, f"> {member.name} se couche.")
@blackjack.command(name="retirer", aliases=["draw", "r", "d"])
async def draw(self, ctx: commands.Context):
@@ -329,5 +329,5 @@ class BlackJack(commands.Cog):
await self.stop(ctx)
async def setup(bot):
await bot.add_cog(BlackJack(bot))
def setup(bot):
bot.add_cog(BlackJack(bot))
@@ -186,5 +186,5 @@ class Casino(commands.Cog):
return account
async def setup(bot):
await bot.add_cog(Casino(bot))
def setup(bot):
bot.add_cog(Casino(bot))
+2 -2
View File
@@ -226,5 +226,5 @@ class Code(commands.Cog):
await ctx.send(text)
async def setup(bot):
await bot.add_cog(Code(bot))
def setup(bot):
bot.add_cog(Code(bot))
@@ -220,8 +220,8 @@ class Commandes(commands.Cog):
await self.search(ctx, *map(lambda r: r.strip(), req.strip().split("|")), regex=False, show_cmd=False)
async def setup(bot: commands.Bot):
await bot.add_cog(Commandes(bot))
def setup(bot: commands.Bot):
bot.add_cog(Commandes(bot))
# TODO: Test the cog
@@ -155,5 +155,5 @@ class Puissance4(commands.Cog):
await self.message.edit(content="Personne ne gagne." + msg)
async def setup(bot):
await bot.add_cog(Puissance4(bot))
def setup(bot):
bot.add_cog(Puissance4(bot))
@@ -175,6 +175,6 @@ class CountDown(commands.Cog):
await self.bot.wait_until_ready()
async def setup(bot):
def setup(bot):
"""Setup the CountDown cog."""
await bot.add_cog(CountDown(bot))
bot.add_cog(CountDown(bot))
+2 -2
View File
@@ -58,5 +58,5 @@ class Debug(commands.Cog):
await ctx.send(embed=embed)
async def setup(bot):
await bot.add_cog(Debug(bot))
def setup(bot):
bot.add_cog(Debug(bot))
@@ -24,7 +24,7 @@ class Docker(commands.Cog):
super().__init__(**kwargs)
self.bot: commands.Bot = bot
self.url: str = os.environ["DOCKER_API_URL"]
self.color = discord.Color(0x4EB3F2)
self.color = discord.Colour(0x4EB3F2)
self.states = dict(
exited=discord.Color.red(),
running=discord.Color.green(),
@@ -324,6 +324,6 @@ class Docker(commands.Cog):
return await ctx.send(f"```md\n{text}\n```")
async def setup(bot: commands.Bot):
def setup(bot: commands.Bot):
"""Setup the ialab cog."""
await bot.add_cog(Docker(bot))
bot.add_cog(Docker(bot))
+2 -2
View File
@@ -40,8 +40,8 @@ class Farm(commands.Cog):
del self.farms[name]
async def setup(bot):
await bot.add_cog(Farm(bot))
def setup(bot):
bot.add_cog(Farm(bot))
"""
+2 -2
View File
@@ -145,5 +145,5 @@ class Game(commands.Cog):
await ctx.send(msg)
async def setup(bot):
await bot.add_cog(Game(bot))
def setup(bot):
bot.add_cog(Game(bot))
+2 -2
View File
@@ -102,5 +102,5 @@ class Git(commands.Cog):
await ctx.send("\n".join([cmd] + out))
async def setup(bot):
await bot.add_cog(Git(bot))
def setup(bot):
bot.add_cog(Git(bot))
+2 -2
View File
@@ -81,6 +81,6 @@ class IALab(commands.Cog):
await self.say(ctx, d["answer"], "en")
async def setup(bot):
def setup(bot):
"""Setup the ialab cog."""
await bot.add_cog(IALab(bot))
bot.add_cog(IALab(bot))
+2 -2
View File
@@ -215,5 +215,5 @@ class Jail(commands.Cog):
# @commands.
async def setup(bot):
await bot.add_cog(Jail(bot))
def setup(bot):
bot.add_cog(Jail(bot))
@@ -20,6 +20,6 @@ class Junior(commands.Cog):
self.bot = bot
async def setup(bot):
def setup(bot):
"""Setup the ialab cog."""
await bot.add_cog(Junior(bot))
bot.add_cog(Junior(bot))
@@ -38,5 +38,5 @@ class LeaderBoard(commands.Cog):
await ctx.send(embed=embed)
async def setup(bot):
await bot.add_cog(LeaderBoard(bot))
def setup(bot):
bot.add_cog(LeaderBoard(bot))
@@ -153,6 +153,6 @@ class Leekwars(commands.Cog):
await ctx.send(msg)
async def setup(bot):
def setup(bot):
"""Setup the leekwars cog."""
await bot.add_cog(Leekwars(bot))
bot.add_cog(Leekwars(bot))
+2 -2
View File
@@ -181,6 +181,6 @@ class Marc(commands.Cog):
return await ctx.send(f"> Stored in **{args[0]}** : **{value}**")
async def setup(bot: commands.Bot):
def setup(bot: commands.Bot):
"""Setup le marc cog."""
await bot.add_cog(Marc(bot))
bot.add_cog(Marc(bot))
@@ -185,5 +185,5 @@ class Messenger(commands.Cog):
return embed
async def setup(bot):
await bot.add_cog(Messenger(bot))
def setup(bot):
bot.add_cog(Messenger(bot))
+2 -2
View File
@@ -273,6 +273,6 @@ class Mongo(commands.Cog):
await ctx.send(f"> {text_path}")
async def setup(bot):
def setup(bot):
"""Setup the cog."""
await bot.add_cog(Mongo(bot))
bot.add_cog(Mongo(bot))
@@ -172,6 +172,6 @@ class Mongo2(commands.Cog):
return await ctx.send("> Rien n'est sélectionné pour l'instant.")
async def setup(bot):
def setup(bot):
mongo = Mongo(bot)
await bot.add_cog(mongo)
bot.add_cog(mongo)
+2 -2
View File
@@ -668,5 +668,5 @@ class Music(commands.Cog):
print("voice_state ok")
async def setup(bot):
await bot.add_cog(Music(bot))
def setup(bot):
bot.add_cog(Music(bot))
@@ -170,5 +170,5 @@ class Music2(commands.Cog):
await ctx.send(result['webpage_url'])
print("this ends now")
async def setup(bot):
await bot.add_cog(Music2(bot))
def setup(bot):
bot.add_cog(Music2(bot))
@@ -234,5 +234,5 @@ class Music3:
else:
skip_count = len(state.skip_votes)
await self.bot.say('Now playing {} [skips: {}/3]'.format(state.current, skip_count))
async def setup(bot):
await bot.add_cog(Music3(bot))
def setup(bot):
bot.add_cog(Music3(bot))
@@ -383,5 +383,5 @@ class Music(commands.Cog):
async def setup(bot):
await bot.add_cog(Music(bot))
def setup(bot):
bot.add_cog(Music(bot))
@@ -369,5 +369,5 @@ class marcMusic(commands.Cog):
await self.join(ctx)
async def setup(bot):
await bot.add_cog(marcMusic(bot))
def setup(bot):
bot.add_cog(marcMusic(bot))
@@ -234,5 +234,5 @@ class Planning(commands.Cog):
return embed
async def setup(bot):
await bot.add_cog(Planning(bot))
def setup(bot):
bot.add_cog(Planning(bot))
@@ -253,5 +253,5 @@ class PlayList(commands.Cog):
# @playlist.command(name)
async def setup(bot):
await bot.add_cog(PlayList(bot))
def setup(bot):
bot.add_cog(PlayList(bot))
+2 -2
View File
@@ -42,5 +42,5 @@ class Quest(commands.Cog, name="Quête"):
"""Retire une quête."""
async def setup(bot: commands.Bot):
await bot.add_cog(Quest(bot))
def setup(bot: commands.Bot):
bot.add_cog(Quest(bot))
+2 -2
View File
@@ -35,5 +35,5 @@ class Quiz(commands.Cog):
await self.quiz.next(ctx).embed
async def setup(bot):
await bot.add_cog(Quiz(bot))
def setup(bot):
bot.add_cog(Quiz(bot))
+2 -2
View File
@@ -110,5 +110,5 @@ class Role(commands.Cog):
# async def role_old(self, ctx:)
async def setup(bot):
await bot.add_cog(Role(bot))
def setup(bot):
bot.add_cog(Role(bot))
+2 -2
View File
@@ -99,6 +99,6 @@ class Shell(commands.Cog):
# shell.close()
async def setup(bot):
def setup(bot):
"""Setup the Shell cog."""
await bot.add_cog(Shell(bot))
bot.add_cog(Shell(bot))
@@ -32,5 +32,5 @@ class Sherlock(commands.Cog):
await ctx.send(embed=embed)
async def setup(bot):
await bot.add_cog(Sherlock(bot))
def setup(bot):
bot.add_cog(Sherlock(bot))
+2 -2
View File
@@ -156,5 +156,5 @@ class Shop(commands.Cog):
pass
async def setup(bot):
await bot.add_cog(Shop(bot))
def setup(bot):
bot.add_cog(Shop(bot))
@@ -129,5 +129,5 @@ class SoundDeck(commands.Cog):
sound.last_used_time = time.time()
async def setup(bot):
await bot.add_cog(SoundDeck(bot))
def setup(bot):
bot.add_cog(SoundDeck(bot))
+2 -2
View File
@@ -110,5 +110,5 @@ class Test(commands.Cog):
await ctx.send(user.id)
async def setup(bot):
await bot.add_cog(Test(bot))
def setup(bot):
bot.add_cog(Test(bot))
@@ -101,5 +101,5 @@ class TextToSpeech(commands.Cog):
ctx.voice_client.play(source, after=after)
async def setup(bot):
await bot.add_cog(TextToSpeech(bot))
def setup(bot):
bot.add_cog(TextToSpeech(bot))
@@ -337,5 +337,5 @@ class TrafficSign(commands.Cog):
"""Compétition sous forme de quiz."""
async def setup(bot):
await bot.add_cog(TrafficSign(bot))
def setup(bot):
bot.add_cog(TrafficSign(bot))
+2 -2
View File
@@ -28,5 +28,5 @@ class Troll(commands.Cog):
await ctx.send("En développement")
async def setup(bot):
await bot.add_cog(Troll(bot))
def setup(bot):
bot.add_cog(Troll(bot))
@@ -70,5 +70,5 @@ class TypingRace(commands.Cog):
await ctx.send(msg)
async def setup(bot):
await bot.add_cog(TypingRace(bot))
def setup(bot):
bot.add_cog(TypingRace(bot))
+2 -2
View File
@@ -269,6 +269,6 @@ class User(commands.Cog, name="Utilisateur"):
)
async def setup(bot):
def setup(bot):
"""Setup the user cog."""
await bot.add_cog(User(bot))
bot.add_cog(User(bot))
@@ -69,7 +69,7 @@ class VoiceChannel(commands.Cog):
logging.info(before)
channel: discord.VoiceChannel = before.channel
members: List[discord.Member] = channel.members
# await self.mute_if_single(members)
await self.mute_if_single(members)
await self.leave_if_alone(members, before.channel)
async def join(
@@ -83,7 +83,7 @@ class VoiceChannel(commands.Cog):
logging.info(after)
channel: discord.VoiceChannel = after.channel
members: List[discord.Member] = channel.members
# await self.mute_if_single(members)
await self.mute_if_single(members)
await self.notify_on_join(member)
await self.join_if_single(members)
@@ -102,7 +102,7 @@ class VoiceChannel(commands.Cog):
before_members: List[discord.Member] = before_channel.members
after_channel: discord.VoiceChannel = after.channel
after_members: List[discord.Member] = after_channel.members
# await self.mute_if_single(before_members)
await self.mute_if_single(before_members)
await self.join_if_single(after_members)
async def mute_if_single(self, members: List[discord.Member]):
@@ -164,5 +164,5 @@ class VoiceChannel(commands.Cog):
break
async def setup(bot):
await bot.add_cog(VoiceChannel(bot))
def setup(bot):
bot.add_cog(VoiceChannel(bot))
+2 -2
View File
@@ -653,5 +653,5 @@ class Web(commands.Cog):
return await ctx.send(shortened_url)
async def setup(bot):
await bot.add_cog(Web(bot))
def setup(bot):
bot.add_cog(Web(bot))
@@ -15,7 +15,7 @@ link = "https://discordapp.com/oauth2/authorize?&client_id=703347349623144549&sc
prefix = os.environ.get("DISCORD_PREFIX") or "."
masters = [478552571510915072] # , 219949869220102147]
wolfram = wolframalpha.Client(wolfram_alpha_id)
ialab_bot_url = os.environ.get("IALAB_URL") or "https://ialab.marcpartensky.com"
ialab_bot_url = "https://ialab.marcpartensky.com"
roles = ["@Maître", "@Admin"]
-355
View File
@@ -1,355 +0,0 @@
#!/bin/env python
# from config.config import masters, access, check
# from config import shops
# from utils import tools
from config.config import access, cluster
from discord.ext import commands, tasks
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
import discord
import os
import logging
import psycopg2, subprocess
import aiofiles
SECONDS = 3600
ROLE = "Backup"
class Backup(commands.Cog):
def __init__(self, bot):
"""Setup postgres logins"""
self.bot = bot
self.host = os.environ.get("POSTGRES_HOST")
self.port = os.environ.get("POSTGRES_PORT")
self.database = os.environ.get("POSTGRES_DATABASE")
self.user = os.environ.get("POSTGRES_USER")
self.password = os.environ.get("POSTGRES_PASSWORD")
self.backup_filename = os.environ.get("POSTGRES_DUMP_FILENAME") or "backup.sql"
self.channel_id = int(os.environ.get("BACKUP_CHANNEL_ID") or 0)
self.default_cron_expression = os.environ.get("BACKUP_DEFAULT_CRON_EXPRESSION") or "0 5 * * *"
self.default_jobname = os.environ.get("BACKUP_DEFAULT_JOBNAME") or "daily"
self.jobs = {}
self.tmp_directory = "/tmp"
self.color = 0x0064a5
self.scheduler = AsyncIOScheduler() # Le scheduler APScheduler
self.scheduler.start() # Démarrer le scheduler
self.setup_default_backup()
# self.connection: psycopg2.connection
# self.cursor: psycopg2.cursor
# self.backup_job.start()
def setup_default_backup(self):
"""Ajoute un job par défaut tous les jours à 5h du matin."""
job_name = self.default_jobname
cron_expression = self.default_cron_expression
trigger = CronTrigger.from_crontab(cron_expression)
job = self.scheduler.add_job(self._backup_postgres, trigger, name=job_name)
self.jobs[job_name] = job
logging.info("Job de backup quotidien à 5h du matin ajouté.")
@property
def storage(self):
return self.bot.get_cog("Storage")
# def setup_jobs(self):
# """Configurer les jobs récurrents avec APScheduler."""
# # Planifier le job quotidien à 5h du matin
# self.scheduler.add_job(
# self._backup_postgres, # La fonction à exécuter
# CronTrigger(hour=5, minute=0), # CronTrigger à 5h00 chaque jour
# name="backup", # Nom du job
# replace_existing=True # Remplacer si le job existe déjà
# )
# logging.info("Job de backup quotidien à 5h du matin configuré.")
async def _execute_pg_command(self, command: list, dump_file_path: str, env: dict):
"""
Execute a PostgreSQL dump command.
"""
try:
_ = subprocess.run(command + ["--file", dump_file_path], env=env, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Backup failed: {e.stderr.decode()}")
async def _backup_postgres(self, *db_names: str) -> str:
"""
Backup specified PostgreSQL databases or all databases if no arguments are provided.
Concatenate the backups into a single file if multiple databases are specified.
"""
if not self.user or not self.password:
raise RuntimeError("PostgreSQL credentials are missing")
env = os.environ.copy()
env["PGPASSWORD"] = self.password
filename = self.backup_filename
if db_names:
# Création d'un fichier de dump unique pour plusieurs bases
dump_file_path = f"{self.tmp_directory}/{filename}"
# Ouvre le fichier pour concaténer les résultats des dumps
async with aiofiles.open(dump_file_path, 'w') as dump_file:
for db_name in db_names:
pg_dump_command = [
"pg_dump",
"--username", self.user,
"--host", self.host,
"--port", self.port,
"--dbname", f"postgresql://{self.user}@{self.host}:{self.port}/{self.database}"
]
try:
# Exécute pg_dump et capture la sortie
result = subprocess.run(pg_dump_command, env=env, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
await dump_file.write(result.stdout.decode()) # Ajoute le dump au fichier
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Backup failed for {db_name}: {e.stderr.decode()}")
else:
# Si aucune base de données spécifiée, on sauvegarde toutes les bases
dump_file_path = f"{self.tmp_directory}/{filename}"
pg_dumpall_command = [
"pg_dumpall",
"--username", self.user,
"--host", self.host,
"--port", self.port
]
try:
await self._execute_pg_command(pg_dumpall_command, dump_file_path, env)
except Exception as e:
raise RuntimeError(f"Backup all databases failed: {str(e)}")
chunks = await self.storage.send_file_in_chunks(dump_file_path, filename, channel_id=self.channel_id)
await self.storage.clean_files(chunks)
os.remove(dump_file_path)
return dump_file_path
# @tasks.loop(seconds=SECONDS)
# async def backup_job(self):
# """La tâche qui sera exécutée tous les jours à 5h."""
# try:
# # Effectuer un backup de toutes les bases
# dump_file_paths = await self._backup_postgres()
# logging.info(f"Backup quotidien réussi : {dump_file_paths}")
# except Exception as e:
# logging.error(f"Erreur lors du backup quotidien : {e}")
def build_embed(self):
"""Return the embed that contains all the infos of the backup cog."""
embed = discord.Embed(
title="PostgreSQL Logins", color=discord.Color(self.color)
)
embed.add_field(name="user", value=str(self.user))
embed.add_field(name="password", value="*"*len(str(self.password)))
embed.add_field(name="database", value=str(self.database))
embed.add_field(name="host", value=str(self.host))
embed.add_field(name="port", value=str(self.port))
embed.add_field(name="channel", value=str(self.channel_id))
return embed
@commands.group(aliases=["bk"])
async def backup(self, ctx: commands.Context):
"""Groupe de commandes du cog 'Backup'."""
if not ctx.invoked_subcommand:
await self.backup_dump(ctx)
# await ctx.send("> No command invoked")
@backup.group(name="role")
@commands.is_owner()
async def role(self, ctx: commands.Context):
"""Groupe de commandes pour controller le role 'Backup'."""
if not ctx.invoked_subcommand:
await ctx.send("> Aucune sous-commande invoquée.")
@role.command(name="create")
async def create_backup_role(self, ctx: commands.Context):
"""Crée un rôle nommé 'Backup' si l'utilisateur est le propriétaire du bot."""
guild = ctx.guild # Obtenir le serveur actuel
role_name = ROLE
# Vérifier si le rôle existe déjà
existing_role = discord.utils.get(guild.roles, name=role_name)
if existing_role:
await ctx.send(f"> Le rôle `{role_name}` existe déjà.")
return
try:
# Créer le nouveau rôle
existing_role = await guild.create_role(name=role_name, color=self.color)
await ctx.author.add_roles(existing_role)
await ctx.send(f"> Le rôle `{role_name}` a été créé et ajouté à **{ctx.author}** avec succès.")
except discord.Forbidden:
await ctx.send("> Je n'ai pas la permission de créer des rôles.")
except discord.HTTPException as e:
await ctx.send(f"> Une erreur est survenue : {e}")
@role.command(name="delete")
async def delete_backup_role(self, ctx: commands.Context):
"""Supprime le rôle nommé 'Backup'."""
role_name = ROLE
# Vérifier si le rôle existe
existing_role = discord.utils.get(ctx.guild.roles, name=role_name)
if not existing_role:
await ctx.send(f"> Le rôle `{role_name}` n'existe pas.")
return
try:
# Supprimer le rôle
await existing_role.delete()
await ctx.send(f"> Le rôle `{role_name}` a été supprimé avec succès.")
except discord.Forbidden:
await ctx.send("> Je n'ai pas la permission de supprimer des rôles.")
except discord.HTTPException as e:
await ctx.send(f"> Une erreur est survenue : {e}")
@backup.group(name="job")
@commands.has_role(ROLE)
async def job(self, ctx: commands.Context):
"""Groupe de commandes pour gérer les jobs de backup."""
if not ctx.invoked_subcommand:
await ctx.send("> Aucune sous-commande invoquée.")
@job.command(name="add")
async def add_job(self, ctx: commands.Context, job_name: str, cron_expression: str):
"""Ajoute un job de backup avec un nom et une expression cron."""
try:
trigger = CronTrigger.from_crontab(cron_expression)
job = self.scheduler.add_job(self._backup_postgres, trigger, name=job_name)
self.jobs[job_name] = job
await ctx.send(f"> Job `{job_name}` ajouté avec succès.")
await ctx.send(embed=await self.build_job_embed())
except Exception as e:
await ctx.send(f"> Erreur lors de l'ajout du job : {e}")
@job.command(name="list")
async def list_jobs(self, ctx: commands.Context):
"""Liste tous les jobs de backup configurés."""
if not self.jobs:
await ctx.send("> Aucun job de backup configuré.")
return
# await ctx.send(f"> Jobs configurés :\n{job_list}")
await ctx.send(embed=await self.build_job_embed())
@job.command(name="delete")
async def delete_job(self, ctx: commands.Context, job_name: str):
"""Supprime un job de backup par son nom."""
job = self.jobs.pop(job_name, None)
if job:
job.remove()
await ctx.send(f"> Job `{job_name}` supprimé avec succès.")
await ctx.send(embed=await self.build_job_embed())
else:
await ctx.send("> Job non trouvé.")
def get_cron_expression(self, trigger: CronTrigger) -> str:
"""Retourne une représentation de l'expression Cron sous forme de chaîne de caractères."""
return f"{trigger.minute} {trigger.hour} {trigger.day} {trigger.month} {trigger.day_of_week}"
async def build_job_embed(self) -> discord.Embed:
"""Affiche les jobs configurés."""
embed = discord.Embed(
title="Jobs de Backup Configurés",
description="Liste de tous les jobs de sauvegarde actuellement configurés.",
color=self.color
)
for job_name, job in self.jobs.items():
embed.add_field(
name=job_name,
value=self.get_cron_expression(job.trigger),
inline=True
)
return embed
@backup.command(name="connect")
@commands.has_role(ROLE)
async def connect_postgres(self, ctx: commands.Context):
"""Change les identifiants postgres avec un string user,password,database,host,port."""
self.connection = psycopg2.connect(
user=self.user,
password=self.password,
host=self.host,
port=self.port,
database=self.database
)
self.cursor = self.connection.cursor()
await ctx.send("Connected to the database")
@backup.command(name="set")
@commands.has_role(ROLE)
async def set_backup(
self,
ctx: commands.Context,
*,
args: str = ""
):
"""
Change les identifiants postgres avec une chaîne de format key=value.
Exemple: ?backup set host=localhost user=postgres
"""
# Analyse des arguments
params = {}
for pair in args.split():
if '=' in pair:
key, value = pair.split('=', 1)
params[key.strip()] = value.strip()
# Mise à jour des attributs
if 'user' in params:
self.user = params['user']
if 'password' in params:
self.password = params['password']
if 'host' in params:
self.host = params['host']
if 'port' in params:
self.port = params['port']
if 'database' in params:
self.database = params['database']
if 'channel_id' in params:
self.channel_id = int(params['channel_id'])
embed = self.build_embed()
await ctx.send(embed=embed)
@backup.command(name="get")
@commands.has_role(ROLE)
async def get_postgres_logins(self, ctx: commands.Context):
"""Affiche les identifiants postgres uniquement si master"""
embed = self.build_embed()
await ctx.send(embed=embed)
@backup.command(name="claim")
@commands.has_role(ROLE)
async def claim_channel(self, ctx: commands.Context):
"""Déclare le salon textuel qui fait office de stockage de backup."""
self.channel_id = ctx.channel.id
embed = self.build_embed()
await ctx.send(embed=embed)
@backup.command(name="dump")
@commands.has_role(ROLE)
async def backup_dump(self, ctx: commands.Context, *db_names: str):
"""
Commande Discord pour créer une sauvegarde (dump) de bases de données PostgreSQL spécifiques
ou de toutes les bases de données si aucun nom n'est fourni.
"""
try:
await self._backup_postgres(*db_names)
await ctx.send("> Backup done")
except Exception as e:
await ctx.send(f"Erreur: {str(e)}")
async def setup(bot):
await bot.add_cog(Backup(bot))
-114
View File
@@ -1,114 +0,0 @@
#!/usr/bin/env python
"""Use groq"""
import typing
import discord
from discord.ext import commands
from discord.ext.commands.core import has_role
from utils.tools import create_role_if_missing, Capturing
import sys
import asyncio, os
from websockets.sync.client import connect
GROQ_ROLE = "Groq"
class Groq(commands.Cog):
"""Control Groq shells"""
def __init__(self, bot: commands.Bot):
"""Initialize the cog with the bot."""
self.host = "localhost"
self.port = 8765
self.bot = bot
self.color: discord.Color = discord.Color.darker_grey()
self.thumbnail: str = "https://wow.groq.com/wp-content/uploads/2021/04/groq-logo.svg"
self.shells: typing.Dict[int, dict] = {}
# self.timeout = 3600
# @commands.command(name="groq-shells")
# async def groq_shells(self, ctx: commands.Context):
# """List opened shells."""
# if len(self.shells) == 0:
# return await ctx.send("> No shell opened.")
# embed = discord.Embed(title="Shells :", color=self.color)
# embed.set_thumbnail(url=self.thumbnail)
# shell_users = []
# for shell_user in map(self.bot.fetch_user, self.shells.keys()):
# shell_users.append(f"- {await shell_user}")
# embed.description = "\n".join(shell_users)
# await ctx.send(embed=embed)
@commands.Cog.listener(name="on_message")
async def on_message(self, msg: discord.Message):
"""Read all messages"""
if msg.author == self.bot.user:
return
if msg.content.startswith(self.bot.command_prefix):
return
if self.hash(msg) in self.shells:
answer = await self.ask(msg, msg.content)
await msg.channel.send(answer)
# async def cog_common_error(self, ctx, error):
# """Close all shell sessions."""
# for shell in self.shells.values():
# shell.close()
@commands.command()
async def groq(self, ctx: commands.Context):
"""Go in groq mode."""
key = self.hash(ctx)
# Create a role if in guild
if ctx.guild:
await create_role_if_missing(ctx, GROQ_ROLE, self.color)
embed = discord.Embed(color=self.color)
embed.set_thumbnail(url=self.thumbnail)
embed.set_author(name=ctx.author.name, icon_url=ctx.author.avatar)
# embed.description = self.shell_path
if ctx.author.id in self.shells:
shell = self.shells[key]
del self.shells[key]
embed.title = "Closed shell"
await ctx.send(embed=embed)
else:
embed.title = "Opened shell"
await ctx.send(embed=embed)
self.shells[key] = dict(messages=[])
def hash(self, ctx):
"""Make a unique hash with channel id and author."""
print(ctx.author.id, ctx.channel.id)
print(self.shells)
return hash((ctx.author.id, ctx.channel.id))
def build_prompt(self, messages: list) -> str:
"""Build prompt."""
prompt = ""
for (me, bot) in messages:
prompt += f"me: {me}\n"
prompt += f"you: {bot}\n"
return prompt
async def ask(self, ctx, message: str) -> str:
"""Return groq answer"""
key = self.hash(ctx)
prompt = self.build_prompt(self.shells[key]["messages"])
prompt += f"me: {message}\n"
prompt += "answer my last message"
with connect(f"ws://{self.host}:{self.port}") as websocket:
websocket.send(prompt)
answer = str(websocket.recv())
print(f"{prompt} => {answer}")
self.shells[key]["messages"].append((message, answer))
return answer
async def setup(bot):
"""Setup the Shell cog."""
await bot.add_cog(Groq(bot))
-66
View File
@@ -1,66 +0,0 @@
"""
All sound effects you want in one command.
"""
import time
import datetime
import discord
from utils.date import months, days
from config.config import cluster, masters
from discord.ext import commands
class Record(commands.Cog):
"""Record voices."""
def __init__(self, bot: commands.Bot):
"""Define the recorder"""
# self.sounds = cluster.sound_deck.sounds
self.bot = bot
self.connections = {}
# @property
# def music(self):
# return self.bot.get_cog("Music")
# @commands.group(name="son", aliases=["sound", "sn"])
# async def sound(self, ctx: commands.Context):
# """Groupe des commandes pour un son."""
# if not ctx.invoked_subcommand:
# return await ctx.send(
# f"> Commande inexistante."
# f"\n> Écrivez `{ctx.bot.command_prefix}help son` pour voir les commandes disponibles."
# ""
# )
# @sound.command(name="voir", aliases=["v", "show", "see"])
# async def show(self, ctx: commands.Context, *, name: str):
# """Affiche un son."""
# embed = self.embed_sound(name)
# await ctx.send(embed=embed)
@commands.command(name="record")
async def record(self, ctx): # If you're using commands.Bot, this will also work.
"""Enregistre une voix."""
voice = ctx.author.voice
if not voice:
await ctx.respond("You aren't in a voice channel!")
vc = await voice.channel.connect() # Connect to the voice channel the author is in.
self.connections.update({ctx.guild.id: vc}) # Updating the cache with the guild and channel.
vc.start_recording(
discord.sinks.WaveSink(), # The sink type to use.
self.once_done, # What to do once done.
ctx.channel # The channel to disconnect from.
)
await ctx.respond("Started recording!")
def once_done(self, *args):
print("its done", args)
async def setup(bot):
await bot.add_cog(Record(bot))
-257
View File
@@ -1,257 +0,0 @@
#!/usr/bin/env python
"""HTTP web service to interact with the bot."""
__author__ = "Marc Partensky"
# from config.config import cluster, access, check
# from utils.check import check
# from utils import tools
import os, time, subprocess
import discord
import aiofiles
from discord.ext import commands
import server
from aiohttp import web
MAX_CHUNK_SIZE = 8 * 1024 * 1024 # 8 MB
class Storage(commands.Cog):
"""Store files on discord using HTTP web service to interact with the bot."""
def __init__(self, bot: commands.Bot):
self.bot = bot
self.channel_id = int(os.environ["CHANNEL_ID_NOTIF"])
self.password = "password"
self.tmp_directory = "/tmp"
def get_timestamp(self):
"""Return a human readable timestamp."""
current_time = time.localtime()
milliseconds = int((time.time() % 1) * 1000)
timestamp = time.strftime(f"%Y-%m-%d_%H-%M-%S_{milliseconds:03d}", current_time)
return timestamp
def sign(self, filepath: str) -> str:
"""Sign a file using its file path and return the signed file path."""
signed_filepath = f"{filepath}.sig"
cmd = f"gpg --output {signed_filepath} --sign {filepath}"
subprocess.run(cmd.split(" "))
return signed_filepath
def encrypt_and_compress(self, filepath: str) -> str:
"""Encrypt and compress a file using its file path
and return the encrypted and compressed file path."""
compressed_filepath = f"{filepath}.7z"
cmd = f"7z a -p{self.password} {compressed_filepath} {filepath}"
subprocess.run(cmd.split(" "))
return compressed_filepath
def compress(self, filepath: str) -> str:
"""Compress a file using its file path and return the compressed file path."""
compressed_filepath = f"{filepath}.7z"
cmd = f"7z -y a {compressed_filepath} {filepath}"
subprocess.run(cmd.split(" "))
return compressed_filepath
async def split_file(self, filepath: str, chunk_size: int = MAX_CHUNK_SIZE):
"""
Split a file into chunks of specified size.
"""
chunk_paths = []
filepath = self.encrypt_and_compress(filepath)
# filepath = self.sign(filepath)
with open(filepath, 'rb') as file:
part_number = 1
while True:
chunk = file.read(chunk_size)
if not chunk:
break
tmp = self.get_timestamp()
chunk_path = f"{self.tmp_directory}/{os.path.basename(filepath)}.{tmp}.{part_number}"
async with aiofiles.open(chunk_path, 'wb') as chunk_file:
await chunk_file.write(chunk)
chunk_paths.append(chunk_path)
part_number += 1
return chunk_paths
async def send_file_in_chunks(self, filepath: str, file_name: str, channel_id: int = 0):
"""
Send a file to Discord in chunks using filepath, file_name and optional channel_id.
"""
channel_id = channel_id or self.channel_id
chunk_paths = await self.split_file(filepath)
channel = self.bot.get_channel(int(channel_id))
if not channel:
raise ValueError("Discord channel not found")
tmp = self.get_timestamp()
for i, chunk_path in enumerate(chunk_paths, start=1):
chunk_file_name = f"{file_name}.{tmp}.{i}"
with open(chunk_path, 'rb') as f:
await channel.send(file=discord.File(f, filename=chunk_file_name))
return chunk_paths
async def clean_files(self, filepaths: list):
"""Remove the specified files."""
for filepath in filepaths:
os.remove(filepath)
@server.add_route(path="/send/file", method="POST", cog="API")
async def send_file_to_channel(self, request: web.Request):
"""Endpoint to send a file to a specific channel by ID."""
filename: str
channel_id: int
reader = await request.multipart()
field = await reader.next()
if field.name == "channel_id":
channel_id = await field.text()
else:
return web.json_response({"error": "Channel ID missing"}, status=400)
field = await reader.next()
if field.name == "file":
filename = field.filename
filepath = f"{self.tmp_directory}/{filename}"
# Save the file temporarily
async with aiofiles.open(filepath, 'wb') as f:
while True:
chunk = await field.read_chunk() # 8192 bytes by default
if not chunk:
break
await f.write(chunk)
else:
return web.json_response({"error": "File missing"}, status=400)
channel: discord.TextChannel = await self.bot.fetch_channel(channel_id)
if not channel:
return web.json_response({"error": "Channel not found"}, status=404)
# await channel.send(file=discord.File(filepath))
await self.send_file_in_chunks(filepath, filename, channel_id)
# Optionally, delete the temp file after sending it
os.remove(filepath)
message = f"Uploaded {filename} channel: {channel_id}"
print(message)
return web.json_response({"message": message}, status=200)
@server.add_route(path="/retrieve/file", method="POST", cog="API")
async def retrieve_file_from_channel(self, request: web.Request):
"""
Endpoint to retrieve, reassemble, and download the original file from a channel
based on its name and the channel ID.
"""
# Extract the JSON data from the request
body = await request.json()
# Extract the required information: channel ID and original filename
channel_id = body.get("channel_id") or self.channel_id
original_filename = body.get("filename")
chosen_timestamp = body.get("timestamp")
print('timestamp:', chosen_timestamp)
if not channel_id or not original_filename:
return web.json_response({"error": "Channel ID or filename missing"}, status=400)
# Fetch the Discord channel
channel: discord.TextChannel = await self.bot.fetch_channel(channel_id)
if not channel:
return web.json_response({"error": "Channel not found"}, status=404)
# Prepare the path to save the reassembled file
reassembled_filepath = f"{self.tmp_directory}/{original_filename}"
# Initialize list to collect all file chunks
file_chunks = []
stop = False
found = False
def is_well_formed(dfilename: str):
if not dfilename.startswith(original_filename):
return False
dextension = dfilename[len(original_filename)+1:]
if dextension.count(".") != 1:
return False
dtimestamp, dpart = dextension.split(".")
# print(f"dtimestamp: {dtimestamp}, {len(dtimestamp)}")
# print(f"dpart: {dpart}")
if len(dtimestamp) != 23:
return False
if not dpart:
return False
# print("passed:", dfilename)
return True
def deconstruct(dfilepath: str):
dfilename = dfilepath.split('/')[-1]
dextension = dfilename[len(original_filename)+1:]
dtimestamp, dpart = dextension.split(".")
return dextension, dtimestamp, dpart
# Iterate through the channel's message history to find the file chunks
async for message in channel.history(limit=1000): # Adjust limit as needed
if stop and found: break
for attachment in message.attachments:
print(f"attachement found: {attachment.filename}")
dfilename = attachment.filename
if not is_well_formed(dfilename):
# print(f"skip and delete {dfilename} because malformed")
await message.delete()
continue
if dfilename.startswith(original_filename):
dextension = dfilename[len(original_filename)+1:]
if dextension.count(".") != 1:
break
dtimestamp, dpart = dextension.split(".")
# print("dfilename", dfilename)
# print("dextension", dextension)
# print("dtimestamp", dtimestamp)
# print("dpart", dpart)
# print("chosen_timestamp", chosen_timestamp)
if not chosen_timestamp:
chosen_timestamp = dtimestamp
if chosen_timestamp == dtimestamp:
# print("found", dtimestamp)
found = True
elif found:
# print("stop", chosen_timestamp, "!=", dtimestamp)
stop = True
break
chunk_path = f"{self.tmp_directory}/{dfilename}"
await attachment.save(chunk_path)
file_chunks.append(chunk_path)
if not file_chunks:
return web.json_response({"error": "No file chunks found in the channel."}, status=404)
# Sort the chunks by part number
print("sorting chunks", file_chunks)
file_chunks.sort(key=lambda filepath: int(deconstruct(filepath)[-1]))
# Reassemble the file
async with aiofiles.open(reassembled_filepath, 'wb') as reassembled_file:
for chunk_path in file_chunks:
async with aiofiles.open(chunk_path, 'rb') as chunk_file:
data = await chunk_file.read()
await reassembled_file.write(data)
# Optionally clean up the chunk files after reassembly
for chunk_path in file_chunks:
os.remove(chunk_path)
# Serve the reassembled file as a download response
headers = {'Content-Disposition': f'attachment; filename="{original_filename}"'}
return web.FileResponse(reassembled_filepath, headers=headers)
async def setup(bot: commands.Bot):
await bot.add_cog(Storage(bot))
+5 -6
View File
@@ -1,19 +1,19 @@
version: "3.9"
services:
dev:
image: marcpartensky/discord-bot:dev
build:
context: .
dockerfile: ./Dockerfile
target: alpine
ports:
- "8000:8000"
env_file:
- .env
volumes:
- ./discord_bot/cogs:/app/cogs
- ./discord_bot/config:/app/config
- ./discord_bot/models:/app/models
- ./discord_bot/__main__.py:/app/__main__.py
- ./cogs:/app/cogs
- ./config:/app/config
- ./models:/app/models
- ./__main__.py:/app/__main__.py
prod:
image: marcpartensky/discord-bot:latest
@@ -21,6 +21,5 @@ services:
build:
context: .
dockerfile: ./Dockerfile
target: alpine
env_file:
- .env

Some files were not shown because too many files have changed in this diff Show More