(ns micro-blog.blue-sky (:require [clj-http.client :as http-client] [micro-blog.pocket-base :as pb] [micro-blog.utils :as utils] [malli.core :as m] [clojure.pprint :refer [pprint]] [micro-blog.config :refer [config]])) (defn create-session [] (let [identifier (@config :blue-sky-username) api-key (@config :blue-sky-api-key) body {:identifier identifier :password api-key} url (str (@config :blue-sky-host) "/com.atproto.server.createSession") res-schema [:map [:did string?] [:accessJwt string?]]] (-> (http-client/post url {:form-params body :content-type :json :as :json}) :body (utils/validate-with-throw res-schema) (#(assoc % :access-jwt (:accessJwt %))) (select-keys [:did :access-jwt])))) (def post-res-schema [:map [:cursor [:maybe :string]] [:feed [:vector [:map [:post [:map [:cid :string] [:author [:map [:handle :string]]] [:embed {:optional true} [:map [:images {:optional true} [:vector [:map [:fullsize :string] [:alt :string]]]]]] [:record [:map [:facets {:optional true} [:vector [:map [:features [:vector [:map [:$type :string] [:tag {:optional true} [:maybe :string]]]]]]]] [:createdAt :string]]]]]]]]]) (defn get-posts-until-id ([session id] (get-posts-until-id session id nil [])) ([session id cursor prev-posts] (let [limit 5 body (-> (http-client/get (str (@config :blue-sky-host) "/app.bsky.feed.getAuthorFeed") {:headers {"Authorization" (str "Bearer " (session :access-jwt))} :query-params (cond-> {:actor (:did session) :limit limit} cursor (assoc :cursor cursor)) :content-type :json :as :json}) :body (utils/validate-with-throw post-res-schema)) posts (map :post (:feed body)) new-cursor (:cursor body) new-posts (take-while #(not= (:cid %) id) posts) new-and-prev-posts (concat new-posts prev-posts)] (cond ;; end of posts (not= (count posts) limit) new-and-prev-posts ;; found post (some #(= id (:cid %)) posts) new-and-prev-posts ;; recur :else (recur session id new-cursor new-and-prev-posts))))) ;; TODO: create post ;; const data = { ;; "remoteId": "test", ;; "authorId": "test", ;; "posted": "2022-01-01 10:00:00.123Z", ;; "source": "pleroma", ;; "tags": [ ;; "RELATION_RECORD_ID" ;; ], ;; "fullPost": "JSON", ;; "images": [ ;; "RELATION_RECORD_ID" ;; ] ;; }; ;; (defn extract-tags [post] (let [facets (get (post :record) :facets []) features (flatten (map :features facets)) tag-features (filter #(= (:$type %) "app.bsky.richtext.facet#tag") features) tags (map :tag tag-features)] tags)) (defn extract-images [post] (let [images (get-in post [:embed :images] [])] (map #(vector (:fullsize %) (:alt %)) images))) (defn run [] (let [session (create-session) last-saved-id (pb/get-latest-post-remote-id-by-source :blue_sky) new-posts (reverse (get-posts-until-id session last-saved-id))] (->> new-posts (map #(hash-map :source :blue_sky :fullPost % :remoteId (:cid %) :authorId (get-in % [:author :handle]) :tags (extract-tags %) :images (extract-images %) :posted (get-in % [:record :createdAt]))) (map pb/save-post))))