import os import json import logging from typing import Dict, Any, List import requests from datetime import datetime import re from flask import Flask, request, jsonify # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class ArabicContentModerator: """ Arabic Story Content Moderation Model using Deepseek API Checks for cultural violations and inappropriate content """ def __init__(self, deepseek_api_key: str = None): """ Initialize the content moderator Args: deepseek_api_key: Deepseek API key """ self.api_key = deepseek_api_key or os.getenv('DEEPSEEK_API_KEY') if not self.api_key: raise ValueError("Deepseek API key is required") self.api_url = "https://api.deepseek.com/chat/completions" self.headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" } # Enhanced Arabic Content Moderation with News Detection self.moderation_prompt = """ أنت مراجع محتوى عربي محترف متخصص في التمييز بين القصص الأدبية والمحتوى الإخباري. مهمتك مراجعة النصوص العربية ورفض أي محتوى غير أدبي. ## معايير الرفض الصارمة: ### 1. المحتوى الإخباري والصحفي - رفض فوري: **يجب رفض النصوص التي تحتوي على:** **أ) التقارير الرياضية:** - "بعد المباراة خرج وقال" - "اللاعب تألق ومنع أهداف" - "فاز بجائزة رجل المباراة" - "المباراة انتهت بنتيجة" - "في الشوط الأول" - "المدرب صرح" **ب) المؤتمرات الصحفية:** - "في مؤتمر صحفي" - "صرح الوزير" - "أعلن المسؤول" - "في تصريحات خاصة" - "قال النائب" - "أكد الخبير" **ج) الاجتماعات والفعاليات:** - "في اجتماع اليوم" - "خلال الجلسة" - "في المنتدى" - "أثناء المؤتمر" - "في الورشة" - "خلال اللقاء" **د) الأخبار السياسية:** - "الرئيس التقى" - "الوزير أعلن" - "البرلمان ناقش" - "الحكومة قررت" - "السفير وصل" - "الوزارة أصدرت" **هـ) الأخبار الاقتصادية:** - "البورصة ارتفعت" - "أسعار النفط" - "الدولار سجل" - "الشركة حققت" - "الاستثمارات بلغت" - "التضخم وصل" **و) التقارير التقنية:** - "التطبيق الجديد" - "الهاتف يتميز" - "الخاصية الجديدة" - "التحديث يتضمن" - "النظام يدعم" - "البرنامج أضاف" **ز) الأخبار المحلية:** - "في محافظة" - "بلدية المدينة" - "المحافظ افتتح" - "المجلس المحلي" - "الأهالي طالبوا" - "الخدمات تحسنت" ### 2. العلامات المميزة للمحتوى الإخباري: - استخدام أسماء حقيقية لأشخاص مشهورين - ذكر مباريات وأحداث رياضية محددة - استخدام مصطلحات إخبارية ("صرح"، "أعلن"، "أكد") - التواريخ والأرقام الإحصائية - ذكر مؤسسات وشركات حقيقية - النبرة الرسمية والتقريرية ### 3. المحتوى الأدبي المقبول: **يجب قبول النصوص التي تحتوي على:** - شخصيات خيالية أو مجهولة الهوية - أحداث متخيلة أو درامية - حوار إبداعي وعاطفي - وصف الشخصيات والأماكن - صراع نفسي أو اجتماعي - نهاية مفتوحة أو رسالة أدبية - استخدام التشبيهات والمجازات - الأسلوب السردي الإبداعي ### 4. الانتهاكات الدينية - فحص صارم: **رفض فوري للمحتوى الذي يحتوي على:** - أي استهزاء أو تهكم على الله أو الأنبياء - انتقاد الآيات القرآنية أو الأحاديث - السخرية من الشعائر الدينية - التطاول على الصحابة - التجديف أو الكفر الصريح - السب بالدين ### 5. السب والشتم - فحص صارم: **رفض فوري للمحتوى الذي يحتوي على:** - الألفاظ الجنسية الصريحة - السب بالأعضاء التناسلية - الألفاظ الإخراجية - إهانة الأم أو العرض - السب العرقي بألفاظ قبيحة - الكلمات المبتذلة الخادشة ## أمثلة للرفض: **مثال إخباري رياضي (يجب رفضه):** "لويس سواريز بعد المباراة خرج قال كنا نستطيع الفوز... الشناوي تألق ومنع 3 أهداف مؤكدة... فاز بجائزة رجل المباراة" **مثال مؤتمر صحفي (يجب رفضه):** "في مؤتمر صحفي اليوم، صرح الوزير بأن الحكومة ستتخذ إجراءات..." **مثال اجتماع (يجب رفضه):** "خلال اجتماع مجلس الإدارة أمس، تم الاتفاق على..." ## أمثلة للقبول: **قصة أدبية (يجب قبولها):** "كان يجلس في المقهى كل مساء، يراقب الناس ويحلم بحياة أخرى. في ذلك المساء، دخلت امرأة غريبة غيرت كل شيء..." **حوار درامي (يجب قبوله):** "قالت له بصوت مرتجف: لماذا تركتني؟ أجاب وهو يتجنب نظراتها: بعض الأشياء لا يمكن إصلاحها..." ## الاستجابة المطلوبة: بعد المراجعة، أجب بكلمة واحدة فقط: - "true" - إذا كان النص قصة أدبية إبداعية خالية من الانتهاكات - "no" - إذا كان النص إخبارياً أو يحتوي على انتهاكات دينية أو سب فاحش النص المطلوب مراجعته: """ def _call_deepseek_api(self, story_content: str) -> Dict[str, Any]: """ Call Deepseek API for content moderation Args: story_content: The Arabic story content to moderate Returns: API response dictionary """ try: payload = { "model": "deepseek-chat", "messages": [ { "role": "system", "content": "أنت مراجع محتوى عربي محترف متخصص في التمييز بين القصص الأدبية والمحتوى الإخباري. يجب عليك رفض أي محتوى إخباري أو صحفي بصرامة." }, { "role": "user", "content": f"{self.moderation_prompt}\n\n{story_content}" } ], "max_tokens": 10, "temperature": 0.0, "stream": False } response = requests.post( self.api_url, headers=self.headers, json=payload, timeout=30 ) if response.status_code == 200: return response.json() else: logger.error(f"API Error: {response.status_code} - {response.text}") return {"error": f"API Error: {response.status_code}"} except Exception as e: logger.error(f"Exception calling Deepseek API: {str(e)}") return {"error": str(e)} def _pre_check_news_content(self, story_content: str) -> bool: """ Pre-check for obvious news content patterns Args: story_content: Content to check Returns: True if appears to be news content, False otherwise """ # News indicators in Arabic news_patterns = [ r'بعد المباراة.*قال', r'في مؤتمر صحفي', r'صرح.*الوزير', r'أعلن.*المسؤول', r'فاز.*بجائزة.*رجل المباراة', r'تألق.*ومنع.*أهداف', r'خلال.*الاجتماع', r'في.*الجلسة', r'الرئيس.*التقى', r'البرلمان.*ناقش', r'الحكومة.*قررت', r'البورصة.*ارتفعت', r'أسعار.*النفط', r'الشركة.*حققت', r'المحافظ.*افتتح', r'بلدية.*المدينة', r'التطبيق.*الجديد', r'الهاتف.*يتميز', r'في.*محافظة' ] # Check for news patterns for pattern in news_patterns: if re.search(pattern, story_content, re.IGNORECASE): return True # Check for sports-specific terms sports_terms = ['المباراة', 'اللاعب', 'المدرب', 'الفريق', 'الهدف', 'الشوط'] news_verbs = ['صرح', 'أعلن', 'أكد', 'قال', 'فاز', 'تألق'] has_sports = any(term in story_content for term in sports_terms) has_news_verbs = any(verb in story_content for verb in news_verbs) if has_sports and has_news_verbs: return True return False def _validate_story_format(self, story_content: str) -> bool: """ Enhanced validation of story format and content Args: story_content: Story content to validate Returns: Boolean indicating if format is valid """ if not story_content or not isinstance(story_content, str): return False # Check minimum length (at least 50 characters for a meaningful story) if len(story_content.strip()) < 50: return False # Check for Arabic characters (must have substantial Arabic content) arabic_pattern = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]') arabic_chars = len(arabic_pattern.findall(story_content)) # Arabic characters should be at least 30% of total characters if arabic_chars < len(story_content.strip()) * 0.3: return False # Pre-check for obvious news content if self._pre_check_news_content(story_content): return False return True def moderate_story(self, story_content: str) -> Dict[str, Any]: """ Main method to moderate Arabic story content with enhanced validation Args: story_content: The Arabic story to moderate Returns: Dictionary with moderation result """ # Enhanced validation if not self._validate_story_format(story_content): return { "approved": False, "response": "no", "reason": "المحتوى يبدو أنه تقرير إخباري أو صحفي وليس قصة أدبية، أو فشل في التحقق من صحة التنسيق", "timestamp": datetime.now().isoformat() } # Clean and prepare content cleaned_content = story_content.strip() # Call Deepseek API api_response = self._call_deepseek_api(cleaned_content) if "error" in api_response: logger.error(f"Moderation failed: {api_response['error']}") return { "approved": False, "response": "no", "reason": "خطأ في خدمة المراجعة", "error": api_response["error"], "timestamp": datetime.now().isoformat() } try: # Extract the moderation decision ai_response = api_response.get("choices", [{}])[0].get("message", {}).get("content", "").strip().lower() # Clean the response (remove any extra whitespace or characters) ai_response = re.sub(r'[^\w]', '', ai_response) # Determine if content is approved (be more strict) approved = ai_response == "true" response_value = "true" if approved else "no" result = { "approved": approved, "response": response_value, "ai_decision": ai_response, "timestamp": datetime.now().isoformat(), "content_length": len(cleaned_content) } if not approved: result["reason"] = "المحتوى ينتهك القواعد المجتمعية أو الثقافية أو الدينية، أو أنه ليس قصة أدبية حقيقية بل محتوى إخباري" else: result["reason"] = "المحتوى مقبول ويلتزم بالمعايير المطلوبة" logger.info(f"Moderation completed: {response_value} for content of length {len(cleaned_content)}") return result except Exception as e: logger.error(f"Error processing API response: {str(e)}") return { "approved": False, "response": "no", "reason": "خطأ في معالجة نتيجة المراجعة", "error": str(e), "timestamp": datetime.now().isoformat() } # Flask application app = Flask(__name__) # Initialize the moderator (API key will be set via environment variable) try: moderator = ArabicContentModerator() logger.info("Arabic Content Moderator initialized successfully") except ValueError as e: logger.error(f"Failed to initialize moderator: {e}") moderator = None @app.route('/', methods=['GET']) def home(): """Home endpoint with API documentation""" return jsonify({ "service": "مراجع المحتوى الأدبي العربي المحسن مع كشف الأخبار", "service_en": "Enhanced Arabic Literary Content Moderator with News Detection", "version": "3.0.0", "description": "AI-powered professional literary critic for Arabic short stories with enhanced news content detection", "description_ar": "ناقد أدبي محترف مدعوم بالذكاء الاصطناعي للقصص العربية القصيرة مع كشف محسن للمحتوى الإخباري", "endpoints": { "/health": "Health check", "/moderate": "POST - Moderate single story", "/moderate/batch": "POST - Moderate multiple stories" }, "features": [ "Enhanced news content detection and rejection", "Sports reporting detection", "Press conference content filtering", "Meeting and event content filtering", "Religious and cultural compliance checking", "Professional literary criticism standards", "Comprehensive profanity detection" ], "rejected_content_types": [ "Sports reports and match analysis", "Press conferences and official statements", "Meeting minutes and proceedings", "Political news and announcements", "Economic reports and market updates", "Technical reviews and product launches", "Local news and municipal updates" ], "usage": { "moderate": { "method": "POST", "payload": {"story_content": "Arabic story text"}, "response": {"approved": "boolean", "response": "true/no"} } }, "status": "healthy" if moderator else "service unavailable" }) @app.route('/health', methods=['GET']) def health_check(): """Health check endpoint""" return jsonify({ "status": "healthy" if moderator else "unhealthy", "service": "Enhanced Arabic Content Moderator with News Detection", "timestamp": datetime.now().isoformat(), "api_available": moderator is not None }) @app.route('/moderate', methods=['POST']) def moderate_content(): """ Enhanced moderation endpoint with news detection Expected JSON payload: { "story_content": "Arabic story text here" } Returns: { "approved": true/false, "response": "true"/"no", "reason": "reason in Arabic", "timestamp": "ISO timestamp" } """ if not moderator: return jsonify({ "error": "خدمة المراجعة غير متوفرة - لم يتم تكوين مفتاح API", "error_en": "Moderation service not available - API key not configured", "approved": False, "response": "no" }), 500 try: data = request.get_json() if not data or 'story_content' not in data: return jsonify({ "error": "محتوى القصة مفقود في الطلب", "error_en": "Missing story_content in request", "approved": False, "response": "no" }), 400 story_content = data['story_content'] result = moderator.moderate_story(story_content) return jsonify(result) except Exception as e: logger.error(f"Error in moderate_content: {str(e)}") return jsonify({ "error": "خطأ داخلي في الخادم", "error_en": "Internal server error", "approved": False, "response": "no", "details": str(e) }), 500 @app.route('/moderate/batch', methods=['POST']) def moderate_batch(): """ Enhanced batch moderation endpoint Expected JSON payload: { "stories": ["story1", "story2", "story3"] } """ if not moderator: return jsonify({ "error": "خدمة المراجعة غير متوفرة - لم يتم تكوين مفتاح API", "error_en": "Moderation service not available - API key not configured" }), 500 try: data = request.get_json() if not data or 'stories' not in data: return jsonify({ "error": "مصفوفة القصص مفقودة في الطلب", "error_en": "Missing stories array in request" }), 400 stories = data['stories'] if not isinstance(stories, list): return jsonify({ "error": "القصص يجب أن تكون في شكل مصفوفة", "error_en": "Stories must be an array" }), 400 results = [] approved_count = 0 for i, story in enumerate(stories): logger.info(f"Moderating story {i+1}/{len(stories)}") result = moderator.moderate_story(story) results.append({ "story_index": i, "result": result }) if result.get("approved", False): approved_count += 1 return jsonify({ "results": results, "summary": { "total_processed": len(results), "approved_count": approved_count, "rejected_count": len(results) - approved_count, "approval_rate": f"{(approved_count/len(results)*100):.1f}%" if results else "0%" }, "timestamp": datetime.now().isoformat() }) except Exception as e: logger.error(f"Error in moderate_batch: {str(e)}") return jsonify({ "error": "خطأ داخلي في الخادم", "error_en": "Internal server error", "details": str(e) }), 500 if __name__ == '__main__': # For local testing port = int(os.environ.get('PORT', 7860)) app.run(host='0.0.0.0', port=port, debug=False)