1
0
mirror of https://gitlab.com/ceda_ei/firefox-web-apps synced 2025-11-04 19:20:06 +01:00

Add poetry and move everything into a module (fwa)

This commit is contained in:
2023-09-14 00:48:28 +05:30
parent dce0da91b2
commit c05f2b045b
5 changed files with 199 additions and 0 deletions

0
fwa/__init__.py Normal file
View File

206
fwa/create_app.py Executable file
View File

@@ -0,0 +1,206 @@
#!/usr/bin/env python3
"Creates a firefox web app"
import argparse
import io
import shlex
import stat
import sys
import os
import os.path as pt
from urllib.parse import urlparse, urlunparse
from collections import Counter
import requests
from bs4 import BeautifulSoup
from PIL import Image
REPO_DIR = pt.abspath(pt.dirname(sys.argv[0]))
BIN_DIR = f"{REPO_DIR}/bin"
ICON_DIR = f"{REPO_DIR}/icons"
def eprint(*args, **kwargs):
"Print an error"
print(*args, **kwargs, file=sys.stderr)
def url_exists(url):
"Tests if the url exists after all redirects"
return requests.head(url, allow_redirects=True).status_code == 200
def absolute_url(base_url, relative_url):
"Returns the absolute_url if the relative_url is relative"
base = urlparse(base_url)
relative = urlparse(relative_url)
# Make the url absolute if it is not
if not relative.hostname:
return urlunparse((*base[:2], *relative[2:]))
return relative_url
# pylint: disable=W0613
def create_webapp(name, url, exec_name, logo, profile):
"Creates the necessary files for the webapp."
local_vars = locals()
# Create a dictionary with shell-quoted version of arguments
quoted = {i: shlex.quote(local_vars[i]) for i in ("name", "url", "exec_name",
"logo", "profile")}
# Download and convert the logo
logo_pt = f"{ICON_DIR}/{exec_name}.png"
res = requests.get(logo, allow_redirects=True)
with Image.open(io.BytesIO(res.content)) as img:
img.save(logo_pt, "PNG")
# Create the binary
script_pt = f"{BIN_DIR}/{exec_name}"
with open(script_pt, "w") as script:
script.write("#!/usr/bin/env sh\n")
script.write(f"firefox --profile {quoted['profile']} {quoted['url']}\n")
os.chmod(script_pt, os.stat(script_pt).st_mode | stat.S_IXUSR | stat.S_IXGRP)
# Create the desktop file
desk_pt = pt.expanduser(f"~/.local/share/applications/{exec_name}.desktop")
with open(desk_pt, "w") as desktop:
desktop.write(f"""
[Desktop Entry]
Name={name} (Web App)
Exec={script_pt}
Terminal=false
Type=Application
Icon={logo_pt}
Categories=Network;X-WebApps
""".strip() + "\n")
def extract_metadata(url):
"Extract metadata using bs4"
# Get and parse the page
content = requests.get(url, allow_redirects=True).content
soup = BeautifulSoup(content, 'html.parser')
metadata = {}
# Find the title
titles = []
if soup.title:
titles = [soup.title.string]
for tag in soup.find_all("meta"):
title_props = ["title", "og:title", "twitter:title"]
if tag.get("property", None) in title_props \
or tag.get("name", None) in title_props:
titles.append(tag["content"])
# Set title to the most common if it occurs more than once, else prefer
# title tag
most_common = Counter(titles).most_common(1)
if not most_common:
metadata["title"] = None
elif most_common[0][1] > 1:
metadata["title"] = most_common[0][0].strip()
else:
if soup.title:
metadata["title"] = soup.title.string.strip()
else:
metadata["title"] = most_common[0][0].strip()
# Find the image.
# Try link first, followed by /favicon.{png,ico}, followed by og:, twitter:
image = None
for favicon in soup.find_all("link", rel="icon"):
if url_exists(absolute_url(url, favicon["href"])):
image = absolute_url(url, favicon["href"])
if not image:
for favicon in [absolute_url(url, i) for i in ("favicon.png", "favicon.ico")]:
if requests.head(favicon, allow_redirects=True).status_code == 200:
image = favicon
break
if not image:
for prop in ["og:image", "twitter:image"]:
prop_tag = soup.find("meta", property=prop)
if prop_tag and url_exists(absolute_url(url, prop_tag["content"])):
image = absolute_url(url, prop_tag["content"])
break
metadata["image"] = image
return metadata
def main():
"Main Function"
parser = argparse.ArgumentParser()
parser.add_argument("url", help="URL for the webapp")
parser.add_argument(
"-n", "--name",
help=("Name of the app as shown in the menu. In absence of this, the "
"title of page will be used.")
)
parser.add_argument(
"-e", "--exec-name",
help="Name of the script that will be created in binary directory."
)
parser.add_argument(
"-l", "--logo",
help="URL/path for the logo. If omitted, the favicon will be used."
)
parser.add_argument(
"-f", "--firefox-profile",
help="Firefox Profile path. If omitted, the default profile is used"
)
args = parser.parse_args()
# Add Missing Arguments with default values
if args.firefox_profile is None:
profile_path = REPO_DIR + "/.firefox_profile"
with open(profile_path) as prof:
args.firefox_profile = prof.readline()[:-1]
parsed_url = urlparse(args.url)
if not parsed_url.scheme:
eprint("Missing URL scheme")
eprint(f"Maybe you meant https://{args.url} ?")
sys.exit(1)
print("Fetching details ...")
metadata = extract_metadata(args.url)
if not args.name:
args.name = metadata["title"]
if not args.logo:
args.logo = metadata["image"]
if not args.exec_name:
args.exec_name = parsed_url.hostname.replace(".", "-") + "-webapp"
if "/" in args.exec_name:
eprint("Executable name can't contain slashes.")
sys.exit(2)
if pt.exists(f"{BIN_DIR}/{args.exec_name}"):
index = 0
while True:
if not pt.exists(f"{BIN_DIR}/{args.exec_name}-{index}"):
args.exec_name = f"{args.exec_name}-{index}"
break
index += 1
print()
print(f"WebApp Name:\t\t{args.name}")
print(f"WebApp URL:\t\t{args.url}")
print(f"Logo URL:\t\t{args.logo}")
print(f"Executable Name:\t{args.exec_name}")
print(f"Firefox Profile:\t{args.firefox_profile}")
print()
print("Do you want to create the app with the above details (Y/n): ",
end=' ')
inp = input()
if not inp or inp[0].upper() != "N":
create_webapp(args.name, args.url, args.exec_name, args.logo,
args.firefox_profile)
if __name__ == "__main__":
main()

