browser-ruler/browser_ruler.py

209 lines
5.0 KiB
Python
Executable File

#! /usr/bin/env python3
import sys
from re import match
from subprocess import Popen
from typing import Any
from typing import Callable
from typing import List
from typing import Iterable
from typing import NamedTuple
from urllib.parse import urlparse
class Intent(NamedTuple):
"""Intent contains a url requested to be opened"""
url: str
# referrer isn't possible to be captured at this time
referrer: str
class BrowserRule(NamedTuple):
"""BrowserRule is matches a url and launches it in a browser"""
match_func: Callable[[Intent], bool]
command: str
args: List[str] = []
def matched(self, intent: Intent) -> bool:
"""Checks if the intent matches the rule using match_func"""
return self.match_func(intent)
def launch(self, intent: Intent) -> None:
"""Launch the command"""
with Popen(["nohup", self.command, *self.args, intent.url], shell=False):
pass
def maybe_launch(self, intent: Intent) -> bool:
"""Launch URL in a browser if it matches the rules"""
if self.matched(intent):
self.launch(intent)
return True
return False
# Matching functions and factories
MatchFunc = Callable[[Intent], bool]
def match_regex(rules: List[str]) -> MatchFunc:
"""Returns a function to handle urls and match them using regex"""
def match_func(intent: Intent) -> bool:
return any(match(rule, intent.url) for rule in rules)
return match_func
def match_host_regex(rules: List[str]) -> MatchFunc:
"""Returns a function to handle urls and match them using regex"""
def match_func(intent: Intent) -> bool:
url = urlparse(intent.url)
if not url.hostname:
return False
return any(match(rule, url.hostname) for rule in rules)
return match_func
def match_hostname(hostnames: List[str]) -> MatchFunc:
"""Returns a matching function that parses URLs and checks their hosts"""
def match_func(intent: Intent) -> bool:
url = urlparse(intent.url)
for hostname in hostnames:
if url.hostname == hostname:
return True
return False
return match_func
def any_match(rules: Iterable[MatchFunc]) -> MatchFunc:
"""Returns a functino that will check against any match rules
Equivalent to using `any(...)`"""
def match_func(intent: Intent) -> bool:
return any(r(intent) for r in rules)
return match_func
def default(_: Any) -> bool:
"""Always returns True"""
return True
def noop(_: Any) -> bool:
"""Always returns False"""
return False
browser_rules = [
# Self-hosted sites
BrowserRule(
match_host_regex([
r".*\.iamthefij\.com$",
r".*\.thefij\.rocks$",
r".*\.thefij$",
]),
"firefox",
),
# Work domains
BrowserRule(
any_match((
match_hostname([
"app.signalfx.com",
"lever.co",
"work.grubhub.com",
"y",
"yelp.rimeto.io",
"yelp.slack.com",
"yelplove.appspot.com",
]),
match_host_regex([
r".*\.lifesize\.com$",
r".*\.myworkday\.com$",
r".*\.salesforce\.com$",
r".*\.yelpcorp\.com$",
]),
)),
"chromium-browser"
),
# Other, generally Googly things
BrowserRule(
any_match((
match_hostname([
"google.com",
"youtube.com",
]),
match_host_regex([
r".*\.google\.com$",
r".*\.youtube\.com$",
]),
)),
"chromium-browser",
),
# Fall back to firefox as the default
BrowserRule(
default,
"firefox",
),
# Sample browsers
# Firefox new tab
BrowserRule(
noop,
"firefox",
),
# Firefox new window
BrowserRule(
noop,
"firefox",
["--new-window"],
),
# Firefox private window
BrowserRule(
noop,
"firefox",
["--private-window"],
),
# Chromium new tab
BrowserRule(
noop,
"chromium-browser",
),
# Chromium new window
BrowserRule(
noop,
"chromium-browser",
["--new-window"],
),
# Chromium incongnito
BrowserRule(
noop,
"chromium-browser",
["--incognito"],
),
]
def open_url(url: str, referrer=None) -> bool:
"""Open a given url based on rule set"""
intent = Intent(url, referrer)
return any(rule.maybe_launch(intent) for rule in browser_rules)
def main(urls: List[str]):
"""Recieves list of urls and opens them each with provided rule sets"""
failed_urls: List[str] = []
for url in urls:
if not open_url(url):
failed_urls.append(url)
if failed_urls:
raise ValueError(f"No rules present for {failed_urls}. Add a default rule")
if __name__ == "__main__":
main(sys.argv[1:])