feat: torrent to mpv - nyaa search, transmission sequential, mpv overlay
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user