migration
All checks were successful
ci/woodpecker/manual/publish Pipeline was successful

* gitlab -> forgejo
* python 3.9 -> 3.11
* readme overhal
* format python file
* basic PIP convention
This commit is contained in:
Mylloon 2024-01-24 18:04:10 +01:00
parent b1d4747046
commit aafde87ee2
Signed by: Anri
GPG key ID: A82D63DFF8D1317F
11 changed files with 128 additions and 152 deletions

15
.forgejo/publish.yml Normal file
View file

@ -0,0 +1,15 @@
steps:
publish:
image: woodpeckerci/plugin-docker-buildx:2
settings:
labels:
platform: linux/amd64
repo: git.mylloon.fr/${CI_REPO,,}
auto_tag: true
registry: git.mylloon.fr
username: ${CI_REPO_OWNER}
password:
from_secret: cb_token
when:
event: push
branch: main

View file

@ -1,65 +0,0 @@
###############################################################
# Setting I use for cleaning up image tags #
# - Running cleanup every week #
# - Keeping 1 tag per image name matching : (?:v.\d+|dev) #
# - Removing tags older than 7 days matching the default : .* #
###############################################################
image: docker:stable
stages:
- build
- push
services:
- docker:dind
before_script:
- echo -n $CI_JOB_TOKEN | docker login -u gitlab-ci-token --password-stdin $CI_REGISTRY
Build:
stage: build
script:
- docker pull $CI_REGISTRY_IMAGE:latest || true
- >
docker build
--pull
--build-arg VCS_REF=$CI_COMMIT_SHA
--build-arg VCS_URL=$CI_PROJECT_URL
--cache-from $CI_REGISTRY_IMAGE:latest
--tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
.
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
Push latest:
variables:
GIT_STRATEGY: none
stage: push
only:
- main
script:
- docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest
- docker push $CI_REGISTRY_IMAGE:latest
# Push dev:
# variables:
# GIT_STRATEGY: none
# stage: push
# only:
# - dev
# script:
# - docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
# - docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:dev
# - docker push $CI_REGISTRY_IMAGE:dev
Push tag:
variables:
GIT_STRATEGY: none
stage: push
only:
- tags
script:
- docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME

View file

@ -1,9 +1,12 @@
FROM python:3.9.5-slim FROM python:3.11.6-alpine3.18
RUN apk add dumb-init
COPY requirements.txt . COPY requirements.txt .
RUN pip install -r requirements.txt RUN pip install -r requirements.txt
WORKDIR /opt WORKDIR /app
COPY src . COPY src .
COPY LICENSE .
CMD [ "python", "-u", "./main.py" ] CMD [ "dumb-init", "python", "-u", "./main.py" ]

View file

