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 .
RUN pip install -r requirements.txt
WORKDIR /opt
WORKDIR /app
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
### Locally
Requires Python `3.9.5`.
Install necessary packages:
```batch
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:
| Field | Explanation |
|----------------|-------------|
| `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) |
| `PREFIX` | Prefix you want to use for your bot |
| `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" \
--PREFIX="yourPrefix" \
--CHANNEL="yourChannel(s)" \
-v /here/your/path/:/opt/db
```
[![status-badge](https://ci.mylloon.fr/api/badges/72/status.svg)](https://ci.mylloon.fr/repos/72)
## Features
Here the list of available commands by default:
Here is the list of available commands by default:
| Command | Alias | Explanation |
|-----------|-----------------------|-------------|
| `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` |
| 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`) |
## Setup with Docker
- Via [docker-compose](./docker-compose.yml)
- Via command line
```bash
docker run -d \
--name="Bot-Tom" \
git.mylloon.fr/confreriedukassoulait/tom:latest \
--ACCESS_TOKEN="yourAccessToken" \
--PREFIX="yourPrefix" \
--CHANNEL="yourChannel(s)" \
-v /here/your/path/:/opt/db
```
## Setup Locally
- 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) |
- Start bot
```bash
cd src; python3 main.py
```

View file

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

View file

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

View file

@ -3,30 +3,40 @@ from os import listdir
from utils.core import load
from utils.commands import CommandesDB
class Client(commands.Bot):
def __init__(self):
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):
CommandesDB().creationTable()
print(f"Logged in as {self.nick}")
async def event_message(self, message):
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
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):
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
raise error
client = Client()
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.run()

View file

@ -2,10 +2,13 @@ from twitchio.ext import commands
from utils.core import load, listCommands
from utils.commands import CommandesDB, existeCommande, existeTouteCommande
def prepare(client: commands.Bot):
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):
self.client = client
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à"
@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"""
if commandName == None or commandMessage == None:
if commandName is None or commandMessage is None:
return
if ctx.author.is_mod:
if existeTouteCommande(self.client, commandName)[0] == False:
if ctx.author.is_mod: # type: ignore
if existeTouteCommande(self.client, commandName)[0] is False:
CommandesDB().ajoutCommande(commandName, commandMessage)
await ctx.send(f"@{ctx.author.name}, commande {commandName} ajoutée !")
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}.")
@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"""
if commandName == None:
if commandName is None:
return
if ctx.author.is_mod:
if ctx.author.is_mod: # type: ignore
if existeCommande(commandName)[0]:
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:
await ctx.send(f"@{ctx.author.name}, {self.notExistingCommand}.")
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):
name = command.name
if command.no_global_checks:
if not ctx.author.is_mod:
if not ctx.author.is_mod: # type: ignore
continue
if command.aliases:
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]}, "
await ctx.send(message[:-2])
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)
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"""
if commandName == None or commandMessage == None:
if commandName is None or commandMessage is None:
return
if ctx.author.is_mod:
if ctx.author.is_mod: # type: ignore
if existeCommande(commandName)[0]:
CommandesDB().suppressionCommande(commandName)
CommandesDB().ajoutCommande(commandName, commandMessage)
@ -81,9 +88,12 @@ class Commandes(commands.Cog): # Les méthodes qui ont no_global_checks de Vrai
else:
await ctx.send(f"@{ctx.author.name}, {self.notModo}.")
@commands.Cog.event()
@commands.Cog.event() # type: ignore
async def event_message(self, message):
if message.content.startswith(self.keys["PREFIX"]):
command = existeCommande(message.content[1:].split(" ")[0]) # récupère le nom de la commande
if command[0]: # vérification si existe
await message.channel.send(f"@{message.author.name}, {command[1]}") # envois le contenu 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
# 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.core import listCommands
class CommandesDB(Database):
def __init__(self):
super().__init__(r"db/bot.sqlite3")
@ -14,7 +15,7 @@ class CommandesDB(Database):
);
"""
self.requete(requete)
def ajoutCommande(self, name: str, message: str):
"""Ajoute une commande."""
requete = """
@ -26,7 +27,7 @@ class CommandesDB(Database):
);
"""
self.requete(requete, [name, message])
def suppressionCommande(self, name: str):
"""Supprime une commande."""
requete = """
@ -39,6 +40,7 @@ class CommandesDB(Database):
"""Retourne la liste des commandes."""
return self.affichageResultat(self.requete("SELECT * FROM commandes;"))
def existeCommande(command: str):
"""Vérifie qu'une commande existe dans la base de donnée."""
commandes = CommandesDB().listeCommande()
@ -47,6 +49,7 @@ def existeCommande(command: str):
return (True, commande[1])
return (False,)
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."""
commandes = []

View file

@ -2,6 +2,7 @@ from os import environ
from dotenv import load_dotenv
from sys import exit
def load(variables):
"""Load env variables"""
keys = {}
@ -10,7 +11,8 @@ def load(variables):
try:
res = environ[var]
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
except KeyError:
print(f"Please set the environment variable {var} (.env file supported)")
@ -18,6 +20,7 @@ def load(variables):
return keys
def listCommands(client):
cogs = client.cogs.values()
commands = []

View file

@ -1,8 +1,12 @@
import sqlite3
class Database:
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):
"""Connexion à une base de donnée SQLite"""
@ -24,14 +28,14 @@ class Database:
else:
return True
def requete(self, requete, valeurs = None):
def requete(self, requete, valeurs=None):
"""Envois une requête vers la base de données"""
try:
curseur = self.connexion.cursor()
if valeurs:
if type(valeurs) not in [list, tuple]:
valeurs = [valeurs]
curseur.execute(requete, valeurs)
curseur.execute(requete, valeurs)
else:
curseur.execute(requete)
self.connexion.commit()
@ -42,7 +46,7 @@ class Database:
def affichageResultat(self, curseur):
"""Affiche le résultat d'une requête"""
tableau = []
if curseur == None:
if curseur is None:
return tableau
lignes = curseur[0].fetchall()
for ligne in lignes: