Add files via upload
254
platform/chromium/js/background.js
Normal file
|
|
@ -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 = /<A HREF="(https?:\/\/[^\s"]+)"/g;
|
||||
let matches;
|
||||
const urls = [];
|
||||
while ((matches = urlRegex.exec(html)) !== null) {
|
||||
urls.push(matches[1]);
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
// Helper function to normalize URLs (removes trailing slashes and "www.")
|
||||
function normalizeUrl(url) {
|
||||
return url.replace(/\/+$/, "").replace(/^https?:\/\/www\./, "https://");
|
||||
}
|
||||
|
||||
// Fetch the unsafe and potentially unsafe filter lists
|
||||
async function fetchFilterLists() {
|
||||
console.log("Fetching filter lists...");
|
||||
|
||||
try {
|
||||
const [unsafeResponse, potentiallyUnsafeResponse] = await Promise.all([
|
||||
fetch(filterListURLUnsafe),
|
||||
fetch(filterListURLPotentiallyUnsafe),
|
||||
]);
|
||||
|
||||
if (unsafeResponse.ok) {
|
||||
const unsafeText = await unsafeResponse.text();
|
||||
unsafeSites = unsafeText
|
||||
.split("\n")
|
||||
.filter((line) => 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();
|
||||
|
|
@ -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": [
|
||||
"<all_urls>"
|
||||
],
|
||||
"description": "FMHY SafeGuard extension to identify unsafe and potentially unsafe sites.",
|
||||
"minimum_chrome_version": "100"
|
||||
}
|
||||
|
|
|
|||
155
platform/chromium/pub/index.html
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>FMHY SafeGuard</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<style>
|
||||
body {
|
||||
text-align: center;
|
||||
background-color: rgb(26, 26, 26);
|
||||
font-family: "Inter", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
|
||||
color: #848a94;
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
min-width: 250px; /* Minimum width to ensure content fits */
|
||||
max-width: 100%; /* Maximum width so it doesn't overflow */
|
||||
word-wrap: break-word; /* Ensures long words break to the next line */
|
||||
box-sizing: border-box; /* Includes padding in the width calculation */
|
||||
}
|
||||
|
||||
.title h1 {
|
||||
font-size: 24px;
|
||||
background: -webkit-linear-gradient(120deg, #c4b5fd 30%, #7bc5e4);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#status-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.status-image {
|
||||
width: 60px; /* Reduced icon size */
|
||||
height: 60px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
#status-message {
|
||||
font-size: 14px; /* Smaller text for compact design */
|
||||
margin-top: 10px;
|
||||
color: #e8e8e8;
|
||||
line-height: 1.3;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
#status-message strong {
|
||||
color: #c4b5fd;
|
||||
}
|
||||
|
||||
#error-message {
|
||||
color: red;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#footer {
|
||||
font-size: 11px; /* Smaller footer text */
|
||||
margin-top: 10px;
|
||||
color: #848a94;
|
||||
}
|
||||
|
||||
#footer a {
|
||||
color: #78b3e2;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#feedback {
|
||||
font-size: 12px;
|
||||
margin-top: 10px;
|
||||
color: #848a94;
|
||||
}
|
||||
|
||||
#feedback a {
|
||||
color: #78b3e2;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#feedback a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Animation for status change */
|
||||
.status-image.active {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
#status-message,
|
||||
#footer,
|
||||
#feedback {
|
||||
max-width: 100%; /* Ensures text blocks don't overflow */
|
||||
word-wrap: break-word; /* Breaks long words that may cause overflow */
|
||||
word-break: break-word; /* Optional: break long words for better display */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="title">
|
||||
<h1>FMHY SafeGuard</h1>
|
||||
</div>
|
||||
|
||||
<div id="status-container">
|
||||
<!-- The status icon will be dynamically updated by the JS -->
|
||||
<img
|
||||
id="status-icon"
|
||||
class="status-image"
|
||||
src="safe.png"
|
||||
alt="Status Icon"
|
||||
/>
|
||||
<!-- The status message will also be updated -->
|
||||
<p id="status-message">Checking site status...</p>
|
||||
</div>
|
||||
|
||||
<!-- An area for error messages, if necessary -->
|
||||
<p id="error-message"></p>
|
||||
|
||||
<!-- Feedback link for users to report mistakes -->
|
||||
<div id="feedback">
|
||||
<p>
|
||||
Think this is a mistake?
|
||||
<a
|
||||
href="https://github.com/fmhy/edit/issues/new?assignees=&labels=&projects=&template=wiki.yml&title=%3Ctitle%3E"
|
||||
target="_blank"
|
||||
>Let us know</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="footer">
|
||||
<p>
|
||||
Powered by
|
||||
<a href="https://github.com/fmhy/FMHYFilterlist" target="_blank"
|
||||
>FMHY Filterlist</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
134
platform/chromium/pub/index.js
Normal file
|
|
@ -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 <strong>unsafe</strong>. Be cautious when interacting with this site.`
|
||||
);
|
||||
break;
|
||||
case "potentially_unsafe":
|
||||
updateUI(
|
||||
"potentially_unsafe",
|
||||
`${displayUrl} is <strong>potentially unsafe</strong>. Proceed with caution.`
|
||||
);
|
||||
break;
|
||||
case "safe":
|
||||
updateUI("safe", `${displayUrl} is <strong>safe</strong> to browse.`);
|
||||
break;
|
||||
case "starred":
|
||||
updateUI(
|
||||
"starred",
|
||||
`${displayUrl} is a <strong>starred</strong> site.`
|
||||
);
|
||||
break;
|
||||
case "no_data":
|
||||
updateUI(
|
||||
"no_data",
|
||||
`No data available for <strong>${displayUrl}</strong>.`
|
||||
);
|
||||
break;
|
||||
default:
|
||||
updateUI(
|
||||
"unknown",
|
||||
`An unknown status was received for <strong>${displayUrl}</strong>.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}`);
|
||||
}
|
||||
});
|
||||
BIN
platform/chromium/res/ext_icon_144.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
platform/chromium/res/icons/error.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
platform/chromium/res/icons/potentially_unsafe.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
platform/chromium/res/icons/potentially_unsafe_19.png
Normal file
|
After Width: | Height: | Size: 880 B |
BIN
platform/chromium/res/icons/potentially_unsafe_38.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
platform/chromium/res/icons/safe.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
platform/chromium/res/icons/safe_19.png
Normal file
|
After Width: | Height: | Size: 865 B |
BIN
platform/chromium/res/icons/safe_38.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
platform/chromium/res/icons/starred.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
platform/chromium/res/icons/starred_19.png
Normal file
|
After Width: | Height: | Size: 830 B |
BIN
platform/chromium/res/icons/starred_38.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
platform/chromium/res/icons/unsafe.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
platform/chromium/res/icons/unsafe_19.png
Normal file
|
After Width: | Height: | Size: 886 B |
BIN
platform/chromium/res/icons/unsafe_38.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |