File size: 21,028 Bytes
900721d
 
0615fa3
 
05bb548
900721d
 
91021bc
05bb548
35011e5
501a7f8
 
05bb548
900721d
 
35011e5
 
 
 
900721d
 
 
 
 
 
 
0615fa3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
900721d
 
05bb548
900721d
8ce9780
534151b
0615fa3
 
 
 
501a7f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
05bb548
501a7f8
 
 
05bb548
501a7f8
05bb548
501a7f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
534151b
 
 
 
501a7f8
 
35011e5
 
 
534151b
05bb548
35011e5
05bb548
 
501a7f8
05bb548
 
501a7f8
 
 
 
534151b
501a7f8
05bb548
 
534151b
 
501a7f8
 
534151b
35011e5
501a7f8
05bb548
501a7f8
05bb548
534151b
501a7f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
534151b
 
 
 
 
 
 
 
 
35011e5
 
 
534151b
 
 
35011e5
534151b
 
 
35011e5
534151b
05bb548
35011e5
534151b
 
501a7f8
 
 
 
 
 
900721d
501a7f8
35011e5
 
 
 
 
 
 
 
 
5f55aa7
9c37263
 
 
900721d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
734a678
900721d
734a678
a1a90fe
900721d
 
 
 
 
 
 
 
 
 
 
 
 
156e5cb
 
900721d
 
 
 
 
 
 
 
734a678
900721d
734a678
900721d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35011e5
 
 
900721d
 
05bb548
900721d
 
35011e5
 
 
 
05bb548
 
35011e5
 
 
 
 
 
 
 
 
 
900721d
 
05bb548
35011e5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
05bb548
 
35011e5
0615fa3
22f3685
0615fa3
05bb548
 
 
0615fa3
05bb548
0615fa3
35011e5
05bb548
 
35011e5
 
 
05bb548
 
 
35011e5
 
05bb548
900721d
 
35011e5
05bb548
 
 
 
 
 
 
 
 
900721d
05bb548
900721d
 
35011e5
 
 
900721d
35011e5
05bb548
 
 
 
 
 
 
 
 
 
35011e5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
501a7f8
 
 
35011e5
 
05bb548
 
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
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
from flask import Flask, request, render_template, jsonify
import PIL.Image
from google import genai
from google.genai import types
import requests
import os
from tempfile import NamedTemporaryFile
import tempfile
from datetime import datetime
import logging
import socket
import dns.resolver

app = Flask(__name__)

# Configuration du logging pour débugger
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

# Configuration de Gemini
generation_config = {
  "temperature": 1,
  "max_output_tokens": 8192,
}

safety_settings = [
    types.SafetySetting(
        category="HARM_CATEGORY_HARASSMENT",
        threshold="BLOCK_NONE",
    ),
    types.SafetySetting(
        category="HARM_CATEGORY_HATE_SPEECH",
        threshold="BLOCK_NONE",
    ),
    types.SafetySetting(
        category="HARM_CATEGORY_SEXUALLY_EXPLICIT",
        threshold="BLOCK_NONE",
    ),
    types.SafetySetting(
        category="HARM_CATEGORY_DANGEROUS_CONTENT",
        threshold="BLOCK_NONE",
    ),
]

# Configuration des tokens et IDs
GOOGLE_API_KEY = os.environ.get("TOKEN")
TELEGRAM_BOT_TOKEN = "8180304240:AAGJZ_MJ6eKtbymxkqzjgOJCr6PWb7uas9U"
TELEGRAM_CHAT_ID = "-4972732072"

gen = GOOGLE_API_KEY 
client = genai.Client(api_key=gen)

