browser-ruler/browser_ruler.py

202 lines
4.6 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):
url: str
referrer: str
class BrowserRule(NamedTuple):
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"""
Popen(["nohup", self.command, *self.args, intent.url], shell=False)
def maybe_launch(self, intent: Intent) -> bool:
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:
for rule in rules:
if match(rule, intent.url):
return True
return False
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)
for rule in rules:
if match(rule, url.hostname):
return True
return False
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:
def match_func(intent: Intent) -> bool:
return any(r(intent) for r in rules)
return match_func
def default(x: Any) -> bool:
"""Always returns True"""
return True
def noop(x: 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:
intent = Intent(url, referrer)
return any(rule.maybe_launch(intent) for rule in browser_rules)
def main(urls: List[str], referrer=None):
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:])