mirror of
https://github.com/fmhy/FMHY-SafeGuard.git
synced 2026-03-11 08:55:40 +00:00
Add files via upload
This commit is contained in:
parent
7d4bf2bb04
commit
b5c9a9cf42
6 changed files with 637 additions and 360 deletions
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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.`;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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.");
|
||||
|
|
|
|||
Loading…
Reference in a new issue