* gitlab -> forgejo * python 3.9 -> 3.11 * readme overhal * format python file * basic PIP convention
This commit is contained in:
parent
b1d4747046
commit
aafde87ee2
11 changed files with 128 additions and 152 deletions
15
.forgejo/publish.yml
Normal file
15
.forgejo/publish.yml
Normal 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
|
|
@ -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
|
|
@ -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" ]
|
||||
|
|
91
README.md
91
README.md
|
@ -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
|
||||
```
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
twitchio==2.0.2
|
||||
python_dotenv==0.19.0
|
||||
twitchio==2.8.2
|
||||
python_dotenv==1.0.1
|
||||
|
|
22
src/main.py
22
src/main.py
|
@ -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 aren’t known)
|
||||
if isinstance(
|
||||
error, commands.errors.CommandNotFound
|
||||
): # Ignore unknown commands (useful because custom commands aren’t 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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue