From 0f0bf03c0a0667ddc5058fc10b54525e9e795176 Mon Sep 17 00:00:00 2001 From: John Sterling Date: Wed, 17 Mar 2021 00:42:25 +0100 Subject: [PATCH 1/3] safe keeping --- Finicky/Finicky/AppDelegate.swift | 31 ++++------- Finicky/Finicky/Info.plist | 12 ++++- browser-extension/background.js | 20 ++++++++ browser-extension/contentScript.js | 82 ++++++++++++++++++++++++++++++ browser-extension/manifest.json | 16 ++++++ 5 files changed, 138 insertions(+), 23 deletions(-) create mode 100644 browser-extension/background.js create mode 100644 browser-extension/contentScript.js create mode 100644 browser-extension/manifest.json diff --git a/Finicky/Finicky/AppDelegate.swift b/Finicky/Finicky/AppDelegate.swift index 8f50021..169157c 100644 --- a/Finicky/Finicky/AppDelegate.swift +++ b/Finicky/Finicky/AppDelegate.swift @@ -112,7 +112,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele LSSetDefaultHandlerForURLScheme("http" as CFString, bundleId as CFString) LSSetDefaultHandlerForURLScheme("https" as CFString, bundleId as CFString) LSSetDefaultHandlerForURLScheme("finicky" as CFString, bundleId as CFString) - LSSetDefaultHandlerForURLScheme("finickys" as CFString, bundleId as CFString) } @IBAction func reloadConfig(_: NSMenuItem? = nil) { @@ -231,17 +230,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele } @IBAction func testUrl(_ sender: NSTextField) { - guard var value = sender.stringValue.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { + guard let value = sender.stringValue.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { logToConsole("Could not parse URL") return } - if value.starts(with: "finickys://") || value.starts(with: "finicky://") { - logToConsole("Finicky will convert finickys:// and finicky:// urls to https:// and http:// respectively") - value = value.replacingOccurrences(of: "finicky://", with: "http://", options: .literal, range: nil) - value = value.replacingOccurrences(of: "finickys://", with: "https://", options: .literal, range: nil) - } - if !value.starts(with: "https://"), !value.starts(with: "http://") { logToConsole("Finicky only understands https:// and http:// urls") return @@ -299,21 +292,15 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele let pid = event!.attributeDescriptor(forKeyword: AEKeyword(keySenderPIDAttr))!.int32Value let opener = Application(pid: pid) - var url = URL(string: event!.paramDescriptor(forKeyword: AEKeyword(keyDirectObject))!.stringValue!)! - - if url.scheme == "finicky" || url.scheme == "finickys" { - if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true) { - if url.scheme == "finicky" { - urlComponents.scheme = "http" - } - - if url.scheme == "finickys" { - urlComponents.scheme = "https" - } - - url = urlComponents.url! - } + + var rawUrl = event!.paramDescriptor(forKeyword: AEKeyword(keyDirectObject))!.stringValue! + + if rawUrl.hasPrefix("finicky://") { + rawUrl.removeFirst("finicky://".count) } + + let url = URL(string: rawUrl)! + shortUrlResolver.resolveUrl(url, callback: { (URL) -> Void in self.callUrlHandlers(opener: opener, url: URL) }) diff --git a/Finicky/Finicky/Info.plist b/Finicky/Finicky/Info.plist index 6f4efa8..a5cea0b 100644 --- a/Finicky/Finicky/Info.plist +++ b/Finicky/Finicky/Info.plist @@ -88,9 +88,19 @@ https + + CFBundleTypeRole + Viewer + CFBundleURLName + FinickyHttpRewrite + CFBundleURLSchemes + + finicky + + CFBundleVersion - 310 + 311 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/browser-extension/background.js b/browser-extension/background.js new file mode 100644 index 0000000..050dbc6 --- /dev/null +++ b/browser-extension/background.js @@ -0,0 +1,20 @@ +const menuItemId = "finicky-open-url"; + +let browser = typeof window !== "undefined" && typeof window.browser !== "undefined" ? window.browser : chrome; + + +browser.contextMenus.onClicked.addListener((info, tab) => { + if (info.menuItemId !== menuItemId) { + return; + } + + browser.tabs.update(tab.id, { url: "finicky://" + info.linkUrl }); +}); + +try { + chrome.contextMenus.create({ + id: menuItemId, + title: "Open with Finicky", + contexts: ["link"], + }); +} catch (ex) {} diff --git a/browser-extension/contentScript.js b/browser-extension/contentScript.js new file mode 100644 index 0000000..c225c20 --- /dev/null +++ b/browser-extension/contentScript.js @@ -0,0 +1,82 @@ +let isIntercepting = false; +console.log("👇 hiya") + +window.addEventListener("keydown", (event) => { + if (event.key.toLowerCase() === "alt") { + isIntercepting = true; + } +}); + +window.addEventListener("keyup", (event) => { + if (event.key.toLowerCase() === "alt") { + isIntercepting = false; + } +}); + +function getAnchor(element) { + do { + + if (element.tagName?.toLowerCase() === "a") { + return element; + } + + element = element.parentNode; + } while (element.parentNode); + + return undefined; +} + + +window.addEventListener( + "mousedown", // doubleclick instead? + function (event) { + if (!isIntercepting) { + return; + } + + event.preventDefault(); + event.stopImmediatePropagation(); + + const anchor = getAnchor(event.target); + + + if (!anchor?.hasAttribute("href")) { + return; + } + console.log('mousedowned something', anchor, isIntercepting) + event.preventDefault(); + event.stopImmediatePropagation(); + + }) + + +window.addEventListener( + "click", // doubleclick instead? + function (event) { + if (!isIntercepting) { + return; + } + event.preventDefault(); + event.stopImmediatePropagation(); + + const anchor = getAnchor(event.target); + + + if (!anchor?.hasAttribute("href")) { + return; + } + console.log('clicked something', anchor, isIntercepting) + + try { + const url = new URL(anchor.href, document.baseURI).href; + console.log("opening url in finicky", url); + window.location = "finicky://" + url + } catch (ex) { + console.log(ex); + } + + event.preventDefault(); + event.stopImmediatePropagation(); + }, + true +); diff --git a/browser-extension/manifest.json b/browser-extension/manifest.json new file mode 100644 index 0000000..f74461f --- /dev/null +++ b/browser-extension/manifest.json @@ -0,0 +1,16 @@ +{ + "name": "Finicky Comrade", + "description": "Hey Finicky!", + "version": "1.0", + "manifest_version": 2, + "background": { + "scripts": ["background.js"] + }, + "content_scripts": [ + { + "matches": [""], + "js": ["contentScript.js"] + } + ], + "permissions": ["contextMenus", "tabs", ""] +} From 0dd5bb7db56b7f3306476cea03dacde5f24785ca Mon Sep 17 00:00:00 2001 From: John Sterling Date: Thu, 18 Mar 2021 22:53:01 +0100 Subject: [PATCH 2/3] Version 0.1 --- browser-extension/contentScript.js | 69 ++++++++++++----------------- browser-extension/icon128.png | Bin 0 -> 1849 bytes browser-extension/icon16.png | Bin 0 -> 367 bytes browser-extension/icon48.png | Bin 0 -> 736 bytes browser-extension/manifest.json | 14 ++++-- 5 files changed, 39 insertions(+), 44 deletions(-) create mode 100644 browser-extension/icon128.png create mode 100644 browser-extension/icon16.png create mode 100644 browser-extension/icon48.png diff --git a/browser-extension/contentScript.js b/browser-extension/contentScript.js index c225c20..2543afd 100644 --- a/browser-extension/contentScript.js +++ b/browser-extension/contentScript.js @@ -1,5 +1,4 @@ let isIntercepting = false; -console.log("👇 hiya") window.addEventListener("keydown", (event) => { if (event.key.toLowerCase() === "alt") { @@ -13,9 +12,12 @@ window.addEventListener("keyup", (event) => { } }); +window.addEventListener("blur", (event) => { + isIntercepting = false; +}); + function getAnchor(element) { do { - if (element.tagName?.toLowerCase() === "a") { return element; } @@ -26,57 +28,44 @@ function getAnchor(element) { return undefined; } - window.addEventListener( - "mousedown", // doubleclick instead? + "mousedown", function (event) { - if (!isIntercepting) { - return; - } - - event.preventDefault(); - event.stopImmediatePropagation(); - const anchor = getAnchor(event.target); + if (!anchor) return; - - if (!anchor?.hasAttribute("href")) { - return; - } - console.log('mousedowned something', anchor, isIntercepting) - event.preventDefault(); - event.stopImmediatePropagation(); - - }) - + console.log("mousedowned something", anchor, isIntercepting); + } +); window.addEventListener( - "click", // doubleclick instead? + "click", function (event) { - if (!isIntercepting) { - return; - } - event.preventDefault(); - event.stopImmediatePropagation(); - - const anchor = getAnchor(event.target); + const anchor = capture(event); + if (!anchor) return; - - if (!anchor?.hasAttribute("href")) { - return; - } - console.log('clicked something', anchor, isIntercepting) + console.log("clicked something", anchor, isIntercepting); try { const url = new URL(anchor.href, document.baseURI).href; - console.log("opening url in finicky", url); - window.location = "finicky://" + url + console.log("opening url in finicky", url); + window.location = "finicky://" + url; } catch (ex) { - console.log(ex); + console.error("Finicky Browser Extension Error", ex); } - - event.preventDefault(); - event.stopImmediatePropagation(); }, true ); + +function capture(event) { + if (!isIntercepting) return; + + const anchor = getAnchor(event.target); + + if (!anchor?.hasAttribute("href")) return; + + event.preventDefault(); + event.stopImmediatePropagation(); + + return anchor; +} \ No newline at end of file diff --git a/browser-extension/icon128.png b/browser-extension/icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..50db1d672633648fa77741e9718d4ab549f34ad2 GIT binary patch literal 1849 zcmbVN{WsH#AOCDNdvIe!H{@%Plaje8X=O9dD$>Iu-Rs#2Jz-{6mqxYC!@{k7Rojfj zO3C9DH756lsm(fWn6;=zxo!;;n})B>{SUtHb6)S)dA;7}_4@Ul9~K(84rzr10AO8k zkl&FtrhNz8aBcsAi+!<1h}57HBmgj4{~aLUHh1Hi2_hW{^Z_18H%+Yx$SFby0RS3{ zjr6fl05FsW`w@<1fj;w!iGS}$_4HDrKpE)zJcb3+CJ7n?mE}g{M&?FM|Tx7U$cD(}~MQ z7$bs_X}Mfp5ZQea#MG!&iQ9*&W+DoeOcd^3tZ+Tev(v@Js1bB^;GkdXp>^-sK&v$R zH&Nw^`J`Rm$pRa4XGFI#M?yc8CAI73cA0h=;2wRMcJZS1g!&uN^@~Z{U5q_gyZJb0`?#3 zl|r$>j*lXd=pwo4X$pA~#8LrGzLCxN7v~0NS0*Rv-efEf){qu%;s9jLFHK|*6ItwArBi}l5}}+1u}f{TQWxQtmq;}qhrE>YCx3EV8LD%q(u`;E3y{-# z7H1M<_}SxSoc#xeM$wF&qt#9W2hvAdVp$?D93HO>skZMDgU2%(1$<@ZrAz0D&3Sa~ z(o*_v_J*#p4D7Icw?@ZJ=no{8HnI}nx)n`t;D>CpDnUPL=KO->#S0@Mfrp8=5`$sl zAKojr@GD*=`BhwQlf!s7`;t5^QYgI)@7#N5pY%=sRMo0=UG;p|**Tka#yR4}$$b%t22I?>Z^1K1{yw{RA_wc5Vmwd3r}OzJ0}Ls<%= z;=x^XFDCpr3F2w#5!p5sQU16&u)AaB>VHB8ba*t44o#ig8D^V;UVT^K7E+jDbx$so z!ZQmAR$sPWM~FwsZ^Nf3Z~7PO5L&49foBdu+Mwy9ezZw@@9OID&%_6kZ=L(~?#mBx zkau?^fmHyfe-U!d_H;TS*%43A39zX@)a__*B|{Pao449f2vL;aYBJcVWwQHKUok!5 zKhG+wHh}&a!Djh)Sz{GW8(Rz@s68lJ@6S2$M6nCvgm7L=4-j0=k63w(RR@`ny=VeAg3OlFC6{+)&fa} z$2@*mn|SQH_-+`i*>3HYm_}-~@21D|yI&KCAXVZFwz~OBYRX*C)UP=W7XEy1uVbd( zrN2``-t7GA!CS3z8lluZduwr~Q#oWe+i+Px0FODK%Zb}abC?V+=tM@1mwkwWJTm$b`XTFVVi>RX;odJkWt?0;TY<0_Fh4| z!K0k9G*Yfv(tOv5dat6g;vsxoFGDe;Z4sPShpM^*H&aKM$wG3v2jSpGUi-gq2N;L1 Y8n~Vu#13GN@BMBC`-l2H@`=g&AJe61vj6}9 literal 0 HcmV?d00001 diff --git a/browser-extension/icon16.png b/browser-extension/icon16.png new file mode 100644 index 0000000000000000000000000000000000000000..d7b8cf9ac672ce4b99282d60f0442ef81b1749cf GIT binary patch literal 367 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBITlYB$B+ufxxt1)O$GuyeYVr4Xudol{DJiZ z{}X*k3iR_Dc9!v55 z=$q(ru2w&1iOy+*`=1>W_(Fsx2QBiyR2uuT^4`Y%K5mR!&4Jqsl`5tjpZN8=SQP6d zm79ST%9D)VMsKXNd49s1Wz7sN$DjMwHz^$OesjbAfQJ3~n_c2MU9M#t)#XJ6o5wgK!guDq}I|11MHXs|24U!G$2G9wh6F3{d2Clw|)uul}3y~eT%jZsY znt+h7Br6U?1N|~01+29(4-YJVNrxAfY&05`KfSQC18n??hxBwh(cy3)4dOV)P_jI- z6FN%21K&K0EsDZUr&Ehl8WKg3HO4^ok!4E0VC4oMbgiN1glEAzmgED34z%Ngx*FRi z$0H`d=A;cPjVjG~?#*}zLK9&`njj`6bP%gr#(pQu>2V z-i(JJbT7^WsW^ziK8&U=d4PE9qvEs)BEa#Vegi_Mtw}>7XIWNFYpA@qIFaGNe!u^s zXt7vGLgA0Wafr%vjRR>j{?RtL{v2#wX|2L6#4KD@O8Lcw{RljW8`p2bfpMDyd@Bb7 z3qFjgzP)?mb0@kn#^)}w!8;yy)H6H-a#()8_J{6(BuV73BrQVaz;KU?^GsQQd|_EIYTVtE29K;P&A`h*AUSt6F6ss4$;&27I7_~I4F0VR14 SQ8g(50000"] + "icons": { + "16": "icon16.png", + "48": "icon48.png", + "128": "icon128.png" + }, + "permissions": ["contextMenus", ""] } From e9b1cb142feaac02603071aa36ea6a4cfb34828f Mon Sep 17 00:00:00 2001 From: John Sterling Date: Fri, 19 Mar 2021 19:15:44 +0100 Subject: [PATCH 3/3] new icons --- browser-extension/icon128.png | Bin 1849 -> 1990 bytes browser-extension/icon16.png | Bin 367 -> 352 bytes browser-extension/icon48.png | Bin 736 -> 780 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/browser-extension/icon128.png b/browser-extension/icon128.png index 50db1d672633648fa77741e9718d4ab549f34ad2..8707d5ad36088687f81ad5367d09e21560f38d94 100644 GIT binary patch delta 1926 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 diff --git a/browser-extension/icon16.png b/browser-extension/icon16.png index d7b8cf9ac672ce4b99282d60f0442ef81b1749cf..8d2a872b66a7d01732f6414ae51d5dfb800778cc 100644 GIT binary patch delta 276 zcmV+v0qg$n0^kCWReu2WNklST5PYHx02?4CaEa*YJpvUSN;ZHE5>e45 zt|`I*NLc`+WC8;dcmu~DfkYyM5htDO!gu~Vy#*RbDTz8L=)o<^Vz2}yc9SIGc-7H$ zUCBAm77d!Fxi5()=6TMw;=b=w{J~@pMUliw|3^PjN{!+vhJQlzlRL|@P2!1d+ur5| zXWO8x=uy>mO&rJRJkJx1v5OD_b^)`QiX1yQ&nMV~IF5zy`&|$O zOfA#ZXg~-7)dv(s@fwDq3&RkF6>N>5@KB^{?+>t!x9LZdff2e6Iu&8MbJzpy0ats= a+Yb|wuhpgvEDiMl0000FL>4f delta 291 zcmV+;0o?xJ0`CHlReu2lNkl5DbnzmXs;!$P@4d$Pe@d&{4t{ zwP1#ZF$KLmA-*8qI^xJc2;JD!cAr#=gr$8N*^y5Hb2crz*8V&f4kwngYFKnbN%P`;bKwu73 z5|dJ*PttW=>3QC$C35-7Fq?8B-v4TBa$-hTd{dO=g3P0te7f6 p`M$4$AizuP*c?B=C_m5HiWe-3n~sdM@nir1002ovPDHLkV1h&Vf!zQA diff --git a/browser-extension/icon48.png b/browser-extension/icon48.png index dd86ea4cb7e58d2e23b42c8d4130efa55c1897d1..7a2ade8d1e123adb91796d5e14023f3e3b9abcc0 100644 GIT binary patch delta 706 zcmV;z0zLiU1&jueR)3*ML_t(|0qvTBZSpV>g&!3Iplkpezy`1ZbOOQz zbc19A*dS~Wm>|6ecIBjLse&B`l~1~oMk2Xq-<{8Hfd{7t3lv2W$j4YLgdGmOUhf@k zAkNRk^1?ou*bpv}7cBUu-ELPzQB?4j4db)2EYm{Vh>!<|e1H08v$5v5X+aQZ5eZzt zDdr2pFmxh-HlNS+EQGL$iu+Qv6)e8Il9t|c>!Svl1t!2$EgW?EDEE%=h?;#;O3waIUs6I+bO44it^-*T@PY?mHk(2AAiR8o z4cg2B?zK!>$QcyFBS{EpdQ5j!xk=PK+5)Kxnl>cnm1kj0@&mp0d?K2 zQ6krD!++Rys_O_YXevNL;^HNbWO#f?F}$ZnJ40JE5nyaevf?XA(=h470Zcr$A?x)T z=kxh;%~nJ>;+l-CsMZltWm?$qXoBTo)dW^1;<8yA;4yR}7GP}i9uHYn^SUE0F=25iL`>$@Bjb+07*qoM6N<$g78jNTmS$7 delta 662 zcmV;H0%`q>2H*vdR)2L#L_t(|0qvUqaq2J($HhDThY_;DV}!g3UONHV05%{SkPVUz z=myXUpc6P7zy_|qiPfe*LJN@{xXb5Gb((;Xup}!EL<9XYA_c6qF%J(ce@TZImTWW{ zl|Q|(vjc4Wiih-cI?>^9APwR;#!#|6vJ*N=zXRVqi!F-6PJgFUi&GjBMUgeeK=qMj zO1@y_1|M{-q348W!8(@Y1B4E=C(Gqh!YCpwSlPyR=ujh>9SLb5ai6BCcjHMr4_L_}wDI(w2Zp-93TDu<*=&Lu zPa1jPDo&NOntzMcZ>3WngseE^BM+qF%x1IRjE5j}FU|w0IEcYMjHWJmfOzYp;S98zyG3Wu~tCEL>Ge`Nf6(2t0@z*Kfjsahn5tD+dD$K8&fpy?=Y+b0@kn#^)}w!8;yy)H6H- za#()8_J{6(BuV73BrQVaz*rYV@%5M3}7f({!!hAL@YbEs;i^!fZ+D_ z3-Ziev=ZkS9O#?Dfxa0W=$p{%j~o~izcz|C#H4EW*|$N?pJ4-Zi_DF6Tf07*qoM6N<$f?#GrmH+?%