@ -1,54 +1,47 @@
# Simple Bot Twitch en Python utilisant la librairie [TwitchIO](https://github.com/TwitchIO/TwitchIO). # Tom, a twitch bot
## Setup and Run [![status-badge](https://ci.mylloon.fr/api/badges/72/status.svg)](https://ci.mylloon.fr/repos/72)
### Locally ## Features
Requires Python `3.9.5`. Here is the list of available commands by default:
Install necessary packages: | Command | Alias | Explanation |
| -------- | ------------------------------- | ------------------------------------------------------------------- |
| `add` | | Add command to the database (`add command_name command_message`) |
| `remove` | `delete` | Remove command from database (`remove command_name`) |
| `list` | `commande`, `commandes`, `help` | Print all available commands (internal and user-defined) |
| `edit` | | Modify a database command (`edit command_name new_command_message`) |
```batch ## Setup with Docker
python3 -m pip install -r requirements.txt
```
Rename `.envexample` file in `.env` in root folder, then open the file and complete the fields, there is an explanation below: - Via [docker-compose](./docker-compose.yml)
- Via command line
| Field | Explanation | ```bash
|----------------|-------------| docker run -d \
| `ACCESS_TOKEN` | Access token of the account you created for your bot that you can take [here](https://twitchtokengenerator.com/) (be sure you selected Bot Chat Token) | --name="Bot-Tom" \
| `PREFIX` | Prefix you want to use for your bot | git.mylloon.fr/confreriedukassoulait/tom:latest \
| `CHANNEL` | The name of the your Twitch channel you want the bot to run at (separate by comma) |
Start Bot:
```batch
cd src
python3 main.py
```
### With Docker
With a [docker-compose](https://gitlab.com/ConfrerieDuKassoulait/Bot-Tom/-/blob/main/docker-compose.yml) or in command line:
```batch
docker run -d \
--name="TwitchBot" \
registry.gitlab.com/confreriedukassoulait/bot-tom:latest \
--ACCESS_TOKEN="yourAccessToken" \ --ACCESS_TOKEN="yourAccessToken" \
--PREFIX="yourPrefix" \ --PREFIX="yourPrefix" \
--CHANNEL="yourChannel(s)" \ --CHANNEL="yourChannel(s)" \
-v /here/your/path/:/opt/db -v /here/your/path/:/opt/db
```
``` ## Setup Locally
## Features - Requires Python `3.11.6`
- Install dependencies
```bash
python3 -m pip install -r requirements.txt
```
- Rename `.envexample` file in `.env`, then open the file and complete the fields
| Field | Explanation |
| -------------- | ------------------------------------------------------------------------------------------------------------ |
| `ACCESS_TOKEN` | [Access token of the bot account](https://twitchtokengenerator.com/) (be sure you selected `Bot Chat` Token) |
| `PREFIX` | Prefix used for your bot |
| `CHANNEL` | Twitch channel(s) where the bot will run (separate by comma) |
Here the list of available commands by default: - Start bot
```bash
| Command | Alias | Explanation | cd src; python3 main.py
|-----------|-----------------------|-------------| ```
| `add` | | Ajoute une commande de la base de donnée du bot : `add` `omDeLaCommande` `messageDeLaCommande` |
| `remove` | `delete` | Supprime une commande de la base de donnée du bot : `remove` `nomDeLaCommande` |
| `list` | `commande.s` / `help` | Affiche la liste des commandes (base de donnée + intégré au bot) |
| `edit` | | Modifie une commande de la base de donnée du bot : `add` `nomDeLaCommande` `nouveauMessageDeLaCommande` |

View file

@ -1,7 +1,7 @@
version: "2.1" version: "2.1"
services: services:
bot-tom: tom:
image: registry.gitlab.com/confreriedukassoulait/bot-tom:latest image: git.mylloon.fr/confreriedukassoulait/tom:latest
container_name: Bot-Tom container_name: Bot-Tom
environment: environment:
- ACCESS_TOKEN=yourAccessToken - ACCESS_TOKEN=yourAccessToken

View file

@ -1,2 +1,2 @@
twitchio==2.0.2 twitchio==2.8.2
python_dotenv==0.19.0 python_dotenv==1.0.1

View file

@ -3,10 +3,15 @@ from os import listdir
from utils.core import load from utils.core import load
from utils.commands import CommandesDB from utils.commands import CommandesDB
class Client(commands.Bot): class Client(commands.Bot):
def __init__(self): def __init__(self):
self.keys = load(["ACCESS_TOKEN", "PREFIX", "CHANNEL"]) self.keys = load(["ACCESS_TOKEN", "PREFIX", "CHANNEL"])
super().__init__(token = self.keys["ACCESS_TOKEN"], prefix = self.keys["PREFIX"], initial_channels = self.keys["CHANNEL"]) super().__init__(
token=self.keys["ACCESS_TOKEN"],
prefix=self.keys["PREFIX"],
initial_channels=self.keys["CHANNEL"],
)
async def event_ready(self): async def event_ready(self):
CommandesDB().creationTable() CommandesDB().creationTable()
@ -16,17 +21,22 @@ class Client(commands.Bot):
if message.echo: # Messages with echo set to True are messages sent by the bot if message.echo: # Messages with echo set to True are messages sent by the bot
return return
await self.handle_commands(message) # Let the bot know we want to handle and invoke our commands await self.handle_commands(
message
) # Let the bot know we want to handle and invoke our commands
async def event_command_error(self, _, error): async def event_command_error(self, _, error):
if isinstance(error, commands.errors.CommandNotFound): # Ignore unknown commands (useful because custom commands arent known) if isinstance(
error, commands.errors.CommandNotFound
): # Ignore unknown commands (useful because custom commands arent known)
return return
raise error raise error
client = Client() client = Client()
for file in listdir("modules"): for file in listdir("modules"):
if file.endswith(".py") and file.startswith("-") == False: if file.endswith(".py") and file.startswith("-") is False:
client.load_module(f"modules.{file[:-3]}") client.load_module(f"modules.{file[:-3]}")
client.run() client.run()

View file

