From 5743e4b70396829d18a02a53300876131b8cb32f Mon Sep 17 00:00:00 2001 From: smfahim25 Date: Thu, 19 Dec 2024 16:49:18 +0600 Subject: [PATCH] update for backend integration --- app.json | 2 +- app/(tabs)/index.tsx | 594 +++++++++++++++++++++++++------------------ package.json | 2 +- 3 files changed, 343 insertions(+), 255 deletions(-) diff --git a/app.json b/app.json index aef4374..b957482 100644 --- a/app.json +++ b/app.json @@ -1,6 +1,6 @@ { "expo": { - "name": "fake-check-bd", + "name": "FactChecker", "slug": "fake-check-bd", "version": "1.0.0", "orientation": "portrait", diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 7e7a53d..2a36f55 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -1,219 +1,228 @@ import { WebView } from "react-native-webview"; -import { useRef } from "react"; +import { useRef, useState } from "react"; +import { Alert, StyleSheet, View } from "react-native"; export default function HomeScreen() { const webviewRef = useRef(null); + const [postData, setPostData] = useState([]); + const [isApiCallInProgress, setIsApiCallInProgress] = useState(false); + + // Function to call the fact-check API + const checkFacts = async (caption) => { + try { + + const response = await fetch('https://factcheck.planpostai.com/check-facts', { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ query: caption }), + }); + + const responseText = await response.text(); + console.log('Raw API Response:', responseText); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status} - ${responseText}`); + } + + const result = JSON.parse(responseText); + + // Send the result back to WebView to show toast + webviewRef.current?.injectJavaScript(` + (function() { + showToast('${result.evidence.replace(/'/g, "\\'")}'); + return true; + })(); + `); + + return result; + } catch (error) { + console.error('API Error Details:', { + message: error.message, + stack: error.stack, + name: error.name + }); + + // Show error in WebView + webviewRef.current?.injectJavaScript(` + (function() { + showToast('Error: ${error.message.replace(/'/g, "\\'")}', 5000, true); + return true; + })(); + `); + + throw error; + } + }; + + const handleMessage = async (event) => { + try { + const data = JSON.parse(event.nativeEvent.data); + + switch (data.type) { + case 'postData': + setPostData(prevData => [...prevData, { + caption: data.caption, + imageUrls: data.imageUrls, + postId: data.postId + }]); + + if (isApiCallInProgress) { + return; + } + + setIsApiCallInProgress(true); + + try { + const result = await checkFacts(data.caption); + + setIsApiCallInProgress(false); + webviewRef.current?.injectJavaScript(` + (function() { + enableAllButtons(); + const button = document.querySelector('button[data-post-id="${data.postId}"]'); + if (button) { + button.textContent = 'Processed'; + button.style.background="green"; + button.style.color="white"; + button.style.padding="5px 5px"; + button.style.opacity = '0.7'; + button.disabled = true; + } + return true; + })(); + `); + } catch (error) { + setIsApiCallInProgress(false); + webviewRef.current?.injectJavaScript(` + (function() { + enableAllButtons(); + const button = document.querySelector('button[data-post-id="${data.postId}"]'); + if (button) { + button.disabled = false; + button.style.opacity = '1'; + button.textContent = 'Check'; + } + return true; + })(); + `); + } finally { + setIsApiCallInProgress(false); + } + break; + + case 'error': + console.error(`WebView Error: ${data.message}`); + break; + } + } catch (error) { + console.error('Error handling message:', error); + } + }; const injectedJavaScript = ` (function() { - try { - // Inject CSS for toast notifications - const style = document.createElement('style'); - style.innerHTML = \` - #toast-container { - position: fixed; - top: 50px; - right: 20px; - left: 20px; - z-index: 999999; - display: flex; - flex-direction: column; - gap: 10px; - pointer-events: auto; - } - .toast { - background-color: #1db146; - color: #fff; - font-weight: 800; - padding: 12px 16px; - border-radius: 8px; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); - font-size: 16px; - opacity: 1; - position: relative; - width: 90%; - z-index: 99999999999; - transition: opacity 0.5s ease; - } - .toast.fade-out { - opacity: 0; - } - \`; - document.head.appendChild(style); - - // Fact-checking logic - let lastClickTime = 0; - const COOLDOWN_DURATION = 60000; - let buttonClicked = false; - let fullCaption = ""; - let imageUrls = []; - let isApiCallInProgress = false; - - // Toast notification function - function showToast(message, duration = 5000) { - const container = document.getElementById('toast-container') || - (() => { - const cont = document.createElement('div'); - cont.id = 'toast-container'; - document.body.appendChild(cont); - return cont; - })(); - - const toast = document.createElement('div'); - toast.className = 'toast'; - toast.textContent = message; - container.appendChild(toast); - - setTimeout(() => { - toast.classList.add('fade-out'); - toast.addEventListener('transitionend', () => { - toast.remove(); - }); - }, duration); + // Add toast CSS + const style = document.createElement('style'); + style.innerHTML = \` + #toast-container { + position: fixed; + top: 50px; + right: 20px; + left: 20px; + z-index: 999999; + display: flex; + flex-direction: column; + gap: 10px; + pointer-events: auto; } - - // Function to disable all buttons - function disableAllButtons() { - document.querySelectorAll("button").forEach((btn) => { - btn.disabled = true; - btn.style.opacity = "0.5"; - btn.style.cursor = "not-allowed"; - }); + .toast { + background-color: #1db146; + color: #fff; + font-weight: 800; + padding: 12px 16px; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); + font-size: 16px; + opacity: 1; + position: relative; + width: 90%; + z-index: 99999999999; + transition: opacity 0.5s ease; } + .toast.fade-out { + opacity: 0; + } + .error-toast { + background-color: #ff4444; + } + \`; + document.head.appendChild(style); - // Function to enable all buttons - function enableAllButtons() { - document.querySelectorAll("button").forEach((btn) => { + // Function to disable all buttons + window.disableAllButtons = function() { + document.querySelectorAll("button").forEach((btn) => { + btn.disabled = true; + btn.style.opacity = "0.5"; + btn.style.cursor = "not-allowed"; + }); + }; + + // Function to enable all buttons + window.enableAllButtons = function() { + document.querySelectorAll("button").forEach((btn) => { + if (btn.textContent !== "Processed") { btn.disabled = false; btn.style.opacity = "1"; btn.style.cursor = "pointer"; - }); - } - - // Fact-checking API call - async function checkFacts(caption, button) { - try { - isApiCallInProgress = true; - disableAllButtons(); - const response = await fetch('http://localhost:8000/check-facts', { - method: 'POST', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ query: caption }), - }); - - if (!response.ok) { - throw new Error('Network response was not ok'); - } - - const result = await response.json(); - showToast(result.evidence); - button.textContent = 'Processed'; - } catch (error) { - console.error('Error:', error); - showToast('An error occurred while checking facts'); - } finally { - isApiCallInProgress = false; - enableAllButtons(); - button.disabled = false; - button.style.opacity = "1"; } - } + }); + }; - function collectPostData(post) { - if (post.dataset.processed === "true") return; + // Toast notification function + window.showToast = function(message, duration = 5000, isError = false) { + const container = document.getElementById('toast-container') || + (() => { + const cont = document.createElement('div'); + cont.id = 'toast-container'; + document.body.appendChild(cont); + return cont; + })(); - const captionElement = post.querySelector( - "div.m div.m div[data-type='text'] div.native-text" - ); - let caption = captionElement ? captionElement.textContent.trim() : null; + const toast = document.createElement('div'); + toast.className = 'toast' + (isError ? ' error-toast' : ''); + toast.textContent = message; + container.appendChild(toast); - const imageElements = post.querySelectorAll("div.m.bg-s13 img"); - let imageURLs = []; - imageElements.forEach((imageElement) => { - if (imageElement.src) imageURLs.push(imageElement.src); + setTimeout(() => { + toast.classList.add('fade-out'); + toast.addEventListener('transitionend', () => { + toast.remove(); }); + }, duration); + }; - if (buttonClicked) { - fullCaption = caption; - imageUrls.push(...imageURLs); - } - post.dataset.processed = "true"; - - const button = createButton(post, caption, imageURLs); - if (caption?.length > 30 || imageURLs.length > 0) { - post.style.position = "relative"; - post.appendChild(button); - } - } - - function createButton(post, initialCaption, initialImageURLs) { - const button = document.createElement("button"); - button.textContent = "Check"; - - Object.assign(button.style, { - position: "absolute", - fontSize: "20px", - right: "10px", - bottom: "10px", - background: "linear-gradient(135deg, #6a11cb, #2575fc)", - color: "#fff", - border: "none", - padding: "12px 20px", - borderRadius: "8px", - cursor: "pointer", - zIndex: 1000000, - display: "flex", - justifyContent: "center", - alignItems: "center", - gap: "8px", - pointerEvents: "auto", - transition: "all 0.3s", - }); - - button.addEventListener("click", (event) => { - event.stopPropagation(); - const currentTime = new Date().getTime(); - - lastClickTime = currentTime; - - button.disabled = true; - button.textContent = "Processing..."; - button.style.opacity = "0.5"; - - const seeMoreElement = post.querySelector("span[style*='color:#65676b']"); - - if (seeMoreElement) { - handleSeeMoreClick( - seeMoreElement, - post, - button, - initialCaption, - initialImageURLs - ); - } else { - collectDataImmediately(post, button, initialCaption, initialImageURLs); - } - }); - - return button; - } - - async function handleSeeMoreClick(seeMoreElement, post, button, initialCaption, initialImageURLs) { + // Function to handle "See More" click + async function handleSeeMoreClick(seeMoreElement, post, button, initialCaption, initialImageURLs, postId) { return new Promise((resolve) => { setTimeout(() => { seeMoreElement.click(); - buttonClicked = true; + setTimeout(() => { + const captionElement = post.querySelector( + "div.m div.m div[data-type='text'] div.native-text" + ); + fullCaption = captionElement ? captionElement.textContent.trim() : initialCaption; - setTimeout(async () => { - await checkFacts(fullCaption, button); - - button.textContent = "Processed"; - button.style.opacity = "0.7"; - button.disabled = true; + // Send data to React Native + window.ReactNativeWebView.postMessage(JSON.stringify({ + type: 'postData', + caption: fullCaption, + imageUrls: initialImageURLs, + postId + })); resolve(); }, 2000); @@ -221,78 +230,157 @@ export default function HomeScreen() { }); } - async function collectDataImmediately( - post, - button, - initialCaption, - initialImageURLs - ) { - buttonClicked = true; - + // Function to collect post data immediately + function collectDataImmediately(post, button, initialCaption, initialImageURLs, postId) { const captionElement = post.querySelector( "div.m div.m div[data-type='text'] div.native-text" ); - let caption = captionElement - ? captionElement.textContent.trim() - : initialCaption; + const caption = captionElement ? captionElement.textContent.trim() : initialCaption; - const imageElements = post.querySelectorAll("div.m img"); - let imageURLs = []; - imageElements.forEach((imageElement) => { - if (imageElement.src) imageURLs.push(imageElement.src); - }); + // Send data to React Native + window.ReactNativeWebView.postMessage(JSON.stringify({ + type: 'postData', + caption, + imageUrls: initialImageURLs, + postId + })); + } - if (imageURLs.length === 0) { - imageURLs = initialImageURLs; + const svgCode=\` + + + + + \` + + + // Function to collect post data and send to React Native + function collectPostData(post) { + if (post.dataset.processed === "true") return; + + // Get the caption + const captionElement = post.querySelector( + "div.m div.m div[data-type='text'] div.native-text" + ); + const caption = captionElement ? captionElement.textContent.trim() : null; + + // Get the first image (if exists) + const singleImageElement = post.querySelector("div.m.bg-s13 img"); + const singleImageURL = singleImageElement && singleImageElement.src ? singleImageElement.src : null; + + // Collect all image elements + const imageElements = post.querySelectorAll("div.m.bg-s13 img"); + const imageURLs = Array.from(imageElements) + .filter(img => img.src) + .map(img => img.src); + + post.dataset.processed = "true"; + + // Prioritize using a single image if only one exists, otherwise use all images + let imagesToUse = []; + if (singleImageURL) { + imagesToUse = [singleImageURL]; + } else if (imageURLs.length > 0) { + imagesToUse = imageURLs; + } + + // Create button if there's a caption or at least one image + if ((caption && caption.length > 40) || imagesToUse.length > 0) { + const postId = 'post_' + Math.random().toString(36).substr(2, 9); + createButton(post, caption, imagesToUse, postId); + } + } + + + // Create check button + function createButton(post, caption, imageURLs, postId) { + post.style.position = "relative"; + + const button = document.createElement("button"); + button.textContent = "Check"; + button.setAttribute('data-post-id', postId); + button.innerHTML=svgCode + + Object.assign(button.style, { + position: "absolute", + right: "5px", + top: "-30px", + border: "none", + borderRadius: "8px", + cursor: "pointer", + zIndex: 1000000, + pointerEvents: "auto", + transition: "all 0.3s", + }); + + button.addEventListener("click", async () => { + disableAllButtons(); + button.textContent = "Processing..."; + button.style.opacity = "0.5"; + button.style.color="white"; + button.style.padding="5px 5px"; + button.style.background="blue"; + + const seeMoreElement = post.querySelector("span[style*='color:#65676b']"); + + if (seeMoreElement) { + await handleSeeMoreClick(seeMoreElement, post, button, caption, imageURLs, postId); + } else { + collectDataImmediately(post, button, caption, imageURLs, postId); } - - await checkFacts(caption, button); - button.textContent = "Processed"; - button.style.opacity = "0.7"; - button.disabled = true; - } - - // Mutation observer to handle new posts - function handleMutations(mutationsList) { - mutationsList.forEach((mutation) => { - if (mutation.type === "childList" || mutation.type === "subtree") { - document.querySelectorAll("div.m.bg-s3").forEach((post) => { - if (!post.querySelector("button")) { - collectPostData(post); - } - }); - } - }); - } - - const observer = new MutationObserver(handleMutations); - observer.observe(document.querySelector(".m"), { - childList: true, - subtree: true, }); - document.addEventListener("DOMContentLoaded", () => { - document.querySelectorAll("div.m.bg-s3").forEach((post) => { - if (!post.dataset.processed) { - collectPostData(post); - } - }); - }); - - true; // Successful injection - } catch (error) { - console.error('Injection error:', error); - false; + post.appendChild(button); } + + // Mutation observer to handle new posts + function handleMutations(mutationsList) { + mutationsList.forEach((mutation) => { + if (mutation.type === "childList" || mutation.type === "subtree") { + document.querySelectorAll("div.m.bg-s3").forEach((post) => { + if (!post.querySelector("button")) { + collectPostData(post); + } + }); + } + }); + } + + const observer = new MutationObserver(handleMutations); + observer.observe(document.querySelector(".m"), { + childList: true, + subtree: true, + }); + + document.addEventListener("DOMContentLoaded", () => { + document.querySelectorAll("div.m.bg-s3").forEach((post) => { + if (!post.dataset.processed) { + collectPostData(post); + } + }); + }); + + return true; })(); `; return ( - + + + ); -} \ No newline at end of file +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + marginTop: 30, + }, +}); diff --git a/package.json b/package.json index ad88c20..5289b39 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "fake-check-bd", + "name": "fact-checker", "main": "expo-router/entry", "version": "1.0.0", "scripts": {