From 359dcc8100a321a4a6423f5ff97db18537391f19 Mon Sep 17 00:00:00 2001 From: guimoz Date: Sat, 28 Jan 2017 16:18:57 +0100 Subject: [PATCH 1/3] =?UTF-8?q?Revert=20"Adaptation=20=C3=A0=20la=20nouvel?= =?UTF-8?q?le=20BDD"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 0b9d05966f41118c28dd92d5f9b4a773d2da348a. --- roulette.py | 144 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 87 insertions(+), 57 deletions(-) diff --git a/roulette.py b/roulette.py index f70f32d..c5c155e 100644 --- a/roulette.py +++ b/roulette.py @@ -1,7 +1,5 @@ # -*- coding: utf8 -* -# TODO : renommer 'etat' dela sqlite en 'fin_ban' - from flask import Flask, request, g, redirect, url_for, \ abort, render_template, flash @@ -12,27 +10,36 @@ import MySQLdb as mdb from time import time, localtime, strftime import locale import random -from getpass import getpass # configuration DEBUG = True SECRET_KEY = "\xf3'\xd2\xf7\xa4[.h\x8e\x11|\xda\x00\x9fyS\xfe\xb3(!\x91'6\x16" -USERnom = 'admin' +USERNAME = 'admin' PASSWORD = 'pipo' -SQLITE_FILEnom = '/var/roulette/players.db' +SQLITE_FILENAME = '/var/roulette/players.db' SQLITE_SCHEMA = 'schema.sql' MYSQL_HOST = 'mysql.rez' -MYSQL_USER = 're2o' -MYSQL_DB = 're2o' +MYSQL_USER = 'rezo_admin_ro' +MYSQL_PASSWORD = 'rezopaspipo' +MYSQL_DB = 'rezo_admin' BAN_DURATION = 30. * 60. IMMUNITY_FILE = '/var/www/roulette/immunity' ASSHOLES_FILE = '/var/www/roulette/assholes' -IMMUNITY = [] +IMMUNITY = [ + 'Lazare Olivry', + 'Brieuc Lacroix', + 'Elliot Butty', + 'Jean-Christophe Carli', + 'Juliette Tibayrenc', + 'Elise Laurent', + 'Goulven Kermarec', + 'Siqi Liu', + ] ASSHOLES = [] @@ -46,28 +53,53 @@ locale.setlocale(locale.LC_ALL, 'fr_FR.utf8') # Utilisation de la base SQLite def connect_sqlite(): - return sqlite3.connect(SQLITE_FILEnom) + return sqlite3.connect(SQLITE_FILENAME) def init_db(): - # Initialisation de la base SQLite with closing(connect_sqlite()) as con_sqlite: with app.open_resource('schema.sql') as f: con_sqlite.cursor().executescript(f.read().decode("utf-8")) con_sqlite.commit() - cur_sqlite.execute('''create table players (uid,prenom,nom, etat)''') - cur_sqlite.execute('''create table machines (id,uid_user,ip)''') - for user in User.objects.filter(school=1): - if user.has_access() and user.is_adherent(): - cur_sqlite.execute("""insert into players values (?,?,?,?)""",(user.uid_number, user.nom, user.surnom, 0)) - for m in Machine.objects.filter(user= user): - for i in Interface.objects.filter(machine = m): - cur_sqlite.execute("""insert into machines values (?,?,?) """,(i.id, user.uid_number, i.ipv4.ipv4)) + # Connexion à la base SQLite locale + con_sqlite = connect_sqlite() + cur_sqlite = con_sqlite.cursor() - con_sqlite.commit() - con_sqlite.close() + # Connexion à la base MySQL sur babel + con_mysql = mdb.connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DB, \ + charset='utf8', use_unicode=True) + cur_mysql = con_mysql.cursor(mdb.cursors.DictCursor) + # Remplissage de la table players à partir de la table utilisateurs + cur_mysql.execute("""select id,prenom,nom from utilisateurs + where etat='STATE_ACTIVE' and ecole_id=1 and id<>1 + and typeUtilisateur='membre'""") + rows = cur_mysql.fetchall() + print('players :') + for row in rows: + if row['prenom'] + ' ' + row['nom'] not in IMMUNITY: + print(row) + cur_sqlite.execute("""insert into players values (?,?,?,?)""", \ + ((row["id"]), row["prenom"], row["nom"], 0)) + + # Remplissage de la table ip à partir de la table equipements + cur_mysql.execute("""select equipements.id,utilisateurs.id,equipements.ip + from utilisateurs + inner join equipements on utilisateurs.id=equipements.utilisateur_id + where utilisateurs.ecole_id=1 and utilisateurs.id<>1 + and utilisateurs.etat='STATE_ACTIVE' and equipements.etat='STATE_ACTIVE' + and utilisateurs.typeUtilisateur='membre'""") + rows = cur_mysql.fetchall() + print('machines :') + for row in rows: + print(row) + cur_sqlite.execute("""insert into machines values (?,?,?)""", \ + (row["id"], row["utilisateurs.id"], row["ip"])) + + con_sqlite.commit() + cur_sqlite.close() + cur_mysql.close() def duration_format(seconds): hours = seconds // 3600 @@ -90,20 +122,20 @@ def get_player(player_id): con = connect_sqlite() cur = con.cursor() - cur.execute("""select uid,prenom,nom,etat + cur.execute("""select id,firstname,name,ban_end from players where id=(?)""", [player_id]) row = cur.fetchone() con.close() - return {'uid': row[0], 'prenom': row[1], 'nom': row[2], 'etat': row[3]} + return {'id': row[0], 'firstname': row[1], 'name': row[2], 'ban_end': row[3]} def get_player_from_ip(ip): con = connect_sqlite() cur = con.cursor() - cur.execute("""select players.id,players.prenom,players.nom, - machines.id,machines.ip,players.etat + cur.execute("""select players.id,players.firstname,players.name, + machines.id,machines.ip,players.ban_end from players inner join machines on players.id=machines.player_id where machines.ip=(?)""", [ip]) @@ -113,28 +145,28 @@ def get_player_from_ip(ip): user = None if row is not None: - user = {'uid': row[0], 'prenom': row[1], 'nom': row[2], \ - 'id': row[3], 'ip': row[4], 'etat': row[5]} + user = {'id': row[0], 'firstname': row[1], 'name': row[2], \ + 'machine_id': row[3], 'ip': row[4], 'ban_end': row[5]} return user -def get_player_from_full_nom(prenom, nom): +def get_player_from_full_name(firstname, name): con = connect_sqlite() cur = con.cursor() - cur.execute("""select players.id,players.prenom,players.nom, - machines.id,machines.ip,players.etat + cur.execute("""select players.id,players.firstname,players.name, + machines.id,machines.ip,players.ban_end from players inner join machines on players.id=machines.player_id - where players.prenom=(?) and players.nom=(?)""", [prenom, nom]) + where players.firstname=(?) and players.name=(?)""", [firstname, name]) row = cur.fetchone() con.close() user = None if row is not None: - user = {'id': row[0], 'prenom': row[1], 'nom': row[2], \ - 'id': row[3], 'ip': row[4], 'etat': row[5]} + user = {'id': row[0], 'firstname': row[1], 'name': row[2], \ + 'machine_id': row[3], 'ip': row[4], 'ban_end': row[5]} return user @@ -142,19 +174,17 @@ def is_banned(user_id): con = connect_sqlite() cur = con.cursor() - cur.execute("""select etat from players where id=(?)""", [user_id]) + cur.execute("""select ban_end from players where id=(?)""", [user_id]) - etat = cur.fetchone()[0] + ban_end = cur.fetchone()[0] con.close() - return time() < etat + return time() < ban_end def playable_required(f): @wraps(f) def decorated_function(*args, **kwargs): ip=get_ip() - # if DEBUG and ip == '172.21.3.124': - user = get_player_from_ip(ip) if not user: @@ -171,22 +201,22 @@ def get_players_not_banned(): con = connect_sqlite() cur = con.cursor() - cur.execute("""select id,prenom,nom from players - where (?) > etat """, [time()]) + cur.execute("""select id,firstname,name from players + where (?) > ban_end """, [time()]) rows = cur.fetchall() con.close() - return [{'id': row[0], 'prenom': row[1], 'nom': row[2]} for row in rows] + return [{'id': row[0], 'firstname': row[1], 'name': row[2]} for row in rows] def cheat(player_id, target_id): success = random.choice([True, False]) try: ok = [line.strip().partition(' ') for line in IMMUNITY] - ok = [get_player_from_full_nom(noms[0], noms[2])['id'] for noms in ok] + ok = [get_player_from_full_name(names[0], names[2])['id'] for names in ok] ko = [line.strip().partition(' ') for line in ASSHOLES] - ko = [get_player_from_full_nom(noms[0], noms[2])['id'] for noms in ko] + ko = [get_player_from_full_name(names[0], names[2])['id'] for names in ko] if target_id in ko: success = True @@ -210,14 +240,14 @@ def ban(player_id, target_id, success): con = connect_sqlite() cur = con.cursor() - cur.execute("""select id,etat from players + cur.execute("""select id,ban_end from players where id=(?)""", [banned_player['id']]) - etat = cur.fetchone()[0] - etat = time() + BAN_DURATION + ban_end = cur.fetchone()[0] + ban_end = time() + BAN_DURATION - cur.execute("""update players set etat=(?) - where id=(?)""", [etat, banned_player['id']]) + cur.execute("""update players set ban_end=(?) + where id=(?)""", [ban_end, banned_player['id']]) cur.execute("""insert into bans (player_id,target_id,success,time) values (?,?,?,?)""", [player['id'], target['id'], \ @@ -230,7 +260,7 @@ def unban(player_id): con = connect_sqlite() cur = con.cursor() - cur.execute("""update players set etat=(?) + cur.execute("""update players set ban_end=(?) where id=(?)""", [time() - BAN_DURATION, player_id]) con.commit() @@ -261,11 +291,11 @@ def banned(): if last_ban['target_id'] == player['id'] and last_ban['success'] == 1: source = get_player(last_ban['player_id']) explanation = u'Tu t\'es fait bannir par %s %s.' \ - % (source['prenom'], source['nom']) + % (source['firstname'], source['name']) else: explanation = u'Tu t\'es banni toi-même, pas de chance...' - timeleft = duration_format(int(player['etat'] - time())) + timeleft = duration_format(int(player['ban_end'] - time())) return render_template('banned.html', \ explanation=explanation, timeleft=timeleft) @@ -283,7 +313,7 @@ def banned_ip(): cur.execute("""select machines.ip from players inner join machines on players.id=machines.player_id - where players.etat>(?)""", [time()]) + where players.ban_end>(?)""", [time()]) rows = cur.fetchall() con.close() @@ -314,17 +344,17 @@ def home(): if target['id'] == player['id']: if ban['success']: entry = ('ban', u'%s : %s %s a réussi à t\'avoir.' \ - % (date, source['prenom'], source['nom'])) + % (date, source['firstname'], source['name'])) else: entry = ('warn', u'%s : %s %s a essayé de te bannir, en vain.' \ - % (date, source['prenom'], source['nom'])) + % (date, source['firstname'], source['name'])) else: if ban['success']: entry = ('ok', u'%s : Tu as banni %s %s avec succès.' \ - % (date, target['prenom'], target['nom'])) + % (date, target['firstname'], target['name'])) else: entry = ('ban', u'%s : Tu as échoué en voulant bannir %s %s.' \ - % (date, target['prenom'], target['nom'])) + % (date, target['firstname'], target['name'])) bans_hist.append(entry) @@ -348,14 +378,14 @@ def play(): target = get_player(target_id) ban(player['id'], target_id, True) flash(u'Trop cool, %s a été tranché pour un bon moment.' \ - % target['prenom']) + % target['firstname']) else: ban(player['id'], target_id, False) return banned() # Liste des joueurs non bannis, triée dans l'ordre croissant ou décroissant players = sorted(get_players_not_banned(), \ - key=lambda player: player['prenom'], \ + key=lambda player: player['firstname'], \ reverse = random.choice([True, False])) # sans le joueur actuel From cdb81873990984536d16090a8e57990d79d17413 Mon Sep 17 00:00:00 2001 From: guimoz Date: Sat, 28 Jan 2017 16:20:16 +0100 Subject: [PATCH 2/3] Revert "Correction de renommage" This reverts commit cb40ac8ee9f7da07381aaec4f1b13599998108ee. --- roulette.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/roulette.py b/roulette.py index c5c155e..7194e35 100644 --- a/roulette.py +++ b/roulette.py @@ -43,8 +43,8 @@ IMMUNITY = [ ASSHOLES = [] -app = Flask(__name__) -app.config.from_object(__name__) +app = Flask(__nom__) +app.config.from_object(__nom__) app.secret_key = SECRET_KEY random.seed(time()) @@ -393,5 +393,5 @@ def play(): return render_template('play.html', players=players) -if __name__ == '__main__': +if __nom__ == '__main__': app.run() From ffef53c61dfcb0d82c4754f692b93fbbd6d8fa52 Mon Sep 17 00:00:00 2001 From: guimoz Date: Sat, 28 Jan 2017 16:37:08 +0100 Subject: [PATCH 3/3] Remove tracking of players.db --- .gitignore | 1 + players.db | Bin 29696 -> 0 bytes 2 files changed, 1 insertion(+) delete mode 100644 players.db diff --git a/.gitignore b/.gitignore index 9a05e2d..dc351da 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +players.db # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/players.db b/players.db deleted file mode 100644 index 07c3a9443307927767edef9d353ced0174399db5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29696 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