Svt / app.py
Docfile's picture
Update app.py
501a7f8 verified
raw
history blame
21 kB
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)