Docfile commited on
Commit
96c5973
·
verified ·
1 Parent(s): 6470bc4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +512 -369
app.py CHANGED
@@ -1,393 +1,536 @@
1
- from flask import Flask, render_template, request, send_file
 
 
 
 
 
 
2
  import os
3
- import convertapi
4
- from docx import Document
5
- from docx.shared import Pt, Cm, RGBColor
6
- from docx.enum.text import WD_ALIGN_PARAGRAPH
7
- from docx.enum.table import WD_TABLE_ALIGNMENT
8
- from docx.oxml.ns import nsdecls
9
- from docx.oxml import parse_xml
10
 
11
- # Configuration des identifiants ConvertAPI
12
- convertapi.api_credentials = 'secret_8wCI6pgOP9AxLVJG'
13
 
14
- # --- Classe de génération de document ---
15
- class EvaluationGymnique:
16
- def __init__(self):
17
- self.document = Document()
18
- self.document.sections[0].page_height = Cm(29.7)
19
- self.document.sections[0].page_width = Cm(21)
20
- self.document.sections[0].left_margin = Cm(1.5)
21
- self.document.sections[0].right_margin = Cm(1.5)
22
- self.document.sections[0].top_margin = Cm(1)
23
- self.document.sections[0].bottom_margin = Cm(1)
24
-
25
- # Informations d'en-tête par défaut
26
- self.centre_examen = "Centre d'examen"
27
- self.type_examen = "Bac Général"
28
- self.serie = "Série"
29
- self.etablissement = "Établissement"
30
- self.session = "2025"
31
- self.nom_candidat = "Candidat"
32
-
33
- # Liste vide pour les éléments techniques
34
- self.elements_techniques = []
35
- self.appreciations = ["M", "PM", "NM", "NR"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
- def ajouter_entete_colore(self):
38
- header_paragraph = self.document.add_paragraph()
39
- header_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
40
- header_paragraph.space_after = Pt(6)
41
- header_run = header_paragraph.add_run("ÉVALUATION GYMNASTIQUE")
42
- header_run.bold = True
43
- header_run.font.size = Pt(14)
44
- header_run.font.color.rgb = RGBColor(0, 32, 96)
45
-
46
- header_table = self.document.add_table(rows=3, cols=2)
47
- header_table.style = 'Table Grid'
48
- header_table.autofit = False
49
-
50
- for row in header_table.rows:
51
- row.height = Cm(0.8)
52
- for row in header_table.rows:
53
- for cell in row.cells:
54
- shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="D9E2F3"/>')
55
- cell._tc.get_or_add_tcPr().append(shading_elm)
56
-
57
- # Première ligne
58
- cell = header_table.cell(0, 0)
59
- paragraph = cell.paragraphs[0]
60
- run = paragraph.add_run("Centre d'examen: ")
61
- run.bold = True
62
- run.font.size = Pt(10)
63
- run.font.color.rgb = RGBColor(0, 32, 96)
64
- paragraph.add_run(self.centre_examen).font.size = Pt(10)
65
-
66
- cell = header_table.cell(0, 1)
67
- paragraph = cell.paragraphs[0]
68
- run = paragraph.add_run("Examen: ")
69
- run.bold = True
70
- run.font.size = Pt(10)
71
- run.font.color.rgb = RGBColor(0, 32, 96)
72
- paragraph.add_run(self.type_examen).font.size = Pt(10)
73
-
74
- # Deuxième ligne
75
- cell = header_table.cell(1, 0)
76
- paragraph = cell.paragraphs[0]
77
- run = paragraph.add_run("Série: ")
78
- run.bold = True
79
- run.font.size = Pt(10)
80
- run.font.color.rgb = RGBColor(0, 32, 96)
81
- paragraph.add_run(self.serie).font.size = Pt(10)
82
-
83
- cell = header_table.cell(1, 1)
84
- paragraph = cell.paragraphs[0]
85
- run = paragraph.add_run("Établissement: ")
86
- run.bold = True
87
- run.font.size = Pt(10)
88
- run.font.color.rgb = RGBColor(0, 32, 96)
89
- paragraph.add_run(self.etablissement).font.size = Pt(10)
90
-
91
- # Troisième ligne
92
- cell = header_table.cell(2, 0)
93
- paragraph = cell.paragraphs[0]
94
- run = paragraph.add_run("Session: ")
95
- run.bold = True
96
- run.font.size = Pt(10)
97
- run.font.color.rgb = RGBColor(0, 32, 96)
98
- paragraph.add_run(self.session).font.size = Pt(10)
99
-
100
- cell = header_table.cell(2, 1)
101
- paragraph = cell.paragraphs[0]
102
- run = paragraph.add_run("Candidat: ")
103
- run.bold = True
104
- run.font.size = Pt(10)
105
- run.font.color.rgb = RGBColor(0, 32, 96)
106
- paragraph.add_run(self.nom_candidat).font.size = Pt(10)
107
-
108
- self.document.add_paragraph().space_after = Pt(4)
109
 
110
- def creer_tableau_elements(self):
111
- table = self.document.add_table(rows=len(self.elements_techniques) + 1, cols=5)
112
- table.style = 'Table Grid'
113
- table.alignment = WD_TABLE_ALIGNMENT.CENTER
114
-
115
- # Configuration des largeurs de colonnes
116
- for cell in table.columns[0].cells:
117
- cell.width = Cm(8)
118
- for cell in table.columns[1].cells:
119
- cell.width = Cm(3)
120
- for cell in table.columns[2].cells:
121
- cell.width = Cm(2)
122
- for cell in table.columns[3].cells:
123
- cell.width = Cm(2.5)
124
- for cell in table.columns[4].cells:
125
- cell.width = Cm(2.5)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
- for row in table.rows:
128
- row.height = Cm(1)
129
 
130
- # En-tête du tableau
131
- header_row = table.rows[0]
132
- for cell in header_row.cells:
133
- shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="BDD7EE"/>')
134
- cell._tc.get_or_add_tcPr().append(shading_elm)
135
 
136
- headers = ["ELEMENTS TECHNIQUES", "CATEGORIES D'ELEMENTS TECHNIQUES ET PONDERATION", "", "APPRECIATIONS", "POINTS Accordés"]
137
- for i, header in enumerate(headers):
138
- cell = table.cell(0, i)
139
- cell.text = header
140
- cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
141
- run = cell.paragraphs[0].runs[0]
142
- run.bold = True
143
- run.font.size = Pt(9)
144
- run.font.color.rgb = RGBColor(0, 32, 96)
145
- table.cell(0, 1).merge(table.cell(0, 2))
146
 
147
- # Ajout des éléments techniques
148
- for i, element in enumerate(self.elements_techniques, 1):
149
- element_cell = table.cell(i, 0)
150
- element_cell.text = element["nom"]
151
- element_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.LEFT
152
- run = element_cell.paragraphs[0].runs[0]
153
- run.bold = True
154
- run.font.size = Pt(9)
155
-
156
- categorie_cell = table.cell(i, 1)
157
- categorie_cell.text = element["categorie"]
158
- categorie_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
159
- run = categorie_cell.paragraphs[0].runs[0]
160
- run.bold = True
161
- run.font.size = Pt(9)
162
- run.italic = True
163
 
164
- points_cell = table.cell(i, 2)
165
- points_cell.text = str(element["points"])
166
- points_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
167
- run = points_cell.paragraphs[0].runs[0]
168
- run.bold = True
169
- run.font.size = Pt(9)
170
- run.italic = True
171
 
172
- def ajouter_note_jury(self):
173
- para = self.document.add_paragraph()
174
- para.space_before = Pt(4)
175
- para.space_after = Pt(4)
176
- run = para.add_run("NB1 : Zone réservée aux membres du jury ! Le jury cochera le point correspondant au niveau de réalisation de l'élément gymnique par le candidat.")
177
- run.bold = True
178
- run.font.color.rgb = RGBColor(255, 0, 0)
179
- run.font.size = Pt(9)
180
 
181
- def creer_tableau_recapitulatif(self):
182
- note_table = self.document.add_table(rows=3, cols=13)
183
- note_table.style = 'Table Grid'
184
- note_table.alignment = WD_TABLE_ALIGNMENT.CENTER
185
-
186
- for row in note_table.rows:
187
- row.height = Cm(0.6)
188
- for cell in note_table.rows[0].cells:
189
- shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="BDD7EE"/>')
190
- cell._tc.get_or_add_tcPr().append(shading_elm)
191
-
192
- for col, (type_lettre, points) in enumerate([("A", "1pt"), ("B", "1,5pt"), ("C", "2pts"), ("D", "2,5pts"), ("E", "3pts")]):
193
- idx = col * 2
194
- if idx + 1 < len(note_table.columns):
195
- cell = note_table.cell(0, idx)
196
- cell.merge(note_table.cell(0, idx + 1))
197
- cell.text = f"Type {type_lettre}\n{points}"
198
- cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
199
- for run in cell.paragraphs[0].runs:
200
- run.bold = True
201
- run.font.size = Pt(9)
202
- run.font.color.rgb = RGBColor(0, 32, 96)
203
-
204
- for col, (titre, points) in enumerate([("ROV", "2pts"), ("Projet", "2pts"), ("Réalisation", "16pts")], 10):
205
- if col < len(note_table.columns):
206
- cell = note_table.cell(0, col)
207
- cell.text = f"{titre}\n{points}"
208
- cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
209
- for run in cell.paragraphs[0].runs:
210
- run.bold = True
211
- run.font.size = Pt(9)
212
- run.font.color.rgb = RGBColor(0, 32, 96)
213
-
214
- for col in range(5):
215
- idx = col * 2
216
- if idx + 1 < len(note_table.columns):
217
- neg_cell = note_table.cell(1, idx)
218
- neg_cell.text = "NEG"
219
- neg_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
220
- for run in neg_cell.paragraphs[0].runs:
221
- run.italic = True
222
- run.font.size = Pt(9)
223
-
224
- note_cell = note_table.cell(1, idx + 1)
225
- note_cell.text = "Note"
226
- note_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
227
- for run in note_cell.paragraphs[0].runs:
228
- run.italic = True
229
- run.font.size = Pt(9)
230
-
231
- for col in range(10, 13):
232
- if col < len(note_table.columns):
233
- cell = note_table.cell(1, col)
234
- cell.text = "Note"
235
- cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
236
- for run in cell.paragraphs[0].runs:
237
- run.italic = True
238
- run.font.size = Pt(9)
239
-
240
- for col, type_lettre in enumerate(["A", "B", "C", "D", "E"]):
241
- idx = col * 2
242
- if idx < len(note_table.columns):
243
- neg_cell = note_table.cell(2, idx)
244
- neg_cell.text = ""
245
- neg_cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
246
 
247
- def ajouter_note_candidat_avec_cadre(self):
248
- note_table = self.document.add_table(rows=1, cols=1)
249
- note_table.style = 'Table Grid'
250
- note_table.alignment = WD_TABLE_ALIGNMENT.CENTER
251
-
252
- cell = note_table.cell(0, 0)
253
- shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="E2EFDA"/>')
254
- cell._tc.get_or_add_tcPr().append(shading_elm)
255
-
256
- p = cell.paragraphs[0]
257
- run = p.add_run("NB2: Après le choix des catégories d'éléments gymniques par le candidat, ce dernier remplira la colonne de pointage selon l'orientation suivante: A (0.25; 0.5; 0.75; 1) B (0.25; 0.5; 0.75; 1; 1.25; 1.5) C (0.5; 0.75; 1; 1.25; 1.5; 2) D (0.75; 1; 1.25; 1.5; 2; 2.5) et E (0.75; 1; 1.5; 2; 2.5; 3) également, le candidat devra fournir 2 copies de son projet sur une page! (appréciations: NR, NM, PM, M).")
258
- run.italic = True
259
- run.font.size = Pt(8)
260
 
261
- def ajouter_zone_note(self):
262
- zone_note = self.document.add_table(rows=1, cols=2)
263
- zone_note.style = 'Table Grid'
264
- zone_note.alignment = WD_TABLE_ALIGNMENT.RIGHT
265
-
266
- # Nouvelle largeur fixe pour les deux cellules (réduite)
267
- cell_width = Cm(1.5)
268
-
269
- cell_libelle = zone_note.cell(0, 0)
270
- cell_libelle.width = cell_width
271
- cell_libelle.text = "Note finale"
272
- cell_libelle.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.RIGHT
273
- run = cell_libelle.paragraphs[0].runs[0]
274
- run.bold = True
275
- run.font.size = Pt(10) # réduit de Pt(11) à Pt(10)
276
- run.font.color.rgb = RGBColor(0, 32, 96)
277
-
278
- cell_saisie = zone_note.cell(0, 1)
279
- cell_saisie.width = cell_width
280
- # Diminution de la hauteur de ligne
281
- zone_note.rows[0].height = Cm(1.5)
282
- shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="F2F2F2"/>')
283
- cell_saisie._tc.get_or_add_tcPr().append(shading_elm)
284
- cell_saisie.text = "/20"
285
- cell_saisie.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
286
- run = cell_saisie.paragraphs[0].runs[0]
287
- run.bold = True
288
- run.font.size = Pt(10)
289
 
290
- def ajouter_lignes_correcteurs(self):
291
- para_intro = self.document.add_paragraph()
292
- para_intro.space_before = Pt(6)
293
-
294
- for role in ["Projet", "principal", "ROV"]:
295
- para = self.document.add_paragraph()
296
- para.space_before = Pt(2)
297
- para.space_after = Pt(2)
298
- run = para.add_run(f"Correcteur {role} : ")
299
- run.bold = True
300
- para.add_run(".................................................................")
 
 
 
 
 
 
 
 
 
301
 
302
- # Méthodes de modification des données
303
- def modifier_centre_examen(self, nom):
304
- self.centre_examen = nom
 
 
 
 
 
 
 
305
 
306
- def modifier_type_examen(self, type_examen):
307
- self.type_examen = type_examen
308
 
309
- def modifier_serie(self, serie):
310
- self.serie = serie
311
 
312
- def modifier_etablissement(self, nom):
313
- self.etablissement = nom
 
 
 
 
 
 
 
 
 
 
314
 
315
- def modifier_session(self, annee):
316
- self.session = annee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
 
318
- def modifier_candidat(self, nom):
319
- self.nom_candidat = nom
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
 
321
- def ajouter_element(self, nom, categorie, points):
322
- self.elements_techniques.append({
323
- "nom": nom,
324
- "categorie": categorie,
325
- "points": float(points)
326
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
 
328
- def generer_document(self, nom_fichier="evaluation_gymnastique.docx"):
329
- self.ajouter_entete_colore()
330
- self.creer_tableau_elements()
331
- self.ajouter_note_jury()
332
- self.creer_tableau_recapitulatif()
333
- self.ajouter_lignes_correcteurs()
334
- self.ajouter_zone_note()
335
- self.ajouter_note_candidat_avec_cadre()
336
- self.document.save(nom_fichier)
337
- return nom_fichier
338
-
339
- # --- Application Flask ---
340
- app = Flask(__name__)
341
-
342
- @app.route("/", methods=["GET", "POST"])
343
- def index():
344
- if request.method == "POST":
345
- # Récupération des informations depuis le formulaire
346
- centre_examen = request.form.get("centre_examen", "Centre d'examen")
347
- type_examen = request.form.get("type_examen", "Bac Général")
348
- serie = request.form.get("serie", "Série")
349
- etablissement = request.form.get("etablissement", "Établissement")
350
- session_value = request.form.get("session", "2025")
351
- nom_candidat = request.form.get("nom_candidat", "Candidat")
352
-
353
- # Création et configuration du document
354
- evaluation = EvaluationGymnique()
355
- evaluation.modifier_centre_examen(centre_examen)
356
- evaluation.modifier_type_examen(type_examen)
357
- evaluation.modifier_serie(serie)
358
- evaluation.modifier_etablissement(etablissement)
359
- evaluation.modifier_session(session_value)
360
- evaluation.modifier_candidat(nom_candidat)
361
-
362
- # Récupération des éléments techniques ajoutés dynamiquement
363
- element_names = request.form.getlist("new_element_name")
364
- element_categories = request.form.getlist("new_element_categorie")
365
- element_points = request.form.getlist("new_element_points")
366
- for name, cat, pts in zip(element_names, element_categories, element_points):
367
- if name and cat and pts:
368
- evaluation.ajouter_element(name, cat, pts)
369
-
370
- # Génération du document DOCX
371
- filename = "evaluation_gymnastique.docx"
372
- evaluation.generer_document(filename)
373
-
374
- # Vérification du format de sortie demandé
375
- output_format = request.form.get("format", "docx")
376
- if output_format == "pdf":
377
- # Conversion en PDF via ConvertAPI
378
- result = convertapi.convert('pdf', {
379
- 'File': filename,
380
- 'FileName': 'evaluation_gymnastique',
381
- 'ConvertMetadata': 'false',
382
- 'ConvertHeadings': 'false'
383
- }, from_format='doc')
384
- pdf_filename = "evaluation_gymnastique.pdf"
385
- result.save_files(pdf_filename)
386
- return send_file(pdf_filename, as_attachment=True)
387
- else:
388
- return send_file(filename, as_attachment=True)
389
 
390
- return render_template("index.html")
391
-
392
- if __name__ == "__main__":
393
- app.run(debug=True)
 
1
+ from flask import Flask, render_template, request, jsonify, redirect, url_for
2
+ import requests
3
+ from bs4 import BeautifulSoup
4
+ import random
5
+ import string
6
+ import json
7
+ import threading
8
  import os
9
+ import time
10
+ from datetime import datetime
 
 
 
 
 
11
 
12
+ app = Flask(__name__)
 
13
 
14
+ # Configuration globale
15
+ config = {
16
+ 'base_url': "https://ivoire-startup-tracker-edithbrou.replit.app",
17
+ 'accounts_file': "accounts_data.json",
18
+ 'is_running': False,
19
+ 'progress': {
20
+ 'total': 0,
21
+ 'current': 0,
22
+ 'success': 0,
23
+ 'failed': 0,
24
+ 'last_username': '',
25
+ 'last_status': '',
26
+ 'start_time': None,
27
+ 'end_time': None
28
+ }
29
+ }
30
+
31
+ # Fonction pour générer un nom d'utilisateur aléatoire (min 80 caractères)
32
+ def generate_random_username(min_length=80, max_length=100):
33
+ """Génère un nom d'utilisateur aléatoire d'au moins 80 caractères"""
34
+ length = random.randint(min_length, max_length)
35
+ return ''.join(random.choice(string.ascii_lowercase) for _ in range(length))
36
+
37
+ # Fonction pour générer une adresse email aléatoire
38
+ def generate_random_email():
39
+ """Génère une adresse email aléatoire"""
40
+ username = ''.join(random.choice(string.ascii_lowercase) for _ in range(12))
41
+ domains = ["gmail.com", "yahoo.com", "outlook.com", "example.com"]
42
+ return f"{username}@{random.choice(domains)}"
43
+
44
+ # Fonction pour générer un mot de passe aléatoire
45
+ def generate_random_password(length=12):
46
+ """Génère un mot de passe aléatoire"""
47
+ chars = string.ascii_letters + string.digits + "!@#$%^&*"
48
+ return ''.join(random.choice(chars) for _ in range(length))
49
+
50
+ # Fonction pour créer un compte
51
+ def create_account(is_startup_rep=False):
52
+ """Crée un compte sur le site web"""
53
+ register_url = f"{config['base_url']}/register"
54
 
55
+ # Première requête pour récupérer le token CSRF
56
+ session = requests.Session()
57
+ try:
58
+ response = session.get(register_url)
59
+
60
+ if response.status_code != 200:
61
+ return {'success': False, 'error': f"Erreur lors de l'accès à la page: {response.status_code}"}
62
+
63
+ # Extraire le token CSRF
64
+ soup = BeautifulSoup(response.text, 'html.parser')
65
+ csrf_token = soup.find('input', {'id': 'csrf_token'}).get('value')
66
+
67
+ if not csrf_token:
68
+ return {'success': False, 'error': "Impossible de trouver le token CSRF"}
69
+
70
+ # Générer des informations de compte aléatoires
71
+ username = generate_random_username()
72
+ email = generate_random_email()
73
+ password = generate_random_password()
74
+
75
+ # Préparer les données du formulaire
76
+ form_data = {
77
+ 'csrf_token': csrf_token,
78
+ 'username': username,
79
+ 'email': email,
80
+ 'password': password,
81
+ 'confirm_password': password,
82
+ 'submit': 'Register'
83
+ }
84
+
85
+ # Ajouter l'option startup rep si nécessaire
86
+ if is_startup_rep:
87
+ form_data['is_startup_rep'] = 'y'
88
+
89
+ # Envoyer le formulaire
90
+ headers = {
91
+ 'Referer': register_url,
92
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
93
+ }
94
+
95
+ response = session.post(register_url, data=form_data, headers=headers)
96
+
97
+ result = {
98
+ 'success': response.status_code == 200 or response.status_code == 302,
99
+ 'username': username,
100
+ 'email': email,
101
+ 'password': password,
102
+ 'is_startup_rep': is_startup_rep,
103
+ 'created_at': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
104
+ 'status_code': response.status_code
105
+ }
106
+
107
+ return result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
109
+ except Exception as e:
110
+ return {'success': False, 'error': str(e)}
111
+
112
+ # Fonction pour créer plusieurs comptes en arrière-plan
113
+ def create_accounts_background(num_accounts, startup_ratio=0.3):
114
+ config['progress'] = {
115
+ 'total': num_accounts,
116
+ 'current': 0,
117
+ 'success': 0,
118
+ 'failed': 0,
119
+ 'last_username': '',
120
+ 'last_status': 'Démarrage...',
121
+ 'start_time': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
122
+ 'end_time': None
123
+ }
124
+
125
+ # Charger les comptes existants
126
+ accounts = []
127
+ if os.path.exists(config['accounts_file']):
128
+ try:
129
+ with open(config['accounts_file'], 'r') as f:
130
+ accounts = json.load(f)
131
+ except:
132
+ accounts = []
133
+
134
+ for i in range(num_accounts):
135
+ if not config['is_running']:
136
+ break
137
+
138
+ is_startup = random.random() < startup_ratio
139
 
140
+ config['progress']['current'] = i + 1
141
+ config['progress']['last_status'] = f"Création du compte {i+1}/{num_accounts}..."
142
 
143
+ result = create_account(is_startup_rep=is_startup)
 
 
 
 
144
 
145
+ if result.get('success', False):
146
+ config['progress']['success'] += 1
147
+ config['progress']['last_username'] = result['username']
148
+ config['progress']['last_status'] = f"Compte {i+1} créé avec succès"
149
+ accounts.append(result)
150
+ else:
151
+ config['progress']['failed'] += 1
152
+ config['progress']['last_status'] = f"Échec de la création du compte {i+1}: {result.get('error', 'Erreur inconnue')}"
 
 
153
 
154
+ # Enregistrer les données régulièrement
155
+ with open(config['accounts_file'], 'w') as f:
156
+ json.dump(accounts, f, indent=2)
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
+ # Petite pause pour éviter de surcharger le serveur
159
+ time.sleep(1)
 
 
 
 
 
160
 
161
+ config['is_running'] = False
162
+ config['progress']['end_time'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
163
+ config['progress']['last_status'] = "Terminé"
 
 
 
 
 
164
 
165
+ # Enregistrement final
166
+ with open(config['accounts_file'], 'w') as f:
167
+ json.dump(accounts, f, indent=2)
168
+
169
+ # Routes Flask
170
+ @app.route('/')
171
+ def index():
172
+ return render_template('index.html', config=config)
173
+
174
+ @app.route('/start', methods=['POST'])
175
+ def start():
176
+ if config['is_running']:
177
+ return jsonify({"status": "error", "message": "Une génération est déjà en cours"})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
 
179
+ num_accounts = int(request.form.get('num_accounts', 10))
180
+ startup_ratio = float(request.form.get('startup_ratio', 0.3))
 
 
 
 
 
 
 
 
 
 
 
181
 
182
+ config['is_running'] = True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
 
184
+ # Démarrer le processus en arrière-plan
185
+ thread = threading.Thread(target=create_accounts_background, args=(num_accounts, startup_ratio))
186
+ thread.daemon = True
187
+ thread.start()
188
+
189
+ return jsonify({"status": "success", "message": "Génération démarrée"})
190
+
191
+ @app.route('/stop', methods=['POST'])
192
+ def stop():
193
+ config['is_running'] = False
194
+ return jsonify({"status": "success", "message": "Arrêt demandé"})
195
+
196
+ @app.route('/progress')
197
+ def progress():
198
+ return jsonify(config['progress'])
199
+
200
+ @app.route('/accounts')
201
+ def view_accounts():
202
+ page = int(request.args.get('page', 1))
203
+ per_page = 20
204
 
205
+ accounts = []
206
+ if os.path.exists(config['accounts_file']):
207
+ try:
208
+ with open(config['accounts_file'], 'r') as f:
209
+ accounts = json.load(f)
210
+ except:
211
+ accounts = []
212
+
213
+ total_accounts = len(accounts)
214
+ total_pages = (total_accounts + per_page - 1) // per_page
215
 
216
+ start_idx = (page - 1) * per_page
217
+ end_idx = start_idx + per_page
218
 
219
+ current_accounts = accounts[start_idx:end_idx]
 
220
 
221
+ return render_template(
222
+ 'accounts.html',
223
+ accounts=current_accounts,
224
+ page=page,
225
+ total_pages=total_pages,
226
+ total_accounts=total_accounts
227
+ )
228
+
229
+ if __name__ == '__main__':
230
+ # Créer le dossier templates s'il n'existe pas
231
+ if not os.path.exists('templates'):
232
+ os.makedirs('templates')
233
 
234
+ # Créer les templates HTML
235
+ with open('templates/index.html', 'w') as f:
236
+ f.write('''
237
+ <!DOCTYPE html>
238
+ <html lang="fr">
239
+ <head>
240
+ <meta charset="UTF-8">
241
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
242
+ <title>Générateur de comptes automatique</title>
243
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
244
+ <style>
245
+ body { background-color: #f5f5f5; }
246
+ .card { box-shadow: 0 4px 8px rgba(0,0,0,0.1); }
247
+ .progress { height: 25px; }
248
+ #status-display { height: 200px; overflow-y: auto; }
249
+ </style>
250
+ </head>
251
+ <body>
252
+ <div class="container mt-5">
253
+ <div class="row">
254
+ <div class="col-md-12 mb-4">
255
+ <div class="card">
256
+ <div class="card-header bg-primary text-white">
257
+ <h3 class="card-title mb-0">Générateur de comptes - Startup Côte d'Ivoire</h3>
258
+ </div>
259
+ <div class="card-body">
260
+ <form id="generator-form">
261
+ <div class="row mb-3">
262
+ <div class="col-md-6">
263
+ <label for="num_accounts" class="form-label">Nombre de comptes à créer</label>
264
+ <input type="number" class="form-control" id="num_accounts" name="num_accounts" min="1" max="1000" value="10">
265
+ </div>
266
+ <div class="col-md-6">
267
+ <label for="startup_ratio" class="form-label">Ratio de représentants de startup (%)</label>
268
+ <input type="number" class="form-control" id="startup_ratio" name="startup_ratio" min="0" max="100" value="30">
269
+ </div>
270
+ </div>
271
+ <div class="d-grid gap-2 d-md-flex justify-content-md-center">
272
+ <button type="submit" id="start-btn" class="btn btn-success btn-lg">
273
+ <i class="fas fa-play"></i> Démarrer
274
+ </button>
275
+ <button type="button" id="stop-btn" class="btn btn-danger btn-lg">
276
+ <i class="fas fa-stop"></i> Arrêter
277
+ </button>
278
+ <a href="/accounts" class="btn btn-primary btn-lg">
279
+ <i class="fas fa-list"></i> Voir les comptes
280
+ </a>
281
+ </div>
282
+ </form>
283
+ </div>
284
+ </div>
285
+ </div>
286
+
287
+ <div class="col-md-12">
288
+ <div class="card">
289
+ <div class="card-header bg-info text-white">
290
+ <h4 class="card-title mb-0">Progression</h4>
291
+ </div>
292
+ <div class="card-body">
293
+ <div class="progress mb-3">
294
+ <div id="progress-bar" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%">0%</div>
295
+ </div>
296
+
297
+ <div class="row text-center mb-3">
298
+ <div class="col">
299
+ <div class="card bg-light">
300
+ <div class="card-body">
301
+ <h5>Total</h5>
302
+ <h3 id="total-count">0</h3>
303
+ </div>
304
+ </div>
305
+ </div>
306
+ <div class="col">
307
+ <div class="card bg-success text-white">
308
+ <div class="card-body">
309
+ <h5>Réussis</h5>
310
+ <h3 id="success-count">0</h3>
311
+ </div>
312
+ </div>
313
+ </div>
314
+ <div class="col">
315
+ <div class="card bg-danger text-white">
316
+ <div class="card-body">
317
+ <h5>Échecs</h5>
318
+ <h3 id="failed-count">0</h3>
319
+ </div>
320
+ </div>
321
+ </div>
322
+ </div>
323
+
324
+ <div class="card mb-3">
325
+ <div class="card-header">
326
+ <h5>Dernier statut</h5>
327
+ </div>
328
+ <div class="card-body">
329
+ <p id="last-status">-</p>
330
+ <p>Dernier compte créé: <span id="last-username" class="text-primary fw-bold">-</span></p>
331
+ </div>
332
+ </div>
333
+
334
+ <div class="row">
335
+ <div class="col-md-6">
336
+ <p>Heure de début: <span id="start-time">-</span></p>
337
+ </div>
338
+ <div class="col-md-6 text-end">
339
+ <p>Heure de fin: <span id="end-time">-</span></p>
340
+ </div>
341
+ </div>
342
+ </div>
343
+ </div>
344
+ </div>
345
+ </div>
346
+ </div>
347
 
348
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
349
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js"></script>
350
+ <script>
351
+ document.addEventListener('DOMContentLoaded', function() {
352
+ // Formulaire de génération
353
+ document.getElementById('generator-form').addEventListener('submit', function(e) {
354
+ e.preventDefault();
355
+
356
+ const numAccounts = document.getElementById('num_accounts').value;
357
+ const startupRatio = document.getElementById('startup_ratio').value / 100;
358
+
359
+ fetch('/start', {
360
+ method: 'POST',
361
+ headers: {
362
+ 'Content-Type': 'application/x-www-form-urlencoded',
363
+ },
364
+ body: `num_accounts=${numAccounts}&startup_ratio=${startupRatio}`
365
+ })
366
+ .then(response => response.json())
367
+ .then(data => {
368
+ if (data.status === 'success') {
369
+ startProgressUpdates();
370
+ } else {
371
+ alert(data.message);
372
+ }
373
+ });
374
+ });
375
+
376
+ // Bouton d'arrêt
377
+ document.getElementById('stop-btn').addEventListener('click', function() {
378
+ fetch('/stop', {
379
+ method: 'POST'
380
+ })
381
+ .then(response => response.json())
382
+ .then(data => {
383
+ alert(data.message);
384
+ });
385
+ });
386
+
387
+ // Mettre à jour la progression
388
+ function updateProgress() {
389
+ fetch('/progress')
390
+ .then(response => response.json())
391
+ .then(data => {
392
+ const progressPercent = data.total > 0 ? Math.round((data.current / data.total) * 100) : 0;
393
+
394
+ document.getElementById('progress-bar').style.width = `${progressPercent}%`;
395
+ document.getElementById('progress-bar').textContent = `${progressPercent}%`;
396
+
397
+ document.getElementById('total-count').textContent = data.total;
398
+ document.getElementById('success-count').textContent = data.success;
399
+ document.getElementById('failed-count').textContent = data.failed;
400
+
401
+ document.getElementById('last-status').textContent = data.last_status;
402
+ document.getElementById('last-username').textContent = data.last_username || '-';
403
+
404
+ document.getElementById('start-time').textContent = data.start_time || '-';
405
+ document.getElementById('end-time').textContent = data.end_time || '-';
406
+ });
407
+ }
408
+
409
+ let progressInterval;
410
+
411
+ function startProgressUpdates() {
412
+ // Arrêter l'intervalle précédent s'il existe
413
+ if (progressInterval) {
414
+ clearInterval(progressInterval);
415
+ }
416
+
417
+ // Mettre à jour immédiatement
418
+ updateProgress();
419
+
420
+ // Puis toutes les 2 secondes
421
+ progressInterval = setInterval(updateProgress, 2000);
422
+ }
423
+
424
+ // Vérifier si une génération est déjà en cours au chargement de la page
425
+ updateProgress();
426
+ });
427
+ </script>
428
+ </body>
429
+ </html>
430
+ ''')
431
 
432
+ with open('templates/accounts.html', 'w') as f:
433
+ f.write('''
434
+ <!DOCTYPE html>
435
+ <html lang="fr">
436
+ <head>
437
+ <meta charset="UTF-8">
438
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
439
+ <title>Liste des comptes générés</title>
440
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
441
+ <style>
442
+ body { background-color: #f5f5f5; }
443
+ .card { box-shadow: 0 4px 8px rgba(0,0,0,0.1); }
444
+ .username-cell {
445
+ max-width: 200px;
446
+ overflow: hidden;
447
+ text-overflow: ellipsis;
448
+ white-space: nowrap;
449
+ }
450
+ .pagination { margin-bottom: 0; }
451
+ </style>
452
+ </head>
453
+ <body>
454
+ <div class="container mt-5">
455
+ <div class="row">
456
+ <div class="col-md-12 mb-4">
457
+ <div class="card">
458
+ <div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
459
+ <h3 class="mb-0">Liste des comptes générés</h3>
460
+ <a href="/" class="btn btn-light">Retour au générateur</a>
461
+ </div>
462
+ <div class="card-body">
463
+ <div class="alert alert-info">
464
+ Total des comptes générés: {{ total_accounts }}
465
+ </div>
466
+
467
+ <div class="table-responsive">
468
+ <table class="table table-striped table-hover">
469
+ <thead>
470
+ <tr>
471
+ <th>#</th>
472
+ <th>Nom d'utilisateur</th>
473
+ <th>Email</th>
474
+ <th>Mot de passe</th>
475
+ <th>Startup Rep</th>
476
+ <th>Date de création</th>
477
+ <th>Statut</th>
478
+ </tr>
479
+ </thead>
480
+ <tbody>
481
+ {% for account in accounts %}
482
+ <tr>
483
+ <td>{{ (page - 1) * 20 + loop.index }}</td>
484
+ <td class="username-cell" title="{{ account.username }}">{{ account.username }}</td>
485
+ <td>{{ account.email }}</td>
486
+ <td>{{ account.password }}</td>
487
+ <td>{% if account.is_startup_rep %}Oui{% else %}Non{% endif %}</td>
488
+ <td>{{ account.created_at }}</td>
489
+ <td>
490
+ {% if account.success %}
491
+ <span class="badge bg-success">Succès</span>
492
+ {% else %}
493
+ <span class="badge bg-danger">Échec</span>
494
+ {% endif %}
495
+ </td>
496
+ </tr>
497
+ {% endfor %}
498
+ </tbody>
499
+ </table>
500
+ </div>
501
+
502
+ {% if total_pages > 1 %}
503
+ <div class="d-flex justify-content-center mt-4">
504
+ <nav aria-label="Page navigation">
505
+ <ul class="pagination">
506
+ <li class="page-item {% if page == 1 %}disabled{% endif %}">
507
+ <a class="page-link" href="{{ url_for('view_accounts', page=page-1) if page > 1 else '#' }}">Précédent</a>
508
+ </li>
509
+
510
+ {% for p in range(1, total_pages + 1) %}
511
+ {% if p == page %}
512
+ <li class="page-item active"><span class="page-link">{{ p }}</span></li>
513
+ {% else %}
514
+ <li class="page-item"><a class="page-link" href="{{ url_for('view_accounts', page=p) }}">{{ p }}</a></li>
515
+ {% endif %}
516
+ {% endfor %}
517
+
518
+ <li class="page-item {% if page == total_pages %}disabled{% endif %}">
519
+ <a class="page-link" href="{{ url_for('view_accounts', page=page+1) if page < total_pages else '#' }}">Suivant</a>
520
+ </li>
521
+ </ul>
522
+ </nav>
523
+ </div>
524
+ {% endif %}
525
+ </div>
526
+ </div>
527
+ </div>
528
+ </div>
529
+ </div>
530
 
531
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
532
+ </body>
533
+ </html>
534
+ ''')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
535
 
536
+ app.run(debug=True)