@ -2,10 +2,13 @@ from twitchio.ext import commands
from utils.core import load, listCommands from utils.core import load, listCommands
from utils.commands import CommandesDB, existeCommande, existeTouteCommande from utils.commands import CommandesDB, existeCommande, existeTouteCommande
def prepare(client: commands.Bot): def prepare(client: commands.Bot):
client.add_cog(Commandes(client)) client.add_cog(Commandes(client))
class Commandes(commands.Cog): # Les méthodes qui ont no_global_checks de Vrai ne sont autorisés qu'aux modérateurs
class Commandes(commands.Cog):
# Les méthodes qui ont no_global_checks = Vrai ne sont autorisés qu'aux modérateurs"""
def __init__(self, client: commands.Bot): def __init__(self, client: commands.Bot):
self.client = client self.client = client
self.keys = load(["PREFIX"]) self.keys = load(["PREFIX"])
@ -14,12 +17,12 @@ class Commandes(commands.Cog): # Les méthodes qui ont no_global_checks de Vrai
self.alreadyExistingCommand = "cette commande existe déjà" self.alreadyExistingCommand = "cette commande existe déjà"
@commands.command(name="add", no_global_checks=True) @commands.command(name="add", no_global_checks=True)
async def _add(self, ctx: commands.Context, commandName = None, commandMessage = None): async def _add(self, ctx: commands.Context, commandName=None, commandMessage=None):
"""Ajoute une commande de la base de donnée du bot : add nomDeLaCommande messageDeLaCommande""" """Ajoute une commande de la base de donnée du bot : add nomDeLaCommande messageDeLaCommande"""
if commandName == None or commandMessage == None: if commandName is None or commandMessage is None:
return return
if ctx.author.is_mod: if ctx.author.is_mod: # type: ignore
if existeTouteCommande(self.client, commandName)[0] == False: if existeTouteCommande(self.client, commandName)[0] is False:
CommandesDB().ajoutCommande(commandName, commandMessage) CommandesDB().ajoutCommande(commandName, commandMessage)
await ctx.send(f"@{ctx.author.name}, commande {commandName} ajoutée !") await ctx.send(f"@{ctx.author.name}, commande {commandName} ajoutée !")
else: else:
@ -28,14 +31,16 @@ class Commandes(commands.Cog): # Les méthodes qui ont no_global_checks de Vrai
await ctx.send(f"@{ctx.author.name}, {self.notModo}.") await ctx.send(f"@{ctx.author.name}, {self.notModo}.")
@commands.command(name="remove", aliases=["delete"], no_global_checks=True) @commands.command(name="remove", aliases=["delete"], no_global_checks=True)
async def _remove(self, ctx: commands.Context, commandName = None): async def _remove(self, ctx: commands.Context, commandName=None):
"""Supprime une commande de la base de donnée du bot : remove nomDeLaCommande""" """Supprime une commande de la base de donnée du bot : remove nomDeLaCommande"""
if commandName == None: if commandName is None:
return return
if ctx.author.is_mod: if ctx.author.is_mod: # type: ignore
if existeCommande(commandName)[0]: if existeCommande(commandName)[0]:
CommandesDB().suppressionCommande(commandName) CommandesDB().suppressionCommande(commandName)
await ctx.send(f"@{ctx.author.name}, commande {commandName} supprimée !") await ctx.send(
f"@{ctx.author.name}, commande {commandName} supprimée !"
)
else: else:
await ctx.send(f"@{ctx.author.name}, {self.notExistingCommand}.") await ctx.send(f"@{ctx.author.name}, {self.notExistingCommand}.")
else: else:
@ -49,7 +54,7 @@ class Commandes(commands.Cog): # Les méthodes qui ont no_global_checks de Vrai
for command in listCommands(self.client): for command in listCommands(self.client):
name = command.name name = command.name
if command.no_global_checks: if command.no_global_checks:
if not ctx.author.is_mod: if not ctx.author.is_mod: # type: ignore
continue continue
if command.aliases: if command.aliases:
name += " (alias: " name += " (alias: "
@ -64,14 +69,16 @@ class Commandes(commands.Cog): # Les méthodes qui ont no_global_checks de Vrai
message += f"{self.keys['PREFIX']}{commande[0]}, " message += f"{self.keys['PREFIX']}{commande[0]}, "
await ctx.send(message[:-2]) await ctx.send(message[:-2])
else: else:
await ctx.send(f"@{ctx.author.name}, aucune commande enrengistrée dans la base de donnée.") await ctx.send(
f"@{ctx.author.name}, aucune commande enrengistrée dans la base de donnée."
)
@commands.command(name="edit", no_global_checks=True) @commands.command(name="edit", no_global_checks=True)
async def _edit(self, ctx: commands.Context, commandName = None, commandMessage = None): async def _edit(self, ctx: commands.Context, commandName=None, commandMessage=None):
"""Modifie une commande de la base de donnée du bot : add nomDeLaCommande nouveauMessageDeLaCommande""" """Modifie une commande de la base de donnée du bot : add nomDeLaCommande nouveauMessageDeLaCommande"""
if commandName == None or commandMessage == None: if commandName is None or commandMessage is None:
return return
if ctx.author.is_mod: if ctx.author.is_mod: # type: ignore
if existeCommande(commandName)[0]: if existeCommande(commandName)[0]:
CommandesDB().suppressionCommande(commandName) CommandesDB().suppressionCommande(commandName)
CommandesDB().ajoutCommande(commandName, commandMessage) CommandesDB().ajoutCommande(commandName, commandMessage)
@ -81,9 +88,12 @@ class Commandes(commands.Cog): # Les méthodes qui ont no_global_checks de Vrai
else: else:
await ctx.send(f"@{ctx.author.name}, {self.notModo}.") await ctx.send(f"@{ctx.author.name}, {self.notModo}.")
@commands.Cog.event() @commands.Cog.event() # type: ignore
async def event_message(self, message): async def event_message(self, message):
if message.content.startswith(self.keys["PREFIX"]): if message.content.startswith(self.keys["PREFIX"]):
command = existeCommande(message.content[1:].split(" ")[0]) # récupère le nom de la commande command = existeCommande(
message.content[1:].split(" ")[0]
) # récupère le nom de la commande
if command[0]: # vérification si existe if command[0]: # vérification si existe
await message.channel.send(f"@{message.author.name}, {command[1]}") # envois le contenu de la commande # envois le contenu de la commande
await message.channel.send(f"@{message.author.name}, {command[1]}") # type: ignore

