diff --git a/platform/chromium/js/background.js b/platform/chromium/js/background.js new file mode 100644 index 0000000..9f05eb8 --- /dev/null +++ b/platform/chromium/js/background.js @@ -0,0 +1,254 @@ +const filterListURLUnsafe = + "https://raw.githubusercontent.com/fmhy/FMHYFilterlist/refs/heads/main/sitelist.txt"; +const filterListURLPotentiallyUnsafe = + "https://raw.githubusercontent.com/fmhy/FMHYFilterlist/refs/heads/main/sitelist-plus.txt"; +const safeListURL = "https://api.fmhy.net/single-page"; +const starredListURL = + "https://raw.githubusercontent.com/fmhy/bookmarks/refs/heads/main/fmhy_in_bookmarks_starred_only.html"; + +let unsafeSites = []; +let potentiallyUnsafeSites = []; +let safeSites = []; +let starredSites = ["https://fmhy.net"]; + +// Helper function to extract URLs from markdown text +function extractUrlsFromMarkdown(markdown) { + const urlRegex = /https?:\/\/[^\s)]+/g; + return markdown.match(urlRegex) || []; +} + +// Helper function to extract URLs from HTML bookmarks +function extractUrlsFromBookmarks(html) { + const urlRegex = / line.trim() && !line.startsWith("#")); + } + + if (potentiallyUnsafeResponse.ok) { + const potentiallyUnsafeText = await potentiallyUnsafeResponse.text(); + potentiallyUnsafeSites = potentiallyUnsafeText + .split("\n") + .filter((line) => line.trim() && !line.startsWith("#")); + } + + console.log("Parsed Unsafe Sites:", unsafeSites); + console.log("Parsed Potentially Unsafe Sites:", potentiallyUnsafeSites); + } catch (error) { + console.error("Error fetching filter lists:", error); + } +} + +// Fetch the safe sites +async function fetchSafeSites() { + console.log("Fetching safe sites..."); + try { + const response = await fetch(safeListURL); + if (response.ok) { + const markdown = await response.text(); + const urls = extractUrlsFromMarkdown(markdown); + urls.forEach((siteUrl) => { + let fullUrl = normalizeUrl(siteUrl.trim()); + if (!safeSites.includes(fullUrl)) { + safeSites.push(fullUrl); + } + }); + } + console.log("Parsed Safe Sites:", safeSites); + } catch (error) { + console.error("Error fetching safe sites:", error); + } +} + +// Fetch the starred sites +async function fetchStarredSites() { + console.log("Fetching starred sites..."); + try { + const response = await fetch(starredListURL); + if (response.ok) { + const html = await response.text(); + const urls = extractUrlsFromBookmarks(html); + + // Normalize and add URLs to the starredSites array + starredSites = [...new Set(urls.map(normalizeUrl))]; + + // Ensure fmhy.net is always in the starred list + if (!starredSites.includes("https://fmhy.net")) { + starredSites.push("https://fmhy.net"); + } + + console.log("Parsed Starred Sites:", starredSites); + } + } catch (error) { + console.error("Error fetching starred sites:", error); + } +} + +// Update the Address Bar icon based on the site's status +function updatePageAction(status, tabId) { + // Define paths for different icon sizes + const icons = { + safe: { + 19: "../res/icons/safe_19.png", + 38: "../res/icons/safe_38.png", + }, + unsafe: { + 19: "../res/icons/unsafe_19.png", + 38: "../res/icons/unsafe_38.png", + }, + potentially_unsafe: { + 19: "../res/icons/potentially_unsafe_19.png", + 38: "../res/icons/potentially_unsafe_38.png", + }, + starred: { + 19: "../res/icons/starred_19.png", + 38: "../res/icons/starred_38.png", + }, + default: { + 19: "../res/ext_icon_144.png", + 38: "../res/ext_icon_144.png", // fallback to a known good icon + }, + }; + + let icon = icons[status] || icons["default"]; + + // Set the icon for the current tab with different sizes + chrome.action.setIcon( + { + tabId: tabId, + path: { + 19: icon[19], // 19x19 icon + 38: icon[38], // 38x38 icon + }, + }, + () => { + if (chrome.runtime.lastError) { + console.error("Error setting icon:", chrome.runtime.lastError.message); + } + } + ); +} + +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.action === "checkSiteStatus" && message.url) { + const normalizedUrl = normalizeUrl(message.url); + + let status = "no_data"; + if ( + starredSites.some((site) => normalizedUrl.startsWith(normalizeUrl(site))) + ) { + status = "starred"; + } else if (safeSites.includes(normalizedUrl)) { + status = "safe"; + } else if ( + unsafeSites.some((site) => normalizedUrl.includes(normalizeUrl(site))) + ) { + status = "unsafe"; + } else if ( + potentiallyUnsafeSites.some((site) => + normalizedUrl.includes(normalizeUrl(site)) + ) + ) { + status = "potentially_unsafe"; + } + + sendResponse({ status }); + } + return true; // Required to indicate that response will be sent asynchronously +}); + +// Check the site status and update the page action icon +function checkSiteAndUpdatePageAction(tabId, url) { + if (!url) return; + + const currentUrl = normalizeUrl(url.trim()); + console.log( + "Checking site status for address bar icon:", + currentUrl, + "TabId:", + tabId + ); + + let isStarred = starredSites.some((site) => + currentUrl.startsWith(normalizeUrl(site)) + ); + let isSafe = safeSites.some((site) => normalizeUrl(site) === currentUrl); + let isUnsafe = unsafeSites.some((site) => + currentUrl.includes(normalizeUrl(site)) + ); + let isPotentiallyUnsafe = potentiallyUnsafeSites.some((site) => + currentUrl.includes(normalizeUrl(site)) + ); + + if (isStarred) { + console.log("Updating address bar icon to starred for:", currentUrl); + updatePageAction("starred", tabId); + } else if (isSafe) { + console.log("Updating address bar icon to safe for:", currentUrl); + updatePageAction("safe", tabId); + } else if (isUnsafe) { + console.log("Updating address bar icon to unsafe for:", currentUrl); + updatePageAction("unsafe", tabId); + } else if (isPotentiallyUnsafe) { + console.log( + "Updating address bar icon to potentially unsafe for:", + currentUrl + ); + updatePageAction("potentially_unsafe", tabId); + } else { + console.log("No data for this site:", currentUrl); + updatePageAction("default", tabId); + } +} + +// Listen for tab updates +chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { + if (changeInfo.status === "complete" && tab.url) { + checkSiteAndUpdatePageAction(tabId, tab.url); + } +}); + +// Listen for tab activation +chrome.tabs.onActivated.addListener((activeInfo) => { + chrome.tabs.get(activeInfo.tabId, (tab) => { + if (tab.url) { + checkSiteAndUpdatePageAction(tab.id, tab.url); + } + }); +}); + +// Initialize the extension +async function initializeExtension() { + await fetchFilterLists(); + await fetchSafeSites(); + await fetchStarredSites(); + + console.log("Extension initialized successfully."); +} + +initializeExtension(); diff --git a/platform/chromium/manifest.json b/platform/chromium/manifest.json index 8b13789..4309284 100644 --- a/platform/chromium/manifest.json +++ b/platform/chromium/manifest.json @@ -1 +1,31 @@ - +{ + "manifest_version": 3, + "name": "FMHY SafeGuard", + "author": "Kenneth Hendricks & contributors", + "version": "1.0.4", + "icons": { + "128": "res/ext_icon_144.png" + }, + "action": { + "default_icon": { + "128": "res/ext_icon_144.png" + }, + "default_popup": "pub/index.html" + }, + "permissions": [ + "activeTab", + "storage", + "alarms", + "declarativeNetRequest", + "notifications", + "declarativeContent" + ], + "background": { + "service_worker": "js/background.js" + }, + "host_permissions": [ + "" + ], + "description": "FMHY SafeGuard extension to identify unsafe and potentially unsafe sites.", + "minimum_chrome_version": "100" +} diff --git a/platform/chromium/pub/index.html b/platform/chromium/pub/index.html new file mode 100644 index 0000000..900d656 --- /dev/null +++ b/platform/chromium/pub/index.html @@ -0,0 +1,155 @@ + + + + + + FMHY SafeGuard + + + + + + + +
+

