# ==============================================================================
# đĄī¸ FraudGuard Myanmar AI - Final Master Pipeline
# ==============================================================================
import gradio as gr
import requests
import whois
import re
import feedparser
import os
import numpy as np
from datetime import datetime
from difflib import SequenceMatcher
from PIL import Image, ImageChops
from sentence_transformers import SentenceTransformer, util
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from io import BytesIO
from bs4 import BeautifulSoup
# --- 1. MODEL LOADING ---
print("System Initializing... Loading All AI Components.")
MODEL_NAME = "Poe255M/myanmar-fraud-detection-final"
try:
# Hugging Face ááž Model áážááˇáē Tokenizer ááᯠááá¯ááēááá¯ááēáá°ááŧááēá¸
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
nlp_model = AutoModelForSequenceClassification.from_pretrained(MODEL_NAME)
print(f"â
Custom Myanmar AI Model Loaded from Hugging Face: {MODEL_NAME}")
except Exception as e:
print(f"â ī¸ áááááąá¸ááģááē: Online Model ááᯠáááŊáąáˇááĢá Base Model ááᯠááŦááŽáá¯áļá¸ááŦá¸ááĢáááēá Error: {e}")
tokenizer = AutoTokenizer.from_pretrained("xlm-roberta-base")
nlp_model = AutoModelForSequenceClassification.from_pretrained("xlm-roberta-base", num_labels=2)
device = 'cuda' if torch.cuda.is_available() else 'cpu'
nlp_model.to(device)
nlp_model.eval()
print("Loading CLIP model for multi-modal verification...")
clip_model = SentenceTransformer('clip-ViT-B-32')
# --- 2. CORE LOGIC MODULES ---
class VerificationSystem:
@staticmethod
def translate_to_en(text):
"""CLIP Model ááŧááˇáē áá¯áļááá¯á
á
áēááąá¸áááēáĄááŊááēááŦ áá¯áļá¸áááē"""
try:
url = f"https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=en&dt=t&q={text}"
res = requests.get(url, timeout=5).json()
return "".join([s[0] for s in res[0]])
except:
return text
@staticmethod
def verify_headline_match(user_text, url):
"""User ááá¯ááēáá˛áˇá
áŦáá˛áˇ URL áá˛á áá°áááēá¸ááąáĢááēá¸á
ááē áá°á ááá° á
á
áēááąá¸áááē"""
if not url or not url.startswith("http"):
return 100, "N/A"
try:
headers = {'User-Agent': 'Mozilla/5.0'}
res = requests.get(url, headers=headers, timeout=10)
soup = BeautifulSoup(res.text, 'html.parser')
original_title = ""
h1 = soup.find('h1')
if h1:
original_title = h1.get_text().strip()
elif soup.title:
original_title = soup.title.get_text().strip()
if not original_title:
return 50, "Could not extract title from source"
similarity = SequenceMatcher(None, user_text.lower(), original_title.lower()).ratio()
match_score = similarity * 100
msg = f"â
Original Title: {original_title[:60]}..." if match_score > 60 else f"â ī¸ Mismatch! Source Title: {original_title[:60]}..."
return match_score, msg
except:
return 50, "Error Fetching Source Title"
@staticmethod
def perform_ela(image_path, quality=90):
if not image_path: return 100, "No image"
try:
original = Image.open(image_path).convert('RGB')
resaved_path = "temp_forensic.jpg"
original.save(resaved_path, 'JPEG', quality=quality)
resaved = Image.open(resaved_path)
ela_diff = ImageChops.difference(original, resaved)
stat = np.array(ela_diff).mean()
if os.path.exists(resaved_path): os.remove(resaved_path)
if stat > 1.2:
score = max(10, 100 - (stat * 40))
return score, "â ī¸ Tampering Detected"
elif stat > 0.8:
score = max(50, 100 - (stat * 20))
return score, "đĄ Minor Edits/Low Quality"
else:
score = min(100, 100 - (stat * 5))
return score, "â
Consistent Pixels"
except: return 50, "Scan Error"
@staticmethod
def extract_image_from_url(url):
if not url: return None
try:
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}
if any(url.lower().endswith(ext) for ext in ['.jpg', '.jpeg', '.png', '.webp']):
res = requests.get(url, headers=headers, timeout=10)
img = Image.open(BytesIO(res.content)).convert('RGB')
path = "temp_url_image.jpg"
img.save(path)
return path
response = requests.get(url, headers=headers, timeout=10)
soup = BeautifulSoup(response.text, 'html.parser')
img_tag = soup.find("meta", property="og:image") or soup.find("meta", attrs={"name": "twitter:image"})
img_url = img_tag["content"] if img_tag and img_tag.has_attr("content") else None
if not img_url:
first_img = soup.find("img")
img_url = first_img["src"] if first_img and first_img.has_attr("src") else None
if img_url:
if img_url.startswith('//'): img_url = 'https:' + img_url
elif img_url.startswith('/'):
from urllib.parse import urljoin
img_url = urljoin(url, img_url)
img_res = requests.get(img_url, headers=headers, timeout=10)
img = Image.open(BytesIO(img_res.content)).convert('RGB')
path = "temp_extracted_image.jpg"
img.save(path)
return path
except: return None
return None
@staticmethod
def get_image_text_similarity(image_path, text, url="", url_image_path=None):
if not image_path or not text: return 50, "Incomplete Data"
try:
is_myanmar = bool(re.search(r'[\u1000-\u109F]', text))
processed_text = VerificationSystem.translate_to_en(text) if is_myanmar else text
img_obj = Image.open(image_path)
img_emb = clip_model.encode(img_obj)
text_emb = clip_model.encode([processed_text])
similarity = util.cos_sim(img_emb, text_emb).item()
is_trusted_source = False
trusted_domains = ["bbc.com", "reuters.com", "apnews.com", "voanews.com", "rfa.org", "nytimes.com"]
if url:
for d in trusted_domains:
if d in url.lower():
is_trusted_source = True
break
is_text_match = similarity >= 0.22
if is_text_match:
final_score = min(100, similarity * 240)
msg = "â
á
áŦááŦá¸áážááˇáē áá¯áļ ááá¯ááēááŽáážá¯áážááááē" if is_myanmar else "â
Context Matches"
else:
if is_trusted_source:
final_score = 55.0
msg = "âšī¸ ááááēá¸áááēá¸ááŧá
áēáážáŦ áá¯áļááŧááēáááąáŦáēáááēḠáá¯áļáážáŦ ááá¯ááēááŧáá¯áļ (Illustrative) ááŦ ááŧá
áēááá¯ááēááĢáááē" if is_myanmar else "âšī¸ Trusted source but image may be illustrative"
else:
final_score = max(10, similarity * 130)
msg = "â á
áŦááŦá¸áážááˇáē áá¯áļ áááá¯ááēááŽááĢ" if is_myanmar else "â Content Mismatch"
img_match_pct = 0
has_url_img = False
if url_image_path and os.path.exists(url_image_path):
has_url_img = True
url_img_emb = clip_model.encode(Image.open(url_image_path))
img_similarity = util.cos_sim(img_emb, url_img_emb).item()
img_match_pct = img_similarity * 100
if img_match_pct > 80:
if not is_text_match:
final_score = 65.0 if is_trusted_source else 45.0
msg = "â ī¸ Link ááĢáá¯áļáážááˇáē áá°ááąáŦáēáááēḠááąáĢááēá¸á
ááēáážááˇáē ááá¯ááēááá¯ááēááááēááá¯ááēááĢ" if is_myanmar else "â ī¸ Image matches Source but Mismatches Headline"
else:
final_score = min(100, final_score + 15)
msg = "â
áá°áááēá¸ááááēá¸ááĢáá¯áļááŧá
áēááŧáŽá¸ á
áŦááŦá¸áážááˇáēáááēḠááá¯ááēááŽááĢáááē" if is_myanmar else "â
Verified Source Image Match"
elif img_match_pct < 50:
final_score = max(10, final_score - 25)
msg += " | â ī¸ Link áá˛ááž áá°áááēá¸áá¯áļááá¯ááēááĢ" if is_myanmar else " | â ī¸ Not the Original Image from Link"
if has_url_img and not is_text_match and img_match_pct < 50 and not is_trusted_source:
final_score = 10.5
msg = "đ¨ ááááēá¸áĄááģááēáĄáááēáĄáŦá¸áá¯áļḠááŊá˛áážáŦá¸ááąááĢáááē (High Risk)" if is_myanmar else "đ¨ High Risk: Complete Information Mismatch"
return round(max(5, final_score), 2), msg
except Exception as e:
return 50, f"Verification Error: {str(e)}"
@staticmethod
def get_source_score(url):
if not url or not url.startswith("http"):
return 30, "Missing/Invalid URL Source"
try:
domain_search = re.search(r'https?://([A-Za-z0-9.-]+)', url)
if not domain_search: return 30, "Invalid Domain"
domain = domain_search.group(1).lower()
# --- áá áááŦá¸áááē ááááēá¸ááŦáááŧáŽá¸ááģáŦḠ(Hard News) ---
trusted_news = ["bbc.com", "reuters.com", "rfa.org", "voanews.com", "dvb.no", "irrawaddy.com", "myanmar-now.org", "khitthitnews.com", "mizzima.com"]
# --- áá ááŦáááēááŧáŽá¸ áĄáá¯áááŦ/áááēáᎠááŽááŽááŦááģáŦḠ(Cele Media) ---
trusted_cele = ["myanmarcelebrity.com", "popularmyanmar.com", "celegabar.com", "shwemon.com", "celeyatkwat.com"]
# (á) ááááēá¸ááŦáááŧáŽá¸ááģáŦḠá
á
áēááąá¸ááŧááēá¸
for m in trusted_news:
if domain == m or domain.endswith("." + m):
return 100, f"Verified News Media ({domain})"
# (á) áĄáá¯áááŦááááēá¸ááŦáááģáŦḠá
á
áēááąá¸ááŧááēá¸
for c in trusted_cele:
if domain == c or domain.endswith("." + c):
return 90, f"Verified Entertainment Media ({domain})"
# --- áá Social Media Links (Facebook, Instagram) á
á
áēááąá¸ááŧááēḠ---
# áááēááŽááááēá¸áĄááģáŦá¸á
á¯áááē Social Media ááąáĢáēááŊááēááŦ áážááááēáááŧááˇáē ááŽá¸áááˇáēá
á
áēááąá¸áááē
if "facebook.com" in domain or "instagram.com" in domain:
path = url.split(domain)[-1].lower()
# Official Page áᯠáá°áááá¯ááēááąáŦ ááášáááŦááģáŦḠ(áĨáááŦ - facebook.com/naytoe.official)
if "official" in path or "original" in path or "verified" in path:
return 85, "Likely Official Social Media Account"
# Facebook Group ááá¯áˇááá¯ááē Video Link ááŽá¸áááˇáēááŧá
áēááąááģážááē (ááááēá¸áá¯ááŧááˇáēáááē áĄáá¯áļá¸ááģáŦá¸ááąáŦ ááąááŦááģáŦá¸)
elif "/groups/" in path or "/watch/" in path or "reel" in path:
return 40, "Social Media Group/Video (Unverified Origin)"
# ááŦáááē Page ááá¯áˇááá¯ááē Profile ááŧá
áēááģážááē (ááŧáŦá¸ááąáĄáážááēááąá¸áááē)
else:
return 60, "General Social Media Source (Needs Cross-check)"
# --- áá áĄáááēááá Domain ááģáŦá¸ááᯠWHOIS ááŧááˇáē áááēáááēá¸á
á
áēááŧááēḠ(Fake Sites ááģáŦá¸ááᯠáááēá¸áááē) ---
w = whois.whois(domain)
creation_date = w.creation_date
if isinstance(creation_date, list):
creation_date = creation_date[0]
if creation_date:
from datetime import datetime
age_days = (datetime.now() - creation_date).days
age_months = age_days // 30
if age_days < 180:
return 15, f"â ī¸ Very New/Suspicious Site (Age: {age_months} months)"
elif age_days < 730:
return 50, f"Neutral Site (Age: {age_months} months)"
else:
return 80, f"Established Site (Age: {age_months // 12} years)"
else:
return 30, "Unknown Identity (No Creation Date)"
except Exception as e:
return 20, "Hidden/Suspicious Source Identity"
@staticmethod
def get_nlp_prediction(text):
if not text or len(text.split()) < 3: return 10.0
# áá AI Model ááž ááááēáá°ááŧááēá¸
inputs = tokenizer(text, max_length=512, padding="max_length", truncation=True, return_tensors="pt").to(device)
import torch
with torch.no_grad():
outputs = nlp_model(**inputs)
probs = torch.nn.functional.softmax(outputs.logits, dim=-1)[0]
real_score = probs[0].item() * 100
text_lower = text.lower()
penalty = 0
# (á) Health Scam áááēá¸áááē (WHO/áá¯ááąáá ááŦáááēáá¯áļá¸ááŧáŽá¸ ááááēááŦá¸ááģážááē)
health_keywords = ["ááģááēá¸ááŦááąá¸", "ááąáŦááĢ", "ááąá¸", "who", "áá¯ááąáá", "áĄáááēááŧá¯"]
if any(kw in text_lower for kw in health_keywords):
scam_words = ["ááá%", "áĄáŦáááļ", "ááģááēááģááēá¸", "ááģážáá¯áˇáážááēááģááē", "áááŧá
áēáááą", "ááģáąáŦááēáááēá¸", "áá¯áļá¸áááŦááŊááē", "áĄáá°á¸áááŧááēáˇ", "ááá¯ááá¯ááááąáŦááē"]
if any(sw in text_lower for sw in scam_words):
penalty += 35 # ááģááēá¸ááŦááąá¸ááááēáááēáážá¯ááŧá
áēá áĄáážááēááģáŦá¸ááģáŦá¸ááģážáąáŦáˇáááē
# (á) Cele News áážááēááģážááē Penalty áááá
áąáááē ááŦááŊááēááŧááēá¸
cele_keywords = ["áááēá¸ááŦá¸", "áááēá¸áááŽá¸", "áĄááá¯ááąáŦáē", "ááá¯ááēááąáŦááē", "áááēááŽ", "áááēáááē", "celebrity"]
is_cele = any(kw in text_lower for kw in cele_keywords)
# áá Case 2 & 3 áĄááŊááē áĄáá°á¸ Red Flags (Heuristics) - (ááŽáá áá°á Code áĄááá¯ááēá¸)
urgency_patterns = [
"ááááēááááēá¸ááŊáŦá¸áááē", "ááááēááááēá¸ááąáŦáˇáááē", "áĄááŧááēáá¯áļá¸", "áááēáááˇáēáááēá¸",
"áĄááŊááēá¸ááááēá¸", "Update ááŧá¯áá¯ááēáááĢáááē", "Personal Information",
"áĄááąáŦááˇáēááááēááááēá¸", "ááŦááŽááááēááááēá¸", "ááá¯áļááá¯ááēá
ááŦ", "ááá% áĄáážááē"
]
if is_cele:
# áááēááŽááááēá¸ááá¯ááģážááē "ááá¯áļááá¯ááēá
ááŦ" áá˛áˇááá¯áˇááąáŦ á
ááŦá¸áá¯áļá¸ááģáŦá¸áĄááŊááē Penalty áááąá¸ááąáŦáˇááĢá
# Clickbait áááˇáēááēáážáááēááá¯ááēá¸ááŦááģáá¯á¸ááá¯áᲠááŽá¸áááˇáēá
á
áēááĢáááēá
clickbait_patterns = ["ááŽááŽááá¯ááŧááˇáēáááē", "áážááēááŦá¸", "áááˇáēááēáááēááŧááˇáē", "áá¯ááēá¸ááąááē", "áááēááąáŦááē"]
for pattern in clickbait_patterns:
if pattern in text_lower:
penalty += 20
else:
for pattern in urgency_patterns:
if pattern in text:
penalty += 20 # áá
áēáá¯ááĢááá¯ááēḠáá% ááģážáąáŦáˇááģáááē
# áá AI á áá% ááģáąáŦáē áĄá
á
áēááá¯áˇááŧáąáŦáááēááąáŦááē Penalty ááĢáááē Score ááᯠááģááēááģááēá¸ááģáááē
final_score = real_score - penalty
# áá Logic Correction (Case 2/3 Fix)
# AI á ááááēáááąááģáŦáá°á¸ (áá
% áĄáąáŦááē) ááá¯áááē 'ááļááááŧá
áēááŊááē' áááēááᯠááá¯ááá¯áˇáááē
if final_score < 75:final_score = final_score * 0.6 # Score ááᯠáááēááģážáąáŦáˇááģááŧááēá¸
return round(max(5, final_score), 2)
@staticmethod
def check_rss_similarity(headline, url=""):
import requests
if not headline or len(headline) < 10: return 0
trusted_domains = [
"bbc.com/burmese", "rfa.org/burmese", "burmese.voanews.com",
"mizzima.com", "khitthitnews.org", "dvb.no", "myanmar-now.org",
"reuters.com", "apnews.com", "nytimes.com", "cnn.com",
"theguardian.com", "aljazeera.com", "dw.com", "france24.com",
"bloomberg.com", "wsj.com", "forbes.com","myanmarcelebrity.com"
]
base_bonus = 0
if url:
for domain in trusted_domains:
if domain in url.lower():
base_bonus = 90
break
feeds = [
"https://burmese.voanews.com/api/z$y_iqve_t",
"https://www.rfa.org/burmese/rss2.xml",
"https://www.bbc.com/burmese/index.xml",
"https://burmese.dvb.no/feed",
"https://www.mizzimaburmese.com/rss",
"https://www.khitthitnews.com/feed",
"https://burmese.irrawaddy.com/feed",
"https://www.myanmarcelebrity.com/feeds/posts/default?alt=rss",
"https://popularmyanmar.com/feed/"
]
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}
max_sim = 0
clean_headline = re.sub(r'[^\\u1000-\\u109F a-zA-Z0-9]', ' ', headline).lower()
headline_keywords = set([w for w in clean_headline.split() if len(w) > 2])
for f_url in feeds:
try:
response = requests.get(f_url, headers=headers, timeout=5)
if response.status_code == 200:
feed = feedparser.parse(response.content)
for entry in feed.entries:
seq_sim = SequenceMatcher(None, headline.lower(), entry.title.lower()).ratio()
entry_clean = re.sub(r'[^\\u1000-\\u109F a-zA-Z0-9]', ' ', entry.title).lower()
entry_keywords = set([w for w in entry_clean.split() if len(w) > 2])
common = headline_keywords.intersection(entry_keywords)
keyword_sim = len(common) / len(headline_keywords) if headline_keywords else 0
max_sim = max(max_sim, seq_sim, keyword_sim)
if max_sim > 0.85: break
except: continue
rss_val = min(100, max_sim * 160)
final_score = max(base_bonus, rss_val)
return final_score
# --- 3. MASTER ENGINE (Final Presentation Version) ---
def master_detector_v12(text, url, image):
try:
v = VerificationSystem()
# áá đ¨ Empty Input Check (á
áŦááŦá¸áááĢááģážááē)
if not text or not text.strip():
return "
â ī¸ ááģáąá¸áá°á¸ááŧá¯á á
á
áēááąá¸ááá¯ááąáŦ ááááēá¸á
áŦááŦá¸ááᯠáááˇáēááŊááēá¸ááĢá
"
# áá đ¨ Short Text Check ("I don't know" Logic - á
áŦááŦá¸ááá¯ááŊááēá¸ááģážááē)
words = text.split()
if len(words) < 10:
return f"""
â ī¸ áĄááģááēáĄáááē ááá¯áļááąáŦááēááĢ (Insufficient Information)
áááēáááˇáēááŊááēá¸ááŦá¸ááąáŦ á
áŦááŦá¸áážáŦ ({len(words)} áá¯áļá¸ááŦ) áážáááŧáŽá¸ ááá¯ááąáŦááēá¸ááŊááēá¸ááĢáááēá
AI ááž ááááģá
áŊáŦ áááēá¸á
á
áēááá¯ááēáááēáĄááŊááē áĄáááēá¸áá¯áļḠá
ááŦá¸áá¯áļḠ(áá) áá¯áļá¸áážááˇáēáĄáááē ááĢáááēááąáŦ ááááēá¸áĄááŧááˇáēáĄá
á¯áļááᯠáááˇáēááŊááēá¸ááąá¸ááĢá
"""
# URL ááĢá áááĢ á
á
áēááąá¸ááŧááēá¸
has_url = bool(url and url.strip() != "")
is_myanmar = bool(re.search(r'[\u1000-\u109F]', text))
# --- đ 3. UI Progress Bar áááēááŽá¸ááąá¸ááąáŦ Helper Function ---
def create_bar(label, value, bar_color, is_mm, nlp_label_local, custom_msg=None):
if custom_msg:
explainer = custom_msg
else:
if label == "Source Trust":
if not has_url:
explainer = "âšī¸ URL áááˇáēááē áááĢáááēáááŧááˇáē ááááēá¸áááēá¸ááŧá
áēááᯠáĄáááēááŧá¯á ááááĢá" if is_mm else "âšī¸ No URL provided. Source unverified."
bar_color = "#94a3b8" # URL áááĢááģážááē áá˛ááąáŦááēááŧáááē
else:
explainer = ("â
áá¯áļááŧááēáááąáŦ áááēá¸ááŧá
áēááŧá
áēáááēá" if value > 75 else "â ī¸ áááēá¸ááŧá
áē ááááēááŧáááēááŧááēḠ(ááá¯áˇ) ááá¯ááááēá¸áááēáááēḠáá¯áááēááŧááēá¸á") if is_mm else ("â
Verified source." if value > 75 else "â ī¸ Low authority source.")
elif label == "Global Consistency":
explainer = ("â
áĄááŧáŦá¸ááŽááŽááŦááģáŦá¸ááŊááēáááēḠááąáŦáēááŧááŦá¸áááēá" if value > 60 else "âšī¸ áĄááŧáŦá¸ááááēá¸ááŦáááģáŦá¸ááŊááē áĄáááēááŧá¯ááģááē áááŊáąáˇáááąá¸ááĢá") if is_mm else ("â
Corroborated." if value > 60 else "âšī¸ Not corroborated yet.")
elif label == "AI Pattern Analysis":
explainer = ("â
AI ááž ááááēá¸áážááē áĄááąá¸áĄááŦá¸áᯠáá¯áļá¸ááŧááēáááēá" if nlp_label_local == "Real" else "â ī¸ AI ááž ááááēá¸áá¯/Clickbait áᯠáááēáážááēáááēá" if nlp_label_local == "Fake" else "âšī¸ AI áĄááŊááē áá¯áļá¸ááŧááēáááē áááēáá˛ááąáŦ ááąáŦááŊáąá¸ááąáááˇáē áĄááąá¸áĄááŦá¸ááŧá
áēáááēá") if is_mm else ("â
Legitimate pattern." if nlp_label_local == "Real" else "â ī¸ Misinformation pattern." if nlp_label_local == "Fake" else "âšī¸ Neutral/Mixed pattern.")
elif label == "Image Integrity":
explainer = ("â
áá¯áļááááēáážáŦ áá°áááēá¸áĄááá¯ááēá¸ááŧá
áēááŧáŽá¸ ááŧááēáááēáážá¯ áááŊáąáˇáááĢá" if value > 85 else "â ī¸ áá¯áļááááēááᯠááŧá¯ááŧááēááŦá¸ááąáŦ ááášáááŦáážááááēá") if is_mm else ("â
No tampering." if value > 85 else "â ī¸ Potential tampering.")
elif label == "Visual Context":
explainer = ("â
áá¯áļáážááˇáēá
áŦááŦḠááá¯ááēááŽáážá¯áážááááēá" if value > 72 else "â ī¸ áá¯áļáážááˇáēá
áŦááŦḠáá
áēááŧáŦá¸á
áŽááŧá
áēááąáááēá") if is_mm else ("â
Context matches." if value > 72 else "â ī¸ Context mismatch.")
else: explainer = ""
return f"""
{label}{value:.1f}%
{explainer}
"""
# --- đ 4. Core Metrics Calculations ---
url_image = v.extract_image_from_url(url) if has_url else None
src_score, src_msg = v.get_source_score(url) if has_url else (0, "No URL")
rss_score = v.check_rss_similarity(text, url=url)
nlp_score = v.get_nlp_prediction(text)
# NLP Score Labeling
if nlp_score >= 65:
nlp_label = "Real"; nlp_val = nlp_score; ai_c = "#10b981"
elif nlp_score <= 40:
nlp_label = "Fake"; nlp_val = 100 - nlp_score; ai_c = "#dc2626"
else:
nlp_label = "Neutral"; nlp_val = nlp_score; ai_c = "#94a3b8"
# --- đŧī¸ 5. Image Processing & Zero-shot Classification ---
image_bars_html = ""
img_msg_extra = ""
sim_msg = ""
if image:
# ááŧááēááŊááēá¸/ááŧááēá ááŧááēááŊááēá¸ááŊá˛ááŧáŦá¸ááŧááēḠ(Zero-shot CLIP)
try:
img_obj = Image.open(image)
loc_prompts = [
"a photo taken in Myanmar, Burmese streets, pagodas, Asian people, Myanmar culture",
"a photo taken in a foreign country, Western people, foreign streets, Europe, America, Africa, Middle East"
]
loc_embs = clip_model.encode(loc_prompts)
img_emb_local = clip_model.encode(img_obj)
loc_scores = util.cos_sim(img_emb_local, loc_embs)[0]
if loc_scores[1] > loc_scores[0] + 0.02:
img_msg_extra = "
đ AI Visual Scan: á¤áá¯áļáááē ááŧááēáááá¯ááēááļááž ááŧááēááŊááēá¸ááŧá
áēááá¯ááēááŧáąááģáŦá¸ááĢáááēá (Foreign Image Detected)"
elif loc_scores[0] > loc_scores[1] + 0.02:
img_msg_extra = "
đ˛đ˛ AI Visual Scan: á¤áá¯áļáááē ááŧááēááŊááēá¸ááž ááŧááēááŊááēá¸ááŧá
áēááá¯ááēááŧáąááģáŦá¸ááĢáááēá (Domestic Image)"
except Exception as e:
pass # Error áááēááģážááē ááģáąáŦáēááŊáŦá¸áááē
img_ela_score, _ = v.perform_ela(image)
img_sim_score, sim_msg_original = v.get_image_text_similarity(image, text, url=url, url_image_path=url_image)
# Combine image similarity message with location detection
sim_msg = sim_msg_original + img_msg_extra
# âī¸ Dynamic Weighting for Image Case
if has_url:
weights = {'source': 0.20, 'nlp': 0.35, 'rss': 0.15, 'ela': 0.10, 'sim': 0.20}
else:
# URL áááĢááģážááē Source ááᯠ0 ááŦá¸ááŧáŽá¸ AI áážááˇáē Image ááᯠáĄááąá¸ááąá¸áááē
weights = {'source': 0.0, 'nlp': 0.45, 'rss': 0.15, 'ela': 0.15, 'sim': 0.25}
final = (src_score * weights['source']) + (nlp_score * weights['nlp']) + \
(rss_score * weights['rss']) + (img_ela_score * weights['ela']) + \
(img_sim_score * weights['sim'])
image_bars_html = f"""
{create_bar("Image Integrity", img_ela_score, "#f59e0b", is_myanmar, nlp_label)}
{create_bar("Visual Context", img_sim_score, "#06b6d4", is_myanmar, nlp_label, custom_msg=sim_msg)}
"""
else:
# âī¸ Dynamic Weighting for Text-Only Case
if has_url:
weights = {'source': 0.35, 'nlp': 0.50, 'rss': 0.15}
else:
# áá¯áļááąáŦá URL ááąáŦ áááĢááģážááē NLP ááᯠ80% áĄáá áĄááąá¸ááąá¸áááē
weights = {'source': 0.0, 'nlp': 0.80, 'rss': 0.20}
final = (src_score * weights['source']) + (nlp_score * weights['nlp']) + (rss_score * weights['rss'])
img_msg = "âšī¸ áá¯áļáááĢáááēáááˇáēáĄááŊááē Visual Analysis áááŧá¯áá¯ááēááĢá" if is_myanmar else "âšī¸ No image for visual analysis."
image_bars_html = f'{img_msg}
'
# --- đ 5.1 Fact-Verification Override (The Veto Power - For Cele News ONLY) đ ---
is_corroborated = rss_score >= 70
cele_keywords = ["áááēá¸ááŦá¸", "áááēá¸áááŽá¸", "áĄááá¯ááąáŦáē", "ááá¯ááēááąáŦááē", "áááēááŽ", "áááēáááē", "celebrity"]
is_cele_news_context = any(kw in text.lower() for kw in cele_keywords)
if is_corroborated and is_cele_news_context:
final = max(final, 85.0)
if nlp_label == "Fake":
nlp_label = "Real (Overridden by Cele Fact-Check)"
# --- đī¸ 6. Final Status & Dynamic Color Logic ---
if final >= 75:
main_bg, accent_c, status = "#ecfdf5", "#059669", "â
áá¯áļááŧááēá
áááēááģáááąáŦ ááááēḠ(RELIABLE VERDICT)"
elif final >= 45 and final < 75:
main_bg, accent_c, status = "#f8fafc", "#64748b", "âī¸ áĄáááēááŧá¯áááēáááēáá˛ááąáŦ ááááēḠ(INCONCLUSIVE / NEUTRAL)"
else:
main_bg, accent_c, status = "#fef2f2", "#dc2626", "đ¨ ááááēá¸áᯠ/ áĄáášáááŦááēáážáááąáŦááááēḠ(FAKE / HIGH RISK)"
# --- đ 7. Analysis & Recommendations ---
analysis_header = "đ áĄááąá¸á
áááē áááēá¸á
á
áēááģááē" if is_myanmar else "đ Detailed Reasoning"
tips_header = "đĄī¸ áĄááŧáļááŧá¯ááģááēáážááˇáē áááááŧá¯áááē" if is_myanmar else "đĄī¸ Recommendations"
reasons = []
tips = []
if final >= 75:
# đ Cele News Veto áĄáá¯ááēáá¯ááēáá˛áˇááģážááē Message ááŧáąáŦááēá¸ááŧáááē đ
if is_corroborated and is_cele_news_context:
reasons.append("đ Cele News Verified: á¤áĄáá¯áááŦááááēá¸ááᯠáĄááŧáŦá¸ááąáŦ áááŦá¸áááē ááŽááŽááŦááģáŦá¸ááŊááēááĢ áĄáááĄááģ ááąáŦáēááŧááŦá¸áááŧááˇáē ááááēá¸áĄáážááēááŧá
áēááŧáąáŦááēḠáĄáááēááŧá¯ááĢáááēá")
else:
reasons.append("â
ááááēá¸áááēá¸ááŧá
áēáážááˇáē áĄááģááēáĄáááēááģáŦḠááá¯ááēááŦáážá¯áážááááēá")
tips.append("đĄ á¤ááááēá¸áááē áá¯áļááŧááēá
áááēááģááááŧááˇáē ááąááģážááá¯ááēááĢáááēá")
elif final >= 45 and final < 75:
reasons.append("âī¸ áĄááģááēáĄáááēááģáŦá¸áážáŦ áĄáážááēáážááˇáē áĄáážáŦḠááąáŦááŊáąá¸ááąááá¯ááēááĢáááēá (ááá¯áˇ) áá¯áļááąáŦááēááąáŦ áááēááąáĄááąáŦááēáĄááŦḠáááŊáąáˇáááąá¸ááĢá")
tips.append("đĄ á¤ááááēá¸ááᯠááģááēááģááēá¸ááá¯áļááŧááēáᲠáĄááŧáŦá¸áááŦá¸áááē ááŽááŽááŦááŧáŽá¸ááģáŦá¸ááŊááē áááēááļá
á
áēááąá¸áááē áĄááŧáļááŧá¯áĄááēááĢáááēá")
else:
if nlp_label == "Fake": reasons.append("â ī¸ AI á
áá
áēááž á¤á
áŦááŦá¸áááē ááááēá¸áĄáá¯/Clickbait áá¯áļá
áļááŧá
áēááąááŧáąáŦááēḠááŊáąáˇáážáááááēá")
if not has_url:
reasons.append("âšī¸ ááááēá¸áááēá¸ááŧá
áē (URL) áááˇáēááŊááēá¸ááŦá¸ááŧááēḠááážááááŧááˇáē áá°áááēá¸áááēá¸ááŧá
áēááᯠáĄáááēááŧá¯áááē áááēáá˛ááĢáááēá")
elif src_score < 40:
reasons.append(f"â ááááēá¸áááēá¸ááŧá
áē ({src_msg}) áááē á
áááēáááģáááĢá")
tips.append("đĄ ááááēá¸áĄáážáŦá¸ááŧá
áēááá¯ááēááŧáą áĄááŊááēááģáŦá¸áááŧááˇáē áĄááŧáŦá¸áá°ááģáŦá¸ááļ áááēáááēáááąááģážáááē áĄááááąá¸áĄááēááĢáááēá")
reasons_html = "".join([f"{r}" for r in reasons])
tips_html = "".join([f"{t}" for t in tips])
# --- đ 8. Final HTML Output
return f"""
{status}
{final:.1f}%
đ Verification Metrics:
{create_bar("Source Trust", src_score, "#3b82f6", is_myanmar, nlp_label)}
{create_bar("Global Consistency", rss_score, "#8b5cf6", is_myanmar, nlp_label)}
{create_bar("AI Pattern Analysis", nlp_val, ai_c, is_myanmar, nlp_label)}
{image_bars_html}
= 75 else "#f8fafc" if final >= 45 else "#fef2f2")}; padding: 15px; border-radius: 10px; border-left: 5px solid {accent_c}; margin-top: 15px;">
{analysis_header}
{reasons_html if reasons_html else "- Analysis complete.
"}
"""
except Exception as e:
import traceback
return f"System Error: {str(e)}
{traceback.format_exc()} "
# --- 4. MODERN UI DESIGN ---
custom_css = """
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
@import url('https://mmwebfonts.comquas.com/fonts/?font=pyidaungsu');
.gradio-container { background-color: #f9fafb !important; font-family: 'Inter', 'Pyidaungsu' !important; }
.card { background: white !important; border-radius: 20px !important; border: 1px solid #e5e7eb !important; padding: 30px !important; }
textarea { font-family: 'Pyidaungsu', sans-serif !important; font-size: 16px !important; }
.btn-primary { background: #111827 !important; color: white !important; border-radius: 10px !important; font-weight: 600 !important; }
"""
with gr.Blocks(css=custom_css) as demo:
gr.HTML("đĄī¸ Fake News Detection (Powered by Myanmar AI)
")
with gr.Tabs():
with gr.TabItem("đ Verifier Dashboard"):
with gr.Row():
with gr.Column(scale=5, elem_classes="card"):
txt = gr.Textbox(label="News Content", lines=8, placeholder="ááááēá¸á
áŦááŦá¸ááᯠá¤ááąááŦááŊááē áááˇáēááĢ...")
url = gr.Textbox(label="Source URL (Optional)")
img = gr.Image(label="Image Attachment", type="filepath")
with gr.Row():
clear = gr.Button("Reset Fields")
submit = gr.Button("Analyze News", variant="primary")
with gr.Column(scale=5):
empty_html = "Ready for analysis...
"
output = gr.HTML(value=empty_html)
with gr.TabItem("âšī¸ About System"):
with gr.Column(elem_classes="card"):
gr.Markdown(r"""
# đĄī¸ Fake News Detection System Methodology
### đ 1. Project Overview & System Rationale
ᤠProject áááē ááááēá¸áᯠ(Fake News) ááģáŦá¸ááᯠáážáŦááŊáąááŦááŊááē á
áŦááŦá¸áááēáááᲠContext áĄáŦá¸áá¯áļá¸ááᯠááŧá¯áļáá¯áļááŧááˇáēáááˇáē **Multi-modal Framework** áá
áēáá¯ááŧá
áēáááēá á¤á
áá
áēáááē ááááēá¸áá
áēáá¯á á
á
áēáážááēáážá¯ááᯠáĄááģááē (á) ááģááēááŧááˇáē ááá¯ááēá¸ááŦááĢáááēá
1. **Linguistic Style:** áĄááąá¸áĄááŦá¸áá¯áļá
áļáážáŦ ááĢáááŧááˇáēá
áŦááŦá¸ááŧá
áēááąáááŦá¸?
2. **Metadata & Source:** ááááēá¸ááŦááŦ áááēá¸ááŧá
áēá áá¯áļááŧááēááááŦá¸?
3. **Visual Integrity:** ááááēá¸ááŊááēááĢááąáŦ áá¯áļáááē ááŧá¯ááŧááēááŦá¸áááŦḠááá¯áˇááá¯ááē á
áŦááŦá¸áážááˇáē ááá¯ááēááŽáážá¯áážááááŦá¸?
4. **Global consistency:** áĄááŧáŦá¸ááąáŦ ááŽááŽááŦááŧáŽá¸ááģáŦá¸áážáŦ ááąáŦáēááŧááŦá¸ááŧááēá¸áážááááŦá¸?
---
### đ§ 2. Core Algorithms & Methodology
#### **A. Transformer-based Classification (XLM-RoBERTa)**
* **Algorithm:** *XLM-RoBERTa (Cross-lingual Language Model)*
* **Implementation:** ᤠModel áááē ááŧááēááŦá
áŦ ááááēá¸áážááēáážááˇáē ááááēá¸áĄáᯠData ááąáŦááēááąáĢááēá¸ááģáŦá¸á
áŊáŦááᯠááá¯ááēááá¯ááē áááēáá° (Fine-tuned) ááŦá¸ááąáŦ ááá¯ááēááá¯ááē AI á
áá
áēááŧá
áēáááēá ááŦááŦááŧááēá
ááŦáááá¯áᲠááŧááēááŦá
áŦááᯠááá¯ááēááá¯ááē ááŦá¸áááēá
á
áēááąá¸ááá¯ááēáááēá
#### **B. Visual Forensics (ELA & CLIP)**
* **Error Level Analysis (ELA):** JPEG áá¯áļááááēáá
áēáá¯ááᯠááŧááēááááēá¸áááˇáēáĄááĢ ááŧá¯ááŧááēááŦá¸ááąáŦ Pixel ááģáŦá¸áááē Error Level ááŊá˛ááŧáŦá¸ááŊáŦá¸ááŧááēá¸ááᯠáĄááŧáąááļá áá¯áļááŧááē/áááŧááēááᯠá
á
áēááąá¸áááēá
* **CLIP (Contrastive Language-Image Pre-training):** NLP áááēá¸áááŦááᯠáĄáá¯áļá¸ááŧá¯á á
áŦááŦá¸áá˛ááŊááē ááĢáááēááąáŦ "áĄááŧáąáŦááēá¸áĄááŦ" áážááˇáē áá¯ááēáá¯áļáá˛ááŊááē ááŧááēááŊáąáˇáááąáŦ "áĄááŧááēáĄáŦáá¯áļááá¯ááēááŦ áááąáŦáááŦḠ(Visual Concept)" ááá¯áˇá ááá¯ááēááŽáážá¯ááᯠText-Image Embedding Alignment áááēá¸áááēá¸ááŧááˇáē ááá¯ááēá¸ááŦáááēá
#### **C. Source Verification & RSS Matching**
* **Whois Analysis:** Domain á áááēáááēá¸ááᯠá
á
áēááąá¸áááēá ááááēá¸áĄáá¯ááá¯ááēáĄááģáŦá¸á
á¯áážáŦ áááēáááēḠ(á) ááĄáąáŦááēááŦ áážááááēáááēá
* **RSS Feed Comparison:** BBC, RFA, VOA á
áááˇáē áá¯áļááŧááēáááąáŦ ááááēá¸ááŦáááŧáŽá¸ááģáŦá¸á áááēáážáááááēá¸ááąáĢááēá¸á
ááēááģáŦá¸áážááˇáē áááˇáēááááēá¸ááᯠááá¯ááēááá¯ááēá
á
áēááąá¸ááŧáŽá¸ áĄááŧáŦá¸ááŽááŽááŦááŊááē ááĢá áááĢ áá¯áļá¸ááŧááēáááēá
---
### âī¸ 3. Mathematical Scoring Model
á
áá
áēááž ááážáááŦááąáŦ Metrics áá
áēáá¯ááģááēá¸á
áŽááᯠáĄáąáŦááēááĢ **Weighted Average Formula** ááŧááˇáē ááąáĢááēá¸á
ááēááŦ Confidence Score áá¯ááēááąá¸ááĢáááēá
$$Score = (W_{nlp} \cdot NLP) + (W_{src} \cdot Source) + (W_{rss} \cdot RSS) + (W_{vis} \cdot Visual)$$
| Metric | Weight (With Image) | Weight (Text Only) |
| :--- | :--- | :--- |
| **AI Pattern (NLP)** | 30% | 45% |
| **Source Authority** | 25% | 45% |
| **Visual Forensics** | 10% | - |
| **Content Consistency**| 20% | - |
| **Global Consensus** | 15% | 10% |
---
### đ¯ 4. Project Deliverables
* â
**Hybrid Detection:** á
áŦááŦá¸ááąáŦ áá¯áļááĢ á
á
áēááąá¸ááá¯ááēááŧááēá¸á
* â
**Evidence-Based Reasoning:** áĄááŧáąáá
áēáá¯áááēḠááá¯ááēáᲠáĄááŧáąáŦááēá¸ááŧááģááēááĢ ááąáŦáēááŧááŧááēá¸á
* â
**Burmese Language Support:** ááŧááēááŦá
áŦááŦá¸ááģáŦá¸ááᯠááá¯ááēááá¯ááē ááŦá¸áááēááąáŦááēááļáˇááąá¸ááŧááēá¸á
""")
submit.click(master_detector_v12, inputs=[txt, url, img], outputs=output)
clear.click(lambda: ["", "", None, empty_html], outputs=[txt, url, img, output])
if __name__ == "__main__":
demo.launch(share=True)