View file

@ -1,6 +1,7 @@
from utils.db import Database from utils.db import Database
from utils.core import listCommands from utils.core import listCommands
class CommandesDB(Database): class CommandesDB(Database):
def __init__(self): def __init__(self):
super().__init__(r"db/bot.sqlite3") super().__init__(r"db/bot.sqlite3")
@ -39,6 +40,7 @@ class CommandesDB(Database):
"""Retourne la liste des commandes.""" """Retourne la liste des commandes."""
return self.affichageResultat(self.requete("SELECT * FROM commandes;")) return self.affichageResultat(self.requete("SELECT * FROM commandes;"))
def existeCommande(command: str): def existeCommande(command: str):
"""Vérifie qu'une commande existe dans la base de donnée.""" """Vérifie qu'une commande existe dans la base de donnée."""
commandes = CommandesDB().listeCommande() commandes = CommandesDB().listeCommande()
@ -47,6 +49,7 @@ def existeCommande(command: str):
return (True, commande[1]) return (True, commande[1])
return (False,) return (False,)
def existeTouteCommande(client, commande: str): def existeTouteCommande(client, commande: str):
"""Vérifie qu'une commande existe dans la base de donnée et dans le bot en lui-même.""" """Vérifie qu'une commande existe dans la base de donnée et dans le bot en lui-même."""
commandes = [] commandes = []

View file

@ -2,6 +2,7 @@ from os import environ
from dotenv import load_dotenv from dotenv import load_dotenv
from sys import exit from sys import exit
def load(variables): def load(variables):
"""Load env variables""" """Load env variables"""
keys = {} keys = {}
@ -10,7 +11,8 @@ def load(variables):
try: try:
res = environ[var] res = environ[var]
if var == "CHANNEL": if var == "CHANNEL":
res = list(set(res.split(',')) - {""}) # create a list for the channels and remove blank channels and doubles # create a list for the channels and remove blank channels and doubles
res = list(set(res.split(",")) - {""})
keys[var] = res keys[var] = res
except KeyError: except KeyError:
print(f"Please set the environment variable {var} (.env file supported)") print(f"Please set the environment variable {var} (.env file supported)")
@ -18,6 +20,7 @@ def load(variables):
return keys return keys
def listCommands(client): def listCommands(client):
cogs = client.cogs.values() cogs = client.cogs.values()
commands = [] commands = []

View file

@ -1,8 +1,12 @@
import sqlite3 import sqlite3
class Database: class Database:
def __init__(self, urlDatabase: str): def __init__(self, urlDatabase: str):
self.connexion = self.createConnection(urlDatabase) connexion = self.createConnection(urlDatabase)
if connexion is None:
raise Exception("Can't connect to database")
self.connexion = connexion
def createConnection(self, path): def createConnection(self, path):
"""Connexion à une base de donnée SQLite""" """Connexion à une base de donnée SQLite"""
@ -24,7 +28,7 @@ class Database:
else: else:
return True return True
def requete(self, requete, valeurs = None): def requete(self, requete, valeurs=None):
"""Envois une requête vers la base de données""" """Envois une requête vers la base de données"""
try: try:
curseur = self.connexion.cursor() curseur = self.connexion.cursor()
@ -42,7 +46,7 @@ class Database:
def affichageResultat(self, curseur): def affichageResultat(self, curseur):
"""Affiche le résultat d'une requête""" """Affiche le résultat d'une requête"""
tableau = [] tableau = []
if curseur == None: if curseur is None:
return tableau return tableau
lignes = curseur[0].fetchall() lignes = curseur[0].fetchall()
for ligne in lignes: for ligne in lignes: