Last active
January 20, 2023 06:20
-
-
Save gkbrk/771852072ed5a6715882bfac734fdd36 to your computer and use it in GitHub Desktop.
Common lisp Mastodon bot
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ql:quickload :drakma) | |
(ql:quickload :cl-json) | |
(ql:quickload :plump) | |
(ql:quickload :babel) | |
(ql:quickload :tooter) | |
(ql:quickload :split-sequence) | |
(defvar *feed-path* "https://lobste.rs/rss") | |
(setf drakma:*drakma-default-external-format* :UTF-8) | |
(defstruct lobsters-post | |
title | |
url | |
guid | |
) | |
(defun get-first-text (tag node) | |
"Search the XML node for the given tag name and return the text of the first one" | |
(plump:render-text (car (plump:get-elements-by-tag-name node tag))) | |
) | |
(defun parse-rss-item (item) | |
"Parse an RSS item into a lobsters-post" | |
(let* ((post (make-lobsters-post)) | |
) | |
(setf (lobsters-post-title post) (get-first-text "title" item)) | |
(setf (lobsters-post-url post) (get-first-text "link" item)) | |
(setf (lobsters-post-guid post) (get-first-text "guid" item)) | |
post | |
)) | |
(defun get-rss-feed () | |
"Gets rss feed of Lobste.rs" | |
(let* ((xml-text (babel:octets-to-string (drakma:http-request *feed-path*))) | |
(plump:*tag-dispatchers* plump:*xml-tags*) | |
(xml-tree (plump:parse xml-text)) | |
(items (plump:get-elements-by-tag-name xml-tree "item")) | |
) | |
(reverse (map 'list #'parse-rss-item items)) | |
)) | |
(defun get-mastodon-client () | |
(make-instance 'tooter:client | |
:base "https://botsin.space" | |
:name "lobsterbot" | |
:key "secret" | |
:secret "secret" | |
:access-token "secret") | |
) | |
(defun send-toot (item) | |
"Takes a lobsters-post and posts it on Mastodon" | |
(tooter:make-status (get-mastodon-client) (format nil "~a - ~a ~a" | |
(lobsters-post-title item) | |
(lobsters-post-guid item) | |
(lobsters-post-url item))) | |
) | |
(defun is-link-seen (item) | |
"Returns if we have processed a link before" | |
(with-open-file (stream "links.txt" | |
:if-does-not-exist :create) | |
(loop for line = (read-line stream nil) | |
while line | |
when (string= line (lobsters-post-guid item)) return t)) | |
) | |
(defun record-link-seen (item) | |
"Writes a link to the links file to keep track of it" | |
(with-open-file (stream "links.txt" | |
:direction :output | |
:if-exists :append | |
:if-does-not-exist :create) | |
(format stream "~a~%" (lobsters-post-guid item))) | |
) | |
(defun run-mastodon-bot () | |
(let* ((first-ten (subseq (get-rss-feed) 0 10)) | |
(new-links (remove-if #'is-link-seen first-ten)) | |
) | |
(loop for item in new-links do | |
(send-toot item) | |
(record-link-seen item)) | |
)) | |
(run-mastodon-bot) |
@vindarel in this case isn't the subseq a list of posts? If so one can get up to 10 items from a list with
(loop
:for post :in (get-rss-feed)
:repeat 5 :collect post)
But in this case because they want to filter the stories it would make sense to move everything to the loop
(defun run-mastodon-bot ()
(loop
:with count := 0
:for rss-entry :in (get-rss-feed)
:while (< count 10)
:unless (link-seen? rss-entry)
:do
(send-toot rss-entry)
(record-link-seen rss-entry)
(incf count)))
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey, nice post :)
my lil' review:
subseq
fails if the end limit is superior to the sequence's length. I fix this with(str:substr 0 3 '(:foo))
(function in thestr
library, which happens to work for sequences).To read lines from a file, I'd use
uiop:read-file-lines
.(also there is no error handling).
for cron-like jobs, I once used the nice Clerk library.
https://www.reddit.com/r/lisp/comments/991678/mastodon_bot_in_common_lisp_gokberk_yaltirakli/