""" Mobile-Friendly Checker Analyzes website mobile responsiveness and provides recommendations """ import requests import re from bs4 import BeautifulSoup from typing import Dict, List def check_mobile_friendly(url: str) -> Dict: """ Check if a website is mobile-friendly Returns score and recommendations """ try: headers = { 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15' } response = requests.get(url, headers=headers, timeout=10) html = response.text soup = BeautifulSoup(html, 'html.parser') score = 100 issues = [] recommendations = [] # Check viewport meta tag viewport = soup.find('meta', attrs={'name': 'viewport'}) if not viewport: score -= 30 issues.append({ 'type': 'missing_viewport', 'severity': 'critical', 'message': 'لا يوجد viewport meta tag - الموقع غير متجاوب' }) recommendations.append('أضف ') else: content = viewport.get('content', '') if 'width=device-width' not in content: score -= 15 issues.append({ 'type': 'incorrect_viewport', 'severity': 'high', 'message': 'viewport غير صحيح' }) # Check for responsive CSS has_media_queries = False for style in soup.find_all('style'): if '@media' in style.string if style.string else '': has_media_queries = True break for link in soup.find_all('link', rel='stylesheet'): href = link.get('href', '') if href: try: css_response = requests.get(href if href.startswith('http') else url + href, timeout=5) if '@media' in css_response.text: has_media_queries = True break except: pass if not has_media_queries: score -= 20 issues.append({ 'type': 'no_media_queries', 'severity': 'high', 'message': 'لا توجد media queries - التصميم غير متجاوب' }) recommendations.append('استخدم CSS media queries للتصميم المتجاوب') # Check font sizes small_fonts = soup.find_all(string=re.compile(r'font-size:\s*[0-9]px')) if len(small_fonts) > 5: score -= 10 issues.append({ 'type': 'small_fonts', 'severity': 'medium', 'message': 'خطوط صغيرة جداً على الموبايل' }) recommendations.append('استخدم أحجام خطوط لا تقل عن 16px للنصوص الأساسية') # Check for fixed width elements fixed_width = soup.find_all(style=re.compile(r'width:\s*[0-9]{4,}px')) if fixed_width: score -= 15 issues.append({ 'type': 'fixed_width', 'severity': 'high', 'message': f'عناصر بعرض ثابت ({len(fixed_width)} عنصر)' }) recommendations.append('استخدم النسب المئوية بدلاً من البكسل الثابت') # Check touch targets buttons = soup.find_all(['button', 'a']) small_buttons = 0 for btn in buttons: style = btn.get('style', '') if 'width' in style or 'height' in style: # Check if too small (less than 44px) if re.search(r'(width|height):\s*[1-3][0-9]px', style): small_buttons += 1 if small_buttons > 3: score -= 10 issues.append({ 'type': 'small_touch_targets', 'severity': 'medium', 'message': f'أزرار صغيرة جداً ({small_buttons} زر)' }) recommendations.append('اجعل الأزرار والروابط بحجم 44x44px على الأقل') # Check for horizontal scrolling if 'overflow-x' in html and 'scroll' in html: score -= 10 issues.append({ 'type': 'horizontal_scroll', 'severity': 'medium', 'message': 'قد يحتوي على تمرير أفقي' }) status = 'ممتاز ✅' if score >= 90 else 'جيد ✓' if score >= 70 else 'يحتاج تحسين ⚠️' if score >= 50 else 'ضعيف ❌' return { 'ok': True, 'score': max(0, score), 'status': status, 'issues': issues, 'recommendations': recommendations, 'details': { 'has_viewport': bool(viewport), 'has_media_queries': has_media_queries, 'small_fonts_count': len(small_fonts), 'fixed_width_count': len(fixed_width), 'small_buttons_count': small_buttons } } except Exception as e: return { 'ok': False, 'error': str(e), 'score': 0, 'status': 'فشل الفحص', 'issues': [], 'recommendations': [] } def get_mobile_score_from_pagespeed(url: str) -> Dict: """ Get mobile score from Google PageSpeed Insights API """ try: import os api_key = os.getenv('GOOGLE_PAGESPEED_KEY') if not api_key: return check_mobile_friendly(url) api_url = f'https://www.googleapis.com/pagespeedonline/v5/runPagespeed' params = { 'url': url, 'key': api_key, 'strategy': 'mobile', 'category': 'performance' } response = requests.get(api_url, params=params, timeout=30) data = response.json() if 'lighthouseResult' not in data: return check_mobile_friendly(url) lighthouse = data['lighthouseResult'] categories = lighthouse.get('categories', {}) performance = categories.get('performance', {}).get('score', 0) * 100 audits = lighthouse.get('audits', {}) viewport_audit = audits.get('viewport', {}) font_size_audit = audits.get('font-size', {}) tap_targets_audit = audits.get('tap-targets', {}) issues = [] recommendations = [] score = int(performance) if viewport_audit.get('score', 1) < 1: issues.append({ 'type': 'viewport', 'severity': 'critical', 'message': viewport_audit.get('title', 'مشكلة في viewport') }) recommendations.append(viewport_audit.get('description', '')) if font_size_audit.get('score', 1) < 1: issues.append({ 'type': 'font_size', 'severity': 'medium', 'message': font_size_audit.get('title', 'خطوط صغيرة') }) recommendations.append(font_size_audit.get('description', '')) if tap_targets_audit.get('score', 1) < 1: issues.append({ 'type': 'tap_targets', 'severity': 'medium', 'message': tap_targets_audit.get('title', 'أزرار صغيرة') }) recommendations.append(tap_targets_audit.get('description', '')) status = 'ممتاز ✅' if score >= 90 else 'جيد ✓' if score >= 70 else 'يحتاج تحسين ⚠️' if score >= 50 else 'ضعيف ❌' return { 'ok': True, 'score': score, 'status': status, 'issues': issues, 'recommendations': [r for r in recommendations if r], 'source': 'pagespeed' } except Exception as e: return check_mobile_friendly(url)