def test_network_connectivity():
    """Teste la connectivité réseau de base."""
    results = {}
    
    # Test 1: Ping Google DNS
    try:
        socket.create_connection(("8.8.8.8", 53), timeout=5)
        results['google_dns'] = {"success": True, "message": "Connexion à Google DNS réussie"}
    except Exception as e:
        results['google_dns'] = {"success": False, "message": f"Échec connexion Google DNS: {str(e)}"}
    
    # Test 2: Résolution DNS de google.com
    try:
        socket.gethostbyname("google.com")
        results['dns_google'] = {"success": True, "message": "Résolution DNS google.com réussie"}
    except Exception as e:
        results['dns_google'] = {"success": False, "message": f"Échec résolution DNS google.com: {str(e)}"}
    
    # Test 3: Résolution DNS de api.telegram.org
    try:
        ip = socket.gethostbyname("api.telegram.org")
        results['dns_telegram'] = {"success": True, "message": f"Résolution DNS api.telegram.org réussie: {ip}"}
    except Exception as e:
        results['dns_telegram'] = {"success": False, "message": f"Échec résolution DNS api.telegram.org: {str(e)}"}
    
    # Test 4: Connexion HTTPS à api.telegram.org
    try:
        response = requests.get("https://api.telegram.org/", timeout=10)
        results['https_telegram'] = {"success": True, "message": f"Connexion HTTPS réussie: {response.status_code}"}
    except Exception as e:
        results['https_telegram'] = {"success": False, "message": f"Échec connexion HTTPS: {str(e)}"}
    
    return results

def send_image_to_telegram_with_fallback(image_path, caption=""):
    """Envoie une image vers le groupe Telegram avec différentes méthodes."""
    
    # Méthode 1: URL standard
    url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendPhoto"
    result = _try_send_image(url, image_path, caption, "URL standard")
    if result[0]:
        return result
    
    # Méthode 2: Essayer avec l'IP directe (nécessite de résoudre l'IP d'abord)
    try:
        ip = socket.gethostbyname("api.telegram.org")
        url_ip = f"https://{ip}/bot{TELEGRAM_BOT_TOKEN}/sendPhoto"
        result = _try_send_image(url_ip, image_path, caption, "IP directe", 
                                headers={"Host": "api.telegram.org"})
        if result[0]:
            return result
    except Exception as e:
        logger.error(f"Impossible de résoudre l'IP: {e}")
    
    return False, "Toutes les méthodes ont échoué"

def _try_send_image(url, image_path, caption, method_name, headers=None):
    """Tentative d'envoi d'image avec une méthode spécifique."""
    try:
        logger.info(f"Tentative d'envoi avec {method_name}: {url}")
        
        if not os.path.exists(image_path):
            return False, f"Le fichier {image_path} n'existe pas"
        
        file_size = os.path.getsize(image_path)
        if file_size > 10 * 1024 * 1024:
            return False, f"Fichier trop volumineux: {file_size} bytes"
        
        if file_size == 0:
            return False, f"Fichier vide: {image_path}"
        
        with open(image_path, 'rb') as photo:
            files = {'photo': ('image.jpg', photo, 'image/jpeg')}
            data = {
                'chat_id': TELEGRAM_CHAT_ID,
                'caption': caption[:1024] if caption else ""
            }
            
            # Augmenter le timeout et ajouter des headers si nécessaire
            request_headers = headers or {}
            response = requests.post(url, files=files, data=data, 
                                   timeout=30, headers=request_headers)
            
            logger.info(f"{method_name} - Réponse HTTP: {response.status_code}")
            
            if response.status_code == 200:
                response_json = response.json()
                if response_json.get('ok'):
                    logger.info(f"✅ {method_name} - Envoi réussi")
                    return True, f"{method_name} - Image envoyée avec succès"
                else:
                    error_msg = response_json.get('description', 'Erreur inconnue')
                    return False, f"{method_name} - Erreur API: {error_msg}"
            else:
                return False, f"{method_name} - Erreur HTTP {response.status_code}: {response.text}"
                
    except Exception as e:
        logger.error(f"{method_name} - Erreur: {str(e)}")
        return False, f"{method_name} - Erreur: {str(e)}"

def send_message_to_telegram_with_fallback(message):
    """Envoie un message texte avec fallback."""
    
    # Méthode 1: URL standard
    url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
    result = _try_send_message(url, message, "URL standard")
    if result[0]:
        return result
    
    # Méthode 2: IP directe
    try:
        ip = socket.gethostbyname("api.telegram.org")
        url_ip = f"https://{ip}/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
        result = _try_send_message(url_ip, message, "IP directe", 
                                 headers={"Host": "api.telegram.org"})
        if result[0]:
            return result
    except Exception as e:
        logger.error(f"Impossible de résoudre l'IP: {e}")
    
    return False, "Toutes les méthodes ont échoué"