FMHY SafeGuard

+
+ +
+ + Status Icon + +

Checking site status...

+
+ + +

+ + +
+

+ Think this is a mistake? + Let us know. +

+
+ + + + + + diff --git a/platform/chromium/pub/index.js b/platform/chromium/pub/index.js new file mode 100644 index 0000000..4d731c4 --- /dev/null +++ b/platform/chromium/pub/index.js @@ -0,0 +1,134 @@ +document.addEventListener("DOMContentLoaded", () => { + console.log("Popup loaded, preparing to check site status..."); + + const statusIcon = document.getElementById("status-icon"); + const statusMessage = document.getElementById("status-message"); + const errorMessage = document.getElementById("error-message"); + + // Helper function to normalize URLs (removes trailing slashes) + const normalizeUrl = (url) => url.replace(/\/+$/, "").trim(); + + // Helper function to extract the root domain from a URL + function extractRootDomain(url) { + let urlObj = new URL(url); + return `${urlObj.protocol}//${urlObj.hostname}`; + } + + try { + // Get the active tab's URL + chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { + const activeTab = tabs[0]; + + if (!activeTab || !activeTab.url) { + throw new Error("No active tab found or URL is unavailable."); + } + + const currentUrl = normalizeUrl(activeTab.url); + const rootDomain = extractRootDomain(currentUrl); + console.log(`Active tab URL: ${currentUrl}, Root domain: ${rootDomain}`); + + // Send a message to the background script to check the site's status + chrome.runtime.sendMessage( + { action: "checkSiteStatus", url: currentUrl }, + (response) => { + if (!response || !response.status) { + throw new Error( + "Failed to retrieve site status from the background script." + ); + } + + // Determine if the root domain or the full URL is marked as safe/starred + chrome.runtime.sendMessage( + { action: "checkSiteStatus", url: rootDomain }, + (isRootDomainMarked) => { + // Handle different site statuses and update the UI accordingly + if ( + isRootDomainMarked.status === response.status && + response.status !== "no_data" + ) { + // If both the root domain and the current URL share the same status, show the root domain in the message + handleStatusUpdate(response.status, rootDomain); + } else { + // Otherwise, show the current URL in the message + handleStatusUpdate(response.status, currentUrl); + } + } + ); + } + ); + }); + } catch (error) { + console.error("Error while checking site status:", error); + errorMessage.textContent = `Error: ${error.message}`; + updateUI("error", "An error occurred while retrieving the site status."); + } + + /** + * Updates the UI based on the site status + * @param {string} status - The status of the site (e.g., "safe", "unsafe") + * @param {string} displayUrl - The URL or root domain to display in the message + */ + function handleStatusUpdate(status, displayUrl) { + switch (status) { + case "unsafe": + updateUI( + "unsafe", + `${displayUrl} is flagged as unsafe. Be cautious when interacting with this site.` + ); + break; + case "potentially_unsafe": + updateUI( + "potentially_unsafe", + `${displayUrl} is potentially unsafe. Proceed with caution.` + ); + break; + case "safe": + updateUI("safe", `${displayUrl} is safe to browse.`); + break; + case "starred": + updateUI( + "starred", + `${displayUrl} is a starred site.` + ); + break; + case "no_data": + updateUI( + "no_data", + `No data available for ${displayUrl}.` + ); + break; + default: + updateUI( + "unknown", + `An unknown status was received for ${displayUrl}.` + ); + } + } + + /** + * Updates the UI with the appropriate icon, message, and effects. + * @param {string} status - The status of the site (e.g., "safe", "unsafe"). + * @param {string} message - The message to display to the user. + */ + function updateUI(status, message) { + const icons = { + unsafe: "../res/icons/unsafe.png", + potentially_unsafe: "../res/icons/potentially_unsafe.png", + safe: "../res/icons/safe.png", + starred: "../res/icons/starred.png", + no_data: "../res/ext_icon_144.png", + error: "../res/icons/error.png", + unknown: "../res/ext_icon_144.png", + }; + + // Update the icon and message + statusIcon.src = icons[status] || icons["unknown"]; + statusMessage.innerHTML = message || "An unknown error occurred."; + + // Add a small animation when the status changes + statusIcon.classList.add("active"); + setTimeout(() => statusIcon.classList.remove("active"), 300); + + console.log(`UI updated: ${message}`); + } +}); diff --git a/platform/chromium/res/ext_icon_144.png b/platform/chromium/res/ext_icon_144.png new file mode 100644 index 0000000..099c350 Binary files /dev/null and b/platform/chromium/res/ext_icon_144.png differ diff --git a/platform/chromium/res/icons/error.png b/platform/chromium/res/icons/error.png new file mode 100644 index 0000000..38a27e8 Binary files /dev/null and b/platform/chromium/res/icons/error.png differ diff --git a/platform/chromium/res/icons/potentially_unsafe.png b/platform/chromium/res/icons/potentially_unsafe.png new file mode 100644 index 0000000..3c88816 Binary files /dev/null and b/platform/chromium/res/icons/potentially_unsafe.png differ diff --git a/platform/chromium/res/icons/potentially_unsafe_19.png b/platform/chromium/res/icons/potentially_unsafe_19.png new file mode 100644 index 0000000..a6138ee Binary files /dev/null and b/platform/chromium/res/icons/potentially_unsafe_19.png differ diff --git a/platform/chromium/res/icons/potentially_unsafe_38.png b/platform/chromium/res/icons/potentially_unsafe_38.png new file mode 100644 index 0000000..7859131 Binary files /dev/null and b/platform/chromium/res/icons/potentially_unsafe_38.png differ diff --git a/platform/chromium/res/icons/safe.png b/platform/chromium/res/icons/safe.png new file mode 100644 index 0000000..2e91dbc Binary files /dev/null and b/platform/chromium/res/icons/safe.png differ diff --git a/platform/chromium/res/icons/safe_19.png b/platform/chromium/res/icons/safe_19.png new file mode 100644 index 0000000..3895060 Binary files /dev/null and b/platform/chromium/res/icons/safe_19.png differ diff --git a/platform/chromium/res/icons/safe_38.png b/platform/chromium/res/icons/safe_38.png new file mode 100644 index 0000000..1369f83 Binary files /dev/null and b/platform/chromium/res/icons/safe_38.png differ diff --git a/platform/chromium/res/icons/starred.png b/platform/chromium/res/icons/starred.png new file mode 100644 index 0000000..fd68271 Binary files /dev/null and b/platform/chromium/res/icons/starred.png differ diff --git a/platform/chromium/res/icons/starred_19.png b/platform/chromium/res/icons/starred_19.png new file mode 100644 index 0000000..cb1bd78 Binary files /dev/null and b/platform/chromium/res/icons/starred_19.png differ diff --git a/platform/chromium/res/icons/starred_38.png b/platform/chromium/res/icons/starred_38.png new file mode 100644 index 0000000..ad17975 Binary files /dev/null and b/platform/chromium/res/icons/starred_38.png differ diff --git a/platform/chromium/res/icons/unsafe.png b/platform/chromium/res/icons/unsafe.png new file mode 100644 index 0000000..1abdf95 Binary files /dev/null and b/platform/chromium/res/icons/unsafe.png differ diff --git a/platform/chromium/res/icons/unsafe_19.png b/platform/chromium/res/icons/unsafe_19.png new file mode 100644 index 0000000..db2a533 Binary files /dev/null and b/platform/chromium/res/icons/unsafe_19.png differ diff --git a/platform/chromium/res/icons/unsafe_38.png b/platform/chromium/res/icons/unsafe_38.png new file mode 100644 index 0000000..1aeed93 Binary files /dev/null and b/platform/chromium/res/icons/unsafe_38.png differ