123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646 |
- ;;; livie.el --- Livie is Video in Emacs -*- lexical-binding: t; -*-
- ;; Copyright (C) 2018 - 2021
- ;; Version: 1.0.0
- ;;; Authors:
- ;; Charlie Ritter <chewzerita@posteo.net>
- ;; Jesus E. <heckyel@hyperbola.info>
- ;; Gabriele Rastello <gabriele.rastello@edu.unito.it>
- ;; Pablo BC <pablo.barraza@protonmail.com>
- ;;; Commentary:
- ;; livie grabs a list of youtube videos based on a search.
- ;; the user can then select a video to watch through `livie-player'
- ;;; Code:
- (require 'cl-lib)
- (require 'json)
- (require 'seq)
- (declare-function livie-channel 'livie-channel)
- (declare-function livie--get-playlist-videos 'livie-playlist)
- (defgroup livie '()
- "Livie is Video in Emacs"
- :prefix "livie-"
- :group 'livie)
- (defcustom livie-sort-criterion 'relevance
- "Criteria to sort the results of the search query."
- :type 'symbol
- :options '(relevance rating upload_date view_count)
- :group 'livie)
- (defcustom livie-type-of-results "video"
- "Set what type of results to get when making a search."
- :type 'string
- :options '("video" "playlist" "channel" "all")
- :group 'livie)
- (defcustom livie-show-fancy-icons nil
- "If t, enable showing fancy icons in the search buffer."
- :type 'boolean
- :group 'livie)
- ;; TODO: Try to add support using all-the-icons, or add images instead.
- (defcustom livie-icons '((video "Video" "✇")
- (playlist "Playlist" "🎞")
- ;; Added a space to this icon so everything is aligned
- (channel "Channel" "📺 ")
- (length "" "⌚:")
- (views "views" "👁")
- (subCount "subscribers" "🅯")
- (videoCount "videos" "▶"))
- "Icons for displaying items in buffer. First string is inserted if `livie-show-fancy-icons' is disabled."
- :type '(alist :value-type (group string string))
- :group 'livie)
- (defvar livie-invidious-api-url "https://invidious.namazso.eu"
- "URL to Invidious instance.")
- (defvar livie--insert-functions '((video . livie--insert-video)
- (playlist . livie--insert-playlist)
- (channel . livie--insert-channel)))
- (defvar livie--default-action-functions '((video . livie--default-video-action)
- (playlist . livie--default-playlist-action)
- (channel . livie--default-channel-action))
- "Functions to call on an entry. To modify an action, set the appropiate variable instead.")
- (defvar livie--default-video-action #'(lambda ()
- (message (livie-video-title (livie-get-current-video))))
- "Action to open a video. By default it just prints the title to the minibuffer.")
- (defvar livie--default-playlist-action #'livie--open-playlist
- "Action to open a playlist.")
- (defvar livie--default-channel-action #'livie--open-channel
- "Action to open a channel.")
- (defvar livie-default-video-query-fields "type,author,lengthSeconds,title,videoId,authorId,viewCount,published"
- "Default fields of interest for video search.")
- (defvar livie-default-channel-query-fields "type,author,authorId,subCount,videoCount"
- "Default fields of interest for channel search.")
- (defvar livie-default-playlist-query-fields "type,title,playlistId,author,authorId,videoCount"
- "Default fields of interest for playlist search.")
- (defvar livie-videos '()
- "List of videos currently on display.")
- (defcustom livie-published-date-time-string "%Y-%m-%d"
- "Time-string used to render the published date of the video.
- See `format-time-string' for information on how to edit this variable."
- :type 'string
- :group 'livie)
- (defvar-local livie-current-page 1
- "Current page of the current `livie-search-term'")
- (defvar-local livie-search-term ""
- "Current search string as used by `livie-search'")
- (defcustom livie-author-name-reserved-space 20
- "Number of characters reserved for the channel's name in the *livie* buffer.
- Note that there will always 3 extra spaces for eventual dots (for names that are
- too long)."
- :type 'integer
- :group 'livie)
- (defcustom livie-title-video-reserved-space 100
- "Number of characters reserved for the video title in the *livie* buffer.
- Note that there will always 3 extra spaces for eventual dots (for names that are
- too long)."
- :type 'integer
- :group 'livie)
- (defcustom livie-title-playlist-reserved-space 30
- "Number of characters reserved for the playlist title in the *livie* buffer.
- Note that there will always 3 extra spaces for eventual dots (for names that are
- too long)."
- :type 'integer
- :group 'livie)
- (defcustom livie-name-channel-reserved-space 50
- "Number of characters reserved for the channel name in the *livie* buffer.
- Note that there will always 3 extra spaces for eventual dots (for names that are
- too long)."
- :type 'integer
- :group 'livie)
- (defface livie-video-published-face
- '((((class color) (background light)) (:foreground "#1B5E20"))
- (((class color) (background dark)) (:foreground "#00E676")))
- "Face used for the video published date.")
- (defface livie-channel-name-face
- '((((class color) (background light)) (:foreground "#FF6D00"))
- (((class color) (background dark)) (:foreground "#FFFF00")))
- "Face used for channel names.")
- (defface livie-video-length-face
- '((((class color) (background light)) (:foreground "#6A1B9A"))
- (((class color) (background dark)) (:foreground "#AA00FF")))
- "Face used for the video length.")
- (defface livie-video-view-face
- '((((class color) (background light)) (:foreground "#00695C"))
- (((class color) (background dark)) (:foreground "#00BFA5")))
- "Face used for the video views.")
- (defface livie-video-title-face
- '((((class color) (background light)) (:foreground "#000000"))
- (((class color) (background dark)) (:foreground "#FFFFFF")))
- "Face used for the video title.")
- (defface livie-item-videoCount-face
- '((t :inherit livie-video-view-face))
- "Face used for the videoCount of an entry.")
- (defface livie-item-subCount-face
- '((t :inherit livie-video-published-face))
- "Face used for the subCount of an entry.")
- (defface livie-parameter-face
- '((t :inherit livie-video-published-face))
- "Face used for the parameters of the current search.")
- (defvar livie-mode-map
- (let ((map (make-sparse-keymap)))
- (suppress-keymap map)
- (define-key map "q" #'livie-quit)
- (define-key map "h" #'describe-mode)
- (define-key map "n" #'next-line)
- (define-key map "p" #'previous-line)
- (define-key map (kbd "<tab>") #'next-line)
- (define-key map (kbd "<backtab>") #'previous-line)
- (define-key map "s" #'livie-search)
- (define-key map ">" #'livie-search-next-page)
- (define-key map "<" #'livie-search-previous-page)
- (define-key map "t" #'livie-search-type)
- (define-key map "S" #'livie-sort-videos)
- (define-key map "C" #'livie-show-channels)
- (define-key map "P" #'livie-show-playlists)
- (define-key map "V" #'livie-show-videos)
- (define-key map "Y" #'livie-yank-channel-feed)
- (define-key map "A" #'livie--open-channel)
- (define-key map (kbd "RET") #'livie-open-entry)
- (define-key map "y" #'livie-watch-this-video)
- map)
- "Keymap for `livie-mode'.")
- (define-derived-mode livie-mode text-mode
- "livie-mode"
- "A major mode to query Youtube content through Invidious."
- :group 'livie
- (setq buffer-read-only t)
- (buffer-disable-undo)
- (make-local-variable 'livie-videos))
- (defun livie-quit ()
- "Quit livie buffer."
- (interactive)
- (kill-buffer))
- (defun livie--format-author (name)
- "Format a channel NAME to be inserted in the *livie* buffer."
- (let* ((n (string-width name))
- (extra-chars (- n livie-author-name-reserved-space))
- (formatted-string (if (<= extra-chars 0)
- (concat name
- (make-string (abs extra-chars) ?\ )
- " ")
- (concat (truncate-string-to-width name livie-author-name-reserved-space)
- "..."))))
- (propertize formatted-string 'face 'livie-channel-name-face)))
- (defun livie--format-title (title)
- "Format a video TITLE to be inserted in the *livie* buffer."
- (let* ((n (string-width title))
- (extra-chars (- n livie-title-video-reserved-space))
- (formatted-string (if (<= extra-chars 0)
- (concat title
- (make-string (abs extra-chars) ?\ )
- " ")
- (concat (truncate-string-to-width title livie-title-video-reserved-space)
- "..."))))
- (propertize formatted-string 'face 'livie-video-title-face)))
- (defun livie--format-playlist-title (title)
- "Format a playlist TITLE to be inserted in the *livie* buffer."
- (let* ((n (string-width title))
- (extra-chars (- n livie-title-playlist-reserved-space))
- (formatted-string (if (<= extra-chars 0)
- (concat title
- (make-string (abs extra-chars) ?\ )
- " ")
- (concat (truncate-string-to-width title livie-title-playlist-reserved-space)
- "..."))))
- (propertize formatted-string 'face 'livie-video-title-face)))
- (defun livie--format-channel-name (name)
- "Format a channel NAME to be inserted in the *livie* buffer."
- (let* ((n (string-width name))
- (extra-chars (- n livie-name-channel-reserved-space))
- (formatted-string (if (<= extra-chars 0)
- (concat name
- (make-string (abs extra-chars) ?\ )
- " ")
- (concat (truncate-string-to-width name livie-name-channel-reserved-space)
- "..."))))
- (propertize formatted-string 'face 'livie-channel-name-face)))
- (defun livie--format-video-length (seconds)
- "Given an amount of SECONDS, format it nicely to be inserted in the *livie* buffer."
- (let ((formatted-string (concat (livie--get-icon 'length)
- (format-seconds "%.2h" seconds)
- ":"
- (format-seconds "%.2m" (mod seconds 3600))
- ":"
- (format-seconds "%.2s" (mod seconds 60)))))
- (propertize formatted-string 'face 'livie-video-length-face)))
- (defun livie--format-video-views (views)
- "Format video VIEWS to be inserted in the *livie* buffer."
- (propertize (format "[%s: %d]" (livie--get-icon 'views) views) 'face 'livie-video-view-face))
- (defun livie--format-video-published (published)
- "Format video PUBLISHED date to be inserted in the *livie* buffer."
- (propertize (format-time-string livie-published-date-time-string (seconds-to-time published))
- 'face 'livie-video-published-face))
- (defun livie--format-videoCount (videoCount)
- "Format video VIDEOCOUNT to be inserted in the *livie* buffer."
- (propertize (format "[%s: %d]" (livie--get-icon 'videoCount) videoCount) 'face 'livie-item-videoCount-face))
- (defun livie--format-subCount (subCount)
- "Format video SUBCOUNT to be inserted in the *livie* buffer."
- (propertize (format "%s: %-10d" (livie--get-icon 'subCount) subCount) 'face 'livie-item-subCount-face))
- (defun livie--format-type (type)
- "Insert an icon of TYPE into buffer."
- (if livie-show-fancy-icons
- (propertize (format "%-2s: " (livie--get-icon type)) 'face 'livie-video-title-face)
- (propertize (format "%-10s: " (livie--get-icon type)) 'face 'livie-video-title-face)))
- (defun livie--get-icon (item)
- "Get the icon for ITEM from `livie-icons'."
- (let* ((getmarks (assoc-default item livie-icons)))
- (if livie-show-fancy-icons
- (when (fboundp 'second)
- (second getmarks))
- (car getmarks))))
- (defun livie--insert-entry (entry)
- "Insert an ENTRY of the form according to its type."
- (let* ((type (if (not (equal livie-type-of-results "all"))
- (intern livie-type-of-results)
- (cond ((livie-video-p entry) 'video)
- ((livie-playlist-p entry) 'playlist)
- ((livie-channel-p entry) 'channel)
- (t (error "Invalid entry type")))))
- (func (cdr (assoc type livie--insert-functions))))
- (when (equal livie-type-of-results "all")
- (insert (livie--format-type type)))
- (funcall func entry)))
- (defun livie--insert-video (video)
- "Insert VIDEO in the current buffer."
- (insert (livie--format-video-published (livie-video-published video))
- " "
- (livie--format-author (livie-video-author video))
- " "
- (livie--format-video-length (livie-video-length video))
- " "
- (livie--format-title (livie-video-title video))
- " "
- (livie--format-video-views (livie-video-views video))))
- ;; TODO: Format playlist and channel entries in buffer
- (defun livie--insert-playlist (playlist)
- "Insert PLAYLIST in the current buffer."
- (insert (livie--format-playlist-title (livie-playlist-title playlist))
- " "
- (livie--format-author (livie-playlist-author playlist))
- " "
- (livie--format-videoCount (livie-playlist-videoCount playlist))))
- (defun livie--insert-channel (channel)
- "Insert CHANNEL in the current buffer."
- (insert (livie--format-channel-name (livie-channel-author channel))
- " "
- (livie--format-subCount (livie-channel-subCount channel))
- " "
- (livie--format-videoCount (livie-channel-videoCount channel))))
- (defun livie--draw-buffer ()
- "Draws the livie buffer i.e. clear everything and write down all videos in `livie-videos'."
- (let ((inhibit-read-only t))
- (erase-buffer)
- (setf header-line-format (concat (propertize (capitalize livie-type-of-results) 'face 'livie-parameter-face)
- " results for "
- (propertize livie-search-term 'face 'livie-parameter-face)
- ", page "
- (propertize (number-to-string livie-current-page) 'face 'livie-parameter-face)
- ", sorted by: "
- (propertize (symbol-name livie-sort-criterion) 'face 'livie-parameter-face)))
- (seq-do (lambda (v)
- (livie--insert-entry v)
- (insert "\n"))
- livie-videos)
- (goto-char (point-min))))
- (defun livie-enable-fancy-icons ()
- "Enable fancy icons in the *livie* buffer, using `livie-icons'."
- (interactive)
- (setf livie-show-fancy-icons t))
- (defun livie-disable-fancy-icons ()
- "Disable fancy icons in the *livie* buffer, using `livie-icons'."
- (interactive)
- (setf livie-show-fancy-icons nil))
- (defun livie-toggle-fancy-icons ()
- "Toggle display of fancy-icons in the *livie* buffer, using `livie-icons'."
- (interactive)
- (setf livie-show-fancy-icons (not livie-show-fancy-icons)))
- (defun livie-search (query)
- "Search youtube for `QUERY', and redraw the buffer."
- (interactive "sSearch: ")
- (switch-to-buffer "*livie*")
- (setf livie-current-page 1)
- (setf livie-search-term query)
- (setf livie-videos (livie--process-results (livie--query query livie-current-page)))
- (livie--draw-buffer))
- (defun livie-search-next-page ()
- "Switch to the next page of the current search. Redraw the buffer."
- (interactive)
- (setf livie-videos (livie--process-results (livie--query livie-search-term
- (1+ livie-current-page))))
- (setf livie-current-page (1+ livie-current-page))
- (livie--draw-buffer))
- (defun livie-search-previous-page ()
- "Switch to the previous page of the current search. Redraw the buffer."
- (interactive)
- (when (> livie-current-page 1)
- (setf livie-videos (livie--process-results (livie--query livie-search-term
- (1- livie-current-page))))
- (setf livie-current-page (1- livie-current-page))
- (livie--draw-buffer)))
- (defun livie-search-type (&optional arg)
- "Ask for what type of results to display, and search.
- If ARG is given, make a new search."
- (interactive "P")
- (when arg
- (setf livie-search-term (read-string "Search: ")))
- (setf livie-current-page 1)
- (setf livie-type-of-results (completing-read "Show: " (get 'livie-type-of-results 'custom-options)))
- (setf livie-videos (livie--process-results (livie--query livie-search-term livie-current-page)))
- (livie--draw-buffer))
- (defun livie-show-videos (&optional arg)
- "Show videos for the current search.
- If ARG is given, make a new search."
- (interactive "P")
- (when arg
- (setf livie-search-term (read-string "Search: ")))
- (setf livie-current-page 1)
- (setf livie-type-of-results "video")
- (setf livie-videos (livie--process-results (livie--query livie-search-term livie-current-page)))
- (livie--draw-buffer))
- (defun livie-show-channels (&optional arg)
- "Show channels for the current search.
- If ARG is given, make a new search."
- (interactive "P")
- (when arg
- (setf livie-search-term (read-string "Search: ")))
- (setf livie-current-page 1)
- (setf livie-type-of-results "channel")
- (setf livie-videos (livie--process-results (livie--query livie-search-term livie-current-page)))
- (livie--draw-buffer))
- (defun livie-show-playlists (&optional arg)
- "Show playlists for the current search.
- If ARG is given, make a new search."
- (interactive "P")
- (when arg
- (setf livie-search-term (read-string "Search: ")))
- (setf livie-current-page 1)
- (setf livie-type-of-results "playlist")
- (setf livie-videos (livie--process-results (livie--query livie-search-term livie-current-page)))
- (livie--draw-buffer))
- (defun livie-sort-videos ()
- "Sort videos from the current search from page 1, according to values of `livie-sort-criterion'."
- (interactive)
- (setf livie-sort-criterion (intern (completing-read "Sort videos by (default value is relevance): " (get 'livie-sort-criterion 'custom-options))))
- (setf livie-current-page 1)
- (setf livie-videos (livie--process-results (livie--query livie-search-term livie-current-page)))
- (livie--draw-buffer))
- (defun livie-get-current-video ()
- "Get the currently selected video."
- (aref livie-videos (1- (line-number-at-pos))))
- (defun livie-watch-this-video ()
- "Stream video at point in mpv."
- (interactive)
- (if (equal (livie--get-entry-type (livie-get-current-video)) 'video)
- (let* ((video (livie-get-current-video))
- (id (livie-video-id video))
- (quality-val (completing-read "Max height resolution for default is 480 (0 for unlimited): "
- '("default" "0" "240" "360" "480" "720" "1080")
- nil nil)))
- (if (not (equal quality-val "default"))
- (setq quality-val (string-to-number quality-val))
- (setq quality-val 480))
- (if (equal quality-val 0)
- (setq quality-arg "")
- (setq quality-arg (format "--ytdl-format=[height<=?%s]" quality-val)))
- (start-process "livie-mpv" nil "mpv" (format "https://www.youtube.com/watch?v=%s" id) quality-arg)
- (message "Playing [%s/watch?v=%s] with height≤%s with mpv..." livie-invidious-api-url id quality-val))
- (message "It's not a video")))
- (defun livie-yank-channel-feed (&optional arg)
- "Yank channel's Youtube RSS feed for the current video at point.
- If ARG is given, format it as a Invidious RSS feed."
- (interactive "P")
- (let* ((entry (livie-get-current-video))
- (author (funcall (livie--get-author-function entry) entry))
- (authorId (funcall (livie--get-authorId-function entry) entry))
- (url (if arg
- (concat livie-invidious-api-url "/feed/channel/" authorId)
- (concat "https://www.youtube.com/feeds/videos.xml?channel_id=" authorId))))
- (kill-new url)
- (message "Copied RSS feed for: %s - %s" author url)))
- (defun livie--get-entry-type (entry)
- "Return the type of ENTRY."
- (if (not (equal livie-type-of-results "all"))
- (intern livie-type-of-results)
- (cond ((livie-video-p entry) 'video)
- ((livie-playlist-p entry) 'playlist)
- ((livie-channel-p entry) 'channel)
- (t (error "Invalid entry type")))))
- (defun livie--get-author-function (entry)
- "Get the author for ENTRY."
- (let* ((type (livie--get-entry-type entry)))
- (pcase type
- ('video #'livie-video-author)
- ('playlist #'livie-playlist-author)
- ('channel #'livie-channel-author)
- (_ (error "Invalid entry type")))))
- (defun livie--get-authorId-function (entry)
- "Get the author for ENTRY."
- (let* ((type (livie--get-entry-type entry)))
- (pcase type
- ('video #'livie-video-authorId)
- ('playlist #'livie-playlist-authorId)
- ('channel #'livie-channel-authorId)
- (_ (error "Invalid entry type")))))
- (defun livie-buffer ()
- "Name for the main livie buffer."
- (get-buffer-create "*livie*"))
- ;;;###autoload
- (defun livie ()
- "Enter livie."
- (interactive)
- (switch-to-buffer (livie-buffer))
- (unless (eq major-mode 'livie-mode)
- (livie-mode))
- (when (seq-empty-p livie-search-term)
- (call-interactively #'livie-search)))
- ;; Youtube interface stuff below.
- (cl-defstruct (livie-video (:constructor livie-video--create)
- (:copier nil))
- "Information about a Youtube video."
- (title "" :read-only t)
- (id 0 :read-only t)
- (author "" :read-only t)
- (authorId "" :read-only t)
- (length 0 :read-only t)
- (views 0 :read-only t)
- (published 0 :read-only t))
- ;; Maybe type should be part of the struct.
- (cl-defstruct (livie-channel (:constructor livie-channel--create)
- (:copier nil))
- "Information about a Youtube channel."
- (author "" :read-only t)
- (authorId "" :read-only t)
- (subCount 0 :read-only t)
- (videoCount 0 :read-only t))
- (cl-defstruct (livie-playlist (:constructor livie-playlist--create)
- (:copier nil))
- "Information about a Youtube playlist."
- (title "" :read-only t)
- (playlistId "" :read-only t)
- (author "" :read-only t)
- (authorId "" :read-only t)
- (videoCount 0 :read-only t))
- (defun livie--API-call (method args)
- "Perform a call to the invidious API method METHOD passing ARGS.
- Curl is used to perform the request. An error is thrown if it exits with a non
- zero exit code otherwise the request body is parsed by `json-read' and returned."
- (with-temp-buffer
- (let ((exit-code (call-process "curl" nil t nil
- "--silent"
- "-X" "GET"
- (concat livie-invidious-api-url
- "/api/v1/" method
- "?" (url-build-query-string args)))))
- (unless (= exit-code 0)
- (error "Curl had problems connecting to Invidious API"))
- (goto-char (point-min))
- (json-read))))
- (defun livie--query (string n)
- "Query youtube for STRING, return the Nth page of results."
- (let ((results (livie--API-call "search" `(("q" ,string)
- ("sort_by" ,(symbol-name livie-sort-criterion))
- ("type" ,livie-type-of-results)
- ("page" ,n)
- ("fields" ,(pcase livie-type-of-results
- ("video" livie-default-video-query-fields)
- ("playlist" livie-default-playlist-query-fields)
- ("channel" livie-default-channel-query-fields)
- ;; I mean, it does get the job done... fix later.
- ("all" (concat livie-default-channel-query-fields
- ","
- livie-default-playlist-query-fields
- ","
- livie-default-video-query-fields))))))))
- results))
- (defun livie--process-results (results &optional type)
- "Process RESULTS and turn them into objects, is TYPE is not given, get it from RESULTS."
- (dotimes (i (length results))
- (let* ((v (aref results i))
- (type (or type (assoc-default 'type v))))
- (aset results i (pcase type
- ("video" (livie-video--create
- :title (assoc-default 'title v)
- :author (assoc-default 'author v)
- :authorId (assoc-default 'authorId v)
- :length (assoc-default 'lengthSeconds v)
- :id (assoc-default 'videoId v)
- :views (assoc-default 'viewCount v)
- :published (assoc-default 'published v)))
- ("playlist" (livie-playlist--create
- :title (assoc-default 'title v)
- :playlistId (assoc-default 'playlistId v)
- :author (assoc-default 'author v)
- :authorId (assoc-default 'authorId v)
- :videoCount (assoc-default 'videoCount v)))
- ("channel" (livie-channel--create
- :author (assoc-default 'author v)
- :authorId (assoc-default 'authorId v)
- :subCount (assoc-default 'subCount v)
- :videoCount (assoc-default 'videoCount v)))))))
- results)
- (defun livie-open-entry ()
- "Open the entry at point depending on it's type."
- (interactive)
- (let* ((entry (livie-get-current-video))
- (type (livie--get-entry-type entry)))
- (funcall (symbol-value (assoc-default type livie--default-action-functions)))))
- (defun livie--open-channel ()
- "Fetch the channel page for the entry at point."
- (interactive)
- (require 'livie-channel)
- (livie-channel))
- (defun livie--open-playlist ()
- "Open the contents of the entry at point, if it's a playlist."
- (interactive)
- (require 'livie-playlist)
- (livie--get-playlist-videos))
- (provide 'livie)
- ;;; livie.el ends here
|