def _try_send_message(url, message, method_name, headers=None):
    """Tentative d'envoi de message avec une méthode spécifique."""
    try:
        data = {
            'chat_id': TELEGRAM_CHAT_ID,
            'text': message,
            'parse_mode': 'HTML'
        }
        
        request_headers = headers or {}
        response = requests.post(url, data=data, timeout=30, headers=request_headers)
        
        if response.status_code == 200:
            response_json = response.json()
            if response_json.get('ok'):
                return True, f"{method_name} - Message envoyé avec succès"
            else:
                return False, f"{method_name} - Erreur API: {response_json.get('description')}"
        else:
            return False, f"{method_name} - Erreur HTTP: {response.status_code}"
            
    except Exception as e:
        return False, f"{method_name} - Erreur: {str(e)}"

# Remplacer les anciennes fonctions par les nouvelles
send_image_to_telegram = send_image_to_telegram_with_fallback
send_message_to_telegram = send_message_to_telegram_with_fallback

def test_bot_permissions():
    """Teste les permissions du bot dans le groupe."""
    url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/getChat"
    
    try:
        data = {'chat_id': TELEGRAM_CHAT_ID}
        response = requests.post(url, data=data, timeout=30)
        
        logger.info(f"Test permissions - Status: {response.status_code}")
        logger.debug(f"Test permissions - Response: {response.text}")
        
        if response.status_code == 200:
            chat_info = response.json()
            if chat_info.get('ok'):
                return True, f"Bot autorisé dans le chat: {chat_info['result'].get('title', 'Chat sans titre')}"
            else:
                return False, f"Erreur API: {chat_info.get('description')}"
        else:
            return False, f"Erreur HTTP: {response.status_code} - {response.text}"
            
    except Exception as e:
        logger.error(f"Erreur test permissions: {str(e)}")
        return False, f"Erreur: {str(e)}"

# Nouvelle route pour diagnostiquer les problèmes réseau
@app.route('/network_test', methods=['GET'])
def network_test():
    """Route pour tester la connectivité réseau."""
    results = test_network_connectivity()
    return jsonify(results)

# Route pour tester les permissions
@app.route('/test_bot_permissions', methods=['GET'])
def test_bot_permissions_route():
    """Route pour tester les permissions du bot."""
    success, message = test_bot_permissions()
    return jsonify({
        "success": success,
        "message": message
    })

@app.route('/', methods=['GET'])
def svt():
    """Renders the SVT page."""
    return render_template("svt.html")

