rss.lisp 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. (in-package #:chatikbot)
  2. (defstruct feed id url title next-fetch (period 300))
  3. (defstruct feed-item feed guid link title description published)
  4. (defparameter *rss-min-period* 60 "Min rss refresh period in seconds")
  5. (defparameter *rss-max-period* 1800 "Max rss refresh period in seconds")
  6. (defparameter *rss-change-rate* 0.1 "Refresh period adjustment rate")
  7. (defun find-rss-links (url)
  8. (handler-case
  9. (multiple-value-bind (root uri) (xml-request url)
  10. (loop for link in (get-by-tag root "link")
  11. when (string= "application/rss+xml" (plump:attribute link "type"))
  12. collect (list (plump:attribute link "title")
  13. (puri:render-uri
  14. (puri:merge-uris
  15. (puri:uri (plump:attribute link "href"))
  16. uri) nil))))
  17. (error (e) (log:error e))))
  18. (defun build-feed (url)
  19. (let ((root (xml-request url)))
  20. (alexandria:when-let (rss (car (get-by-tag root "rss")))
  21. (make-feed :url url :title (child-text rss "title")))))
  22. (defun adjust-period (period new-items)
  23. "Adjust the period of feed based on whenever there were new items. With clamping"
  24. (let ((diff (round (* period *rss-change-rate*))))
  25. (min *rss-max-period*
  26. (max *rss-min-period*
  27. (- period (* diff (if (zerop new-items) -1 new-items)))))))
  28. (defun need-fetch-p (feed)
  29. (or (null (feed-next-fetch feed))
  30. (local-time:timestamp> (local-time:now) (feed-next-fetch feed))))
  31. (defun refresh-feed (feed &optional (skip-p #'not))
  32. (let* ((items
  33. (loop for item in (fetch-feed-items feed)
  34. unless (funcall skip-p item)
  35. collect item))
  36. (new-period (adjust-period (feed-period feed) (length items))))
  37. (setf (feed-period feed) new-period
  38. (feed-next-fetch feed) (local-time:timestamp+ (local-time:now) new-period :sec))
  39. items))
  40. (defun trim-nil (text)
  41. (when text
  42. (let ((text (string-trim " " text)))
  43. (unless (zerop (length text))
  44. text))))
  45. (defun text-with-cdata (node)
  46. "Compiles all text nodes within the nesting-node into one string."
  47. (with-output-to-string (stream)
  48. (labels ((r (node)
  49. (loop for child across (plump:children node)
  50. do (typecase child
  51. (plump:text-node (write-string (plump:text child) stream))
  52. (plump:cdata (write-string (plump:text child) stream))
  53. (plump:nesting-node (r child))))))
  54. (r node))))
  55. (defun child-text (node tag)
  56. (alexandria:when-let (child (car (get-by-tag node tag)))
  57. (trim-nil (text-with-cdata child))))
  58. (defun clean-text (text)
  59. (when text (trim-nil (plump:text (plump:parse text)))))
  60. (defun fetch-feed-items (feed)
  61. (let ((plump:*tag-dispatchers* plump:*xml-tags*))
  62. (loop for item in (get-by-tag (xml-request (feed-url feed)) "item")
  63. collect (make-feed-item :feed feed
  64. :guid (or (child-text item "guid") (child-text item "link"))
  65. :link (child-text item "link")
  66. :title (clean-text (child-text item "title"))
  67. :description (clean-text (child-text item "description"))
  68. :published
  69. (ignore-errors
  70. (alexandria:when-let
  71. (pub (or (child-text item "pubDate")
  72. (child-text item "published")))
  73. (local-time:universal-to-timestamp
  74. (date-time-parser:parse-date-time pub))))))))
  75. (defun format-feed-item (item)
  76. (format nil "~A~@[ @ ~A~]~%~A~%~A"
  77. (feed-title (feed-item-feed item))
  78. (alexandria:when-let (ts (feed-item-published item))
  79. (local-time:format-timestring
  80. nil ts
  81. :format '((:year 2) "-" (:month 2) "-" (:day 2)
  82. " " (:hour 2) ":" (:min 2))))
  83. (feed-item-title item)
  84. (feed-item-link item)))
  85. (defun feed-next-fetch-unix (feed)
  86. (alexandria:when-let (ts (feed-next-fetch feed))
  87. (local-time:timestamp-to-unix ts)))
  88. (defun feed-item-published-unix (item)
  89. (alexandria:when-let (ts (feed-item-published item))
  90. (local-time:timestamp-to-unix ts)))