aboutsummaryrefslogtreecommitdiff
path: root/lisp/mastodon-tl.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/mastodon-tl.el')
-rw-r--r--lisp/mastodon-tl.el215
1 files changed, 205 insertions, 10 deletions
diff --git a/lisp/mastodon-tl.el b/lisp/mastodon-tl.el
index a5b5ed7..e732420 100644
--- a/lisp/mastodon-tl.el
+++ b/lisp/mastodon-tl.el
@@ -1,6 +1,7 @@
;;; mastodon-tl.el --- HTTP request/response functions for mastodon.el -*- lexical-binding: t -*-
;; Copyright (C) 2017-2019 Johnson Denen
+;; Copyright (C) 2020-2022 Marty Hiatt
;; Author: Johnson Denen <johnson.denen@gmail.com>
;; Marty Hiatt <martianhiatus@riseup.net>
;; Maintainer: Marty Hiatt <martianhiatus@riseup.net>
@@ -79,6 +80,14 @@
(autoload 'mastodon-http--build-params-string "mastodon-http")
(autoload 'mastodon-notifications--filter-types-list "mastodon-notifications")
(autoload 'mastodon-toot--get-toot-edits "mastodon-toot")
+(autoload 'mastodon-toot--update-status-fields "mastodon-toot")
+(autoload 'mastodon-toot--compose-buffer "mastodon-toot")
+(autoload 'mastodon-toot--set-toot-properties "mastodon-toot")
+(autoload 'mastodon-toot--schedule-toot "mastodon-toot")
+(autoload 'mastodon-toot--iso-to-human "mastodon-toot")
+
+(defvar mastodon-toot--visibility)
+(defvar mastodon-active-user)
(when (require 'mpv nil :no-error)
(declare-function mpv-start "mpv"))
@@ -251,6 +260,15 @@ types of mastodon links and not just shr.el-generated ones.")
(keymap-canonicalize map))
"Keymap for when point is on list name.")
+(defvar mastodon-tl--scheduled-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "r") 'mastodon-tl--reschedule-toot)
+ (define-key map (kbd "c") 'mastodon-tl--cancel-scheduled-toot)
+ (define-key map (kbd "e") 'mastodon-tl--edit-scheduled-as-new)
+ (define-key map (kbd "<return>") 'mastodon-tl--edit-scheduled-as-new)
+ (keymap-canonicalize map))
+ "Keymap for when point is on a scheduled toot.")
+
(defvar mastodon-tl--byline-link-keymap
(when (require 'mpv nil :no-error)
(let ((map (make-sparse-keymap)))
@@ -564,25 +582,25 @@ TIMESTAMP is assumed to be in the past."
(relative-result
(cond
((< seconds-difference 60)
- (cons "less than a minute ago"
+ (cons "just now"
60))
((< seconds-difference (* 1.5 60))
- (cons "one minute ago"
+ (cons "1 minute ago"
90)) ;; at 90 secs
((< seconds-difference (* 60 59.5))
(funcall regular-response seconds-difference 60 "minute"))
((< seconds-difference (* 1.5 60 60))
- (cons "one hour ago"
+ (cons "1 hour ago"
(* 60 90))) ;; at 90 minutes
((< seconds-difference (* 60 60 23.5))
(funcall regular-response seconds-difference (* 60 60) "hour"))
((< seconds-difference (* 1.5 60 60 24))
- (cons "one day ago"
+ (cons "1 day ago"
(* 1.5 60 60 24))) ;; at a day and a half
((< seconds-difference (* 60 60 24 6.5))
(funcall regular-response seconds-difference (* 60 60 24) "day"))
((< seconds-difference (* 1.5 60 60 24 7))
- (cons "one week ago"
+ (cons "1 week ago"
(* 1.5 60 60 24 7))) ;; a week and a half
((< seconds-difference (* 60 60 24 7 52))
(if (= 52 (floor (+ 0.5 (/ seconds-difference 60 60 24 7))))
@@ -590,7 +608,7 @@ TIMESTAMP is assumed to be in the past."
(* 60 60 24 7 52))
(funcall regular-response seconds-difference (* 60 60 24 7) "week")))
((< seconds-difference (* 1.5 60 60 24 365))
- (cons "one year ago"
+ (cons "1 year ago"
(* 60 60 24 365 1.5))) ;; a year and a half
(t
(funcall regular-response seconds-difference (* 60 60 24 365.25) "year")))))
@@ -1157,7 +1175,7 @@ this just means displaying toot client."
(let* ((poll (mastodon-tl--field 'poll toot))
(expiry (mastodon-tl--field 'expires_at poll))
(expired-p (if (eq (mastodon-tl--field 'expired poll) :json-false) nil t))
- (multi (mastodon-tl--field 'multiple poll))
+ ;; (multi (mastodon-tl--field 'multiple poll))
(voters-count (mastodon-tl--field 'voters_count poll))
(vote-count (mastodon-tl--field 'votes_count poll))
(options (mastodon-tl--field 'options poll))
@@ -1440,6 +1458,8 @@ webapp"
(reblog (alist-get 'reblog json)))
(if reblog (alist-get 'id reblog) id)))
+;;; THREADS
+
(defun mastodon-tl--single-toot (id)
"View toot at point in separate buffer.
ID is that of the toot to view."
@@ -1469,7 +1489,8 @@ ID is that of the toot to view."
(if (or (string= type "follow_request")
(string= type "follow")) ; no can thread these
(error "No thread")
- (let* ((url (mastodon-http--api (format "statuses/%s/context" id)))
+ (let* ((endpoint (format "statuses/%s/context" id))
+ (url (mastodon-http--api endpoint))
(buffer (format "*mastodon-thread-%s*" id))
(toot
;; refetch current toot in case we just faved/boosted:
@@ -1492,7 +1513,7 @@ ID is that of the toot to view."
(switch-to-buffer buffer)
(mastodon-mode)
(mastodon-tl--set-buffer-spec buffer
- (format "statuses/%s/context" id)
+ endpoint
nil)
(let ((inhibit-read-only t))
(mastodon-tl--timeline (alist-get 'ancestors context))
@@ -1507,6 +1528,65 @@ ID is that of the toot to view."
;; else just print the lone toot:
(mastodon-tl--single-toot id)))))))
+
+(defun mastodon-tl--mute-thread ()
+ "Mute the thread displayed in the current buffer.
+Note that you can only (un)mute threads you have posted in."
+ (interactive)
+ (mastodon-tl--mute-or-unmute-thread))
+
+(defun mastodon-tl--unmute-thread ()
+ "Mute the thread displayed in the current buffer.
+Note that you can only (un)mute threads you have posted in."
+ (interactive)
+ (mastodon-tl--mute-or-unmute-thread :unmute))
+
+(defun mastodon-tl--mute-or-unmute-thread (&optional unmute)
+ "Mute a thread.
+If UNMUTE, unmute it."
+ (let ((endpoint (mastodon-tl--get-endpoint)))
+ (if (string-suffix-p "context" endpoint) ; thread view
+ (let* ((id
+ (save-match-data
+ (string-match "statuses/\\(?2:[[:digit:]]+\\)/context"
+ endpoint)
+ (match-string 2 endpoint)))
+ (we-posted-p (mastodon-tl--user-in-thread-p id))
+ (url (mastodon-http--api
+ (if unmute
+ (format "statuses/%s/unmute" id)
+ (format "statuses/%s/mute" id)))))
+ (if (not we-posted-p)
+ (message "You can only (un)mute a thread you have posted in.")
+ (when (if unmute
+ (y-or-n-p "Unnute this thread? ")
+ (y-or-n-p "Mute this thread? "))
+ (let ((response (mastodon-http--post url)))
+ (mastodon-http--triage response
+ (lambda ()
+ (if unmute
+ (message "Thread unmuted!")
+ (message "Thread muted!")))))))))))
+
+(defun mastodon-tl--user-in-thread-p (id)
+ "Return non-nil if the logged-in user has posted to the current thread.
+ID is that of the post the context is currently displayed for."
+ (let* ((context-json (mastodon-http--get-json
+ (mastodon-http--api (format "statuses/%s/context" id))
+ nil :silent))
+ (ancestors (alist-get 'ancestors context-json))
+ (descendants (alist-get 'descendants context-json))
+ (a-ids (mapcar (lambda (status)
+ (alist-get 'id
+ (alist-get 'account status)))
+ ancestors))
+ (d-ids (mapcar (lambda (status)
+ (alist-get 'id
+ (alist-get 'account status)))
+ descendants)))
+ (or (member (mastodon-auth--get-account-id) a-ids)
+ (member (mastodon-auth--get-account-id) d-ids))))
+
;;; LISTS
(defun mastodon-tl--get-users-lists ()
@@ -1770,6 +1850,111 @@ If ID is provided, use that list."
(let* ((url (mastodon-http--api (format "lists/%s/accounts" list-id))))
(mastodon-http--get-json url)))
+;;; SCHEDULED TOOTS
+
+(defun mastodon-tl--get-scheduled-toots (&optional id)
+ "Get the user's currently scheduled toots.
+If ID, just return that toot."
+ (let* ((endpoint (if id
+ (format "scheduled_statuses/%s" id)
+ "scheduled_statuses"))
+ (url (mastodon-http--api endpoint)))
+ (mastodon-http--get-json url)))
+
+(defun mastodon-tl--reschedule-toot ()
+ "Reschedule the scheduled toot at point."
+ (interactive)
+ (mastodon-toot--schedule-toot :reschedule))
+
+(defun mastodon-tl--view-scheduled-toots ()
+ "Show the user's scheduled toots in a new buffer."
+ (interactive)
+ (mastodon-tl--init-sync "scheduled-toots"
+ "scheduled_statuses"
+ 'mastodon-tl--insert-scheduled-toots))
+
+(defun mastodon-tl--insert-scheduled-toots (json)
+ "Insert the user's scheduled toots, from JSON."
+ (let ((scheduleds (mastodon-tl--get-scheduled-toots)))
+ (erase-buffer)
+ (insert (mastodon-tl--set-face
+ (concat "\n ------------\n"
+ " YOUR SCHEDULED TOOTS\n"
+ " ------------\n\n")
+ 'success)
+ (mastodon-tl--set-face
+ "[n/p - prev/next\n r - reschedule\n e/RET - edit toot\n c - cancel]\n\n"
+ 'font-lock-comment-face))
+ (mapc (lambda (x)
+ (mastodon-tl--insert-scheduled-toot x))
+ scheduleds)
+ (goto-char (point-min))
+ (when json
+ (mastodon-tl--goto-next-toot))))
+
+(defun mastodon-tl--insert-scheduled-toot (toot)
+ "Insert scheduled TOOT into the buffer."
+ (let* ((id (alist-get 'id toot))
+ (scheduled (alist-get 'scheduled_at toot))
+ (params (alist-get 'params toot))
+ (text (alist-get 'text params)))
+ (insert
+ (propertize (concat text
+ " | "
+ (mastodon-toot--iso-to-human scheduled))
+ 'byline t ; so we nav here
+ 'toot-id "0" ; so we nav here
+ 'face 'font-lock-comment-face
+ 'keymap mastodon-tl--scheduled-map
+ 'scheduled-json toot
+ 'id id)
+ "\n")))
+
+(defun mastodon-tl--copy-scheduled-toot-text ()
+ "Copy the text of the scheduled toot at point."
+ (interactive)
+ (let* ((toot (get-text-property (point) 'toot))
+ (params (alist-get 'params toot))
+ (text (alist-get 'text params)))
+ (kill-new text)))
+
+(defun mastodon-tl--cancel-scheduled-toot (&optional id no-confirm)
+ "Cancel the scheduled toot at point.
+ID is that of the scheduled toot to cancel.
+NO-CONFIRM means there is no ask or message, there is only do."
+ (interactive)
+ (let* ((id (or id (get-text-property (point) 'id)))
+ (url (mastodon-http--api (format "scheduled_statuses/%s" id))))
+ (when (or no-confirm
+ (y-or-n-p "Cancel scheduled toot?"))
+ (let ((response (mastodon-http--delete url)))
+ (mastodon-http--triage response
+ (lambda ()
+ (mastodon-tl--view-scheduled-toots)
+ (unless no-confirm
+ (message "Toot cancelled!"))))))))
+
+(defun mastodon-tl--edit-scheduled-as-new ()
+ "Edit scheduled status as new toot."
+ (interactive)
+ (let* ((toot (get-text-property (point) 'scheduled-json))
+ (id (alist-get 'id toot))
+ (scheduled (alist-get 'scheduled_at toot))
+ (params (alist-get 'params toot))
+ (text (alist-get 'text params))
+ (visibility (alist-get 'visibility params))
+ (cw (alist-get 'spoiler_text params))
+ (lang (alist-get 'language params))
+ ;; (poll (alist-get 'poll params))
+ (reply-id (alist-get 'in_reply_to_id params)))
+ ;; (media (alist-get 'media_attachments toot)))
+ (mastodon-toot--compose-buffer)
+ (goto-char (point-max))
+ (insert text)
+ ;; adopt properties from scheduled toot:
+ (mastodon-toot--set-toot-properties reply-id visibility cw
+ lang scheduled id)))
+
;;; FILTERS
(defun mastodon-tl--create-filter ()
@@ -1895,7 +2080,7 @@ RESPONSE is the JSON returned by the server."
(message "Looks like there's no toot or user at point?")
,@body))
-;;;; INSTANCES
+;;; INSTANCES
(defun mastodon-tl--view-own-instance (&optional brief)
"View details of your own instance.
@@ -2175,6 +2360,16 @@ LANGS is the accumulated array param alist if we re-run recursively."
(message "Looks like you have no mutes to unmute!")
(mastodon-tl--do-user-action-and-response user-handle "unmute" t)))
+(defun mastodon-tl--dm-user (user-handle)
+ "Query for USER-HANDLE from current status and compose a message to that user."
+ (interactive
+ (list
+ (mastodon-tl--interactive-user-handles-get "message")))
+ (mastodon-tl--do-if-toot
+ (mastodon-toot--compose-buffer (concat "@" user-handle))
+ (setq mastodon-toot--visibility "direct")
+ (mastodon-toot--update-status-fields)))
+
(defun mastodon-tl--interactive-user-handles-get (action)
"Get the list of user-handles for ACTION from the current toot."
(mastodon-tl--do-if-toot