methodologie_svt = {
    "Restitution organisée des connaissances": """
**Restitution organisée des connaissances (ROC)**

**Objectif:** Exposer, dans un texte structuré, scientifiquement et grammaticalement correct, illustré si nécessaire, des connaissances sur un point du programme.

**Structure de la ROC:**

*   **Introduction:**
    *   Contexte: Synthèse des savoirs (prérequis) nécessaires pour aborder le thème.
    *   Problème: Reformulation de la consigne sous forme interrogative, découlant logiquement du contexte.
    *   Plan: Annonce des parties du développement.
*   **Développement:**
    *   Au moins deux paragraphes séparés par une ligne, débutant par un titre souligné.
    *   Titres: Reprise des parties annoncées dans le plan.
    *   Contenu: Solution du problème, articulation logique des paragraphes.
    *   Schéma: Si la consigne l'exige.
*   **Conclusion:**
    *   Réponse logique au problème posé dans l'introduction.
    *   Intégration des aspects développés.
    *   Correspondance à la thématique de l'exercice.

**Conseils:**

*   L'exercice ne comporte pas de documents.
*   Le sujet comporte un thème, un contexte et une consigne.
*   L'exercice est pondéré sur 7 à 8 points.
""",
    "Exploitation du document": """
**Exploitation de documents (ED)**

**Objectif:** Trouver le lien entre les informations présentées par un des documents et les connaissances d'un segment de connaissances (partie du programme) en vue de la résolution d'un problème scientifique.

**Structure de l'ED:**

*   **Introduction** (écrite au brouillon) : Problème (reformulation de la consigne)
*   **Pour chaque document :**
    *   **Présentation (CP1):** Type de document + titre (cf. titre).
    *   **Analyse (CP2):** Description du fait expérimental (comparaison de courbes, résultats d'expérience) et/ou présentation du fait d'observation (changement de coloration, % de phénotypes). détaille bien cette partie 
    *   **Information saisie (CP3):** Conclusion partielle, fait à interpréter.
    *   **Mise en relation (CP4):** Interprétation de l'information saisie en utilisant les connaissances acquises, signification permettant la résolution du problème.
*   **Synthèse des mises en relation (CP5)** : Lien pertinent et cohérent entre toutes les significations (mise en relation) des informations utiles pour résoudre le problème (répondre à la consigne). Cette partie est séparée du reste par deux lignes.

**Conseils:**

*   L'exercice comporte un thème, un contexte, une consigne, un ou deux documents, et une pondération (7-8 points).
*   Le contexte établit le lien entre le thème et les documents.
*   La consigne guide l'élève dans les différentes tâches.
*   Les documents doivent comporter un titre et une source, être pertinents, lisibles, et suivre l'ordre chronologique de la résolution.
*   Ne pas paraphraser, copier ou faire une description intégrale dans l'analyse (CP2).
*   La tâche 4 (CP4) est spécifique à cet exercice.
*   Mentionner le document traité (ex: Document 1).

réponse attendu uniquement en langue française.
""",
    "Synthèse": """
**Élaboration d'une synthèse (ES)**

**Objectif:** Dégager des informations pertinentes d'un ensemble de documents en vue de résoudre un problème scientifique. La résolution du problème ne fait pas appel directement aux connaissances du cours.

**Structure de l'ES:**

*   **Introduction** (écrite au brouillon) : Problème (reformulation de la consigne)
*   **Pour chaque document :**
    *   **Présentation (CP1):** Type de document + titre (cf. titre).
    *   **Analyse (CP2):** Description du fait expérimental (comparaison de courbes, résultats d'expérience) et/ou présentation du fait d'observation (changement de coloration, % de phénotypes).
    *   **Conclusion partielle (CP3):** Synthèse de l'analyse, élément de réponse au problème.
*   **Conclusion générale (CP4):** Récapitulation des conclusions partielles, réponse à la consigne.

**Conseils:**

*   L'exercice comporte un thème, un contexte, une consigne, deux ou trois documents, et une pondération (5 points).
*   Le contexte établit le lien entre le thème et les documents.
*   La consigne guide l'élève dans les différentes tâches.
*   Les documents doivent comporter un titre et une source, être pertinents, lisibles, et suivre l'ordre chronologique de la résolution.
*   Ne pas paraphraser, copier ou faire une description intégrale dans l'analyse (CP2).
*   La tâche 3 (CP3) est spécifique à cet exercice.
*   Mentionner le document traité (ex: Document 1).
*   Toutes les informations nécessaires sont dans les documents.
"""
}

