-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.py
410 lines (348 loc) · 15 KB
/
server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
import socket
import threading
from PyQt5.QtCore import pyqtSignal, QObject, pyqtSlot
from PyQt5.QtWidgets import QApplication
from mysql.connector import Error
import time
import mysql.connector
# Classe pour gérer les interactions avec la base de données
class DatabaseManager:
"""
Gère les interactions avec la base de données MySQL.
Attributes:
db_config (dict): Configuration de la base de données.
"""
def __init__(self):
# Configuration de la base de données
self.db_config = {
"host": "localhost",
"user": "root",
"password": "votre_mot_de_passe",
"database": "SAE"
}
# Exécute une requête SQL
def execute_query(self, query, params=None):
"""
Exécute une requête SQL sur la base de données.
Args:
query (str): La requête SQL à exécuter.
params (tuple, optional): Les paramètres à utiliser avec la requête.
Returns:
list: Résultats de la requête pour les requêtes SELECT, sinon None.
"""
# Gestion de la connexion à la base de données et exécution de la requête
try:
connection = mysql.connector.connect(**self.db_config)
cursor = connection.cursor()
cursor.execute(query, params or ())
if query.strip().upper().startswith("SELECT"):
results = cursor.fetchall()
return results
connection.commit()
return None
except Error as e:
print(f"Erreur base de données: {e}")
finally:
if connection.is_connected():
cursor.close()
connection.close()
# Bannit un utilisateur en ajoutant son nom d'utilisateur à la table des utilisateurs bannis
def ban_user(self, username):
"""
Bannit un utilisateur en ajoutant son nom d'utilisateur à la table des utilisateurs bannis.
Args:
username (str): Le nom d'utilisateur à bannir.
"""
# Requête SQL pour bannir un utilisateur
sql = "INSERT INTO banned_users (username) VALUES (%s)"
val = (username,)
self.execute_query(sql, val)
# Vérifie si un utilisateur est banni
def is_user_banned(self, username):
"""
Vérifie si un utilisateur est banni.
Args:
username (str): Le nom d'utilisateur à vérifier.
Returns:
bool: True si l'utilisateur est banni, False sinon.
"""
# Requête SQL pour vérifier si un utilisateur est banni
sql = "SELECT username FROM banned_users WHERE username = %s"
val = (username,)
result = self.execute_query(sql, val)
return len(result) > 0
# Débannit un utilisateur
def deban_user(self, username):
"""
Débannit un utilisateur.
Args:
username (str): Le nom d'utilisateur à débannir.
"""
# Requête SQL pour débannir un utilisateur
try:
connection = mysql.connector.connect(**self.db_config)
cursor = connection.cursor()
sql = "DELETE FROM banned_users WHERE username = %s"
val = (username,)
cursor.execute(sql, val)
connection.commit()
except Error as e:
print(f"Erreur lors de la suppression de l'utilisateur banni: {e}")
finally:
if connection.is_connected():
cursor.close()
connection.close()
# Classe principale du serveur
class ServerBackend(QObject):
"""
Classe principale du serveur, gérant les connexions clients et la communication.
Attributes:
new_message (pyqtSignal): Signal émis lors de la réception d'un nouveau message.
new_connection (pyqtSignal): Signal émis lors de la connexion d'un nouveau client.
"""
new_message = pyqtSignal(str)
new_connection = pyqtSignal(str)
def __init__(self, host, port):
"""
Initialise le serveur avec l'adresse et le port spécifiés.
Args:
host (str): L'adresse du serveur.
port (int): Le port du serveur.
"""
super().__init__()
self.host = host
self.port = port
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.clients = {}
self.running = True
self.db_manager = DatabaseManager()
# Envoie un message à tous les clients connectés
def send_server_message(self, message):
"""
Envoie un message de la part du serveur à tous les clients connectés.
Args:
message (str): Le message à envoyer.
"""
formatted_message = f"Server:{message}"
self.broadcast_message(formatted_message)
# Envoie l'historique des messages à un client spécifique
def send_message_history_to_client(self, client_socket):
"""
Envoie l'historique des messages à un client spécifique.
Args:
client_socket (socket): Le socket du client auquel envoyer l'historique.
"""
message_history = self.get_message_history()
history_messages = []
for message in message_history:
username, content, timestamp = message
# Formatez l'horodatage pour n'inclure que l'heure et les minutes
formatted_timestamp = timestamp.strftime("%H:%M")
formatted_message = f"history {formatted_timestamp} - {username}: {content}"
history_messages.append(formatted_message)
# Joindre tous les messages historiques avec des sauts de ligne
full_history = "\n".join(history_messages)
self.send_message_to_client(client_socket, full_history)
# Sauvegarde un message dans la base de données
def save_message_to_db(self, username, channel, message):
"""
Sauvegarde un message dans la base de données.
Args:
username (str): Le nom d'utilisateur qui a envoyé le message.
channel (str): Le canal où le message a été envoyé.
message (str): Le contenu du message.
"""
try:
sql = "INSERT INTO messages (username, content) VALUES (%s, %s)"
val = (username, f"{channel}:{message}")
result = self.db_manager.execute_query(sql, val)
except mysql.connector.Error as err:
print(f"Erreur lors de l'insertion du message: {err}")
# Récupère l'historique des messages de la base de données
def get_message_history(self):
"""
Récupère l'historique des messages de la base de données.
Returns:
list: Une liste des messages enregistrés dans la base de données.
"""
query = "SELECT username, content, timestamp FROM messages"
return self.db_manager.execute_query(query)
def start(self):
"""
Démarre le serveur et commence à écouter les nouvelles connexions.
"""
self.server_socket.bind((self.host, self.port))
self.server_socket.listen()
threading.Thread(target=self.accept_clients, daemon=True).start()
# Accepte les clients et les ajoute à la liste des clients
def accept_clients(self):
"""
Accepte les clients et les ajoute à la liste des clients.
"""
while self.running:
try:
client_socket, client_address = self.server_socket.accept()
print(f"Nouvelle tentative de connexion de {client_address}")
# Attendre brièvement pour que le message du client arrive
time.sleep(0.5) # Attendre 0.5 seconde (ajuster selon les besoins)
try:
message = client_socket.recv(1024).decode()
if message.startswith("Username:"):
username = message.split(":", 1)[1]
else:
print("Format de message inattendu pour le nom d'utilisateur")
client_socket.close()
continue
except Exception as e:
print(f"Erreur lors de la réception du nom d'utilisateur: {e}")
client_socket.close()
continue
# Vérifier si l'utilisateur est banni
if self.db_manager.is_user_banned(username):
print(f"L'utilisateur banni {username} a tenté de se connecter.")
client_socket.close() # Fermer la connexion
continue # Passer à la prochaine tentative de connexion
self.send_message_history_to_client(client_socket)
# Si l'utilisateur n'est pas banni, procédez normalement
threading.Thread(target=self.client_thread, args=(client_socket, username)).start()
except Exception as e:
print(f"Erreur lors de l'acceptation d'une nouvelle connexion: {e}")
# Gère la communication avec un client
def client_thread(self, client_socket, username):
"""
Gère la communication avec un client connecté.
Args:
client_socket (socket): Le socket du client.
username (str): Le nom d'utilisateur du client.
"""
# Ajoutez le client à la liste des clients actifs
print(username)
self.clients[client_socket] = {'address': client_socket.getpeername(), 'username': username}
print(f"Nom d'utilisateur '{username}' reçu de {client_socket.getpeername()}")
while self.running:
try:
message = client_socket.recv(1024).decode()
if not message:
break # Sortir de la boucle si aucun message n'est reçu
# Traitement des messages normaux
formatted_message = f"{username}:{message}"
self.new_message.emit(formatted_message) # Emettre un signal pour l'UI
self.broadcast_message(formatted_message) # Diffuser le message à tous les clients
except Exception as e:
print(f"Erreur: {e}")
break
# Nettoyage après la déconnexion du client
client_socket.close()
if client_socket in self.clients:
del self.clients[client_socket]
print(f"Client déconnecté: {username}")
# Envoie un message à un canal spécifique
def send_message_to_channel(self, channel_name, message):
"""
Envoie un message à un canal spécifique.
Args:
channel_name (str): Le nom du canal où envoyer le message.
message (str): Le message à envoyer.
"""
formatted_message = f"{channel_name}: {message}"
for client_socket in self.clients.keys():
client_socket.send(formatted_message.encode())
# Traite les commandes d'administration (kick, ban, etc.)
def handle_command(self, command, args):
"""
Traite une commande d'administration envoyée par un client.
Args:
command (str): La commande à exécuter.
args (str): Les arguments de la commande.
"""
if command == "kick":
self.kick_user(args)
elif command == "kill":
self.kill_server()
elif command == "ban":
self.ban_user(args)
elif command == "deban":
self.deban_user(args)
# Expulse un utilisateur
def kick_user(self, username):
"""
Expulse un utilisateur du serveur.
Args:
username (str): Le nom d'utilisateur de l'utilisateur à expulser.
"""
client_to_kick = None
for client_socket, info in self.clients.items():
if info['username'] == username and client_socket.fileno() != -1:
client_to_kick = client_socket
break
if client_to_kick:
try:
client_to_kick.close()
except Exception as e:
print(f"Erreur lors de la fermeture du socket pour {username}: {e}")
if client_to_kick in self.clients:
del self.clients[client_to_kick]
print(f"L'utilisateur {username} a été expulsé.")
else:
print(f"L'utilisateur {username} introuvable ou déjà déconnecté.")
# Ferme le serveur
def kill_server(self):
"""
Arrête le serveur et ferme toutes les connexions.
"""
print("Fermeture du serveur...") # Message de débogage
self.running = False
self.server_socket.close()
for client_socket in list(self.clients.keys()):
client_socket.close()
self.clients.clear()
QApplication.quit()
# Bannit un utilisateur
def ban_user(self, username):
"""
Bannit un utilisateur et ferme sa connexion.
Args:
username (str): Le nom d'utilisateur de l'utilisateur à bannir.
"""
self.kick_user(username)
self.db_manager.ban_user(username)
self.broadcast_message(f"Server: L'utilisateur {username} a été banni.")
print(f"L'utilisateur {username} a été banni.")
# Débannit un utilisateur
def deban_user(self, username):
"""
Débannit un utilisateur.
Args:
username (str): Le nom d'utilisateur de l'utilisateur à débannir.
"""
self.db_manager.deban_user(username)
self.broadcast_message(f"Server: L'utilisateur {username} a été débanni.")
print(f"L'utilisateur {username} a été débanni.")
# Envoie un message à un client spécifique
def send_message_to_client(self, client_socket, message):
"""
Envoie un message à un client spécifique.
Args:
client_socket (socket): Le socket du client.
message (str): Le message à envoyer.
"""
client_socket.send(message.encode())
# Diffuse un message à tous les clients connectés
def broadcast_message(self, message):
"""
Diffuse un message à tous les clients connectés.
Args:
message (str): Le message à diffuser.
"""
for client_socket in self.clients.keys():
try:
if client_socket.fileno() != -1: # Vérifiez si le socket est toujours ouvert
client_socket.send(message.encode())
print(f"Message envoyé à {self.clients[client_socket]['username']}") # Debug
# Extraction des informations du message pour les stocker dans la base de données
parts = message.split(':', 2)
if len(parts) == 3:
username, channel, msg = parts
self.save_message_to_db(username, channel, msg)
except Exception as e:
print(f"Erreur lors de l'envoi du message: {e}")