From d79fae86d0199d3cd0089281cf0da4f3382b1d7b Mon Sep 17 00:00:00 2001 From: Thierry Pouplier Date: Tue, 11 Aug 2026 11:08:46 -0400 Subject: [PATCH] feat: torrent to mpv - nyaa search, transmission sequential, mpv overlay --- doom/.config/doom/README.org | 303 ++++++++++++++++++++++++++++++++++ doom/.config/doom/config.el | 296 +++++++++++++++++++++++++++++++++ doom/.config/doom/packages.el | 3 + 3 files changed, 602 insertions(+) diff --git a/doom/.config/doom/README.org b/doom/.config/doom/README.org index 0da4009..eb2e12b 100644 --- a/doom/.config/doom/README.org +++ b/doom/.config/doom/README.org @@ -4284,3 +4284,306 @@ Need chrome... :( :localleader :desc "Edit Edna rules" "E" #'org-edna-edit)) #+end_src + +* +Torrent to MPV+ + +Search Nyaa.si from Emacs → add to Transmission with sequential download +→ watch with mpv before the download finishes. + +#+begin_src emacs-lisp +;;; Torrent to MPV — Nyaa search + Transmission + mpv playback + +;; dom.el provides dom-by-tag, dom-text for RSS XML parsing +(require 'dom) + +;; ── Transmission daemon config ── +;; NB: no (require 'transmission) here — Doom autoloads it on first use. + +(after! transmission + (setq transmission-host "localhost" + transmission-service 9091 + transmission-refresh-interval 3 + transmission-refresh-modes '(transmission-mode transmission-files-mode))) + +;; ── Sequential download (add torrent + play) ── + +(defun gortium/transmission-add-sequential (magnet &optional directory callback) + "Add MAGNET to Transmission with sequential download enabled. +If CALLBACK is provided, call it with the torrent name on success." + (interactive "sMagnet: ") + (transmission-request-async + (lambda (response) + (let-alist response + (let ((name (or .torrent-added.name .torrent-duplicate.name))) + (if name + (progn + (message "Added: %s (sequential)" name) + (when callback (funcall callback name))) + (message "Added torrent (sequential)"))))) + "torrent-add" + (append `(:filename ,(if (transmission-btih-p magnet) + (concat "magnet:?xt=urn:btih:" magnet) + magnet) + :sequential_download t + :paused :json-false) + (when directory (list :download-dir (expand-file-name directory)))))) + +;; ── Play file from Transmission file list ── + +(defun gortium/transmission-play-file (&optional file) + "Play FILE (or file at point) with mpv overlay. +Works on partial downloads (.part files) too." + (interactive) + (let ((path (or file + (condition-case nil + (transmission-files-file-at-point) + (error nil))))) + (if (null path) + (user-error "No file at point or file not found") + (let ((real-path + (or (and (file-exists-p path) path) + (and (file-exists-p (concat path ".part")) + (concat path ".part")) + ;; Check incomplete dir too + (let ((inc (replace-regexp-in-string + "/complete/" "/incomplete/" path))) + (or (and (file-exists-p inc) inc) + (and (file-exists-p (concat inc ".part")) + (concat inc ".part"))))))) + (unless real-path + (user-error "File not found: %s" path)) + (start-process "transmission-mpv" nil "mpv" + "--quiet" "--really-quiet" + "--geometry=30%x30%-30-50" "--ontop" "--no-border" + "--title=Transmission MPV" + real-path) + (message "Playing: %s" (file-name-nondirectory real-path)))))) +;; Override transmission-find-file to always play via mpv instead +;; (advice catches all callers: major mode, Evil, menu, M-x) +(defun gortium/--play-advice (&rest _) + "Redirect find-file to mpv play for partial downloads." + (call-interactively #'gortium/transmission-play-file)) +(advice-add 'transmission-find-file :around #'gortium/--play-advice) + +;; ══════════════════════════════════════════════════ +;; Nyaa.si Search +;; ══════════════════════════════════════════════════ + +(defvar gortium/nyaa-base-url "https://nyaa.si") +(defvar gortium/nyaa-search-history '()) +(defvar-local gortium/nyaa-results nil + "Alist of (ID . plist) for current Nyaa search buffer.") + +(defvar gortium/nyaa-category-short + '(("1_1" . "AMV") ("1_2" . "Eng") ("1_3" . "NonEng") + ("1_4" . "Raw") ("2_1" . "Flac") ("2_2" . "Lossy") + ("3_1" . "L-Eng") ("3_2" . "L-NonE") ("3_3" . "L-Raw") + ("4_1" . "LiveEng") ("4_2" . "LiveNEn")("4_3" . "LiveRaw") + ("5_1" . "SW-App") ("5_2" . "SW-Game")("6_1" . "XXX")) + "Short category labels for column display.") + +(defun gortium/nyaa-cat-label (id) + (or (cdr (assoc id gortium/nyaa-category-short)) id)) + +(defvar gortium/nyaa-search-query nil + "Last query string, for reverting.") + +;; ── Interactive search ── + +(defun gortium/nyaa-search (query &optional trusted-only) + "Search Nyaa.si RSS for QUERY, display results in tabulated-list. +With \\[universal-argument], show trusted uploads only." + (interactive + (list (read-string "Nyaa search: " nil 'gortium/nyaa-search-history) + current-prefix-arg)) + (setq gortium/nyaa-search-query query) + (let ((url (format "%s/?page=rss&q=%s&s=seeders&o=desc%s" + gortium/nyaa-base-url + (url-encode-url query) + (if trusted-only "&f=1" "")))) + (message "Searching Nyaa for \"%s\"..." query) + (let ((buf (get-buffer-create "*Nyaa Search*"))) + (with-current-buffer buf (setq gortium/nyaa-results nil))) + (url-retrieve url #'gortium/nyaa--handle-response (list query)))) + +;; ── RSS response handler ── + +(defun gortium/nyaa--handle-response (status query) + "Handle Nyaa RSS response." + (condition-case err + (let ((results (gortium/nyaa--parse-rss))) + (if (null results) + (progn + (message "Nyaa: no results for \"%s\"" query) + (when (buffer-local-value 'gortium/nyaa-search-query + (get-buffer "*Nyaa Search*")) + (with-current-buffer (get-buffer-create "*Nyaa Search*") + (setq mode-name "Nyaa-Search (no results)")))) + (gortium/nyaa--display-results results query))) + (error (message "Nyaa search error: %S" err)))) + +(defun gortium/nyaa--parse-rss () + "Parse Nyaa RSS XML from current buffer into plist list. +Returns ((:title :link :guid :pubdate :info-hash :category-id + :size :seeders :leechers :trusted :magnet) ...)" + (require 'dom) ; ensure dom-by-tag et al. are loaded + (goto-char (point-min)) + (when (re-search-forward "^\r?$" nil t) ; skip HTTP headers + (condition-case err + (let* ((xml (libxml-parse-xml-region (point) (point-max))) + (items (dom-by-tag xml 'item)) + results) + (dolist (item items) + (let* ((title (dom-text (car (dom-by-tag item 'title)))) + (link (dom-text (car (dom-by-tag item 'link)))) + (guid (dom-text (car (dom-by-tag item 'guid)))) + (ihash (dom-text (car (dom-by-tag item 'infoHash)))) + (title-enc (url-encode-url title)) + (magnet (format "magnet:?xt=urn:btih:%s&dn=%s" ihash title-enc))) + (push (list :title title :link link :guid guid + :pubdate (dom-text (car (dom-by-tag item 'pubDate))) + :info-hash ihash + :category-id (dom-text (car (dom-by-tag item 'categoryId))) + :size (dom-text (car (dom-by-tag item 'size))) + :seeders (string-to-number (dom-text (car (dom-by-tag item 'seeders)))) + :leechers (string-to-number (dom-text (car (dom-by-tag item 'leechers)))) + :trusted (dom-text (car (dom-by-tag item 'trusted))) + :magnet magnet) + results))) + (nreverse results)) + (error (message "Nyaa RSS parse error: %s" (error-message-string err)) + nil)))) + +;; ── Results display (tabulated-list) ── + +(defvar gortium/nyaa-search-mode-map + (let ((map (make-sparse-keymap))) + (set-keymap-parent map tabulated-list-mode-map) + (define-key map (kbd "RET") #'gortium/nyaa-download-only) + (define-key map "d" #'gortium/nyaa-download-only) + (define-key map "g" #'gortium/nyaa-search) + (define-key map "c" #'gortium/nyaa-copy-magnet) + map) + "Keymap for `gortium/nyaa-search-mode'.") + +(define-derived-mode gortium/nyaa-search-mode tabulated-list-mode + "Nyaa-Search" + "Browse Nyaa.si search results. +\\{gortium/nyaa-search-mode-map}" + (setq tabulated-list-format + [("Title" 58 t) + ("Size" 10 t :right-align t) + ("S" 5 t :right-align t) + ("L" 5 t :right-align t) + ("Category" 9 t)]) + (setq tabulated-list-padding 2) + (setq tabulated-list-sort-key (cons "S" t)) + (tabulated-list-init-header) + (setq-local revert-buffer-function #'gortium/nyaa--revert)) + +(defun gortium/nyaa--revert (&optional _auto _noconfirm) + (when (and (boundp 'gortium/nyaa-search-query) gortium/nyaa-search-query) + (gortium/nyaa-search gortium/nyaa-search-query))) + +(defun gortium/nyaa--display-results (results query) + "Fill tabulated-list buffer with RESULTS." + (let* ((buf (get-buffer-create "*Nyaa Search*")) + (entries + (let ((idx 0)) + (mapcar + (lambda (r) + (cl-incf idx) + (let* ((title (plist-get r :title)) + (disp (truncate-string-to-width title 56 nil nil t))) + (list idx + (vector disp + (plist-get r :size) + (number-to-string (plist-get r :seeders)) + (number-to-string (plist-get r :leechers)) + (gortium/nyaa-cat-label + (plist-get r :category-id)))))) + results)))) + ;; Switch to current window instead of popping a split + (with-current-buffer buf + (gortium/nyaa-search-mode) + (setq gortium/nyaa-results (cl-mapcar #'cons + (mapcar #'car entries) results)) + (setq tabulated-list-entries entries) + (setq gortium/nyaa-search-query query) + (tabulated-list-print) + (switch-to-buffer (current-buffer))) + (message "Nyaa: %d results for \"%s\"" (length results) query))) + +;; ── Actions on results ── + +(defun gortium/nyaa--result-at-point () + "Return plist for Nyaa result at point, or nil." + (let ((id (tabulated-list-get-id))) + (and id (cdr (assoc id gortium/nyaa-results))))) + +(defun gortium/nyaa-download-only () + "Add torrent at point to Transmission with sequential download. +Opens the Transmission buffer in a split window to monitor progress." + (interactive) + (let ((r (gortium/nyaa--result-at-point))) + (unless r (user-error "No result at point")) + (gortium/transmission-add-sequential (plist-get r :magnet)) + (require 'transmission) + (select-window (split-window-right)) + (let ((display-buffer-overriding-action '(display-buffer-same-window))) + (call-interactively #'transmission)) + (message "Downloading: %s" (plist-get r :title)))) + +(defun gortium/nyaa-copy-magnet () + "Copy magnet URI at point to kill-ring." + (interactive) + (let ((r (gortium/nyaa--result-at-point))) + (unless r (user-error "No result at point")) + (let ((magnet (plist-get r :magnet))) + (kill-new magnet) + (message "Magnet copied: %s" (substring magnet 0 80))))) + +;; ══════════════════════════════════════════════════ +;; Auto-play (REMOVED — use P in transmission file list) +;; ══════════════════════════════════════════════════ + +;; ── Transmission: open in current window ── + +(defun gortium/transmission () + "Open Transmission in the current window (not a popup/buffer switch)." + (interactive) + (require 'transmission) + (let ((display-buffer-overriding-action '(display-buffer-same-window))) + (call-interactively #'transmission))) + +;; ── Quick-add from a string ── + +(defun gortium/nyaa-add-magnet (magnet) + "Add a magnet link to Transmission with sequential download." + (interactive "sMagnet or info hash: ") + (gortium/transmission-add-sequential magnet)) + +;; Global leader bindings (SPC t prefix for torrent) +(map! :leader + :prefix "t" + :desc "Nyaa search" "n" #'gortium/nyaa-search + :desc "Add magnet" "m" #'gortium/nyaa-add-magnet + :desc "Transmission" "t" #'gortium/transmission) + +;; ── Torrent operations menu (simple interactive defun, no transient dependency) ── +(defun gortium/torrent-menu () + "Torrent operations: search, add, browse." + (interactive) + (let ((key (read-char-choice + "Torrent: (n)yaa (m)agnet add (t)ransmission Display (p)lay-file (c)opy-magnet (d)ownload-only " + '(?n ?m ?t ?p ?c ?d)))) + (cl-case key + (?n (call-interactively #'gortium/nyaa-search)) + (?m (call-interactively #'gortium/nyaa-add-magnet)) + (?t (call-interactively #'gortium/transmission)) + (?p (call-interactively #'gortium/transmission-play-file)) + (?c (call-interactively #'gortium/nyaa-copy-magnet)) + (?d (call-interactively #'gortium/nyaa-download-only))))) + +(map! :leader :desc "Torrent menu" "T" #'gortium/torrent-menu) +#+end_src diff --git a/doom/.config/doom/config.el b/doom/.config/doom/config.el index 713c1ce..3348a45 100644 --- a/doom/.config/doom/config.el +++ b/doom/.config/doom/config.el @@ -3216,3 +3216,299 @@ fallback to today's daily note. Ensures the daily has an Org-roam ID." ;; Make the frame more temporary-like (set-frame-parameter frame 'delete-before-kill-buffer t) (set-window-dedicated-p (selected-window) t)))) + +;;; Torrent to MPV — Nyaa search + Transmission + mpv playback + +;; dom.el provides dom-by-tag, dom-text for RSS XML parsing +(require 'dom) + +;; ── Transmission daemon config ── +;; NB: no (require 'transmission) here — Doom autoloads it on first use. + +(after! transmission + (setq transmission-host "localhost" + transmission-service 9091 + transmission-refresh-interval 3 + transmission-refresh-modes '(transmission-mode transmission-files-mode))) + +;; ── Sequential download (add torrent + play) ── + +(defun gortium/transmission-add-sequential (magnet &optional directory callback) + "Add MAGNET to Transmission with sequential download enabled. +If CALLBACK is provided, call it with the torrent name on success." + (interactive "sMagnet: ") + (transmission-request-async + (lambda (response) + (let-alist response + (let ((name (or .torrent-added.name .torrent-duplicate.name))) + (if name + (progn + (message "Added: %s (sequential)" name) + (when callback (funcall callback name))) + (message "Added torrent (sequential)"))))) + "torrent-add" + (append `(:filename ,(if (transmission-btih-p magnet) + (concat "magnet:?xt=urn:btih:" magnet) + magnet) + :sequential_download t + :paused :json-false) + (when directory (list :download-dir (expand-file-name directory)))))) + +;; ── Play file from Transmission file list ── + +(defun gortium/transmission-play-file (&optional file) + "Play FILE (or file at point) with mpv overlay. +Works on partial downloads (.part files) too." + (interactive) + (let ((path (or file + (condition-case nil + (transmission-files-file-at-point) + (error nil))))) + (if (null path) + (user-error "No file at point or file not found") + (let ((real-path + (or (and (file-exists-p path) path) + (and (file-exists-p (concat path ".part")) + (concat path ".part")) + ;; Check incomplete dir too + (let ((inc (replace-regexp-in-string + "/complete/" "/incomplete/" path))) + (or (and (file-exists-p inc) inc) + (and (file-exists-p (concat inc ".part")) + (concat inc ".part"))))))) + (unless real-path + (user-error "File not found: %s" path)) + (start-process "transmission-mpv" nil "mpv" + "--quiet" "--really-quiet" + "--geometry=30%x30%-30-50" "--ontop" "--no-border" + "--title=Transmission MPV" + real-path) + (message "Playing: %s" (file-name-nondirectory real-path)))))) +;; Override transmission-find-file to always play via mpv instead +;; (advice catches all callers: major mode, Evil, menu, M-x) +(defun gortium/--play-advice (&rest _) + "Redirect find-file to mpv play for partial downloads." + (call-interactively #'gortium/transmission-play-file)) +(advice-add 'transmission-find-file :around #'gortium/--play-advice) + +;; ══════════════════════════════════════════════════ +;; Nyaa.si Search +;; ══════════════════════════════════════════════════ + +(defvar gortium/nyaa-base-url "https://nyaa.si") +(defvar gortium/nyaa-search-history '()) +(defvar-local gortium/nyaa-results nil + "Alist of (ID . plist) for current Nyaa search buffer.") + +(defvar gortium/nyaa-category-short + '(("1_1" . "AMV") ("1_2" . "Eng") ("1_3" . "NonEng") + ("1_4" . "Raw") ("2_1" . "Flac") ("2_2" . "Lossy") + ("3_1" . "L-Eng") ("3_2" . "L-NonE") ("3_3" . "L-Raw") + ("4_1" . "LiveEng") ("4_2" . "LiveNEn")("4_3" . "LiveRaw") + ("5_1" . "SW-App") ("5_2" . "SW-Game")("6_1" . "XXX")) + "Short category labels for column display.") + +(defun gortium/nyaa-cat-label (id) + (or (cdr (assoc id gortium/nyaa-category-short)) id)) + +(defvar gortium/nyaa-search-query nil + "Last query string, for reverting.") + +;; ── Interactive search ── + +(defun gortium/nyaa-search (query &optional trusted-only) + "Search Nyaa.si RSS for QUERY, display results in tabulated-list. +With \\[universal-argument], show trusted uploads only." + (interactive + (list (read-string "Nyaa search: " nil 'gortium/nyaa-search-history) + current-prefix-arg)) + (setq gortium/nyaa-search-query query) + (let ((url (format "%s/?page=rss&q=%s&s=seeders&o=desc%s" + gortium/nyaa-base-url + (url-encode-url query) + (if trusted-only "&f=1" "")))) + (message "Searching Nyaa for \"%s\"..." query) + (let ((buf (get-buffer-create "*Nyaa Search*"))) + (with-current-buffer buf (setq gortium/nyaa-results nil))) + (url-retrieve url #'gortium/nyaa--handle-response (list query)))) + +;; ── RSS response handler ── + +(defun gortium/nyaa--handle-response (status query) + "Handle Nyaa RSS response." + (condition-case err + (let ((results (gortium/nyaa--parse-rss))) + (if (null results) + (progn + (message "Nyaa: no results for \"%s\"" query) + (when (buffer-local-value 'gortium/nyaa-search-query + (get-buffer "*Nyaa Search*")) + (with-current-buffer (get-buffer-create "*Nyaa Search*") + (setq mode-name "Nyaa-Search (no results)")))) + (gortium/nyaa--display-results results query))) + (error (message "Nyaa search error: %S" err)))) + +(defun gortium/nyaa--parse-rss () + "Parse Nyaa RSS XML from current buffer into plist list. +Returns ((:title :link :guid :pubdate :info-hash :category-id + :size :seeders :leechers :trusted :magnet) ...)" + (require 'dom) ; ensure dom-by-tag et al. are loaded + (goto-char (point-min)) + (when (re-search-forward "^\r?$" nil t) ; skip HTTP headers + (condition-case err + (let* ((xml (libxml-parse-xml-region (point) (point-max))) + (items (dom-by-tag xml 'item)) + results) + (dolist (item items) + (let* ((title (dom-text (car (dom-by-tag item 'title)))) + (link (dom-text (car (dom-by-tag item 'link)))) + (guid (dom-text (car (dom-by-tag item 'guid)))) + (ihash (dom-text (car (dom-by-tag item 'infoHash)))) + (title-enc (url-encode-url title)) + (magnet (format "magnet:?xt=urn:btih:%s&dn=%s" ihash title-enc))) + (push (list :title title :link link :guid guid + :pubdate (dom-text (car (dom-by-tag item 'pubDate))) + :info-hash ihash + :category-id (dom-text (car (dom-by-tag item 'categoryId))) + :size (dom-text (car (dom-by-tag item 'size))) + :seeders (string-to-number (dom-text (car (dom-by-tag item 'seeders)))) + :leechers (string-to-number (dom-text (car (dom-by-tag item 'leechers)))) + :trusted (dom-text (car (dom-by-tag item 'trusted))) + :magnet magnet) + results))) + (nreverse results)) + (error (message "Nyaa RSS parse error: %s" (error-message-string err)) + nil)))) + +;; ── Results display (tabulated-list) ── + +(defvar gortium/nyaa-search-mode-map + (let ((map (make-sparse-keymap))) + (set-keymap-parent map tabulated-list-mode-map) + (define-key map (kbd "RET") #'gortium/nyaa-download-only) + (define-key map "d" #'gortium/nyaa-download-only) + (define-key map "g" #'gortium/nyaa-search) + (define-key map "c" #'gortium/nyaa-copy-magnet) + map) + "Keymap for `gortium/nyaa-search-mode'.") + +(define-derived-mode gortium/nyaa-search-mode tabulated-list-mode + "Nyaa-Search" + "Browse Nyaa.si search results. +\\{gortium/nyaa-search-mode-map}" + (setq tabulated-list-format + [("Title" 58 t) + ("Size" 10 t :right-align t) + ("S" 5 t :right-align t) + ("L" 5 t :right-align t) + ("Category" 9 t)]) + (setq tabulated-list-padding 2) + (setq tabulated-list-sort-key (cons "S" t)) + (tabulated-list-init-header) + (setq-local revert-buffer-function #'gortium/nyaa--revert)) + +(defun gortium/nyaa--revert (&optional _auto _noconfirm) + (when (and (boundp 'gortium/nyaa-search-query) gortium/nyaa-search-query) + (gortium/nyaa-search gortium/nyaa-search-query))) + +(defun gortium/nyaa--display-results (results query) + "Fill tabulated-list buffer with RESULTS." + (let* ((buf (get-buffer-create "*Nyaa Search*")) + (entries + (let ((idx 0)) + (mapcar + (lambda (r) + (cl-incf idx) + (let* ((title (plist-get r :title)) + (disp (truncate-string-to-width title 56 nil nil t))) + (list idx + (vector disp + (plist-get r :size) + (number-to-string (plist-get r :seeders)) + (number-to-string (plist-get r :leechers)) + (gortium/nyaa-cat-label + (plist-get r :category-id)))))) + results)))) + ;; Switch to current window instead of popping a split + (with-current-buffer buf + (gortium/nyaa-search-mode) + (setq gortium/nyaa-results (cl-mapcar #'cons + (mapcar #'car entries) results)) + (setq tabulated-list-entries entries) + (setq gortium/nyaa-search-query query) + (tabulated-list-print) + (switch-to-buffer (current-buffer))) + (message "Nyaa: %d results for \"%s\"" (length results) query))) + +;; ── Actions on results ── + +(defun gortium/nyaa--result-at-point () + "Return plist for Nyaa result at point, or nil." + (let ((id (tabulated-list-get-id))) + (and id (cdr (assoc id gortium/nyaa-results))))) + +(defun gortium/nyaa-download-only () + "Add torrent at point to Transmission with sequential download. +Opens the Transmission buffer in a split window to monitor progress." + (interactive) + (let ((r (gortium/nyaa--result-at-point))) + (unless r (user-error "No result at point")) + (gortium/transmission-add-sequential (plist-get r :magnet)) + (require 'transmission) + (select-window (split-window-right)) + (let ((display-buffer-overriding-action '(display-buffer-same-window))) + (call-interactively #'transmission)) + (message "Downloading: %s" (plist-get r :title)))) + +(defun gortium/nyaa-copy-magnet () + "Copy magnet URI at point to kill-ring." + (interactive) + (let ((r (gortium/nyaa--result-at-point))) + (unless r (user-error "No result at point")) + (let ((magnet (plist-get r :magnet))) + (kill-new magnet) + (message "Magnet copied: %s" (substring magnet 0 80))))) + +;; ══════════════════════════════════════════════════ +;; Auto-play (REMOVED — use P in transmission file list) +;; ══════════════════════════════════════════════════ + +;; ── Transmission: open in current window ── + +(defun gortium/transmission () + "Open Transmission in the current window (not a popup/buffer switch)." + (interactive) + (require 'transmission) + (let ((display-buffer-overriding-action '(display-buffer-same-window))) + (call-interactively #'transmission))) + +;; ── Quick-add from a string ── + +(defun gortium/nyaa-add-magnet (magnet) + "Add a magnet link to Transmission with sequential download." + (interactive "sMagnet or info hash: ") + (gortium/transmission-add-sequential magnet)) + +;; Global leader bindings (SPC t prefix for torrent) +(map! :leader + :prefix "t" + :desc "Nyaa search" "n" #'gortium/nyaa-search + :desc "Add magnet" "m" #'gortium/nyaa-add-magnet + :desc "Transmission" "t" #'gortium/transmission) + +;; ── Torrent operations menu (simple interactive defun, no transient dependency) ── +(defun gortium/torrent-menu () + "Torrent operations: search, add, browse." + (interactive) + (let ((key (read-char-choice + "Torrent: (n)yaa (m)agnet add (t)ransmission Display (p)lay-file (c)opy-magnet (d)ownload-only " + '(?n ?m ?t ?p ?c ?d)))) + (cl-case key + (?n (call-interactively #'gortium/nyaa-search)) + (?m (call-interactively #'gortium/nyaa-add-magnet)) + (?t (call-interactively #'gortium/transmission)) + (?p (call-interactively #'gortium/transmission-play-file)) + (?c (call-interactively #'gortium/nyaa-copy-magnet)) + (?d (call-interactively #'gortium/nyaa-download-only))))) + +(map! :leader :desc "Torrent menu" "T" #'gortium/torrent-menu) diff --git a/doom/.config/doom/packages.el b/doom/.config/doom/packages.el index 9d2900d..920c22b 100644 --- a/doom/.config/doom/packages.el +++ b/doom/.config/doom/packages.el @@ -224,3 +224,6 @@ ;; (unpin! pinned-package another-pinned-package) ;; ...Or *all* packages (NOT RECOMMENDED; will likely break things) ;; (unpin! t) + +;; Transmission BitTorrent client interface +(package! transmission)