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"🤖 Réponse générée\n\n📝 Type: {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"❌ Erreur lors du traitement\n\n🔍 Type: {option}\n💥 Erreur: {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)