From b31e767134bb9039404e2b68e5d090bee5a1efa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20R=C3=B6ger?= Date: Thu, 24 Jul 2025 02:00:40 +0200 Subject: [PATCH] System Gen84 @ 2025-07-24-02:00:39 by jonas@monolith --- flake.nix | 1 + hosts/monolith/configuration.nix | 14 +++++- modules/default.nix | 1 + modules/programs/spotify-shortcuts.nix | 28 +++++++++++ pkgs/default.nix | 1 + pkgs/spotify-shortcuts/.envrc | 1 + pkgs/spotify-shortcuts/default.nix | 2 + pkgs/spotify-shortcuts/derivation.nix | 7 +++ pkgs/spotify-shortcuts/setup.py | 14 ++++++ pkgs/spotify-shortcuts/shell.nix | 8 +++ .../spotify_shortcuts/__init__.py | 0 .../spotify_shortcuts/config.py | 50 +++++++++++++++++++ .../spotify_shortcuts/spotify_auth.py | 18 +++++++ .../spotify_shortcuts/spotify_like.py | 21 ++++++++ secrets/spotify-shortcuts.yaml | 44 ++++++++++++++++ 15 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 modules/programs/spotify-shortcuts.nix create mode 100644 pkgs/spotify-shortcuts/.envrc create mode 100644 pkgs/spotify-shortcuts/default.nix create mode 100644 pkgs/spotify-shortcuts/derivation.nix create mode 100644 pkgs/spotify-shortcuts/setup.py create mode 100644 pkgs/spotify-shortcuts/shell.nix create mode 100644 pkgs/spotify-shortcuts/spotify_shortcuts/__init__.py create mode 100644 pkgs/spotify-shortcuts/spotify_shortcuts/config.py create mode 100644 pkgs/spotify-shortcuts/spotify_shortcuts/spotify_auth.py create mode 100644 pkgs/spotify-shortcuts/spotify_shortcuts/spotify_like.py create mode 100644 secrets/spotify-shortcuts.yaml diff --git a/flake.nix b/flake.nix index 064d4b0..9580c2d 100644 --- a/flake.nix +++ b/flake.nix @@ -209,6 +209,7 @@ devShells.${system} = { transcode-davinci-resolve = (import ./pkgs/transcode-davinci-resolve/shell.nix) {pkgs = nixpkgs.legacyPackages.${system};}; + spotify-shortcuts = (import ./pkgs/spotify-shortcuts/shell.nix) {pkgs = nixpkgs.legacyPackages.${system};}; }; overlays.default = import ./pkgs; diff --git a/hosts/monolith/configuration.nix b/hosts/monolith/configuration.nix index 546c3ba..e973118 100644 --- a/hosts/monolith/configuration.nix +++ b/hosts/monolith/configuration.nix @@ -3,7 +3,6 @@ # and in the NixOS manual (accessible by running ‘nixos-help’). { config, - inputs, pkgs, ... }: { @@ -18,6 +17,14 @@ sopsFile = ../../secrets/monolith/wg.yaml; key = "privateKey"; }; + sops.secrets.spotify-shortcuts-clientId = { + sopsFile = ../../secrets/spotify-shortcuts.yaml; + key = "clientId"; + }; + sops.secrets.spotify-shortcuts-clientSecret = { + sopsFile = ../../secrets/spotify-shortcuts.yaml; + key = "clientSecret"; + }; # Users users.users.jonas = { @@ -70,6 +77,11 @@ video-editing-light = true; video-editing-heavy = true; }; + hive.programs.spotify-shortcuts = { + enable = true; + clientIdFile = config.sops.secrets.spotify-shortcuts-clientId.path; + clientSecretFile = config.sops.secrets.spotify-shortcuts-clientSecret.path; + }; # system packages environment.systemPackages = with pkgs; [ diff --git a/modules/default.nix b/modules/default.nix index 9ea1834..6604626 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -24,6 +24,7 @@ ./networking/wireguard ./programs/creative.nix ./programs/games.nix + ./programs/spotify-shortcuts.nix ./services/borg-server.nix ./services/kdeconnect.nix ./services/nextcloud-instance.nix diff --git a/modules/programs/spotify-shortcuts.nix b/modules/programs/spotify-shortcuts.nix new file mode 100644 index 0000000..30fe4ef --- /dev/null +++ b/modules/programs/spotify-shortcuts.nix @@ -0,0 +1,28 @@ +{ + config, + lib, + pkgs, + ... +}: let + cfg = config.hive.programs.spotify-shortcuts; +in { + options.hive.programs.spotify-shortcuts = { + enable = lib.mkEnableOption "Enable Spotify Shortcuts"; + clientIdFile = lib.mkOption { + type = lib.types.path; + description = "Spotify API Client ID Path"; + }; + clientSecretFile = lib.mkOption { + type = lib.types.path; + description = "Spotify API Client Secret Path"; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [pkgs.hive.spotify-shortcuts]; + environment.etc."profile.d/spotify-shortcuts.sh".text = '' + export SPOTIFY_SHORTCUTS_CLIENT_ID=$(cat ${cfg.clientIdFile}) + export SPOTIFY_SHORTCUTS_CLIENT_SECRET=$(cat ${cfg.clientSecretFile}) + ''; + }; +} diff --git a/pkgs/default.nix b/pkgs/default.nix index 1b17d63..3c24567 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -2,5 +2,6 @@ final: _: { hive = { crossover = final.callPackage ./crossover.nix {}; transcode-davinci-resolve = final.callPackage ./transcode-davinci-resolve {}; + spotify-shortcuts = final.callPackage ./spotify-shortcuts {}; }; } diff --git a/pkgs/spotify-shortcuts/.envrc b/pkgs/spotify-shortcuts/.envrc new file mode 100644 index 0000000..110dc74 --- /dev/null +++ b/pkgs/spotify-shortcuts/.envrc @@ -0,0 +1 @@ +use flake ../../#spotify-shortcuts --show-trace diff --git a/pkgs/spotify-shortcuts/default.nix b/pkgs/spotify-shortcuts/default.nix new file mode 100644 index 0000000..dfb7691 --- /dev/null +++ b/pkgs/spotify-shortcuts/default.nix @@ -0,0 +1,2 @@ +{pkgs ? import {}}: +pkgs.callPackage ./derivation.nix {} diff --git a/pkgs/spotify-shortcuts/derivation.nix b/pkgs/spotify-shortcuts/derivation.nix new file mode 100644 index 0000000..f4cb8a5 --- /dev/null +++ b/pkgs/spotify-shortcuts/derivation.nix @@ -0,0 +1,7 @@ +{python3Packages}: +with python3Packages; + buildPythonApplication { + name = "spotify-shortcuts"; + propagatedBuildInputs = [spotipy pyxdg]; + src = ./.; + } diff --git a/pkgs/spotify-shortcuts/setup.py b/pkgs/spotify-shortcuts/setup.py new file mode 100644 index 0000000..9a280e0 --- /dev/null +++ b/pkgs/spotify-shortcuts/setup.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python + +from setuptools import setup, find_packages + +setup( + name="spotify_shortcuts", + version="1.0", + packages=find_packages(), + entry_points={ + "console_scripts": [ + "spotify-like=spotify_shortcuts.spotify_like:main", + ], + }, +) diff --git a/pkgs/spotify-shortcuts/shell.nix b/pkgs/spotify-shortcuts/shell.nix new file mode 100644 index 0000000..c41f7d0 --- /dev/null +++ b/pkgs/spotify-shortcuts/shell.nix @@ -0,0 +1,8 @@ +{pkgs ? import {}}: +pkgs.mkShell { + packages = [ + pkgs.pyright + pkgs.black + ]; + inputsFrom = [(pkgs.callPackage ./derivation.nix {})]; +} diff --git a/pkgs/spotify-shortcuts/spotify_shortcuts/__init__.py b/pkgs/spotify-shortcuts/spotify_shortcuts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pkgs/spotify-shortcuts/spotify_shortcuts/config.py b/pkgs/spotify-shortcuts/spotify_shortcuts/config.py new file mode 100644 index 0000000..dbaeb31 --- /dev/null +++ b/pkgs/spotify-shortcuts/spotify_shortcuts/config.py @@ -0,0 +1,50 @@ +from pathlib import Path +from dataclasses import dataclass +from argparse import ArgumentParser +from typing import List +from os import getenv +from sys import argv +from xdg.BaseDirectory import xdg_cache_home + + +@dataclass +class Config: + cache_file: Path = Path(xdg_cache_home) / Path("spotify-shortcuts.json") + client_id: str | None = None + client_secret: str | None = None + + +def load_config(args: List[str] = argv[1:]) -> Config: + parser = ArgumentParser(description="Spotify CLI Tool") + parser.add_argument( + "--cache-file", + type=Path, + default=Config.cache_file, + help="Path to the cache file for Spotify authentication", + ) + parser.add_argument( + "--client-id", + type=str, + default=Config.client_id, + help="Spotify API Client ID", + ) + parser.add_argument( + "--client-secret", + type=str, + default=Config.client_secret, + help="Spotify API Client Secret", + ) + + ns = parser.parse_args(args) + + cfg = Config( + cache_file=ns.cache_file, + client_id=ns.client_id, + client_secret=ns.client_secret, + ) + + return Config( + cache_file=Path(getenv("SPOTIFY_SHORTCUTS_CACHE_FILE", cfg.cache_file)), + client_id=getenv("SPOTIFY_SHORTCUTS_CLIENT_ID", cfg.client_id), + client_secret=getenv("SPOTIFY_SHORTCUTS_CLIENT_SECRET", cfg.client_secret), + ) diff --git a/pkgs/spotify-shortcuts/spotify_shortcuts/spotify_auth.py b/pkgs/spotify-shortcuts/spotify_shortcuts/spotify_auth.py new file mode 100644 index 0000000..810bfc6 --- /dev/null +++ b/pkgs/spotify-shortcuts/spotify_shortcuts/spotify_auth.py @@ -0,0 +1,18 @@ +from spotipy.oauth2 import SpotifyOAuth +from spotipy import Spotify +from spotify_shortcuts.config import Config + + +def authenticated_session(cfg: Config) -> Spotify: + assert cfg.client_id, "Spotify client ID is required" + assert cfg.client_secret, "Spotify client secret is required" + + return Spotify( + auth_manager=SpotifyOAuth( + client_id=cfg.client_id, + client_secret=cfg.client_secret, + redirect_uri="http://127.0.0.1:45632/callback", + scope="user-library-read user-library-modify user-read-playback-state", + cache_path=cfg.cache_file, + ) + ) diff --git a/pkgs/spotify-shortcuts/spotify_shortcuts/spotify_like.py b/pkgs/spotify-shortcuts/spotify_shortcuts/spotify_like.py new file mode 100644 index 0000000..b0ebd78 --- /dev/null +++ b/pkgs/spotify-shortcuts/spotify_shortcuts/spotify_like.py @@ -0,0 +1,21 @@ +from spotify_shortcuts.spotify_auth import authenticated_session +from spotify_shortcuts.config import load_config + + +def main(): + cfg = load_config() + sp = authenticated_session(cfg) + + if (playback := sp.current_playback()) is None: + print("No current playback found.") + return + + if (uri := playback.get("item", {}).get("uri", None)) is None: + print("No track URI found in current playback.") + return + + sp.current_user_saved_tracks_add(tracks=[uri]) + + +if __name__ == "__main__": + main() diff --git a/secrets/spotify-shortcuts.yaml b/secrets/spotify-shortcuts.yaml new file mode 100644 index 0000000..a6570d4 --- /dev/null +++ b/secrets/spotify-shortcuts.yaml @@ -0,0 +1,44 @@ +clientId: ENC[AES256_GCM,data:YJYM62aqGafgqsS2rFQZlS8REuR21biiGtQUaUG18Qg=,iv:TIL+rlKbBVo2/JlaGRDqLFwQ+ETx3jkqSqmGu6kyvJc=,tag:0J29BXv0kppcmqm5xbAvew==,type:str] +clientSecret: ENC[AES256_GCM,data:3pw6QRC+cjZlQ3iGnzLVjQh3sHPld6UC2jC8yq+J+34=,iv:9xctJRA4RFGy7x18Y2aPVuSC1lZ0Rko0R8Hr/gE2YIw=,tag:nB4VENRf0YeZCpMM5QFA9Q==,type:str] +sops: + age: + - recipient: age1expg8vyduf290pz7l4f3mjzvk9f0azfdn48pyjzs3m6p7v4qjq0qwtn36z + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBodVlLVW5PODFLcUh2anlU + Yys2czJSVzc3cHdpL3RZU01HcXhEUkNRcVV3CjcvNTROVStid2x0TUt6eEpQanJD + NkNSTlVkLzBqMUZQSUdHQyt4VUZZQW8KLS0tIEZiTm9HNUdtelNrVTUwSmFnb0p5 + K2NsMnZ3WDJIWWpmYml5cjNWZWFPWEUKfP9HxpJ35R/SiI5lzdRiVPIJUnhdbPJl + OtEUHJOrAYnKgX0uOo1argaQxN/8V0Py5njSdiiLd+mw1OCY4xef8w== + -----END AGE ENCRYPTED FILE----- + - recipient: age1wf0rq27v0n27zfy0es8ns3n25e2fdt063dgn68tt3f89rgrtu9csq4yhsp + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLY1N5aXZzRzVaU2QzcmxS + cjBTSlA1dlNMRGhhRDUrQWphR2ZJcHhTZjBBCmxjR3g0NVc4UUxBV25rNFVCSzhM + Tk5xeHZtZGkxRlpwRElZa0c4eUFKcXcKLS0tIHlCSXBKQ3BSSWNEeklBdmw1VHBI + RHVwTmd2dDlWSkZia2t6cE01bGxKUUkKQ9QuzfhuoGd7x42p5bP2E8Tatw1Ihqu2 + XFem9XLMIX1gNqRJkL7DmVybI/hE34pcAhALUKEG/K1vr51I/nKdGA== + -----END AGE ENCRYPTED FILE----- + - recipient: age1xkmnvzus6fhundn4c0f6hyuwrj0f0m7x3hxtuhnez6cecr6m032qalw308 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMVWFwcE5GcHVnQzlCa2hz + UXlsMEFBRURlUWdRTXJUcktFRytzV0VWK3kwCnFDc0U3MzV1Mnhvek1DWmtvVXdW + bEJVM3ZpSm9IR2F2d0FtZHptOHlwWVEKLS0tIEJrL2YycVFGbGNuQ0JQZy80aFJa + YnNjSi9lSm1VWDlRRnJwb2U4cUhPWHcKXUB9ExCEgw29CvbT/OHZJa9F+DChep3r + twET8VbrV19O5zscu0OJ5s7hrobm+eWzPkoYlBXPC3bfwgeRRbsIvA== + -----END AGE ENCRYPTED FILE----- + - recipient: age1clh2c489j7mx94aqr44u6k2cx5axqme9rlshqu9l2mcynluwhq6qwn0sv0 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqWENabXAwRlF0QmlRMGlS + SGx2UEFYcXJ6aWE0TDlrQ3FQNXFucTlVQXlBCmZSRERNMlFacUNDcHRtdVd1aFlh + WElZenhMT3JBMUhFK21MOEJhT083TVEKLS0tIEFHNzZZUzJtTkE3TEZNMlJreG1l + cHQxWXFCWlkvUFRvWUZycTdWMWRVMUEKN0jKYMQ9ARfnUUXprSYWdAAbVdOng8Yn + nV9sKYlTQ4fFBZ7w8hIBoJP3pWdIuZfEwKgA/7OyE02dts5wxIAuQw== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2025-07-23T23:55:41Z" + mac: ENC[AES256_GCM,data:dS+rBxe/+Qce4rqQB8dhtuMmAnmUzvXOXPMTWH2tglxxdYcJrQgatGC+0qNdJIa4vEi1cb9IcHiQE4fv615OSQuc4uDIPzLyLK7eDFz9DkK/eXehK6V7t1ZUPYKiSswZYLBcApJTQFYzaG5OqcFm7rcFIz4toXo4TNM94s0dRH8=,iv:CWv9zYvMOn3qvkHnpT+Bk67VJbQs5daMxjpjYm6dr8I=,tag:+0ENRlYqliohdf7ytH7IcA==,type:str] + unencrypted_suffix: _unencrypted + version: 3.10.2