141
fwa/setup.sh Executable file
View File

@@ -0,0 +1,141 @@
#!/usr/bin/env bash
# Usage: ./setup.sh
set -euo pipefail
REPO_DIR="$(dirname "$0")"
BIN_DIR="$REPO_DIR/bin"
ICON_DIR="$REPO_DIR/icons"
FIRST_LAUNCH="https://gitlab.com/ceda_ei/firefox-web-apps/-/wikis/Getting-Started"
HELP_TEXT="
Usage:
$0 [-f|--firefox-profile] <firefox_profile> [-n|--new] <profile_name> [-h|--help]
Configure a firefox profile for web apps.
Options:
-f, --firefox-profile Path to an existing firefox profile (unless -n is
<firefox_profile> also provided)
-n, --new <profile_name> Creates a new profile with the given name. -f
configures the new profile path when passed along
with -n
-h, --help This help page
"
[[ -d $BIN_DIR ]] || mkdir -- "$BIN_DIR"
[[ -d $ICON_DIR ]] || mkdir -- "$ICON_DIR"
FIREFOX_PROFILE=""
PROFILE_NAME="firefox-web-apps"
NEW=0
OPTIONS=f:n:h
LONGOPTS=firefox-profile:,new:,help
PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@")
eval set -- "$PARSED"
while true; do
case "$1" in
-f|--firefox-profile)
shift
FIREFOX_PROFILE="$1"
shift
;;
-n|--new)
NEW=1
shift
PROFILE_NAME="$1"
shift
;;
-h|--help)
echo "$HELP_TEXT"
exit
;;
--)
break
;;
*)
echo "Error parsing arguments!"
exit 1
esac
done
# Check if firefox is running
if pidof firefox &> /dev/null; then
echo "It is recommended to close firefox before running this script."
echo -n "Do you want to run the script anyways? (y/N): "
read -r input
if [[ ${input^^} != "Y" ]]; then
exit 2
fi
fi
# Prompt to create Firefox profile
if [[ $FIREFOX_PROFILE == "" ]] || (( NEW == 1 )); then
if (( NEW == 0 )); then
echo -n "Use an existing profile for apps? (y/N): "
read -r input
if [[ ${input^^} == "Y" ]]; then
echo -n "Enter path to existing profile (or run the script with --firefox_profile): "
read -r FIREFOX_PROFILE
else
NEW=1
fi
fi
if (( NEW == 1 )); then
FIREFOX_PROFILE="${FIREFOX_PROFILE:-$HOME/.mozilla/firefox/${PROFILE_NAME}}"
firefox -CreateProfile "${PROFILE_NAME} ${FIREFOX_PROFILE}"
fi
fi
# Check if firefox_profile is valid
if ! [[ -d $FIREFOX_PROFILE ]]; then
echo "Invalid Firefox Profile Path"
exit 3
fi
# Store Profile to be used
echo "$FIREFOX_PROFILE" > "$REPO_DIR/.firefox_profile"
echo "Enabling userChrome.css support"
echo -e '\nuser_pref("toolkit.legacyUserProfileCustomizations.stylesheets", true);' >> "$FIREFOX_PROFILE/user.js"
# Starting firefox for customizability
firefox --profile "$FIREFOX_PROFILE" "$FIRST_LAUNCH" &
echo ""
echo "Hit Enter once you have completed customizing the profile."
read -r
# userChrome Hacks
#
# Initially stores all selectors to be hidden in HIDDEN_SELECTORS, followed by
# writing a CSS rule that hides them all
mkdir "$FIREFOX_PROFILE/chrome" &> /dev/null || true
HIDDEN_SELECTORS=()
echo -n "Do you want to hide tabs? (y/N) "
read -r input
if [[ ${input^^} == "Y" ]]; then
HIDDEN_SELECTORS=("${HIDDEN_SELECTORS[@]}" "#tabbrowser-tabs")
fi
echo -n "Do you want to hide main toolbar (address bar, back, forward, etc)? (y/N) "
read -r input
if [[ ${input^^} == "Y" ]]; then
HIDDEN_SELECTORS=("${HIDDEN_SELECTORS[@]}" "#nav-bar")
fi
function join_by {
local IFS="$1";
shift;
echo -n "$*";
}
if (( ${#HIDDEN_SELECTORS[@]} > 0 )); then
join_by , "${HIDDEN_SELECTORS[@]}" >> "$FIREFOX_PROFILE/chrome/userChrome.css"
echo "{
visibility: collapse !important;
}" >> "$FIREFOX_PROFILE/chrome/userChrome.css"
fi
echo "Optional: Add $(cd "$ICON_DIR"; pwd) to your PATH to allowing launching the app from command line"