Add files via upload

This commit is contained in:
Kenneth Hendricks 2026-01-25 13:59:44 -05:00 committed by GitHub
parent 7d4bf2bb04
commit b5c9a9cf42
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 637 additions and 360 deletions

View file

@ -34,6 +34,8 @@ const safeListURLs = [
];
const fmhyFilterListURL =
"https://raw.githubusercontent.com/fmhy/FMHY-SafeGuard/refs/heads/main/fmhy-filterlist.txt";
const unsafeReasonsURL =
"https://raw.githubusercontent.com/fmhy/FMHYFilterlist/refs/heads/main/filterlists-reasons.json";
const notesBaseURL =
"https://raw.githubusercontent.com/fmhy/edit/main/docs/.vitepress/notes/";
@ -43,6 +45,7 @@ let potentiallyUnsafeSitesRegex = null;
let fmhySitesRegex = null;
let safeSites = [];
let starredSites = [];
let unsafeReasons = {}; // Object to store reasons for unsafe sites
const approvedUrls = new Map(); // Map to store approved URLs per tab
const notesCache = new Map(); // Cache for fetched notes
@ -234,6 +237,39 @@ function getNoteSlugForDomain(hostname) {
return null;
}
// Get reason for an unsafe domain
async function getReasonForDomain(hostname) {
const domain = hostname.replace(/^www\./, "").toLowerCase();
// If in-memory unsafeReasons is empty, try loading from storage
if (!unsafeReasons || Object.keys(unsafeReasons).length === 0) {
try {
const stored = await browserAPI.storage.local.get("unsafeReasons");
if (stored.unsafeReasons && Object.keys(stored.unsafeReasons).length > 0) {
unsafeReasons = stored.unsafeReasons;
console.log(`getReasonForDomain: Loaded ${Object.keys(unsafeReasons).length} unsafe reasons from storage`);
} else {
// Storage is also empty, fetch from URL
console.log("getReasonForDomain: Storage empty, fetching from URL...");
const response = await fetch(unsafeReasonsURL);
if (response.ok) {
unsafeReasons = await response.json();
await browserAPI.storage.local.set({ unsafeReasons });
console.log(`getReasonForDomain: Fetched and stored ${Object.keys(unsafeReasons).length} unsafe reasons`);
}
}
} catch (e) {
console.error("Error loading unsafeReasons:", e);
}
}
// Try exact match first
if (unsafeReasons && unsafeReasons[domain]) return unsafeReasons[domain];
// Try with www prefix
if (unsafeReasons && unsafeReasons["www." + domain]) return unsafeReasons["www." + domain];
return null;
}
// Fetch note content from GitHub
async function fetchNoteContent(noteSlug) {
// Check cache first
@ -368,11 +404,12 @@ function isSearchEngine(url) {
async function fetchFilterLists() {
console.log("Fetching filter lists...");
try {
const [unsafeResponse, potentiallyUnsafeResponse, fmhyResponse] =
const [unsafeResponse, potentiallyUnsafeResponse, fmhyResponse, reasonsResponse] =
await Promise.all([
fetch(filterListURLUnsafe),
fetch(filterListURLPotentiallyUnsafe),
fetch(fmhyFilterListURL),
fetch(unsafeReasonsURL),
]);
let unsafeSites = [];
@ -399,10 +436,25 @@ async function fetchFilterLists() {
fmhySitesRegex = generateRegexFromList(fmhySites);
}
// Fetch unsafe site reasons
if (reasonsResponse.ok) {
try {
unsafeReasons = await reasonsResponse.json();
console.log(`Loaded ${Object.keys(unsafeReasons).length} unsafe site reasons`);
} catch (e) {
console.error("Error parsing unsafe reasons JSON:", e);
unsafeReasons = {};
}
} else {
console.warn("Failed to fetch unsafe reasons, status:", reasonsResponse.status);
unsafeReasons = {};
}
await browserAPI.storage.local.set({
unsafeSites,
potentiallyUnsafeSites,
fmhySites,
unsafeReasons,
unsafeFilterCount: unsafeSites.length,
potentiallyUnsafeFilterCount: potentiallyUnsafeSites.length,
fmhyFilterCount: fmhySites.length,
@ -680,133 +732,151 @@ browserAPI.runtime.onMessage.addListener((message, sender, sendResponse) => {
matchedUrl = rootUrl;
}
sendResponse({ status, matchedUrl });
// Get reason if unsafe
let reason = null;
if (status === "unsafe" || status === "potentially_unsafe") {
try {
const urlObj = new URL(matchedUrl);
reason = getReasonForDomain(urlObj.hostname);
} catch (e) {
console.error("Error getting reason:", e);
}
}
sendResponse({ status, matchedUrl, reason });
return true;
}
if (message.action === "getSiteStatus") {
try {
// Get the URL from the message
const url = message.url;
if (!url) {
sendResponse({ status: "no_data", matchedUrl: null });
return true;
}
(async () => {
try {
// Get the URL from the message
const url = message.url;
if (!url) {
sendResponse({ status: "no_data", matchedUrl: null });
return;
}
console.log(`getSiteStatus: checking status for ${url}`);
console.log(`getSiteStatus: checking status for ${url}`);
// Normalize the URL
const normalizedUrl = normalizeUrl(url);
if (!normalizedUrl) {
sendResponse({ status: "no_data", matchedUrl: null });
return true;
}
// Normalize the URL
const normalizedUrl = normalizeUrl(url);
if (!normalizedUrl) {
sendResponse({ status: "no_data", matchedUrl: null });
return;
}
// Extract domain for domain-level checking
const urlObj = new URL(normalizedUrl);
const domain = urlObj.hostname;
// Extract domain for domain-level checking
const urlObj = new URL(normalizedUrl);
const domain = urlObj.hostname;
// First check if it's an extension page
if (url.startsWith(browserAPI.runtime.getURL(""))) {
sendResponse({ status: "extension_page", matchedUrl: url });
return true;
}
// First check if it's an extension page
if (url.startsWith(browserAPI.runtime.getURL(""))) {
sendResponse({ status: "extension_page", matchedUrl: url });
return;
}
// Special handling for repository sites
const isRepoSite = ["github.com", "gitlab.com", "sourceforge.net"].some(
(domain) =>
urlObj.hostname === domain || urlObj.hostname.endsWith("." + domain)
);
// Special handling for repository sites
const isRepoSite = ["github.com", "gitlab.com", "sourceforge.net"].some(
(d) => urlObj.hostname === d || urlObj.hostname.endsWith("." + d)
);
// Variables to track status and matched URL
let status = "no_data";
let matchedUrl = null;
// Variables to track status and matched URL
let status = "no_data";
let matchedUrl = null;
// Check full URL first
if (unsafeSitesRegex?.test(normalizedUrl)) {
status = "unsafe";
matchedUrl = normalizedUrl;
} else if (potentiallyUnsafeSitesRegex?.test(normalizedUrl)) {
status = "potentially_unsafe";
matchedUrl = normalizedUrl;
} else if (fmhySitesRegex?.test(normalizedUrl)) {
status = "fmhy";
matchedUrl = normalizedUrl;
} else if (starredSites.includes(normalizedUrl)) {
status = "starred";
matchedUrl = normalizedUrl;
} else if (safeSites.includes(normalizedUrl)) {
status = "safe";
matchedUrl = normalizedUrl;
}
// If no match for full URL and it's a repository site, don't try domain matching
if (status === "no_data" && isRepoSite) {
console.log(`No match for repository URL: ${normalizedUrl}`);
sendResponse({ status: "no_data", matchedUrl: normalizedUrl });
return true;
}
// If no match for full URL and it's a regular site, try domain-level matching
if (status === "no_data" && !isRepoSite) {
console.log(`No match for full URL, trying domain: ${domain}`);
// Check domain against regex patterns
if (unsafeSitesRegex?.test(domain)) {
// Check full URL first
if (unsafeSitesRegex?.test(normalizedUrl)) {
status = "unsafe";
matchedUrl = `https://${domain}`;
} else if (potentiallyUnsafeSitesRegex?.test(domain)) {
matchedUrl = normalizedUrl;
} else if (potentiallyUnsafeSitesRegex?.test(normalizedUrl)) {
status = "potentially_unsafe";
matchedUrl = `https://${domain}`;
} else if (fmhySitesRegex?.test(domain)) {
matchedUrl = normalizedUrl;
} else if (fmhySitesRegex?.test(normalizedUrl)) {
status = "fmhy";
matchedUrl = `https://${domain}`;
matchedUrl = normalizedUrl;
} else if (starredSites.includes(normalizedUrl)) {
status = "starred";
matchedUrl = normalizedUrl;
} else if (safeSites.includes(normalizedUrl)) {
status = "safe";
matchedUrl = normalizedUrl;
}
// Check domain against starred and safe lists
if (status === "no_data") {
for (const starredUrl of starredSites) {
try {
const starredUrlObj = new URL(starredUrl);
if (starredUrlObj.hostname === domain) {
status = "starred";
matchedUrl = starredUrl;
break;
// If no match for full URL and it's a repository site, don't try domain matching
if (status === "no_data" && isRepoSite) {
console.log(`No match for repository URL: ${normalizedUrl}`);
sendResponse({ status: "no_data", matchedUrl: normalizedUrl });
return;
}
// If no match for full URL and it's a regular site, try domain-level matching
if (status === "no_data" && !isRepoSite) {
console.log(`No match for full URL, trying domain: ${domain}`);
// Check domain against regex patterns
if (unsafeSitesRegex?.test(domain)) {
status = "unsafe";
matchedUrl = `https://${domain}`;
} else if (potentiallyUnsafeSitesRegex?.test(domain)) {
status = "potentially_unsafe";
matchedUrl = `https://${domain}`;
} else if (fmhySitesRegex?.test(domain)) {
status = "fmhy";
matchedUrl = `https://${domain}`;
}
// Check domain against starred and safe lists
if (status === "no_data") {
for (const starredUrl of starredSites) {
try {
const starredUrlObj = new URL(starredUrl);
if (starredUrlObj.hostname === domain) {
status = "starred";
matchedUrl = starredUrl;
break;
}
} catch (e) {
continue;
}
}
}
if (status === "no_data") {
for (const safeUrl of safeSites) {
try {
const safeUrlObj = new URL(safeUrl);
if (safeUrlObj.hostname === domain) {
status = "safe";
matchedUrl = safeUrl;
break;
}
} catch (e) {
continue;
}
} catch (e) {
continue;
}
}
}
if (status === "no_data") {
for (const safeUrl of safeSites) {
try {
const safeUrlObj = new URL(safeUrl);
if (safeUrlObj.hostname === domain) {
status = "safe";
matchedUrl = safeUrl;
break;
}
} catch (e) {
continue;
}
}
// Get reason if unsafe
let reason = null;
if (status === "unsafe" || status === "potentially_unsafe") {
reason = await getReasonForDomain(domain);
}
console.log(
`getSiteStatus result for ${url}: ${status}, matched: ${matchedUrl}`
);
sendResponse({ status: status, matchedUrl: matchedUrl, reason: reason });
} catch (error) {
console.error("Error in getSiteStatus handler:", error);
sendResponse({
status: "no_data",
matchedUrl: null,
error: error.message,
});
}
console.log(
`getSiteStatus result for ${url}: ${status}, matched: ${matchedUrl}`
);
sendResponse({ status: status, matchedUrl: matchedUrl });
} catch (error) {
console.error("Error in getSiteStatus handler:", error);
sendResponse({
status: "no_data",
matchedUrl: null,
error: error.message,
});
}
})();
return true; // Keep the message channel open for async response
}
@ -938,10 +1008,24 @@ async function openWarningPage(tabId, unsafeUrl) {
tabApprovedUrls.push(normalizedUrl);
approvedUrls.set(tabId, tabApprovedUrls);
// Get the reason for this unsafe site
let hostname;
try {
hostname = new URL(unsafeUrl).hostname;
} catch (e) {
hostname = unsafeUrl.replace(/^https?:\/\//, "").split("/")[0];
}
const reason = await getReasonForDomain(hostname);
console.log(`openWarningPage: hostname=${hostname}, reason=${reason ? "found" : "not found"}`);
// Redirect to the warning page if it is enabled in settings
const warningPageUrl = browserAPI.runtime.getURL(
let warningPageUrl = browserAPI.runtime.getURL(
`../pub/warning-page.html?url=${encodeURIComponent(unsafeUrl)}`
);
if (reason) {
warningPageUrl += `&reason=${encodeURIComponent(reason)}`;
}
console.log(`openWarningPage: redirecting to ${warningPageUrl}`);
browserAPI.tabs.update(tabId, { url: warningPageUrl });
}
@ -1034,6 +1118,7 @@ async function initializeExtension() {
"fmhySites",
"starredSites",
"safeSiteList",
"unsafeReasons",
]);
if (storedData.unsafeSites && storedData.unsafeSites.length > 0) {
@ -1053,6 +1138,24 @@ async function initializeExtension() {
fmhySitesRegex = generateRegexFromList(storedData.fmhySites);
}
if (storedData.unsafeReasons && Object.keys(storedData.unsafeReasons).length > 0) {
unsafeReasons = storedData.unsafeReasons;
console.log(`Loaded ${Object.keys(unsafeReasons).length} unsafe reasons from storage`);
} else {
// If no unsafe reasons in storage, fetch them now
console.log("No unsafeReasons in storage, fetching...");
try {
const reasonsResponse = await fetch(unsafeReasonsURL);
if (reasonsResponse.ok) {
unsafeReasons = await reasonsResponse.json();
await browserAPI.storage.local.set({ unsafeReasons });
console.log(`Fetched and stored ${Object.keys(unsafeReasons).length} unsafe reasons`);
}
} catch (e) {
console.error("Error fetching unsafeReasons:", e);
}
}
// Load starred sites from storage
if (storedData.starredSites && storedData.starredSites.length > 0) {
starredSites = storedData.starredSites;
@ -1098,6 +1201,21 @@ browserAPI.runtime.onMessage.addListener((message, sender, sendResponse) => {
return true;
}
if (message.action === "forceUpdate") {
console.log("Force update triggered manually");
Promise.all([
fetchFilterLists(),
fetchSafeSites(),
fetchStarredSites()
]).then(() => {
sendResponse({ status: "updated" });
}).catch((error) => {
console.error("Force update failed:", error);
sendResponse({ status: "error", error: error.message });
});
return true;
}
if (message.action === "refreshAllTabs") {
// Get all tabs and refresh
browserAPI.tabs.query({}).then(async (tabs) => {

View file

@ -46,6 +46,7 @@ let unsafeDomains = new Set();
let safeDomains = new Set();
let userTrusted = new Set();
let userUntrusted = new Set();
let unsafeReasons = {};
// Search engines where highlighting should be applied
const searchEngines = [
@ -177,9 +178,10 @@ async function loadSettings() {
// Load domain lists from extension storage
async function loadDomainLists() {
try {
const { unsafeSites, safeSiteList } = await browserAPI.storage.local.get([
const { unsafeSites, safeSiteList, unsafeReasons: storedReasons } = await browserAPI.storage.local.get([
"unsafeSites",
"safeSiteList",
"unsafeReasons",
]);
if (unsafeSites && Array.isArray(unsafeSites)) {
@ -194,6 +196,10 @@ async function loadDomainLists() {
);
}
if (storedReasons) {
unsafeReasons = storedReasons;
}
// Apply user overrides
applyUserOverrides();
@ -246,12 +252,12 @@ function setupObserver() {
if (!isSupportedSite(currentDomain)) {
return;
}
// Special handling for Brave Search - continuously recheck and re-add badges/highlighting for all site types
if (currentDomain.includes("brave")) {
// Track domains we've processed to avoid repetitive work
const braveProcessedDomains = new Set();
// Set up a more frequent interval specifically for Brave Search
braveSearchBadgeInterval = setInterval(() => {
// Process all links in Brave Search results
@ -260,48 +266,50 @@ function setupObserver() {
if (!link.href || link.href.startsWith('javascript:') || link.href.startsWith('#')) {
return;
}
const linkDomain = normalizeDomain(new URL(link.href).hostname);
// Skip internal Brave links
if (linkDomain.includes('brave.com')) {
return;
}
// Always check all links, even if we've seen them before
// This ensures styling is reapplied if Brave removes it
// CASE 1: Unsafe sites (user untrusted or in unsafe list)
if (userUntrusted.has(linkDomain) ||
(!userTrusted.has(linkDomain) && unsafeDomains.has(linkDomain))) {
if (userUntrusted.has(linkDomain) ||
(!userTrusted.has(linkDomain) && unsafeDomains.has(linkDomain))) {
// Mark as unsafe
link.setAttribute('data-fmhy-unsafe', 'true');
// Apply highlighting with !important to ensure it's not overridden
link.style.setProperty('text-shadow', `0 0 4px ${settings.untrustedColor}`, 'important');
link.style.setProperty('font-weight', 'bold', 'important');
// Force style recalculation by toggling a property
link.style.display = 'inline';
link.style.display = '';
// Also apply a class for redundancy
link.classList.add('fmhy-highlighted-unsafe');
// Add badge for unsafe sites
const resultContainer = link.closest('article') ||
link.closest('li') ||
link.closest('.snippet') ||
findParentByTag(link, 5, ['ARTICLE', 'LI', 'DIV']);
const resultContainer = link.closest('article') ||
link.closest('li') ||
link.closest('.snippet') ||
findParentByTag(link, 5, ['ARTICLE', 'LI', 'DIV']);
if (resultContainer) {
const siteDiv = resultContainer.querySelector('div[class^="site svelte-"]');
if (siteDiv && !siteDiv.querySelector('.fmhy-unsafe-badge')) {
const badge = document.createElement('span');
badge.className = 'fmhy-unsafe-badge';
badge.innerHTML = '<span style="display: inline-block; font-size: 14px;">⚠️</span> FMHY Unsafe Site';
const reason = getReasonForDomain(linkDomain);
const reasonText = reason ? `: ${reason}` : "";
badge.innerHTML = `<span style="display: inline-block; font-size: 14px;">⚠️</span> FMHY Unsafe Site${reasonText}`;
badge.dataset.domain = linkDomain;
siteDiv.appendChild(badge);
}
@ -311,36 +319,36 @@ function setupObserver() {
else if (userTrusted.has(linkDomain) || safeDomains.has(linkDomain)) {
// Mark as safe
link.setAttribute('data-fmhy-safe', 'true');
// Only add highlighting if the setting is enabled
if (settings.highlightTrusted) {
link.style.setProperty('text-shadow', `0 0 4px ${settings.trustedColor}`, 'important');
link.style.setProperty('font-weight', 'bold', 'important');
}
}
} catch (e) {}
} catch (e) { }
});
}, 50); // Check more frequently to ensure styling persists
}
const observer = new MutationObserver((mutations) => {
let needsReprocess = false;
for (const mutation of mutations) {
// Check if existing badges were removed
if (mutation.removedNodes && mutation.removedNodes.length) {
for (const node of mutation.removedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.classList &&
(node.classList.contains('fmhy-badge-wrapper') ||
node.textContent && node.textContent.includes('FMHY Unsafe Site'))) {
if (node.classList &&
(node.classList.contains('fmhy-badge-wrapper') ||
node.textContent && node.textContent.includes('FMHY Unsafe Site'))) {
needsReprocess = true;
break;
}
}
}
}
// Process added nodes
if (mutation.addedNodes && mutation.addedNodes.length) {
for (const node of mutation.addedNodes) {
@ -349,7 +357,7 @@ function setupObserver() {
if (node.tagName === "A" && node.href) {
processLink(node, currentDomain);
}
// Process any links inside the added node
if (node.querySelectorAll) {
node
@ -360,7 +368,7 @@ function setupObserver() {
}
}
}
// If badges were removed, reprocess the page
if (needsReprocess) {
// Use setTimeout to avoid too frequent reprocessing
@ -375,8 +383,8 @@ function setupObserver() {
});
// Watch for both childList and attributes changes, and subtree modifications
observer.observe(document.body, {
childList: true,
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['href']
@ -388,7 +396,7 @@ function processLink(link, currentDomain) {
// Skip if already processed
if (processedLinks.has(link)) return;
processedLinks.add(link);
// Add a class to mark as processed for easier detection
link.classList.add("fmhy-processed");
@ -426,7 +434,8 @@ function processLink(link, currentDomain) {
}
if (settings.showWarningBanners && !processedDomains.has(linkDomain)) {
addWarningBanner(link);
const reason = getReasonForDomain(linkDomain);
addWarningBanner(link, reason);
processedDomains.add(linkDomain);
}
}
@ -454,19 +463,19 @@ function highlightLink(link, type) {
}
// Add a warning banner after an unsafe link
function addWarningBanner(link) {
function addWarningBanner(link, reason = null) {
const currentDomain = normalizeDomain(window.location.hostname);
// Special handling for Brave Search - use original highlighting style
if (currentDomain.includes("brave")) {
try {
// Mark the link with attribute for CSS styling
link.setAttribute('data-fmhy-unsafe', 'true');
// Use the original highlighting approach with text shadow
link.style.setProperty('text-shadow', `0 0 4px ${settings.untrustedColor}`, 'important');
link.style.setProperty('font-weight', 'bold', 'important');
// Add CSS for the badge styling only
if (!document.getElementById('fmhy-brave-style')) {
const style = document.createElement('style');
@ -492,10 +501,10 @@ function addWarningBanner(link) {
`;
document.head.appendChild(style);
}
// Find the closest search result container
let resultContainer = link.closest('article') || link.closest('li') || link.closest('.snippet');
if (!resultContainer) {
// If no direct container, traverse up a few levels
resultContainer = link;
@ -508,26 +517,27 @@ function addWarningBanner(link) {
}
}
}
// Look for the site div specifically (what the user requested)
const siteDiv = resultContainer.querySelector('div[class^="site svelte-"]');
if (siteDiv) {
// Check if we already added a badge to this site div
if (!siteDiv.querySelector('.fmhy-unsafe-badge')) {
const badge = document.createElement('span');
badge.className = 'fmhy-unsafe-badge';
badge.innerHTML = '<span style="display: inline-block; font-size: 14px;">⚠️</span> FMHY Unsafe Site';
const reasonText = reason ? `: ${reason}` : "";
badge.innerHTML = `<span style="display: inline-block; font-size: 14px;">⚠️</span> FMHY Unsafe Site${reasonText}`;
siteDiv.appendChild(badge);
}
}
return; // Exit early
} catch (e) {
console.error("[FMHY SafeGuard] Error styling Brave Search link:", e);
}
}
// For all other search engines, create a badge element
const badge = document.createElement("span");
Object.assign(badge.style, {
@ -538,37 +548,38 @@ function addWarningBanner(link) {
borderRadius: "4px",
fontSize: "12px",
display: "inline-block",
transform: "rotate(180deg) scaleX(-1) !important",
transform: "rotate(180deg) scaleX(-1) !important",
WebkitTransform: "rotate(180deg) scaleX(-1) !important",
msTransform: "rotate(180deg) scaleX(-1) !important",
position: "relative",
zIndex: "9999"
});
// Add the warning icon and text
badge.innerHTML = '<span style="display: inline-block; font-size: 14px;">⚠️</span> FMHY Unsafe Site';
const reasonText = reason ? `: ${reason}` : "";
badge.innerHTML = `<span style="display: inline-block; font-size: 14px;">⚠️</span> FMHY Unsafe Site${reasonText}`;
// Google-specific margin adjustment
if (currentDomain.includes("google")) {
badge.style.margin = "0 15px";
}
// Different insertion strategy for Google vs other engines
if (currentDomain.includes("google")) {
// For Google, find a suitable container
let container = link;
let parent = link.parentElement;
// Look for a suitable container
for (let i = 0; i < 3 && parent; i++) {
if (parent.tagName === "DIV" || parent.tagName === "LI" ||
parent.querySelector("cite") || parent.querySelector(".link")) {
if (parent.tagName === "DIV" || parent.tagName === "LI" ||
parent.querySelector("cite") || parent.querySelector(".link")) {
container = parent;
break;
}
parent = parent.parentElement;
}
// Try to place after cite element if it exists
const citeElement = container.querySelector("cite");
if (citeElement) {
@ -589,6 +600,15 @@ function addWarningBanner(link) {
}
// Helper functions
function getReasonForDomain(hostname) {
const domain = hostname.replace(/^www\./, "").toLowerCase();
// Try exact match first
if (unsafeReasons[domain]) return unsafeReasons[domain];
// Try with www prefix
if (unsafeReasons["www." + domain]) return unsafeReasons["www." + domain];
return null;
}
function normalizeDomain(hostname) {
return hostname.replace(/^www\./, "").toLowerCase();
}
@ -646,13 +666,13 @@ function refreshPage() {
processedDomains.clear();
highlightCountTrusted.clear();
highlightCountUntrusted.clear();
// Clear any existing Brave Search interval and restart the observer
if (braveSearchBadgeInterval) {
clearInterval(braveSearchBadgeInterval);
braveSearchBadgeInterval = null;
}
// Restart the processing
processPage();
setupObserver();

View file

@ -182,6 +182,50 @@
transform: scale(1.1);
}
/* Reason Section Styles */
#reason-container {
margin-top: 15px;
padding: 10px;
background: var(--hover-bg);
border-radius: 8px;
text-align: left;
display: none;
}
#reason-container.visible {
display: block;
}
#reason-title {
font-size: 13px;
font-weight: 600;
color: #ff6b6b;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 6px;
}
#reason-title svg {
stroke: #ff6b6b;
}
#reason-content {
font-size: 12px;
line-height: 1.5;
color: var(--text-color-light);
}
#reason-content a {
color: #ff6b6b;
text-decoration: none;
word-break: break-all;
}
#reason-content a:hover {
text-decoration: underline;
}
/* Note Section Styles */
#note-container {
margin-top: 15px;
@ -262,6 +306,17 @@
<p id="status-message">Checking site status...</p>
</div>
<p id="error-message"></p>
<div id="reason-container">
<div id="reason-title">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"></path>
<path d="M12 9v4"></path>
<path d="M12 17h.01"></path>
</svg>
Reason
</div>
<div id="reason-content"></div>
</div>
<div id="note-container">
<div id="note-title">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">

View file

@ -4,6 +4,8 @@ document.addEventListener("DOMContentLoaded", async () => {
const statusIcon = document.getElementById("status-icon");
const statusMessage = document.getElementById("status-message");
const errorMessage = document.getElementById("error-message");
const reasonContainer = document.getElementById("reason-container");
const reasonContent = document.getElementById("reason-content");
const noteContainer = document.getElementById("note-container");
const noteContent = document.getElementById("note-content");
@ -166,10 +168,21 @@ document.addEventListener("DOMContentLoaded", async () => {
}
// Get the status from the background script
const response = await browserAPI.runtime.sendMessage({
action: "getSiteStatus",
url: currentUrl,
});
let response;
try {
response = await browserAPI.runtime.sendMessage({
action: "getSiteStatus",
url: currentUrl,
});
} catch (msgError) {
console.warn("Message send failed, retrying...", msgError);
// Retry once after a short delay (background script may be initializing)
await new Promise(resolve => setTimeout(resolve, 100));
response = await browserAPI.runtime.sendMessage({
action: "getSiteStatus",
url: currentUrl,
});
}
console.log("Status response:", response);
if (!response || !response.status) {
@ -248,7 +261,7 @@ document.addEventListener("DOMContentLoaded", async () => {
}
// Update the popup with the result
handleStatusUpdate(response.status, displayUrl);
handleStatusUpdate(response.status, displayUrl, response.reason);
} catch (error) {
console.error("Error checking site status:", error);
errorMessage.textContent = `Error: ${error.message}`;
@ -256,12 +269,23 @@ document.addEventListener("DOMContentLoaded", async () => {
}
}
function handleStatusUpdate(status, displayUrl) {
function handleStatusUpdate(status, displayUrl, reason) {
let message;
// Handle reason display in dedicated container
if (reason && (status === "unsafe" || status === "potentially_unsafe")) {
// Convert URLs to clickable links
const urlRegex = /(https?:\/\/[^\s]+)/g;
const formattedReason = reason.replace(urlRegex, '<a href="$1" target="_blank">$1</a>');
reasonContent.innerHTML = formattedReason;
reasonContainer.classList.add("visible");
} else {
reasonContainer.classList.remove("visible");
}
switch (status) {
case "unsafe":
message = `${displayUrl} is flagged as <strong>unsafe</strong>. Its Recommended To Avoid this Site.`;
message = `${displayUrl} is flagged as <strong>unsafe</strong>. It's recommended to avoid this site.`;
break;
case "potentially_unsafe":
message = `${displayUrl} is <strong>potentially unsafe</strong>. Proceed with caution.`;

View file

@ -1,212 +1,216 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FMHY SafeGuard - Warning</title>
<link rel="icon" type="image/x-icon" href="../res/ext_icon_144.png" />
<script src="warning-page.js"></script>
<style>
@font-face {
font-family: "Inter";
src: url("../res/fonts/inter.woff2") format("woff2");
}
:root {
--background: rgb(26, 26, 26);
--text-primary: #e8e8e8;
--text-secondary: #848a94;
--accent-purple: #c4b5fd;
--accent-blue: #7bc5e4;
--danger: #ff4444;
--card-bg: rgba(255, 255, 255, 0.05);
}
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FMHY SafeGuard - Warning</title>
<link rel="icon" type="image/x-icon" href="../res/ext_icon_144.png" />
<script src="warning-page.js"></script>
<style>
@font-face {
font-family: "Inter";
src: url("../res/fonts/inter.woff2") format("woff2");
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--background: rgb(26, 26, 26);
--text-primary: #e8e8e8;
--text-secondary: #848a94;
--accent-purple: #c4b5fd;
--accent-blue: #7bc5e4;
--danger: #ff4444;
--card-bg: rgba(255, 255, 255, 0.05);
}
body {
background-color: var(--background);
font-family: "Inter", sans-serif;
color: var(--text-primary);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem;
line-height: 1.5;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
max-width: 600px;
width: 100%;
text-align: center;
}
body {
background-color: var(--background);
font-family: "Inter", sans-serif;
color: var(--text-primary);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem;
line-height: 1.5;
}
.warning-card {
background: var(--card-bg);
border-radius: 12px;
padding: 2rem;
margin: 2rem 0;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.container {
max-width: 600px;
width: 100%;
text-align: center;
}
.title {
font-size: 2rem;
font-weight: 700;
margin-bottom: 0.5rem;
background: linear-gradient(
120deg,
.warning-card {
background: var(--card-bg);
border-radius: 12px;
padding: 2rem;
margin: 2rem 0;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.title {
font-size: 2rem;
font-weight: 700;
margin-bottom: 0.5rem;
background: linear-gradient(120deg,
var(--accent-purple) 30%,
var(--accent-blue)
);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
var(--accent-blue));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.warning-icon {
width: 80px;
height: 80px;
margin: 1.5rem 0;
animation: pulse 2s infinite;
}
.url {
color: var(--danger);
font-weight: 500;
margin-bottom: 1rem;
word-break: break-all;
}
.warning-text {
color: var(--text-secondary);
margin-bottom: 2rem;
}
.buttons {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
}
.btn {
padding: 0.75rem 1.5rem;
border-radius: 8px;
border: none;
font-family: inherit;
font-weight: 600;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
font-size: 1rem;
cursor: pointer;
transition: transform 0.2s, opacity 0.2s;
}
.btn:hover {
transform: translateY(-2px);
}
.btn:active {
transform: translateY(0);
}
.btn-primary {
background: linear-gradient(120deg,
var(--accent-purple),
var(--accent-blue));
color: white;
}
.btn-secondary {
background: transparent;
border: 1px solid var(--text-secondary);
color: var(--text-secondary);
}
.footer {
margin-top: 2rem;
color: var(--text-secondary);
font-size: 0.875rem;
}
.footer a {
color: var(--accent-blue);
text-decoration: none;
}
.footer a:hover {
text-decoration: underline;
}
#reasonText a {
color: var(--accent-blue);
text-decoration: none;
word-break: break-all;
}
#reasonText a:hover {
text-decoration: underline;
}
@keyframes pulse {
0% {
transform: scale(1);
}
.warning-icon {
width: 80px;
height: 80px;
margin: 1.5rem 0;
animation: pulse 2s infinite;
50% {
transform: scale(1.05);
}
.url {
color: var(--danger);
font-weight: 500;
margin-bottom: 1rem;
word-break: break-all;
}
.warning-text {
color: var(--text-secondary);
margin-bottom: 2rem;
100% {
transform: scale(1);
}
}
@media (max-width: 480px) {
.buttons {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
flex-direction: column;
}
.btn {
padding: 0.75rem 1.5rem;
border-radius: 8px;
border: none;
font-family: inherit;
font-weight: 600;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
font-size: 1rem;
cursor: pointer;
transition: transform 0.2s, opacity 0.2s;
width: 100%;
}
}
</style>
</head>
.btn:hover {
transform: translateY(-2px);
}
.btn:active {
transform: translateY(0);
}
.btn-primary {
background: linear-gradient(
120deg,
var(--accent-purple),
var(--accent-blue)
);
color: white;
}
.btn-secondary {
background: transparent;
border: 1px solid var(--text-secondary);
color: var(--text-secondary);
}
.footer {
margin-top: 2rem;
color: var(--text-secondary);
font-size: 0.875rem;
}
.footer a {
color: var(--accent-blue);
text-decoration: none;
}
.footer a:hover {
text-decoration: underline;
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
@media (max-width: 480px) {
.buttons {
flex-direction: column;
}
.btn {
width: 100%;
}
}
</style>
</head>
<body>
<div class="container">
<h1 class="title">FMHY SafeGuard</h1>
<div class="warning-card">
<img
src="../res/icons/unsafe.png"
alt="Warning"
class="warning-icon"
id="warningIcon"
/>
<p class="url" id="unsafeUrl"></p>
<p class="warning-text">
This site has been flagged as <strong>unsafe</strong>.<br />
Its Recommended To Avoid this Site.
</p>
<div class="buttons">
<button class="btn btn-secondary" id="goBack">
Go Back (Recommended)
</button>
<button class="btn btn-primary" id="proceed">Proceed Anyway</button>
</div>
</div>
<div class="footer">
<p>
Think this is a mistake?
<a
href="https://github.com/fmhy/FMHY-SafeGuard/issues"
target="_blank"
>Let us know</a
>
</p>
<p>
Powered by
<a href="https://github.com/fmhy/FMHYFilterlist" target="_blank"
>FMHY Filterlist</a
>
</p>
<body>
<div class="container">
<h1 class="title">FMHY SafeGuard</h1>
<div class="warning-card">
<img src="../res/icons/unsafe.png" alt="Warning" class="warning-icon" id="warningIcon" />
<p class="url" id="unsafeUrl"></p>
<p class="warning-text">
This site has been flagged as <strong>unsafe</strong>.<br />
It's recommended to avoid this site.
</p>
<p class="reason-text" id="reasonContainer"
style="display: none; color: #a0a0a0; font-size: 0.9em; margin-bottom: 1.5rem; padding: 0.75rem; background: rgba(255,68,68,0.1); border-radius: 8px; border-left: 3px solid var(--danger);">
<strong>Reason:</strong> <span id="reasonText"></span>
</p>
<div class="buttons">
<button class="btn btn-secondary" id="goBack">
Go Back (Recommended)
</button>
<button class="btn btn-primary" id="proceed">Proceed Anyway</button>
</div>
</div>
</body>
</html>
<div class="footer">
<p>
Think this is a mistake?
<a href="https://github.com/fmhy/FMHY-SafeGuard/issues" target="_blank">Let us know</a>
</p>
<p>
Powered by
<a href="https://github.com/fmhy/FMHYFilterlist" target="_blank">FMHY Filterlist</a>
</p>
</div>
</div>
</body>
</html>

View file

@ -1,12 +1,68 @@
document.addEventListener("DOMContentLoaded", () => {
document.addEventListener("DOMContentLoaded", async () => {
// Cross-browser compatibility shim
const browserAPI = typeof browser !== "undefined" ? browser : chrome;
const urlParams = new URLSearchParams(window.location.search);
const unsafeUrl = urlParams.get("url") || "unknown site";
const unsafeUrl = decodeURIComponent(urlParams.get("url") || "unknown site");
const reasonFromUrl = urlParams.get("reason");
document.getElementById("unsafeUrl").textContent = unsafeUrl;
console.log(`Warning page loaded for URL: ${unsafeUrl}`);
// Display reason for unsafe site - prefer URL parameter, fallback to storage
let reason = reasonFromUrl ? decodeURIComponent(reasonFromUrl) : null;
// Helper function to convert URLs to clickable links
function formatReasonWithLinks(text) {
const urlRegex = /(https?:\/\/[^\s]+)/g;
return text.replace(urlRegex, '<a href="$1" target="_blank">$1</a>');
}
if (reason) {
console.log("Reason provided via URL parameter");
document.getElementById("reasonText").innerHTML = formatReasonWithLinks(reason);
document.getElementById("reasonContainer").style.display = "block";
} else {
// Fallback: try to fetch from storage
try {
const { unsafeReasons } = await browserAPI.storage.local.get("unsafeReasons");
console.log("Loaded unsafeReasons:", unsafeReasons ? Object.keys(unsafeReasons).length + " entries" : "null");
if (unsafeReasons && Object.keys(unsafeReasons).length > 0) {
// Extract domain from the unsafe URL
let domain;
try {
const urlObj = new URL(unsafeUrl);
domain = urlObj.hostname.replace(/^www\./, "").toLowerCase();
} catch (e) {
// If URL parsing fails, try to extract domain directly
domain = unsafeUrl
.replace(/^https?:\/\//, "")
.replace(/^www\./, "")
.split("/")[0]
.toLowerCase();
}
console.log("Looking up reason for domain:", domain);
// Check for reason - try multiple variations
reason = unsafeReasons[domain] ||
unsafeReasons["www." + domain] ||
unsafeReasons[domain.replace(/\/$/, "")]; // Without trailing slash
console.log("Found reason:", reason ? "yes" : "no");
if (reason) {
document.getElementById("reasonText").innerHTML = formatReasonWithLinks(reason);
document.getElementById("reasonContainer").style.display = "block";
}
} else {
console.log("No unsafeReasons in storage - filter lists may need to be refreshed");
}
} catch (error) {
console.error("Error fetching reason:", error);
}
}
// "Go Back" button functionality to return to the previous page
document.getElementById("goBack").addEventListener("click", () => {
console.log("User clicked Go Back.");