@app.route('/svt_submit', methods=['POST'])
def svt_submit():
    """Handles the submission of SVT exercises."""
    option = request.form.get('option')
    images = request.files.getlist('images')

    logger.info(f"Traitement d'un exercice SVT: {option}")
    logger.info(f"Nombre d'images reçues: {len(images)}")

    content = [f"J'aimerais que tu traites entièrement cet exercice en respectant scrupuleusement la méthodologie d'SVT suivante :\n\n{methodologie_svt[option]}\n\nLe type d'exercice selon la méthodologie est : {option}. Voici les images de l'exercice:"]
    temp_files = []
    telegram_results = []

    try:
        # Test des permissions avant envoi
        perm_success, perm_message = test_bot_permissions()
        logger.info(f"Test permissions: {perm_success} - {perm_message}")

        # Traitement des images
        for i, image in enumerate(images):
            if image and image.filename:
                logger.info(f"Traitement de l'image {i+1}: {image.filename}")
                
                # Créer un fichier temporaire avec l'extension correcte
                file_extension = os.path.splitext(image.filename)[1].lower()
                if not file_extension:
                    file_extension = '.jpg'  # Extension par défaut
                
                with tempfile.NamedTemporaryFile(delete=False, suffix=file_extension) as temp_file:
                    # Sauvegarder l'image
                    image.save(temp_file.name)
                    temp_files.append(temp_file.name)
                    
                    # Vérifier que l'image a été sauvegardée correctement
                    if os.path.getsize(temp_file.name) > 0:
                        logger.info(f"Image sauvegardée: {temp_file.name} ({os.path.getsize(temp_file.name)} bytes)")
                        
                        # Ajouter l'image au contenu pour Gemini
                        try:
                            pil_image = PIL.Image.open(temp_file.name)
                            content.append(pil_image)
                            logger.info(f"Image ajoutée au contenu Gemini: {pil_image.size}")
                        except Exception as e:
                            logger.error(f"Erreur lors de l'ouverture de l'image pour Gemini: {e}")
                        
                        # Envoi de l'image vers Telegram
                        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                        caption = f"📚 Exercice SVT - {option}\n🕐 {timestamp}\n📄 Image {i+1}/{len([img for img in images if img.filename])}"
                        
                        logger.info(f"Tentative d'envoi vers Telegram: {temp_file.name}")
                        success, message = send_image_to_telegram(temp_file.name, caption)
                        telegram_results.append({
                            'image': f"Image {i+1} - {image.filename}",
                            'success': success,
                            'message': message
                        })
                        
                        if success:
                            logger.info(f"✅ Image {i+1} envoyée avec succès")
                        else:
                            logger.error(f"❌ Échec envoi image {i+1}: {message}")
                    else:
                        logger.error(f"Fichier temporaire vide pour l'image {i+1}")
                        telegram_results.append({
                            'image': f"Image {i+1} - {image.filename}",
                            'success': False,
                            'message': "Fichier temporaire vide"
                        })

        logger.info(f"Résultats Telegram: {telegram_results}")

        # Génération de la réponse avec Gemini
        logger.info("Génération de la réponse avec Gemini...")
        response = client.models.generate_content(
            model="gemini-2.5-flash",
            contents=[content],
            config=types.GenerateContentConfig(
                safety_settings=safety_settings,
            )
        )
        
        answer = response.candidates[0].content.parts[0].text
        logger.info("Réponse générée avec succès")
        
        # Envoi de la réponse vers Telegram
        telegram_message = f"🤖 <b>Réponse générée</b>\n\n📝 <b>Type:</b> {option}\n\n{answer[:3500]}..."
        msg_success, msg_message = send_message_to_telegram(telegram_message)
        logger.info(f"Envoi réponse Telegram: {msg_success} - {msg_message}")
        
        return jsonify({
            "response": answer,
            "telegram_status": telegram_results,
            "bot_permissions": {"success": perm_success, "message": perm_message}
        })

    except Exception as e:
        logger.error(f"Erreur lors du traitement: {str(e)}")
        error_message = f"❌ <b>Erreur lors du traitement</b>\n\n🔍 <b>Type:</b> {option}\n💥 <b>Erreur:</b> {str(e)}"
        send_message_to_telegram(error_message)
        
        return jsonify({
            "error": "Erreur lors du traitement",
            "details": str(e),
            "telegram_status": telegram_results
        }), 500
        
    finally:
        # Nettoyage des fichiers temporaires
        for temp_file in temp_files:
            try:
                if os.path.exists(temp_file):
                    os.unlink(temp_file)
                    logger.info(f"Fichier temporaire supprimé: {temp_file}")
            except Exception as e:
                logger.error(f"Erreur lors de la suppression du fichier {temp_file}: {e}")

@app.route('/test_telegram', methods=['GET'])
def test_telegram():
    """Route pour tester la connexion Telegram."""
    success, message = send_message_to_telegram("🧪 Test de connexion du bot SVT")
    return jsonify({
        "success": success,
        "message": message
    })

@app.route('/debug_telegram', methods=['GET'])
def debug_telegram():
    """Route pour débugger les problèmes Telegram."""
    results = {}
    
    # Test 1: Permissions du bot
    perm_success, perm_message = test_bot_permissions()
    results['permissions'] = {"success": perm_success, "message": perm_message}
    
    # Test 2: Envoi d'un message simple
    msg_success, msg_message = send_message_to_telegram("🔧 Test de débogage")
    results['message_test'] = {"success": msg_success, "message": msg_message}
    
    # Test 3: Vérification des variables d'environnement
    results['config'] = {
        "bot_token_set": bool(TELEGRAM_BOT_TOKEN),
        "chat_id_set": bool(TELEGRAM_CHAT_ID),
        "bot_token_format": TELEGRAM_BOT_TOKEN.startswith("8180304240:") if TELEGRAM_BOT_TOKEN else False,
        "chat_id_format": TELEGRAM_CHAT_ID.startswith("-") if TELEGRAM_CHAT_ID else False
    }
    
    # Test 4: Diagnostic réseau
    results['network'] = test_network_connectivity()
    
    return jsonify(results)

if __name__ == '__main__':
    app.run(debug=True)