diff --git a/flake.lock b/flake.lock index 531c637..f928494 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ ] }, "locked": { - "lastModified": 1750372185, - "narHash": "sha256-lVBKxd9dsZOH1fA6kSE5WNnt8e+09fN+NL/Q3BjTWHY=", + "lastModified": 1753216019, + "narHash": "sha256-zik7WISrR1ks2l6T1MZqZHb/OqroHdJnSnAehkE0kCk=", "owner": "hyprwm", "repo": "aquamarine", - "rev": "7cef49d261cbbe537e8cb662485e76d29ac4cbca", + "rev": "be166e11d86ba4186db93e10c54a141058bdce49", "type": "github" }, "original": { @@ -61,11 +61,11 @@ }, "locked": { "dir": "pkgs/firefox-addons", - "lastModified": 1750737804, - "narHash": "sha256-wClGd2PhxdjjphR6wIgoiDcR+Gfg4/+FyseSOjIIzVU=", + "lastModified": 1754512310, + "narHash": "sha256-gXE5lTYMOhpDJo+siLXW/3BzySPmLMD12GVB1QFVbyw=", "owner": "rycee", "repo": "nur-expressions", - "rev": "aaaf4fec792bad465ea4a35c0be5bc2a54f33095", + "rev": "2008f9aa7a5ccde48bfc1de5a919be5898da09c2", "type": "gitlab" }, "original": { @@ -78,11 +78,11 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "lastModified": 1747046372, + "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", "owner": "edolstra", "repo": "flake-compat", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", "type": "github" }, "original": { @@ -136,11 +136,11 @@ ] }, "locked": { - "lastModified": 1750783375, - "narHash": "sha256-oKccVOF1igIwTncVTHZ9RHgjOQEMbg8NK5am2IjOCCI=", + "lastModified": 1753592768, + "narHash": "sha256-oV695RvbAE4+R9pcsT9shmp6zE/+IZe6evHWX63f2Qg=", "owner": "nix-community", "repo": "home-manager", - "rev": "d457fa3c764e53e7bdd7354467c605766407620d", + "rev": "fc3add429f21450359369af74c2375cb34a2d204", "type": "github" }, "original": { @@ -166,11 +166,11 @@ ] }, "locked": { - "lastModified": 1749155331, - "narHash": "sha256-XR9fsI0zwLiFWfqi/pdS/VD+YNorKb3XIykgTg4l1nA=", + "lastModified": 1753964049, + "narHash": "sha256-lIqabfBY7z/OANxHoPeIrDJrFyYy9jAM4GQLzZ2feCM=", "owner": "hyprwm", "repo": "hyprcursor", - "rev": "45fcc10b4c282746d93ec406a740c43b48b4ef80", + "rev": "44e91d467bdad8dcf8bbd2ac7cf49972540980a5", "type": "github" }, "original": { @@ -195,11 +195,11 @@ ] }, "locked": { - "lastModified": 1750371717, - "narHash": "sha256-cNP+bVq8m5x2Rl6MTjwfQLCdwbVmKvTH7yqVc1SpiJM=", + "lastModified": 1754305013, + "narHash": "sha256-u+M2f0Xf1lVHzIPQ7DsNCDkM1NYxykOSsRr4t3TbSM4=", "owner": "hyprwm", "repo": "hyprgraphics", - "rev": "15c6f8f3a567fec9a0f732cd310a7ff456deef88", + "rev": "4c1d63a0f22135db123fc789f174b89544c6ec2d", "type": "github" }, "original": { @@ -226,11 +226,11 @@ "xdph": "xdph" }, "locked": { - "lastModified": 1750771433, - "narHash": "sha256-AG2TRRcc84066tAOdJ1hdy1ZbpR53UbqGxmaL3VecRc=", + "lastModified": 1754844749, + "narHash": "sha256-QNT0yXHyjvZ++vrJICAWFBMrcrTVbgRIZLplmOv1W7s=", "owner": "hyprwm", "repo": "Hyprland", - "rev": "aea81320015130bf850242d5a8695fcdcbf4f0c1", + "rev": "584b844aaf72cd7ea6851117f1bd598b7467ffc1", "type": "github" }, "original": { @@ -256,11 +256,11 @@ ] }, "locked": { - "lastModified": 1750778528, - "narHash": "sha256-X0QpVEhpkhf0RvU0n5+qsBH3JIXY2uZ8m56HhP7FzU8=", + "lastModified": 1754766309, + "narHash": "sha256-pANfQZ22RNF6sCFxrMahjE70v/HbGfA4lPZ7pTmfwUQ=", "owner": "hyprwm", "repo": "hyprland-plugins", - "rev": "aa23323de3325e3026fc26f9c23205954be4d337", + "rev": "833af8e8c6f035a53a167aff59e5e85bf0386d93", "type": "github" }, "original": { @@ -349,11 +349,11 @@ ] }, "locked": { - "lastModified": 1750371812, - "narHash": "sha256-D868K1dVEACw17elVxRgXC6hOxY+54wIEjURztDWLk8=", + "lastModified": 1753819801, + "narHash": "sha256-tHe6XeNeVeKapkNM3tcjW4RuD+tB2iwwoogWJOtsqTI=", "owner": "hyprwm", "repo": "hyprland-qtutils", - "rev": "b13c7481e37856f322177010bdf75fccacd1adc8", + "rev": "b308a818b9dcaa7ab8ccab891c1b84ebde2152bc", "type": "github" }, "original": { @@ -378,11 +378,11 @@ ] }, "locked": { - "lastModified": 1750371198, - "narHash": "sha256-/iuJ1paQOBoSLqHflRNNGyroqfF/yvPNurxzcCT0cAE=", + "lastModified": 1753622892, + "narHash": "sha256-0K+A+gmOI8IklSg5It1nyRNv0kCNL51duwnhUO/B8JA=", "owner": "hyprwm", "repo": "hyprlang", - "rev": "cee01452bca58d6cadb3224e21e370de8bc20f0b", + "rev": "23f0debd2003f17bd65f851cd3f930cff8a8c809", "type": "github" }, "original": { @@ -403,11 +403,11 @@ ] }, "locked": { - "lastModified": 1750371096, - "narHash": "sha256-JB1IeJ41y7kWc/dPGV6RMcCUM0Xj2NEK26A2Ap7EM9c=", + "lastModified": 1754481650, + "narHash": "sha256-6u6HdEFJh5gY6VfyMQbhP7zDdVcqOrCDTkbiHJmAtMI=", "owner": "hyprwm", "repo": "hyprutils", - "rev": "38f3a211657ce82a1123bf19402199b67a410f08", + "rev": "df6b8820c4a0835d83d0c7c7be86fbc555f1f7fd", "type": "github" }, "original": { @@ -428,11 +428,11 @@ ] }, "locked": { - "lastModified": 1750371869, - "narHash": "sha256-lGk4gLjgZQ/rndUkzmPYcgbHr8gKU5u71vyrjnwfpB4=", + "lastModified": 1751897909, + "narHash": "sha256-FnhBENxihITZldThvbO7883PdXC/2dzW4eiNvtoV5Ao=", "owner": "hyprwm", "repo": "hyprwayland-scanner", - "rev": "aa38edd6e3e277ae6a97ea83a69261a5c3aab9fd", + "rev": "fcca0c61f988a9d092cbb33e906775014c61579d", "type": "github" }, "original": { @@ -451,11 +451,11 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1750763263, - "narHash": "sha256-3JW7xEfobw0qXZsOZ0BwGV5+JMzOE+fZ3+v0ypOwKt0=", + "lastModified": 1751591814, + "narHash": "sha256-A4lgvuj4v+Pr8MniXz1FBG0DXOygi8tTECR+j53FMhM=", "owner": "lilyinstarlight", "repo": "nixos-cosmic", - "rev": "dabf86334f0eab8ced9a5e7219d34a28b055bb2a", + "rev": "fef2d0c78c4e4d6c600a88795af193131ff51bdc", "type": "github" }, "original": { @@ -466,11 +466,11 @@ }, "nixos-hardware": { "locked": { - "lastModified": 1750431636, - "narHash": "sha256-vnzzBDbCGvInmfn2ijC4HsIY/3W1CWbwS/YQoFgdgPg=", + "lastModified": 1754564048, + "narHash": "sha256-dz303vGuzWjzOPOaYkS9xSW+B93PSAJxvBd6CambXVA=", "owner": "NixOS", "repo": "nixos-hardware", - "rev": "1552a9f4513f3f0ceedcf90320e48d3d47165712", + "rev": "26ed7a0d4b8741fe1ef1ee6fa64453ca056ce113", "type": "github" }, "original": { @@ -482,11 +482,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1750622754, - "narHash": "sha256-kMhs+YzV4vPGfuTpD3mwzibWUE6jotw5Al2wczI0Pv8=", + "lastModified": 1754767907, + "narHash": "sha256-8OnUzRQZkqtUol9vuUuQC30hzpMreKptNyET2T9lB6g=", "owner": "nixos", "repo": "nixpkgs", - "rev": "c7ab75210cb8cb16ddd8f290755d9558edde7ee1", + "rev": "c5f08b62ed75415439d48152c2a784e36909b1bc", "type": "github" }, "original": { @@ -498,11 +498,11 @@ }, "nixpkgs-stable": { "locked": { - "lastModified": 1750646418, - "narHash": "sha256-4UAN+W0Lp4xnUiHYXUXAPX18t+bn6c4Btry2RqM9JHY=", + "lastModified": 1751048012, + "narHash": "sha256-MYbotu4UjWpTsq01wglhN5xDRfZYLFtNk7SBY0BcjkU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "1f426f65ac4e6bf808923eb6f8b8c2bfba3d18c5", + "rev": "a684c58d46ebbede49f280b653b9e56100aa3877", "type": "github" }, "original": { @@ -514,11 +514,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1750506804, - "narHash": "sha256-VLFNc4egNjovYVxDGyBYTrvVCgDYgENp5bVi9fPTDYc=", + "lastModified": 1754725699, + "narHash": "sha256-iAcj9T/Y+3DBy2J0N+yF9XQQQ8IEb5swLFzs23CdP88=", "owner": "nixos", "repo": "nixpkgs", - "rev": "4206c4cb56751df534751b058295ea61357bbbaa", + "rev": "85dbfc7aaf52ecb755f87e577ddbe6dbbdbc1054", "type": "github" }, "original": { @@ -538,11 +538,11 @@ ] }, "locked": { - "lastModified": 1748196248, - "narHash": "sha256-1iHjsH6/5UOerJEoZKE+Gx1BgAoge/YcnUsOA4wQ/BU=", + "lastModified": 1754501628, + "narHash": "sha256-FExJ54tVB5iu7Dh2tLcyCSWpaV+lmUzzWKZUkemwXvo=", "owner": "pjones", "repo": "plasma-manager", - "rev": "b7697abe89967839b273a863a3805345ea54ab56", + "rev": "cca090f8115c4172b9aef6c5299ae784bdd5e133", "type": "github" }, "original": { @@ -561,11 +561,11 @@ ] }, "locked": { - "lastModified": 1749636823, - "narHash": "sha256-WUaIlOlPLyPgz9be7fqWJA5iG6rHcGRtLERSCfUDne4=", + "lastModified": 1754416808, + "narHash": "sha256-c6yg0EQ9xVESx6HGDOCMcyRSjaTpNJP10ef+6fRcofA=", "owner": "cachix", "repo": "git-hooks.nix", - "rev": "623c56286de5a3193aa38891a6991b28f9bab056", + "rev": "9c52372878df6911f9afc1e2a1391f55e4dfc864", "type": "github" }, "original": { @@ -597,11 +597,11 @@ ] }, "locked": { - "lastModified": 1750732748, - "narHash": "sha256-HR2b3RHsPeJm+Fb+1ui8nXibgniVj7hBNvUbXEyz0DU=", + "lastModified": 1751251399, + "narHash": "sha256-y+viCuy/eKKpkX1K2gDvXIJI/yzvy6zA3HObapz9XZ0=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "4b4494b2ba7e8a8041b2e28320b2ee02c115c75f", + "rev": "b22d5ee8c60ed1291521f2dde48784edd6bf695b", "type": "github" }, "original": { @@ -617,11 +617,11 @@ ] }, "locked": { - "lastModified": 1750119275, - "narHash": "sha256-Rr7Pooz9zQbhdVxux16h7URa6mA80Pb/G07T4lHvh0M=", + "lastModified": 1754328224, + "narHash": "sha256-glPK8DF329/dXtosV7YSzRlF4n35WDjaVwdOMEoEXHA=", "owner": "Mic92", "repo": "sops-nix", - "rev": "77c423a03b9b2b79709ea2cb63336312e78b72e2", + "rev": "49021900e69812ba7ddb9e40f9170218a7eca9f4", "type": "github" }, "original": { @@ -673,11 +673,11 @@ ] }, "locked": { - "lastModified": 1750372504, - "narHash": "sha256-VBeZb1oqZM1cqCAZnFz/WyYhO8aF/ImagI7WWg/Z3Og=", + "lastModified": 1753633878, + "narHash": "sha256-js2sLRtsOUA/aT10OCDaTjO80yplqwOIaLUqEe0nMx0=", "owner": "hyprwm", "repo": "xdg-desktop-portal-hyprland", - "rev": "400308fc4f9d12e0a93e483c2e7a649e12af1a92", + "rev": "371b96bd11ad2006ed4f21229dbd1be69bed3e8a", "type": "github" }, "original": { 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/home/jroeger.nix b/home/jroeger.nix index 86cd977..c6d7085 100644 --- a/home/jroeger.nix +++ b/home/jroeger.nix @@ -16,10 +16,6 @@ hive.doom.withNixPkgs = true; hive.doom.withShellPkgs = true; hive.doom.withCXXPkgs = true; - hive.programs.creative = { - enable = true; - video-editing-light = true; - }; # This value determines the Home Manager release that your configuration is # compatible with. This helps avoid breakage when a new Home Manager release diff --git a/hosts/monolith/configuration.nix b/hosts/monolith/configuration.nix index a9897d9..adb144b 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.spotifyShortcutsClientId = { + sopsFile = ../../secrets/spotify-shortcuts.yaml; + key = "clientId"; + }; + sops.secrets.spotifyShortcutsClientSecret = { + sopsFile = ../../secrets/spotify-shortcuts.yaml; + key = "clientSecret"; + }; # Users users.users.jonas = { @@ -44,6 +51,7 @@ # hive modules hive.nix-scripts.enable = true; hive.displayManager.name = "sddm"; + hive.themes.layan.enable = true; hive.plasma.enable = true; hive.kwallet.enable = true; hive.kwallet.forUsers = ["jonas"]; @@ -70,6 +78,11 @@ video-editing-light = true; video-editing-heavy = true; }; + hive.programs.spotify-shortcuts = { + enable = true; + clientIdSopsKey = config.sops.secrets.spotifyShortcutsClientId.name; + clientSecretSopsKey = config.sops.secrets.spotifyShortcutsClientSecret.name; + }; # system packages environment.systemPackages = with pkgs; [ @@ -85,6 +98,7 @@ feh firefox git + gramps insomnia libreoffice mosquitto @@ -114,6 +128,9 @@ services.udev.packages = [pkgs.openhantek6022]; virtualisation.docker.enable = true; + # Corsair drivers + hardware.ckb-next.enable = true; + # dpi correction services.xserver.dpi = 91; environment.variables = { diff --git a/modules/default.nix b/modules/default.nix index 9ea1834..ccc3b8e 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -18,12 +18,14 @@ # pure system modules ./desktop/de ./desktop/dm + ./desktop/themes ./hardware/bluetooth.nix ./hardware/sound.nix ./hardware/yubikey.nix ./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/desktop/themes/default.nix b/modules/desktop/themes/default.nix new file mode 100644 index 0000000..65faac2 --- /dev/null +++ b/modules/desktop/themes/default.nix @@ -0,0 +1,5 @@ +{...}: { + imports = [ + ./layan.nix + ]; +} diff --git a/modules/desktop/themes/layan.nix b/modules/desktop/themes/layan.nix new file mode 100644 index 0000000..a89573c --- /dev/null +++ b/modules/desktop/themes/layan.nix @@ -0,0 +1,21 @@ +{ + lib, + config, + pkgs, + ... +}: let + cfg = config.hive.themes.layan; +in { + options.hive.themes.layan = { + enable = lib.mkEnableOption "Layan theme configuration"; + }; + config = lib.mkIf cfg.enable { + environment.systemPackages = [ + pkgs.hive.layan-qt6 + pkgs.kdePackages.qtstyleplugin-kvantum + pkgs.unstable.layan-cursors + pkgs.layan-gtk-theme + pkgs.tela-circle-icon-theme + ]; + }; +} diff --git a/modules/home/doom/static/config.el b/modules/home/doom/static/config.el index 9043ec9..fec267a 100644 --- a/modules/home/doom/static/config.el +++ b/modules/home/doom/static/config.el @@ -140,6 +140,9 @@ (setq ccls-initialization-options '(:index (:comments 2) :completion (:detailedLabel t) :compilationDatabaseDirectory "build" :cache (:directory ".cache/ccls" :format "binary"))) (set-lsp-priority! 'ccls 1)) + +(setq apheleia-formatters-respect-indent-level nil) + (defun my-ui-doc-glance-then-focus () "Glances UI-Doc if not present, otherwise focus" (interactive) @@ -244,6 +247,8 @@ (bash-ts-mode . lsp-deferred) (json-ts-mode . lsp-deferred))) +(setq auto-mode-alist (cons '("\\.smt$" . smtlib-mode) auto-mode-alist)) +(autoload 'smtlib-mode "smtlib" "Major mode for SMTLIB" t) (let ((config-dir (expand-file-name "config.d" doom-user-dir))) (when (file-directory-p config-dir) diff --git a/modules/home/doom/static/packages.el b/modules/home/doom/static/packages.el index e08b8f4..a2d21e1 100644 --- a/modules/home/doom/static/packages.el +++ b/modules/home/doom/static/packages.el @@ -62,6 +62,13 @@ :local-repo "ws-butler") :pin "9ee5a7657a22e836618813c2e2b64a548d27d2ff") +(package! smtlib-mode + :recipe (:host github + :repo "chsticksel/smtlib-mode" + :branch "master" + :local-repo "smtlib-mode") + :pin "ed387e63b64091228e6a8a429b02b8fba165f5b5") + (package! direnv) (package! pdf-tools) (package! eww) diff --git a/modules/home/themes/layan.nix b/modules/home/themes/layan.nix index aee12f9..b81470f 100644 --- a/modules/home/themes/layan.nix +++ b/modules/home/themes/layan.nix @@ -5,21 +5,12 @@ ... }: let cfg = config.hive.themes.layan; - layan-qt6 = pkgs.kdePackages.callPackage ./layan-qt6.nix {}; in { options.hive.themes.layan = { enable = lib.mkEnableOption "Layan theme configuration"; }; config = lib.mkIf cfg.enable { - home.packages = [ - layan-qt6 - pkgs.kdePackages.qtstyleplugin-kvantum - pkgs.unstable.layan-cursors - pkgs.layan-gtk-theme - pkgs.tela-circle-icon-theme - ]; - qt.enable = false; qt.style.name = "kvantum"; qt.style.package = pkgs.kdePackages.qtstyleplugin-kvantum; diff --git a/modules/programs/creative.nix b/modules/programs/creative.nix index 033f7ba..e31b8b0 100644 --- a/modules/programs/creative.nix +++ b/modules/programs/creative.nix @@ -49,7 +49,13 @@ in { environment.systemPackages = with pkgs; lib.optionals cfg.image-editing [gimp krita drawio] ++ lib.optional cfg.image-management digikam - ++ lib.optionals cfg.image-raw-processing [darktable hdrmerge] + ++ lib.optionals cfg.image-raw-processing [ + darktable + rawtherapee + enblend-enfuse + hdrmerge + hugin + ] ++ lib.optionals cfg.video-editing-light [ffmpeg losslesscut-bin] ++ lib.optionals cfg.video-editing-heavy [ davinci-resolve diff --git a/modules/programs/spotify-shortcuts.nix b/modules/programs/spotify-shortcuts.nix new file mode 100644 index 0000000..39214c3 --- /dev/null +++ b/modules/programs/spotify-shortcuts.nix @@ -0,0 +1,36 @@ +{ + config, + lib, + pkgs, + ... +}: let + cfg = config.hive.programs.spotify-shortcuts; +in { + options.hive.programs.spotify-shortcuts = { + enable = lib.mkEnableOption "Enable Spotify Shortcuts"; + clientIdSopsKey = lib.mkOption { + type = lib.types.singleLineStr; + description = "Spotify API Client ID sops secret name"; + }; + clientSecretSopsKey = lib.mkOption { + type = lib.types.singleLineStr; + description = "Spotify API Client Secret Path sops secret name"; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [pkgs.hive.spotify-shortcuts]; + environment.variables = { + SPOTIFY_SHORTCUTS_CONFIG = config.sops.templates."spotify-shortcuts-client.json".path; + }; + sops.templates."spotify-shortcuts-client.json" = { + mode = "444"; + content = '' + { + "clientId": "${config.sops.placeholder.${cfg.clientIdSopsKey}}", + "clientSecret": "${config.sops.placeholder.${cfg.clientSecretSopsKey}}" + } + ''; + }; + }; +} diff --git a/pkgs/default.nix b/pkgs/default.nix index 1b17d63..2ef740f 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -2,5 +2,7 @@ final: _: { hive = { crossover = final.callPackage ./crossover.nix {}; transcode-davinci-resolve = final.callPackage ./transcode-davinci-resolve {}; + spotify-shortcuts = final.callPackage ./spotify-shortcuts {}; + layan-qt6 = final.kdePackages.callPackage ./layan-qt6.nix {}; }; } diff --git a/modules/home/themes/layan-qt6.nix b/pkgs/layan-qt6.nix similarity index 97% rename from modules/home/themes/layan-qt6.nix rename to pkgs/layan-qt6.nix index 7090f11..0b52051 100644 --- a/modules/home/themes/layan-qt6.nix +++ b/pkgs/layan-qt6.nix @@ -8,7 +8,7 @@ plasma-workspace, }: stdenv.mkDerivation rec { - pname = "layan-kde"; + pname = "layan-kde-qt6"; version = "2025-02-13"; src = fetchFromGitHub { owner = "vinceliuice"; 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..8906869 --- /dev/null +++ b/pkgs/spotify-shortcuts/derivation.nix @@ -0,0 +1,7 @@ +{python3Packages}: +with python3Packages; + buildPythonApplication { + name = "spotify-shortcuts"; + propagatedBuildInputs = [spotipy pyxdg desktop-notifier]; + src = ./.; + } diff --git a/pkgs/spotify-shortcuts/setup.py b/pkgs/spotify-shortcuts/setup.py new file mode 100644 index 0000000..474c0fe --- /dev/null +++ b/pkgs/spotify-shortcuts/setup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +from setuptools import setup, find_packages + +setup( + name="spotify_shortcuts", + version="1.0", + packages=find_packages(), + entry_points={ + "console_scripts": [ + "spotisc=spotify_shortcuts.run:main", + "spotify-like=spotify_shortcuts.spotify_like:main", + "spotify-pl-add=spotify_shortcuts.spotify_pl_add:main", + ], + }, +) diff --git a/pkgs/spotify-shortcuts/shell.nix b/pkgs/spotify-shortcuts/shell.nix new file mode 100644 index 0000000..a863d83 --- /dev/null +++ b/pkgs/spotify-shortcuts/shell.nix @@ -0,0 +1,15 @@ +{pkgs ? import {}}: let + drv = pkgs.callPackage ./derivation.nix {}; +in + pkgs.mkShell { + packages = [ + pkgs.pyright + pkgs.black + ]; + + inputsFrom = [drv]; + + shellHook = '' + export PYTHONPATH="$PYTHONPATH:$(pwd)" + ''; + } 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/__main__.py b/pkgs/spotify-shortcuts/spotify_shortcuts/__main__.py new file mode 100644 index 0000000..8f3c09e --- /dev/null +++ b/pkgs/spotify-shortcuts/spotify_shortcuts/__main__.py @@ -0,0 +1,4 @@ +from spotify_shortcuts.run import main + +if __name__ == "__main__": + main() diff --git a/pkgs/spotify-shortcuts/spotify_shortcuts/config.py b/pkgs/spotify-shortcuts/spotify_shortcuts/config.py new file mode 100644 index 0000000..69901e1 --- /dev/null +++ b/pkgs/spotify-shortcuts/spotify_shortcuts/config.py @@ -0,0 +1,74 @@ +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 +from json import loads + + +@dataclass +class Config: + cache_file: Path = Path(xdg_cache_home) / Path("spotify-shortcuts.json") + client_id: str | None = None + client_secret: str | None = None + notifications: bool = True + + @staticmethod + def from_file(path: Path): + if not path.exists(): + raise FileNotFoundError(f"Configuration file {path} does not exist.") + with open(path, "r") as f: + data = loads(f.read()) + + return Config( + cache_file=Path(data.get("cacheFile", Config.cache_file)), + client_id=data.get("clientId", Config.client_id), + client_secret=data.get("clientSecret", Config.client_secret), + ) + + +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", + ) + parser.add_argument( + "--config-file", + type=str, + help="Path to a json configuration file with keys clientId and clientSecret", + ) + parser.add_argument( + "--no-notifications", + action="store_true", + help="Disable desktop notifications", + ) + + ns = parser.parse_args(args) + + cfg = Config() + if (cfg_file := ns.config_file or getenv("SPOTIFY_SHORTCUTS_CONFIG", None)) != None: + cfg = Config.from_file(Path(cfg_file)) + + return Config( + cache_file=ns.cache_file or cfg.cache_file, + client_id=ns.client_id or cfg.client_id, + client_secret=ns.client_secret or cfg.client_secret, + notifications=not ns.no_notifications or cfg.notifications, + ) diff --git a/pkgs/spotify-shortcuts/spotify_shortcuts/registry.py b/pkgs/spotify-shortcuts/spotify_shortcuts/registry.py new file mode 100644 index 0000000..7e6f694 --- /dev/null +++ b/pkgs/spotify-shortcuts/spotify_shortcuts/registry.py @@ -0,0 +1,7 @@ +from spotify_shortcuts.spotify_like import SpotifyLike +from spotify_shortcuts.spotify_pl_add import SpotifyPlAdd + +SHORTCUT_REGISTRY = { + "like": SpotifyLike(), + "pl_add": SpotifyPlAdd(), +} diff --git a/pkgs/spotify-shortcuts/spotify_shortcuts/run.py b/pkgs/spotify-shortcuts/spotify_shortcuts/run.py new file mode 100644 index 0000000..423ff6a --- /dev/null +++ b/pkgs/spotify-shortcuts/spotify_shortcuts/run.py @@ -0,0 +1,47 @@ +from spotify_shortcuts.config import Config, load_config +from spotify_shortcuts.shortcut import Shortcut +from spotify_shortcuts.spotify_auth import authenticated_session +from itertools import chain + + +def all_scopes() -> list[str]: + from spotify_shortcuts.registry import SHORTCUT_REGISTRY + + return list( + set( + chain.from_iterable( + shortcut.get_scopes() for shortcut in SHORTCUT_REGISTRY.values() + ) + ) + ) + + +def run_shortcut(shortcut: Shortcut, config: Config): + client = authenticated_session( + config, scopes=all_scopes() # use all scopes to avoid re-authentication + ) + + shortcut.execute(client, config) + + +def main(): + from spotify_shortcuts.registry import SHORTCUT_REGISTRY + import sys + + if len(sys.argv) < 2: + print(f"Usage: {sys.argv[0]} ") + sys.exit(1) + + shortcut_name = sys.argv[1] + if shortcut_name not in SHORTCUT_REGISTRY: + print(f"Shortcut '{shortcut_name}' not found.") + sys.exit(1) + + shortcut = SHORTCUT_REGISTRY[shortcut_name] + config = load_config(args=sys.argv[2:]) + + run_shortcut(shortcut, config) + + +if __name__ == "__main__": + main() diff --git a/pkgs/spotify-shortcuts/spotify_shortcuts/shortcut.py b/pkgs/spotify-shortcuts/spotify_shortcuts/shortcut.py new file mode 100644 index 0000000..e8d871b --- /dev/null +++ b/pkgs/spotify-shortcuts/spotify_shortcuts/shortcut.py @@ -0,0 +1,21 @@ +from abc import ABC, abstractmethod + +from spotify_shortcuts.config import Config +from spotipy import Spotify + + +class Shortcut(ABC): + @abstractmethod + def execute(self, client: Spotify, config: Config): + """Execute the shortcut action.""" + pass + + @abstractmethod + def get_help(self) -> str: + """Return a description of the shortcut.""" + pass + + @abstractmethod + def get_scopes(self) -> list[str]: + """Return the spotify API scopes required for the shortcut.""" + pass 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..f4f6574 --- /dev/null +++ b/pkgs/spotify-shortcuts/spotify_shortcuts/spotify_auth.py @@ -0,0 +1,20 @@ +from spotipy.oauth2 import SpotifyOAuth +from spotipy import Spotify +from spotify_shortcuts.config import Config + +CALLBACK_URI = "http://127.0.0.1:45632/callback" + + +def authenticated_session(cfg: Config, scopes: list[str]) -> 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=CALLBACK_URI, + scope=" ".join(scopes), + 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..32787ce --- /dev/null +++ b/pkgs/spotify-shortcuts/spotify_shortcuts/spotify_like.py @@ -0,0 +1,46 @@ +from spotify_shortcuts.run import run_shortcut +from spotify_shortcuts.shortcut import Shortcut +from spotify_shortcuts.config import load_config +from desktop_notifier import DesktopNotifierSync + +SCOPES = [ + "user-library-read", + "user-library-modify", + "user-read-playback-state", +] + + +class SpotifyLike(Shortcut): + def execute(self, client, config): + if (playback := client.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 + + client.current_user_saved_tracks_add(tracks=[uri]) + + if config.notifications: + dn = DesktopNotifierSync() + dn.send( + title="Track Liked", + message=f"Track \"{playback.get('item', {}).get('name', '')}\" by \"{ + ", ".join(a.get('name', '') for a in playback.get('item', {}).get('artists', [])) + }\" has been liked.", + ) + + def get_help(self) -> str: + return "Like the currently playing track." + + def get_scopes(self) -> list[str]: + return SCOPES + + +def main(): + run_shortcut(SpotifyLike(), load_config()) + + +if __name__ == "__main__": + main() diff --git a/pkgs/spotify-shortcuts/spotify_shortcuts/spotify_pl_add.py b/pkgs/spotify-shortcuts/spotify_shortcuts/spotify_pl_add.py new file mode 100644 index 0000000..63d5426 --- /dev/null +++ b/pkgs/spotify-shortcuts/spotify_shortcuts/spotify_pl_add.py @@ -0,0 +1,57 @@ +from spotify_shortcuts.shortcut import Shortcut +from spotify_shortcuts.config import load_config +from desktop_notifier import DesktopNotifierSync +from spotify_shortcuts.run import run_shortcut + +SCOPES = [ + "playlist-modify-public", + "playlist-modify-private", + "user-read-playback-state", +] + + +class SpotifyPlAdd(Shortcut): + def execute(self, client, config): + if (playback := client.current_playback()) is None: + print("No current playback found.") + return + + if (track_uri := playback.get("item", {}).get("uri", None)) is None: + print("No track URI found in current playback.") + return + + if (context_uri := playback.get("context", {}).get("uri", None)) is None: + print("No context URI found in current playback.") + return + + client.playlist_add_items(context_uri, items=[track_uri]) + + if config.notifications: + track_name = playback.get("item", {}).get("name", "") + artists = ", ".join( + a.get("name", "") + for a in playback.get("item", {}).get("artists", []) + ) + playlist_name = (client.playlist(context_uri) or {}).get( + "name", "" + ) + + dn = DesktopNotifierSync() + dn.send( + title="Track Added to Playlist", + message=f'Track "{track_name}" by "{artists}" has been added to {playlist_name}.', + ) + + def get_help(self) -> str: + return "Add the currently playing track to the current playlist." + + def get_scopes(self) -> list[str]: + return SCOPES + + +def main(): + run_shortcut(SpotifyPlAdd(), load_config()) + + +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