beginning annotation with typescript
This commit is contained in:
156
src/hide-slop.ts
Normal file
156
src/hide-slop.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
class SearchLink {
|
||||
|
||||
node: Element
|
||||
target: string
|
||||
url: URL
|
||||
checked: boolean
|
||||
result: any
|
||||
|
||||
constructor(link_node: Element) {
|
||||
this.node = link_node
|
||||
this.target = link_node.getAttribute("href")
|
||||
this.url = new URL(link_node.getAttribute("href"))
|
||||
this.checked = false
|
||||
this.result = undefined
|
||||
}
|
||||
}
|
||||
|
||||
class ResultLinks extends Map {
|
||||
// map domains to paths and their associated nodes
|
||||
setLink(domain: string, path: string, search_link: SearchLink) {
|
||||
if(!super.get(domain)) {
|
||||
const nested_map = new Map()
|
||||
nested_map.set(path, search_link)
|
||||
super.set(domain, nested_map)
|
||||
} else {
|
||||
super.get(domain).set(path, search_link)
|
||||
}
|
||||
}
|
||||
|
||||
setNode(link_node: Element) {
|
||||
const search_link = new SearchLink(link_node)
|
||||
this.setLink(search_link.url.hostname, search_link.url.pathname, search_link)
|
||||
}
|
||||
|
||||
get(domain: string, path: string = "/") {
|
||||
return super.get(domain).get(path)
|
||||
}
|
||||
|
||||
getDomain(domain: string) {
|
||||
return super.get(domain)
|
||||
}
|
||||
|
||||
getUrl(url: string) {
|
||||
const urlobj = new URL(url)
|
||||
return this.get(urlobj.hostname, urlobj.pathname)
|
||||
}
|
||||
|
||||
getSearchLinks() {
|
||||
// return an iterator over the nested SearchLink objects
|
||||
const domain_value_iterator = super.values() as MapIterator<Map<string, SearchLink>>
|
||||
const search_link_iterator = domain_value_iterator.flatMap((domain_map) => {
|
||||
return domain_map.values()
|
||||
})
|
||||
return search_link_iterator
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const ddg_result_selector = "a[data-testid=\"result-title-a\""
|
||||
const ddg_result_list_selector = "ol.react-results--main"
|
||||
|
||||
let result_list_node: Element
|
||||
let result_list_observer
|
||||
const page_links = new ResultLinks()
|
||||
|
||||
|
||||
function check_links(search_links: SearchLink[]) {
|
||||
// send a message to background script with a list of URLs to check
|
||||
const urls = search_links.map((search_link) => {
|
||||
search_link.checked = true
|
||||
return search_link.target
|
||||
})
|
||||
browser.runtime.sendMessage({type: "check", urls: urls.toArray()})
|
||||
}
|
||||
|
||||
async function message_listener(message) {
|
||||
// handle slop reports returned from the background script
|
||||
if(message.type === "check_result") {
|
||||
if (message.domain) {
|
||||
const paths = page_links.getDomain(message.domain)
|
||||
paths.forEach((search_link) => {
|
||||
search_link.node.setAttribute("style", "color: red;")
|
||||
search_link.result = message.result
|
||||
})
|
||||
} else if (message.url) {
|
||||
const link = page_links.getUrl(message.url)
|
||||
link.node.setAttribute("style", "color: red;")
|
||||
link.result = message.result
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function get_initial_links() {
|
||||
// get links from initial page load
|
||||
const links = document.querySelectorAll(ddg_result_selector)
|
||||
links.forEach((node) => {
|
||||
page_links.setNode(node)
|
||||
})
|
||||
const link_targets = page_links.getSearchLinks()
|
||||
check_links(link_targets)
|
||||
}
|
||||
|
||||
function update_links() {
|
||||
// the result list has updated, add new links and check them
|
||||
const links = document.querySelectorAll(ddg_result_selector)
|
||||
links.forEach((node) => {
|
||||
page_links.setNode(node)
|
||||
})
|
||||
const link_iter = page_links.getSearchLinks().filter((search_link) => {
|
||||
return !(search_link.checked)
|
||||
})
|
||||
|
||||
check_links(link_iter)
|
||||
}
|
||||
|
||||
function setup_result_observer() {
|
||||
// observe changes in the result list to respond to newly loaded results
|
||||
const config = { childList: true }
|
||||
result_list_observer = new MutationObserver(update_links)
|
||||
result_list_observer.observe(result_list_node, config)
|
||||
}
|
||||
|
||||
async function wait_for_results() {
|
||||
let results = new Promise(async (resolve) => {
|
||||
let node = document.querySelector(ddg_result_list_selector)
|
||||
while (!node) {
|
||||
await new Promise((resolve) => {setTimeout(()=>{resolve()}, 100)})
|
||||
node = document.querySelector(ddg_result_list_selector)
|
||||
}
|
||||
resolve(node)
|
||||
})
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
async function onload_handler() {
|
||||
|
||||
|
||||
// get results ol node to observe
|
||||
result_list_node = await wait_for_results()
|
||||
|
||||
get_initial_links()
|
||||
setup_result_observer()
|
||||
}
|
||||
|
||||
// listen for messages from the background script
|
||||
browser.runtime.onMessage.addListener(message_listener)
|
||||
|
||||
// initialize state on document load
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", onload_handler)
|
||||
} else {
|
||||
wait_for_results().then(onload_handler)
|
||||
}
|
||||
247
src/report-slop.ts
Normal file
247
src/report-slop.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
const API_URL: string = "https://api.slopfarmer.jack-case.pro"
|
||||
let access_token: string
|
||||
|
||||
const login_form = document.getElementById("login-form")
|
||||
if(login_form) {
|
||||
const login_status = document.getElementById("login-status")
|
||||
if (localStorage.getItem("accessToken")) {
|
||||
login_status.setAttribute("style", "visibility: visible;")
|
||||
}
|
||||
login_form.addEventListener("submit", (event) => {event.preventDefault(); submit_login_form()})
|
||||
}
|
||||
|
||||
function setup_storage_db() {
|
||||
/* create indexeddb object store to retain objects in the form of
|
||||
* {"domain": "domain.tld",
|
||||
* "paths": [
|
||||
* "page/1",
|
||||
* "page/2"
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
let db
|
||||
const db_request = window.indexedDB.open("SlopDB", 1)
|
||||
|
||||
db_request.onerror = (event) => {
|
||||
// handle error
|
||||
console.log(event)
|
||||
}
|
||||
|
||||
db_request.onsuccess = (event) => {
|
||||
// create objectstore
|
||||
console.log(event)
|
||||
db = event.target.result
|
||||
}
|
||||
|
||||
db_request.onupgradeneeded = (event) => {
|
||||
console.log(event)
|
||||
db = event.target.result
|
||||
const slop_store = db.createObjectStore("slop", {keyPath: "domain"})
|
||||
}
|
||||
}
|
||||
|
||||
function on_install_handler() {
|
||||
setup_storage_db()
|
||||
}
|
||||
|
||||
async function get_slop_store(readwrite: boolean) {
|
||||
|
||||
const slop_store_promise: Promise<IDBObjectStore> = new Promise((resolve, reject) => {
|
||||
const db_request = window.indexedDB.open("SlopDB", 1)
|
||||
|
||||
db_request.onsuccess = (event) => {
|
||||
const db = event.target.result
|
||||
const transaction = db.transaction(["slop"], readwrite ? "readwrite" : undefined)
|
||||
const slop_store = transaction.objectStore("slop")
|
||||
resolve(slop_store)
|
||||
}
|
||||
db_request.onerror = (event) => {
|
||||
reject(event)
|
||||
}
|
||||
})
|
||||
|
||||
return await slop_store_promise
|
||||
}
|
||||
|
||||
async function insert_slop(domain: string, path: string) {
|
||||
let db
|
||||
const db_request = window.indexedDB.open("SlopDB", 1)
|
||||
|
||||
db_request.onsuccess = (event) => {
|
||||
db = event.target.result
|
||||
const transaction = db.transaction(["slop"], "readwrite")
|
||||
const slop_store = transaction.objectStore("slop")
|
||||
|
||||
// is this domain already stored?
|
||||
const request = slop_store.get(domain)
|
||||
request.onsuccess = (event) => {
|
||||
let result = request.result
|
||||
if (result) {
|
||||
// domain exists, add this path
|
||||
result.paths.add(path)
|
||||
}
|
||||
|
||||
else {
|
||||
// create a new domain object
|
||||
const paths_set = new Set()
|
||||
paths_set.add(path)
|
||||
result = {domain: domain, paths: paths_set}
|
||||
}
|
||||
|
||||
// persist to indexeddb
|
||||
const store_request = slop_store.put(result)
|
||||
store_request.onsuccess = () => {
|
||||
console.log(domain, path, "stored")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const report_url = new URL("/report", API_URL)
|
||||
const request = new Request(report_url,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Bearer": get_access_token()
|
||||
},
|
||||
body: JSON.stringify({slop_urls: [new URL(path, "http://"+domain).toString()]})
|
||||
})
|
||||
fetch(request)
|
||||
}
|
||||
|
||||
async function check_local_slop(url: string) {
|
||||
const slop_url = new URL(url)
|
||||
const slop_store = await get_slop_store(false)
|
||||
const known_slop = new Promise((resolve, reject) => {
|
||||
const request = slop_store.get(slop_url.hostname)
|
||||
request.onsuccess = (event) => {
|
||||
resolve(request.result)
|
||||
}
|
||||
request.onerror = (event) => {
|
||||
reject(event)
|
||||
}
|
||||
})
|
||||
|
||||
const slop_object = await known_slop
|
||||
let result = {slop_domain: false, slop_path: false}
|
||||
if (slop_object) {
|
||||
// domain was found
|
||||
result.slop_domain = true
|
||||
if (slop_object.paths.has(slop_url.pathname)) {
|
||||
// specific page was found
|
||||
result.slop_path = true
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
async function check_remote_slop(urls: string[]) {
|
||||
const check_url = new URL("/check", API_URL)
|
||||
const request = new Request(check_url, {method: "POST", headers: { "Content-Type": "application/json", "Bearer": get_access_token() }, body: JSON.stringify({slop_urls: urls})})
|
||||
const response = await fetch(request)
|
||||
let domain_objects = await response.json()
|
||||
domain_objects.forEach((domain: any) => {insert_slop(domain.domain_name, "/")})
|
||||
return domain_objects
|
||||
}
|
||||
|
||||
async function on_button_clicked_handler(tab: Tab) {
|
||||
// insert the current tab's page into slop storage
|
||||
const tab_url = new URL(tab.url)
|
||||
|
||||
const domain = tab_url.hostname
|
||||
const path = tab_url.pathname
|
||||
|
||||
await insert_slop(domain, path)
|
||||
update_page_action_icon({frameId: 0, tabId: tab.id, url: tab.url})
|
||||
}
|
||||
|
||||
async function update_page_action_icon(details) {
|
||||
if(details.frameId != 0) {
|
||||
return
|
||||
}
|
||||
const is_slop = await check_local_slop(details.url)
|
||||
if(is_slop.slop_path) {
|
||||
browser.pageAction.setIcon({
|
||||
path: "icons/virus_red.png",
|
||||
tabId: details.tabId
|
||||
})
|
||||
}
|
||||
else if(is_slop.slop_domain) {
|
||||
browser.pageAction.setIcon({
|
||||
path: "icons/virus_yellow.png",
|
||||
tabId: details.tabId
|
||||
})
|
||||
}
|
||||
else {
|
||||
browser.pageAction.setIcon({
|
||||
path: "icons/virus-slash.png",
|
||||
tabId: details.tabId
|
||||
})
|
||||
}
|
||||
console.log(is_slop)
|
||||
}
|
||||
|
||||
async function message_listener(message, sender) {
|
||||
const tabid = sender.tab.id
|
||||
if (message.type === "check") {
|
||||
let check_promises = new Array()
|
||||
let not_found_local = new Array()
|
||||
|
||||
message.urls.forEach((url) => {
|
||||
check_promises.push(check_local_slop(url).then(async (result) => {
|
||||
if (result.slop_domain) {
|
||||
browser.tabs.sendMessage(tabid, { type: "check_result", url: url, result: result })
|
||||
}
|
||||
else {
|
||||
not_found_local.push(url)
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
await Promise.all(check_promises)
|
||||
|
||||
let remote_slop = await check_remote_slop(not_found_local)
|
||||
remote_slop.forEach((result) => {
|
||||
browser.tabs.sendMessage(tabid, { type: "check_result", domain: result.domain_name, result: result })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function get_access_token() {
|
||||
access_token = localStorage.getItem("accessToken")
|
||||
if (!access_token) {
|
||||
// get an access token from the API
|
||||
}
|
||||
return access_token
|
||||
}
|
||||
|
||||
async function submit_login_form() {
|
||||
|
||||
const login_url = new URL("/login", API_URL)
|
||||
|
||||
const request = new Request(login_url,
|
||||
{
|
||||
method: "POST",
|
||||
body: new FormData(login_form)
|
||||
})
|
||||
|
||||
const response = await fetch(request)
|
||||
|
||||
if(response.ok) {
|
||||
const body = await response.json()
|
||||
const token = body.access_token
|
||||
localStorage.setItem("accessToken", token)
|
||||
const status_el = document.getElementById("login-status")
|
||||
status_el.setAttribute("style", "visibility: visible;")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(!login_form) {
|
||||
browser.runtime.onInstalled.addListener(on_install_handler)
|
||||
browser.runtime.onStartup.addListener(get_access_token)
|
||||
browser.pageAction.onClicked.addListener(on_button_clicked_handler)
|
||||
browser.webNavigation.onCommitted.addListener(update_page_action_icon)
|
||||
browser.runtime.onMessage.addListener(message_listener)
|
||||
}
|
||||
Reference in New Issue
Block a user