Skip to content
Permalink
Browse files

Track what's been tweeted with a local state directory instead of twi…

…tter itself.
  • Loading branch information
xach committed Dec 14, 2019
1 parent 8e0c7d6 commit ae06d0d8946c18cea95dda4f2523724f81761b75
Showing with 69 additions and 28 deletions.
  1. +4 −4 README.md
  2. +10 −5 cli.lisp
  3. +2 −2 planetfeed.asd
  4. +53 −17 planetfeed.lisp
@@ -20,10 +20,10 @@ for posting will be saved in the specified credentials file. Make sure
the file is only accessible to people and processes you want to be
able to tweet.

To run planetfeed to update twitter, use `planetfeed
--credentials-file /path/to/file.txt`. planetfeed will fetch the
latest Planet Lisp feed, fetch recent @planet_lisp tweets, and tweet
any feed items that haven't been tweeted recently.
To run planetfeed to update twitter, use `planetfeed --data-directory
/path/to/data/directory --credentials-file
/path/to/file.txt`. planetfeed will fetch the latest Planet Lisp feed
and tweet any feed items that haven't been tweeted recently.

## Feedback

@@ -10,17 +10,22 @@
(defun cli (argv)
(sb-ext:disable-debugger)
(let ((options (rest argv))
(credentials-file nil ))
(credentials-file nil)
(login nil))
(loop
(let ((option (pop options)))
(cond ((null option)
(unless credentials-file
(error "--credentials-file is required"))
(return (command-line-update credentials-file)))
(unless *data-directory*
(error "--data-directory is required"))
(if login
(return (twitter-login credentials-file))
(return (command-line-update credentials-file))))
((equal option "--login")
(unless credentials-file
(error "--credentials-file is required"))
(return (twitter-login credentials-file)))
(setf login t))
((equal option "--data-directory")
(setf *data-directory* (pop options)))
((equal option "--credentials-file")
(setf credentials-file (pop options)))
(t
@@ -2,10 +2,10 @@

(asdf:defsystem #:planetfeed
:serial t
:description "Parse Planet Lisp's feed into a westbrook feed."
:description "Parse Planet Lisp's feed into a westbrook feed and tweet about it."
:license "MIT"
:author "Zach Beane <xach@xach.com>"
:depends-on (#:cxml #:westbrook #:drakma #:chirp #:alexandria)
:depends-on (#:cxml #:westbrook #:drakma #:chirp #:alexandria #:ironclad #:babel)
:components ((:file "package")
(:file "tweeter")
(:file "date-parser")
@@ -52,16 +52,26 @@
(error "Not enough statuses to skip ~D of them" skip))
(subseq statuses skip)))

(defun postable-items (recent-tweets feed)
"Return all postable items from FEED that are not found in RECENT-TWEETS."
(let ((strings (mapcar #'chirp:text recent-tweets))
(items (westbrook:items feed)))
(when (< (length strings) (length items))
(error "Too few tweets to check against the feed items"))
(loop for item in items
for title = (westbrook::title item)
unless (member title strings :test 'prefixp)
collect item)))
(defvar *data-directory* nil)

(defun item-identifier (item)
"Return a text string usable to uniquely identify ITEM."
(ironclad:byte-array-to-hex-string
(ironclad:digest-sequence 'ironclad:sha1
(babel:string-to-octets (item-tweet-text item)))))

(defun item-storage-pathname (item)
(unless *data-directory*
(error "~S unset" '*data-directory*))
(let* ((id (item-identifier item))
(a (subseq id 0 1))
(b (subseq id 1 2))
(directory (list :relative a b)))
(merge-pathnames
(make-pathname :directory directory
:name id
:type "dat")
*data-directory*)))

(defun item-tweet-text (item)
(format nil "~A ~A"
@@ -77,13 +87,39 @@
thing))))
(chirp:statuses/update text)))

(defun already-posted-error-p (error)
(and (typep error 'chirp-objects:oauth-request-error)
;; Wotta nitemare!
(eql 187
(cdr
(assoc :code
(first
(cdr (assoc :errors (chirp:http-body error)))))))))

(deftype already-posted-error ()
`(satisfies already-posted-error-p))

(defun ensure-tweeted (item)
(let ((file (item-storage-pathname item)))
(ensure-directories-exist file)
(with-open-file (stream file :direction :output :if-exists nil)
(cond (stream
(handler-case
(let* ((status (tweet item))
(id (chirp:id status)))
(prin1 id stream)
status)
(already-posted-error ()
(prin1 -1 stream)
-1)))
(t
nil)))))

;;; Now do the thing

(defun update-twitter ()
(let* ((feed (latest-feed))
(tweets (latest-tweets :count (* (length (westbrook:items feed))
2)))
(postable (postable-items tweets feed)))
(dolist (item postable)
(format t "Posting ~A...~%" item)
(tweet item))))
(let* ((feed (latest-feed)))
(dolist (item (westbrook:items feed))
(let ((status (ensure-tweeted item)))
(when status
(format t "Posted ~A...~%" item))))))

0 comments on commit ae06d0d

Please sign in to comment.
You can’t perform that action at this time.