Ajout de fonctionnalités majeures Twitch, Discord et interface web

Nouvelles fonctionnalités :
- Système de modération Twitch complet (bans, timeouts, avertissements)
- Filtre de liens intelligent pour Twitch avec whitelist/blacklist
- Notifications d'événements Twitch (follows, subs, raids, etc.)
- Système Freeloot Discord avec flux RSS dédié
- Salons automatiques Discord (création/suppression dynamique)
- Authentification utilisateur pour l'interface web (login/register)
- Gestion des utilisateurs et permissions
- Interface de modération Twitch dans la webapp
- Interface de gestion des événements Twitch
- Configuration des paramètres utilisateur
- Migration BDD pour les mots bannis

Améliorations de l'interface :
- Refonte complète des templates (configurations, commandes, humeurs, etc.)
- Nouvelle page de settings utilisateur
- Page de gestion des utilisateurs (admin)
- Page d'erreur 403 personnalisée
- Amélioration de la navigation et du design global
- Intégration de nouvelles sections dans le menu principal

Modifications techniques :
- Ajout de nouveaux modèles en base de données
- Extension des helpers database
- Mise à jour des dépendances (requirements.txt)
- Amélioration de la gestion des annonces Twitch
- Refactorisation du code pour meilleure maintenabilité

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-12 22:37:22 +01:00
co-authored by Cursor
parent d941fdcf9c
commit b274dcaa51
52 changed files with 7700 additions and 665 deletions
+202 -11
View File
@@ -59,9 +59,9 @@ def _doPreImportMigration(cursor:Cursor):
_renameTable('game_bundle', 'game_bundle_old', cursor)
def _doPostImportMigration(cursor:Cursor):
if _tableEmpty('game_bundle', cursor) :
if _tableEmpty('game_bundle', cursor) and _tableExists('game_bundle_old', cursor):
logging.info("remplir game_bundle avec game_bundle_old")
bundles = cursor.execute(f'SELECT * FROM game_bundle_old').fetchall()
bundles = cursor.execute('SELECT * FROM game_bundle_old').fetchall()
for bundle in bundles :
name = bundle[1]
json_data = json.loads(bundle[2])
@@ -90,18 +90,209 @@ def _doPostImportMigration(cursor:Cursor):
except Exception as e:
logging.warning(f"Colonne youtube_notification.{col_name}: {e}")
if _tableExists('commande', cursor) and not _tableHaveColumn('commande', 'twitch_permission', cursor):
try:
cursor.execute("ALTER TABLE commande ADD COLUMN twitch_permission VARCHAR(16) DEFAULT 'viewer'")
logging.info("Colonne twitch_permission ajoutée à commande")
except Exception as e:
logging.warning(f"Colonne commande.twitch_permission: {e}")
def _doAddColumnMigrations(cursor: Cursor):
"""Migrations d'ajout de colonnes. Exécutées à part pour ne pas dépendre du script principal."""
if _tableExists('commande', cursor) and not _tableHaveColumn('commande', 'twitch_permission', cursor):
try:
cursor.execute("ALTER TABLE commande ADD COLUMN twitch_permission VARCHAR(16) DEFAULT 'viewer'")
logging.info("Colonne twitch_permission ajoutée à commande")
except Exception as e:
logging.warning(f"Colonne commande.twitch_permission: {e}")
if _tableExists('live_alert', cursor) and not _tableHaveColumn('live_alert', 'watch_activity', cursor):
try:
cursor.execute("ALTER TABLE live_alert ADD COLUMN watch_activity BOOLEAN NOT NULL DEFAULT 0")
logging.info("Colonne watch_activity ajoutée à live_alert")
except Exception as e:
logging.warning(f"Colonne live_alert.watch_activity: {e}")
# Colonnes embed pour live_alert (message par défaut en embed)
if _tableExists('live_alert', cursor):
live_alert_embed_columns = [
('embed_title', 'VARCHAR(256)'),
('embed_description', 'VARCHAR(2000)'),
('embed_color', 'VARCHAR(8) DEFAULT "9146FF"'),
('embed_footer', 'VARCHAR(2048)'),
('embed_author_name', 'VARCHAR(256)'),
('embed_author_icon', 'VARCHAR(512)'),
('embed_thumbnail', 'BOOLEAN DEFAULT 1'),
('embed_image', 'BOOLEAN DEFAULT 1'),
]
for col_name, col_type in live_alert_embed_columns:
if not _tableHaveColumn('live_alert', col_name, cursor):
try:
cursor.execute(f'ALTER TABLE live_alert ADD COLUMN {col_name} {col_type}')
logging.info(f"Colonne {col_name} ajoutée à live_alert")
except Exception as e:
logging.warning(f"Colonne live_alert.{col_name}: {e}")
# Table twitch_event_notification + seed des 4 types
if not _tableExists('twitch_event_notification', cursor):
try:
cursor.execute("""
CREATE TABLE twitch_event_notification (
id INTEGER PRIMARY KEY AUTOINCREMENT,
event_type VARCHAR(32) UNIQUE NOT NULL,
enable BOOLEAN NOT NULL DEFAULT 1,
notify_twitch_chat BOOLEAN NOT NULL DEFAULT 1,
notify_discord BOOLEAN NOT NULL DEFAULT 0,
discord_channel_id INTEGER NULL,
message_twitch VARCHAR(500) NOT NULL DEFAULT '',
message_discord VARCHAR(2000) NULL,
embed_color VARCHAR(8) DEFAULT '9146FF',
embed_title VARCHAR(256) NULL,
embed_description VARCHAR(2000) NULL,
embed_thumbnail BOOLEAN NOT NULL DEFAULT 1,
last_clip_id VARCHAR(128) NULL
)
""")
logging.info("Table twitch_event_notification créée")
except Exception as e:
logging.warning(f"Table twitch_event_notification: {e}")
if _tableExists('twitch_event_notification', cursor) and _tableEmpty('twitch_event_notification', cursor):
for ev in ('sub', 'follow', 'raid', 'clip'):
try:
cursor.execute(
"INSERT INTO twitch_event_notification (event_type, message_twitch) VALUES (?, ?)",
(ev, 'Merci {user} !' if ev != 'raid' else 'Bienvenue aux viewers de {from_broadcaster_name} !'),
)
except Exception as e:
logging.warning(f"Seed twitch_event_notification {ev}: {e}")
# Table webapp_user (auth)
if not _tableExists('webapp_user', cursor):
try:
cursor.execute("""
CREATE TABLE webapp_user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username VARCHAR(64) UNIQUE NOT NULL,
email VARCHAR(256) UNIQUE NOT NULL,
password_hash VARCHAR(256) NOT NULL,
role VARCHAR(64) NOT NULL DEFAULT 'viewer_twitch',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
)
""")
logging.info("Table webapp_user créée")
except Exception as e:
logging.warning(f"Table webapp_user: {e}")
# Tables webapp_role et webapp_page_permission
if not _tableExists('webapp_role', cursor):
try:
cursor.execute("""
CREATE TABLE webapp_role (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(64) UNIQUE NOT NULL,
level INTEGER NOT NULL DEFAULT 0
)
""")
logging.info("Table webapp_role créée")
except Exception as e:
logging.warning(f"Table webapp_role: {e}")
if not _tableExists('webapp_page_permission', cursor):
try:
cursor.execute("""
CREATE TABLE webapp_page_permission (
id INTEGER PRIMARY KEY AUTOINCREMENT,
page_key VARCHAR(64) UNIQUE NOT NULL,
min_level INTEGER NOT NULL DEFAULT 0,
write_level INTEGER NULL
)
""")
logging.info("Table webapp_page_permission créée")
except Exception as e:
logging.warning(f"Table webapp_page_permission: {e}")
def _doSeedAuth(cursor: Cursor):
"""Seed rôles par défaut et permissions des pages si vides."""
from database.models import ROLE_ORDER
if not _tableExists('webapp_role', cursor):
return
if cursor.execute("SELECT COUNT(*) FROM webapp_role").fetchone()[0] > 0:
return
default_roles = [
("viewer_twitch", 0),
("utilisateur_discord", 1),
("moderateur_discord", 2),
("expert_discord", 3),
("moderateur_twitch", 4),
("super_administrateur", 5),
]
for name, level in default_roles:
try:
cursor.execute("INSERT INTO webapp_role (name, level) VALUES (?, ?)", (name, level))
except Exception as e:
logging.warning(f"Seed role {name}: {e}")
logging.info("Rôles par défaut insérés")
if not _tableExists('webapp_page_permission', cursor):
return
if cursor.execute("SELECT COUNT(*) FROM webapp_page_permission").fetchone()[0] > 0:
return
# page_key, min_level, write_level (NULL = même que min_level)
default_pages = [
("index", 0, None),
("configurations", 5, 5),
("commandes", 1, 2),
("humeurs", 1, 2),
("live_alert", 0, 4),
("announcements", 0, 4),
("twitch_moderation", 0, 4),
("link_filter", 0, 4),
("twitch_events", 0, 4),
("youtube", 1, 2),
("protondb", 1, 2),
("freeloot", 1, 2),
("moderation", 1, 2),
("users", 5, 5),
("settings", 5, 5),
]
for page_key, min_level, wl in default_pages:
try:
cursor.execute(
"INSERT INTO webapp_page_permission (page_key, min_level, write_level) VALUES (?, ?, ?)",
(page_key, min_level, wl),
)
except Exception as e:
logging.warning(f"Seed page {page_key}: {e}")
logging.info("Permissions pages par défaut insérées")
# Inscriptions activées par défaut
if _tableExists("configuration", cursor):
try:
cursor.execute(
"INSERT OR IGNORE INTO configuration (key, value) VALUES ('registration_enabled', 'true')"
)
except Exception as e:
logging.warning(f"Config registration_enabled: {e}")
with webapp.app_context():
with open('database/schema.sql', 'r') as f:
sql = f.read()
connection : Connection = db.session.connection().connection
connection: Connection = db.session.connection().connection
try:
cursor = connection.cursor()
_doPreImportMigration(cursor)
cursor.executescript(sql)
_doPostImportMigration(cursor)
connection.commit()
except Exception as e:
logging.error(f"lors de l'import de la bdd : {e}")
finally:
try:
cursor : Cursor = connection.cursor()
_doPreImportMigration(cursor)
cursor.executescript(sql)
_doPostImportMigration(cursor)
cursor = connection.cursor()
_doAddColumnMigrations(cursor)
_doSeedAuth(cursor)
connection.commit()
cursor.close()
except Exception as e:
logging.error(f"lors de l'import de la bdd : {e}")
finally:
connection.close()
logging.warning(f"migrations colonnes : {e}")
connection.close()
+11
View File
@@ -0,0 +1,11 @@
-- Migration: Ajout de la table twitch_banned_word
-- Date: 2026-02-10
-- Description: Permet de gérer les mots interdits dans le chat Twitch
CREATE TABLE IF NOT EXISTS `twitch_banned_word` (
id INTEGER PRIMARY KEY AUTOINCREMENT,
`word` VARCHAR(256) UNIQUE NOT NULL,
`enabled` BOOLEAN NOT NULL DEFAULT TRUE,
`timeout_duration` INTEGER NOT NULL DEFAULT 60,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
+144 -1
View File
@@ -1,4 +1,62 @@
from datetime import datetime
from database import db
from flask_login import UserMixin
# Rôles par défaut (niveau 0 à 5) — seed en base via migration
ROLE_ORDER = [
"viewer_twitch",
"utilisateur_discord",
"moderateur_discord",
"expert_discord",
"moderateur_twitch",
"super_administrateur",
]
def role_level(role_name: str) -> int:
"""Retourne le niveau du rôle depuis la table webapp_role (-1 si inconnu)."""
if not role_name:
return -1
r = WebappRole.query.filter_by(name=role_name).first()
return r.level if r else -1
class WebappRole(db.Model):
__tablename__ = "webapp_role"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True, nullable=False)
level = db.Column(db.Integer, nullable=False, default=0)
description = db.Column(db.String(256), nullable=True)
color = db.Column(db.String(7), nullable=True, default="#6B7280") # Couleur hexadécimale pour l'affichage
icon = db.Column(db.String(32), nullable=True) # Nom d'icône (ex: 'user', 'shield', 'crown')
class PagePermission(db.Model):
__tablename__ = "webapp_page_permission"
id = db.Column(db.Integer, primary_key=True)
page_key = db.Column(db.String(64), unique=True, nullable=False)
min_level = db.Column(db.Integer, nullable=False, default=0)
write_level = db.Column(db.Integer, nullable=True)
category = db.Column(db.String(32), nullable=True, default="general") # Catégorie: 'general', 'moderation', 'content', 'config'
description = db.Column(db.String(256), nullable=True) # Description de la page
class WebappUser(db.Model, UserMixin):
__tablename__ = "webapp_user"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, nullable=False)
email = db.Column(db.String(256), unique=True, nullable=False)
password_hash = db.Column(db.String(256), nullable=False)
role = db.Column(db.String(64), nullable=False, default="viewer_twitch")
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
def get_level(self) -> int:
return role_level(self.role)
def has_role_at_least(self, min_role: str) -> bool:
return self.get_level() >= role_level(min_role)
def has_level_at_least(self, level: int) -> bool:
return self.get_level() >= level
def has_any_role(self, roles: list) -> bool:
return self.role in roles
class Configuration(db.Model):
key = db.Column(db.String(32), primary_key=True)
@@ -25,7 +83,17 @@ class LiveAlert(db.Model):
online = db.Column(db.Boolean, default=False)
login = db.Column(db.String(128))
notify_channel = db.Column(db.Integer)
message = db.Column(db.String(2000))
message = db.Column(db.String(2000)) # message optionnel avant l'embed
watch_activity = db.Column(db.Boolean, default=False)
# Personnalisation de l'embed Discord (comme YouTube)
embed_title = db.Column(db.String(256))
embed_description = db.Column(db.String(2000))
embed_color = db.Column(db.String(8), default='9146FF') # violet Twitch
embed_footer = db.Column(db.String(2048))
embed_author_name = db.Column(db.String(256))
embed_author_icon = db.Column(db.String(512))
embed_thumbnail = db.Column(db.Boolean, default=True)
embed_image = db.Column(db.Boolean, default=True)
class TwitchAnnouncement(db.Model):
__tablename__ = 'twitch_announcement'
@@ -37,12 +105,14 @@ class TwitchAnnouncement(db.Model):
min_chat_messages = db.Column(db.Integer, default=0)
last_sent = db.Column(db.DateTime, nullable=True)
# Niveaux de permission Twitch pour les commandes personnalisées : viewer, sub, vip, moderator
class Commande(db.Model):
id = db.Column(db.Integer, primary_key=True)
discord_enable = db.Column(db.Boolean, default=True)
twitch_enable = db.Column(db.Boolean, default=True)
trigger = db.Column(db.String(32), unique=True)
response = db.Column(db.String(2000))
twitch_permission = db.Column(db.String(16), default='viewer') # viewer | sub | vip | moderator
class ModerationEvent(db.Model):
id = db.Column(db.Integer, primary_key=True)
@@ -55,6 +125,46 @@ class ModerationEvent(db.Model):
staff_name = db.Column(db.String(256))
duration = db.Column(db.Integer)
class TwitchModerationLog(db.Model):
__tablename__ = 'twitch_moderation_log'
id = db.Column(db.Integer, primary_key=True)
action = db.Column(db.String(32))
moderator = db.Column(db.String(256))
target = db.Column(db.String(256))
details = db.Column(db.String(512))
created_at = db.Column(db.DateTime)
class TwitchLinkFilter(db.Model):
__tablename__ = 'twitch_link_filter'
id = db.Column(db.Integer, primary_key=True)
enabled = db.Column(db.Boolean, default=False)
allow_subscribers = db.Column(db.Boolean, default=True)
allow_vips = db.Column(db.Boolean, default=True)
allow_moderators = db.Column(db.Boolean, default=True)
timeout_duration = db.Column(db.Integer, default=60)
warning_message = db.Column(db.String(500), default="Les liens ne sont pas autorises dans le chat.")
class TwitchAllowedDomain(db.Model):
__tablename__ = 'twitch_allowed_domain'
id = db.Column(db.Integer, primary_key=True)
domain = db.Column(db.String(256), unique=True)
class TwitchPermit(db.Model):
__tablename__ = 'twitch_permit'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(256))
expires_at = db.Column(db.DateTime)
class TwitchAllowedUser(db.Model):
__tablename__ = 'twitch_allowed_user'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(256), unique=True)
class AntiCheatCache(db.Model):
__tablename__ = 'anticheat_cache'
steam_id = db.Column(db.String(32), primary_key=True)
@@ -84,3 +194,36 @@ class YouTubeNotification(db.Model):
embed_thumbnail = db.Column(db.Boolean, default=True)
embed_image = db.Column(db.Boolean, default=True)
class FreeLootEntry(db.Model):
__tablename__ = 'freeloot_entry'
entry_id = db.Column(db.String(256), primary_key=True)
class TwitchEventNotification(db.Model):
"""Configuration des notifications par type d'événement (sub, follow, raid, clip)."""
__tablename__ = 'twitch_event_notification'
id = db.Column(db.Integer, primary_key=True)
event_type = db.Column(db.String(32), unique=True, nullable=False) # sub, follow, raid, clip
enable = db.Column(db.Boolean, default=True)
notify_twitch_chat = db.Column(db.Boolean, default=True)
notify_discord = db.Column(db.Boolean, default=False)
discord_channel_id = db.Column(db.Integer, nullable=True)
message_twitch = db.Column(db.String(500), default='')
message_discord = db.Column(db.String(2000), nullable=True)
embed_color = db.Column(db.String(8), default='9146FF')
embed_title = db.Column(db.String(256), nullable=True)
embed_description = db.Column(db.String(2000), nullable=True)
embed_thumbnail = db.Column(db.Boolean, default=True)
last_clip_id = db.Column(db.String(128), nullable=True) # pour détecter les nouveaux clips
class TwitchBannedWord(db.Model):
"""Mots interdits dans le chat Twitch."""
__tablename__ = 'twitch_banned_word'
id = db.Column(db.Integer, primary_key=True)
word = db.Column(db.String(256), unique=True, nullable=False)
enabled = db.Column(db.Boolean, default=True)
timeout_duration = db.Column(db.Integer, default=60) # durée du timeout en secondes
created_at = db.Column(db.DateTime, default=datetime.utcnow)
+103 -2
View File
@@ -28,7 +28,16 @@ CREATE TABLE IF NOT EXISTS live_alert (
`online` BOOLEAN NOT NULL DEFAULT FALSE,
`login` VARCHAR(128) UNIQUE NOT NULL,
`notify_channel` INTEGER NOT NULL,
`message` VARCHAR(2000) NOT NULL
`message` VARCHAR(2000),
`watch_activity` BOOLEAN NOT NULL DEFAULT FALSE,
`embed_title` VARCHAR(256),
`embed_description` VARCHAR(2000),
`embed_color` VARCHAR(8) DEFAULT '9146FF',
`embed_footer` VARCHAR(2048),
`embed_author_name` VARCHAR(256),
`embed_author_icon` VARCHAR(512),
`embed_thumbnail` BOOLEAN DEFAULT TRUE,
`embed_image` BOOLEAN DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS `twitch_announcement` (
@@ -46,7 +55,8 @@ CREATE TABLE IF NOT EXISTS `commande` (
`discord_enable` BOOLEAN NOT NULL DEFAULT TRUE,
`twitch_enable` BOOLEAN NOT NULL DEFAULT TRUE,
`trigger` VARCHAR(16) UNIQUE NOT NULL,
`response` VARCHAR(2000) NOT NULL
`response` VARCHAR(2000) NOT NULL,
`twitch_permission` VARCHAR(16) DEFAULT 'viewer'
);
CREATE TABLE IF NOT EXISTS `moderation_event` (
@@ -61,6 +71,15 @@ CREATE TABLE IF NOT EXISTS `moderation_event` (
`duration` INTEGER NULL
);
CREATE TABLE IF NOT EXISTS `twitch_moderation_log` (
id INTEGER PRIMARY KEY AUTOINCREMENT,
`action` VARCHAR(32) NOT NULL,
`moderator` VARCHAR(256) NOT NULL,
`target` VARCHAR(256),
`details` VARCHAR(512),
`created_at` DATETIME NOT NULL
);
CREATE TABLE IF NOT EXISTS `anticheat_cache` (
steam_id VARCHAR(32) PRIMARY KEY,
game_name VARCHAR(256) NOT NULL,
@@ -80,6 +99,40 @@ CREATE TABLE IF NOT EXISTS `member_invites` (
`join_date` DATETIME NOT NULL
);
CREATE TABLE IF NOT EXISTS `twitch_link_filter` (
id INTEGER PRIMARY KEY AUTOINCREMENT,
`enabled` BOOLEAN NOT NULL DEFAULT FALSE,
`allow_subscribers` BOOLEAN NOT NULL DEFAULT TRUE,
`allow_vips` BOOLEAN NOT NULL DEFAULT TRUE,
`allow_moderators` BOOLEAN NOT NULL DEFAULT TRUE,
`timeout_duration` INTEGER NOT NULL DEFAULT 60,
`warning_message` VARCHAR(500) NOT NULL DEFAULT 'Les liens ne sont pas autorises dans le chat.'
);
CREATE TABLE IF NOT EXISTS `twitch_allowed_domain` (
id INTEGER PRIMARY KEY AUTOINCREMENT,
`domain` VARCHAR(256) UNIQUE NOT NULL
);
CREATE TABLE IF NOT EXISTS `twitch_permit` (
id INTEGER PRIMARY KEY AUTOINCREMENT,
`username` VARCHAR(256) NOT NULL,
`expires_at` DATETIME NOT NULL
);
CREATE TABLE IF NOT EXISTS `twitch_allowed_user` (
id INTEGER PRIMARY KEY AUTOINCREMENT,
`username` VARCHAR(256) UNIQUE NOT NULL
);
CREATE TABLE IF NOT EXISTS `twitch_banned_word` (
id INTEGER PRIMARY KEY AUTOINCREMENT,
`word` VARCHAR(256) UNIQUE NOT NULL,
`enabled` BOOLEAN NOT NULL DEFAULT TRUE,
`timeout_duration` INTEGER NOT NULL DEFAULT 60,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS `youtube_notification` (
id INTEGER PRIMARY KEY AUTOINCREMENT,
`enable` BOOLEAN NOT NULL DEFAULT TRUE,
@@ -97,3 +150,51 @@ CREATE TABLE IF NOT EXISTS `youtube_notification` (
`embed_thumbnail` BOOLEAN NOT NULL DEFAULT TRUE,
`embed_image` BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE IF NOT EXISTS `webapp_user` (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username VARCHAR(64) UNIQUE NOT NULL,
email VARCHAR(256) UNIQUE NOT NULL,
password_hash VARCHAR(256) NOT NULL,
role VARCHAR(64) NOT NULL DEFAULT 'viewer_twitch',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS `webapp_role` (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(64) UNIQUE NOT NULL,
level INTEGER NOT NULL DEFAULT 0,
description VARCHAR(256) NULL,
color VARCHAR(7) NULL DEFAULT '#6B7280',
icon VARCHAR(32) NULL
);
CREATE TABLE IF NOT EXISTS `webapp_page_permission` (
id INTEGER PRIMARY KEY AUTOINCREMENT,
page_key VARCHAR(64) UNIQUE NOT NULL,
min_level INTEGER NOT NULL DEFAULT 0,
write_level INTEGER NULL,
category VARCHAR(32) NULL DEFAULT 'general',
description VARCHAR(256) NULL
);
CREATE TABLE IF NOT EXISTS `freeloot_entry` (
entry_id VARCHAR(256) PRIMARY KEY
);
-- Notifications d'événements Twitch (sub, follow, raid, clip) : chat Twitch et/ou canal Discord
CREATE TABLE IF NOT EXISTS `twitch_event_notification` (
id INTEGER PRIMARY KEY AUTOINCREMENT,
event_type VARCHAR(32) UNIQUE NOT NULL,
`enable` BOOLEAN NOT NULL DEFAULT TRUE,
notify_twitch_chat BOOLEAN NOT NULL DEFAULT TRUE,
notify_discord BOOLEAN NOT NULL DEFAULT FALSE,
discord_channel_id INTEGER NULL,
message_twitch VARCHAR(500) NOT NULL DEFAULT '',
message_discord VARCHAR(2000) NULL,
embed_color VARCHAR(8) DEFAULT '9146FF',
embed_title VARCHAR(256) NULL,
embed_description VARCHAR(2000) NULL,
embed_thumbnail BOOLEAN NOT NULL DEFAULT TRUE,
last_clip_id VARCHAR(128) NULL
);