improved automatic script alias creation and switched to lookup paths for nix-shell invocations
This commit is contained in:
parent
41a1a6a5f5
commit
209294075c
7 changed files with 21 additions and 10 deletions
0
scripts/py/common/__init__.py
Normal file
0
scripts/py/common/__init__.py
Normal file
77
scripts/py/common/common.py
Normal file
77
scripts/py/common/common.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from shutil import which
|
||||
|
||||
|
||||
def notify(
|
||||
application_name: str,
|
||||
title: str,
|
||||
message: str,
|
||||
urgency: str = "low",
|
||||
category: str | None = None,
|
||||
icon: Path | None = None,
|
||||
desktop_entry: str | None = None,
|
||||
) -> None:
|
||||
args = ["notify-send", "-a", application_name, "-u", urgency]
|
||||
if category:
|
||||
args.append("-c")
|
||||
args.append(category)
|
||||
if icon:
|
||||
args.append("-i")
|
||||
args.append(str(icon))
|
||||
if desktop_entry:
|
||||
args.append("-h")
|
||||
args.append(f"string:desktop-entry:{desktop_entry}")
|
||||
args.append(title)
|
||||
args.append(message)
|
||||
print(args)
|
||||
subprocess.run(args)
|
||||
|
||||
|
||||
def read_secret_file(secret: str) -> str:
|
||||
path = f"/var/secrets/{secret}"
|
||||
if not os.path.exists(path):
|
||||
raise FileNotFoundError(f"Secret file {path} does not exist or cannot be read.")
|
||||
with open(f"/var/secrets/{secret}", "r") as f:
|
||||
secret = f.read().strip()
|
||||
if not secret:
|
||||
raise ValueError(f"Secret file {path} is empty.")
|
||||
return secret
|
||||
|
||||
|
||||
def does_desktop_entry_exist(desktop_entry: str) -> bool:
|
||||
if not desktop_entry:
|
||||
raise ValueError("Please provide the full filename of the desktop entry.")
|
||||
|
||||
if not desktop_entry.endswith(".desktop"):
|
||||
desktop_entry += ".desktop"
|
||||
|
||||
entry_paths = []
|
||||
|
||||
if which("qtpaths"):
|
||||
result = subprocess.run(
|
||||
["qtpaths", "--paths", "ApplicationsLocation"],
|
||||
stdout=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
entry_paths = result.stdout.strip().split(":")
|
||||
else:
|
||||
print("qtpaths is not installed, falling back to XDG_DATA_DIRS.")
|
||||
xdg_data_dirs = os.getenv("XDG_DATA_DIRS", "/usr/share:/usr/local/share").split(
|
||||
":"
|
||||
)
|
||||
entry_paths = [os.path.join(path, "applications") for path in xdg_data_dirs]
|
||||
entry_paths.append(os.path.expanduser("~/.local/share/applications"))
|
||||
|
||||
print(f"Checking the following paths for {desktop_entry}:\n{entry_paths}\n{'-'*20}")
|
||||
|
||||
for entry_path in entry_paths:
|
||||
entry_file = Path(entry_path) / f"{desktop_entry}"
|
||||
print(f"Checking for {entry_file}")
|
||||
if entry_file.is_file():
|
||||
print(f"{desktop_entry} found in {entry_path}")
|
||||
return True
|
||||
|
||||
print(f"Desktop entry {desktop_entry} does not exist.")
|
||||
return False
|
116
scripts/py/spectacle-screenshot.py
Executable file
116
scripts/py/spectacle-screenshot.py
Executable file
|
@ -0,0 +1,116 @@
|
|||
#! /usr/bin/env nix-shell
|
||||
#! nix-shell /etc/nixos/scripts/nix/python.nix -i python
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from shutil import which
|
||||
|
||||
from common.common import notify # type: ignore
|
||||
|
||||
|
||||
def spectacle_screenshot(
|
||||
url: str | None = None, record: bool = False, file_path: Path | None = None
|
||||
) -> None:
|
||||
try:
|
||||
if not which("spectacle"):
|
||||
raise FileNotFoundError("spectacle is not installed.")
|
||||
|
||||
if file_path and Path(file_path).exists():
|
||||
raise FileExistsError(
|
||||
'File already exists. Please provide a different file path, or use the zipline function to upload the file.\nExample: zipline "{file_path}"'
|
||||
)
|
||||
|
||||
if not file_path:
|
||||
use_temp_file = True
|
||||
suffix = ".webm" if record else ".png"
|
||||
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=suffix)
|
||||
file_path = Path(temp_file.name)
|
||||
temp_file.close()
|
||||
else:
|
||||
use_temp_file = False
|
||||
|
||||
command = [
|
||||
"spectacle",
|
||||
"--nonotify",
|
||||
"--background",
|
||||
"--pointer",
|
||||
"--copy-image",
|
||||
"--output",
|
||||
file_path,
|
||||
]
|
||||
|
||||
if record:
|
||||
command.append("--record=region")
|
||||
else:
|
||||
command.append("--region")
|
||||
|
||||
try:
|
||||
subprocess.run(command, check=True) # type: ignore
|
||||
except subprocess.CalledProcessError as e:
|
||||
if Path(file_path).exists() and use_temp_file:
|
||||
os.remove(file_path)
|
||||
raise e
|
||||
|
||||
if not Path(file_path).stat().st_size:
|
||||
os.remove(file_path)
|
||||
raise FileNotFoundError("The file was not created properly.")
|
||||
|
||||
try:
|
||||
opts = [
|
||||
"zipline.py",
|
||||
file_path,
|
||||
"--application-name",
|
||||
"Spectacle",
|
||||
"--desktop-entry",
|
||||
"org.kde.spectacle",
|
||||
]
|
||||
if url:
|
||||
opts.extend(["--url", url])
|
||||
subprocess.run(opts) # type: ignore
|
||||
finally:
|
||||
if Path(file_path).exists() and use_temp_file:
|
||||
os.remove(file_path)
|
||||
except BaseException as e:
|
||||
notify(
|
||||
application_name="Spectacle",
|
||||
title="An error occurred",
|
||||
message=str(e),
|
||||
urgency="normal",
|
||||
category="transfer.error",
|
||||
desktop_entry="org.kde.spectacle",
|
||||
)
|
||||
raise e
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="spectacle-screenshot.py",
|
||||
description="Take a screenshot or recording with Spectacle and automatically upload it to a Zipline instance.",
|
||||
epilog="Example usage: spectacle-screenshot.py",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--url",
|
||||
help="The URL of the Zipline instance to upload the screenshot or recording to. Defaults to 'https://csw.im'.",
|
||||
default="https://csw.im",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--record",
|
||||
help="If this is set, Spectacle will record the region instead of taking a screenshot.",
|
||||
action="store_true",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--file-path",
|
||||
help="The path to save the screenshot or recording to. If not provided, the screenshot or recording will be saved to a temporary file.",
|
||||
default=None,
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
spectacle_screenshot(
|
||||
url=args.url,
|
||||
record=args.record,
|
||||
file_path=args.file_path,
|
||||
)
|
116
scripts/py/zipline.py
Executable file
116
scripts/py/zipline.py
Executable file
|
@ -0,0 +1,116 @@
|
|||
#! /usr/bin/env nix-shell
|
||||
#! nix-shell /etc/nixos/scripts/nix/python.nix -i python
|
||||
|
||||
import argparse
|
||||
import mimetypes
|
||||
import os
|
||||
from pathlib import Path
|
||||
from shutil import which
|
||||
from typing import Any
|
||||
|
||||
import requests # type: ignore
|
||||
from common.common import ( # type: ignore
|
||||
does_desktop_entry_exist,
|
||||
notify,
|
||||
read_secret_file,
|
||||
)
|
||||
from pyperclip import copy # type: ignore
|
||||
|
||||
|
||||
def zipline(
|
||||
file_path: Path,
|
||||
instance_url: str,
|
||||
application_name: str | None = None,
|
||||
desktop_entry: str | None = None,
|
||||
) -> Any:
|
||||
token = read_secret_file("zipline")
|
||||
|
||||
if not os.path.isfile(file_path):
|
||||
raise FileNotFoundError(f"File at {file_path} does not exist.")
|
||||
|
||||
use_notify_send = False
|
||||
if application_name and desktop_entry:
|
||||
if not does_desktop_entry_exist(desktop_entry=desktop_entry):
|
||||
raise FileNotFoundError("Desktop entry does not exist.")
|
||||
if not which("notify-send"):
|
||||
raise FileNotFoundError("notify-send is not installed.")
|
||||
use_notify_send = True
|
||||
|
||||
content_type = mimetypes.guess_type(file_path)[0] or "application/octet-stream"
|
||||
|
||||
try:
|
||||
headers = {"authorization": token}
|
||||
files = {
|
||||
"file": (os.path.basename(file_path), open(file_path, "rb"), content_type)
|
||||
}
|
||||
response = requests.post(
|
||||
f"{instance_url.rstrip('/')}/api/upload", headers=headers, files=files
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
response_data = response.json()
|
||||
link = response_data.get("files", [None])[0]
|
||||
|
||||
if link:
|
||||
copy(text=link)
|
||||
print(f"Link copied to clipboard: {link}")
|
||||
|
||||
if use_notify_send:
|
||||
notify(
|
||||
application_name=application_name,
|
||||
title="Upload Successful",
|
||||
message=f"Link copied to clipboard: {link}",
|
||||
urgency="low",
|
||||
category="transfer.complete",
|
||||
icon=file_path,
|
||||
)
|
||||
else:
|
||||
raise ValueError("Invalid response format.")
|
||||
else:
|
||||
error_message = response.text
|
||||
raise Exception(error_message)
|
||||
|
||||
except BaseException as e:
|
||||
if use_notify_send:
|
||||
notify(
|
||||
application_name=application_name,
|
||||
title="Upload Failed",
|
||||
message=f"An error occurred: {e}",
|
||||
urgency="normal",
|
||||
category="transfer.error",
|
||||
icon=file_path,
|
||||
)
|
||||
raise e
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="zipline.py",
|
||||
description="Upload a file to a Zipline instance.",
|
||||
epilog="Example usage: zipline.py /path/to/file.txt",
|
||||
)
|
||||
parser.add_argument("file", help="The file to upload.")
|
||||
parser.add_argument(
|
||||
"--url",
|
||||
help="The URL of the Zipline instance. Defaults to 'https://csw.im'.",
|
||||
default="https://csw.im",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--application-name",
|
||||
help="The name of the application that is uploading the file. Defaults to 'Zipline'.",
|
||||
default="Zipline",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--desktop-entry",
|
||||
help="The desktop entry file for the application that is uploading the file. If this is provided, notify-send will be invoked to display a notification if the upload succeeds or fails.",
|
||||
default=None,
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
zipline(
|
||||
file_path=args.file,
|
||||
instance_url=args.url,
|
||||
application_name=args.application_name,
|
||||
desktop_entry=args.desktop_entry,
|
||||
)
|
Loading…
Add table
Add a link
Reference in a new issue