summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authoreltrac <hi@eltr.ac>2026-03-23 22:56:01 +0800
committereltrac <hi@eltr.ac>2026-03-23 22:56:01 +0800
commit538c362a2cc8d01a418ec9aa7f7efd1d37d2977a (patch)
tree58d87a9cabe0ae8ae15b2e83d5dbe9a2065fe1fc /src
parent6a806614c57832179cbc24f923c718b684e7eca3 (diff)
refactor: separate microformats and wrapper from processor
Diffstat (limited to 'src')
-rw-r--r--src/weepinbell/microformats.clj34
-rw-r--r--src/weepinbell/processor.clj83
-rw-r--r--src/weepinbell/wrapper.clj47
3 files changed, 87 insertions, 77 deletions
diff --git a/src/weepinbell/microformats.clj b/src/weepinbell/microformats.clj
new file mode 100644
index 0000000..8fd2545
--- /dev/null
+++ b/src/weepinbell/microformats.clj
@@ -0,0 +1,34 @@
+(ns weepinbell.microformats
+ (:require [clojure.string :refer [includes? lower-case]]
+ [taoensso.timbre :as timbre])
+ (:import [com.kylewm.mf2j Mf2Parser]
+ [java.net URI]))
+
+(defn parse
+ "Parse microformats from a :as digestURL."
+ [url]
+ (let [max-retries 3]
+ (loop [attempt 1]
+ (let [result (try
+ (let [parser (doto (Mf2Parser.)
+ (.setIncludeAlternates true)
+ (.setIncludeRelUrls true))]
+ {:ok true :data (.parse parser (URI. url))})
+ (catch Exception e
+ {:ok false :exception e}))]
+ (if (:ok result)
+ (:data result)
+ (let [e (:exception result)
+ msg (.getMessage e)]
+ (if (and (<= attempt max-retries)
+ (or (instance? java.net.SocketTimeoutException e)
+ (includes? (lower-case (str msg)) "timeout")))
+ (let [wait-time (* attempt 2000)]
+ (timbre/warn (format "MF2 Parsing timeout for %s (attempt %d/%d). Retrying in %dms..."
+ url attempt max-retries wait-time))
+ (Thread/sleep wait-time)
+ (recur (inc attempt)))
+ (do
+ (timbre/warn (format "MF2 Parsing failed for %s after %d attempts. Error: %s"
+ url attempt (or msg "Unknown error")))
+ nil))))))))
diff --git a/src/weepinbell/processor.clj b/src/weepinbell/processor.clj
index 64df22e..413dd31 100644
--- a/src/weepinbell/processor.clj
+++ b/src/weepinbell/processor.clj
@@ -1,18 +1,12 @@
(ns weepinbell.processor
(:require [clojure.core.async :refer [go-loop <!]]
- [clojure.string :refer [includes? lower-case]]
+ [clojure.string :refer [includes?]]
[clj-http.client :as client]
[weepinbell.storage :as storage]
- [weepinbell.utils :refer [validate-content-types
- javamap->map
- file-exists?
- download-file]]
- [weepinbell.config :refer [get-config]]
+ [weepinbell.utils :refer [validate-content-types]]
+ [weepinbell.wrapper :as wrapper]
[weepinbell.webhook :as webhook]
- [taoensso.timbre :as timbre]
- [digest :as digest])
- (:import [com.kylewm.mf2j Mf2Parser]
- [java.net URI]))
+ [taoensso.timbre :as timbre]))
(defn verify-mention
"Get source and follow redirects to check if target link is present."
@@ -32,71 +26,6 @@
(timbre/error "Error verifying mention:" (.getMessage e))
false)))
-(defn parse-microformats
- "Parse microformats from a :as digestURL."
- [url]
- (let [max-retries 3]
- (loop [attempt 1]
- (let [result (try
- (let [parser (doto (Mf2Parser.)
- (.setIncludeAlternates true)
- (.setIncludeRelUrls true))]
- {:ok true :data (.parse parser (URI. url))})
- (catch Exception e
- {:ok false :exception e}))]
- (if (:ok result)
- (:data result)
- (let [e (:exception result)
- msg (.getMessage e)]
- (if (and (<= attempt max-retries)
- (or (instance? java.net.SocketTimeoutException e)
- (clojure.string/includes? (lower-case (str msg)) "timeout")))
- (let [wait-time (* attempt 2000)]
- (timbre/warn (format "MF2 Parsing timeout for %s (attempt %d/%d). Retrying in %dms..."
- url attempt max-retries wait-time))
- (Thread/sleep wait-time)
- (recur (inc attempt)))
- (do
- (timbre/warn (format "MF2 Parsing failed for %s after %d attempts. Error: %s"
- url attempt (or msg "Unknown error")))
- nil))))))))
-
-
-(defn wrap-webmention
- "Parse microformats and collect essential info. Wrap source, target and collected data into a Clojure map and return it."
- [source target]
- (let [mf (-> source
- parse-microformats
- javamap->map
- :items first
- javamap->map)
- wm {:source source
- :target target
- :id (digest/md5 (str source target))
- :modified (System/currentTimeMillis)}
- raw-photo (or (-> mf
- (get-in [:properties "author"])
- first
- (get-in ["properties" "photo"])
- first)
- (-> mf (get-in [:properties "photo"]) first))
- ;; download if found photo
- proxied-photo (if raw-photo
- (let [photo-url (str raw-photo)
- file-ext (or (re-find #"\.(?:jpg|jpeg|png|gif|webp)" photo-url) ".jpg")
- filename (str (digest/md5 photo-url) file-ext)
- local-path (str "data/avatars/" filename)
- public-path (str "/avatars/" filename)]
- ;; download photo if not exists
- (when-not (file-exists? local-path)
- (timbre/info "Downloading microformats photo to proxy external image.")
- (download-file photo-url local-path))
- (str (get-config :base-url) public-path))
- nil)]
- {:webmention wm
- :microformats mf
- :extra {:proxied-photo proxied-photo}}))
-
(defn process-new-webmention
"Store new webmention if verifed. Do nothing otherwise."
[source target]
@@ -104,7 +33,7 @@
(timbre/info "\nšŸ™‹ā€ā™‚ļø Valid webmention received: \n"
"from " source "\n"
"to " target "\n\n")
- (let [data (wrap-webmention source target)]
+ (let [data (wrapper/wrap source target)]
(storage/append-wm data))))
(defn process-existing-webmention
@@ -114,7 +43,7 @@
(if (verify-mention source target)
(do
(storage/update-wm source target
- (wrap-webmention source target))
+ (wrapper/wrap source target))
(timbre/info "Webmention updated."))
(do
(storage/delete-wm source target)
diff --git a/src/weepinbell/wrapper.clj b/src/weepinbell/wrapper.clj
new file mode 100644
index 0000000..f1493c4
--- /dev/null
+++ b/src/weepinbell/wrapper.clj
@@ -0,0 +1,47 @@
+(ns weepinbell.wrapper
+ (:require [weepinbell.utils :refer [javamap->map
+ file-exists?
+ download-file]]
+ [weepinbell.config :refer [get-config]]
+ [weepinbell.microformats :as microformats]
+ [digest :as digest]
+ [taoensso.timbre :as timbre]))
+
+(defn- find-mf-photo [mf]
+ (or (-> mf
+ (get-in [:properties "author"])
+ first
+ (get-in ["properties" "photo"])
+ first)
+ (-> mf (get-in [:properties "photo"]) first)))
+
+(defn- proxy-mf-photo [raw-photo]
+ (if raw-photo
+ (let [photo-url (str raw-photo)
+ file-ext (or (re-find #"\.(?:jpg|jpeg|png|gif|webp)" photo-url) ".jpg")
+ filename (str (digest/md5 photo-url) file-ext)
+ local-path (str "data/avatars/" filename)
+ public-path (str "/avatars/" filename)]
+ ;; download photo if not exists
+ (when-not (file-exists? local-path)
+ (timbre/info "Downloading microformats photo to proxy external image.")
+ (download-file photo-url local-path))
+ (str (get-config :base-url) public-path))
+ nil))
+
+(defn wrap
+ "Parse microformats and collect essential info. Wrap source, target and collected data into a Clojure map and return it."
+ [source target]
+ (let [mf (-> source
+ microformats/parse
+ :items first
+ javamap->map)
+ wm {:source source
+ :target target
+ :id (digest/md5 (str source target))
+ :modified (System/currentTimeMillis)}
+ proxied-photo (-> mf find-mf-photo proxy-mf-photo)]
+ {:webmention wm
+ :microformats mf
+ :extra {:proxied-photo proxied-photo}}))
+