diff --git a/README.md b/README.md index 18590e0..2deb3f0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ ### description -the bot will read the timeline from the specified Twitter accounts, -and post it to Mastodon +the bot will read the timeline from the specified Twitter/Tumblr accounts, and post it to Mastodon ### installation @@ -13,18 +12,28 @@ and post it to Mastodon * create a Mastodon API key following the instructions [here](https://tinysubversions.com/notes/mastodon-bot/) * create a Twitter API key follwing the instructions [here](https://developer.twitter.com/en/docs/basics/authentication/guides/access-tokens) +* create a Tumblr API key following the instructions [here](http://www.developerdrive.com/2014/05/how-to-get-started-with-the-tumblr-api-part-1/) * create a file called `config.edn` with the following contents: ```clojure -{:twitter {:access-keys +{;; add Twitter config to mirror Twitter accounts + :twitter {:access-keys {:consumer_key "XXXX" :consumer_secret "XXXX" :access_token_key "XXXX" :access_token_secret "XXXX"} :accounts ["arstechnica" "WIRED"]} ;; accounts you wish to mirror + ;; add Tumblr config to mirror Tumblr accounts + :tumblr {:access-keys + {:consumer_key "XXXX" + :consumer_secret "XXXX" + :token "XXXX" + :token_secret "XXXX"} + :accounts ["cyberpunky.tumblr.com" "scipunk.tumblr.com"]} :mastodon {:access_token "XXXX" :api_url "https://botsin.space/api/v1/"}} ``` + * the bot looks for `config.edn` at its relative path by default, an alternative location can be specified either using the `MASTODON_BOT_CONFIG` environment variable or passing the path to config as an argument * run the bot: `./mastodon-bot.cljs` diff --git a/mastodon-bot.cljs b/mastodon-bot.cljs index 8ae4871..5f81fed 100755 --- a/mastodon-bot.cljs +++ b/mastodon-bot.cljs @@ -3,20 +3,24 @@ (:require [cljs.core :refer [*command-line-args*]] [cljs.reader :as edn] + [clojure.set :refer [rename-keys]] + [clojure.string :as string] ["fs" :as fs] + ["http" :as http] ["https" :as https] ["mastodon-api" :as mastodon] + ["tumblr" :as tumblr] ["twitter" :as twitter])) -(def config (-> (or (first *command-line-args*) - (-> js/process .-env .-MASTODON_BOT_CONFIG) - "config.edn") - fs/readFileSync - str - edn/read-string)) -(def mastodon-client (mastodon. (-> config :mastodon clj->js))) -(def twitter-client (twitter. (-> config :twitter :access-keys clj->js))) -(def twitter-accounts (-> config :twitter :accounts)) +(defn find-config [] + (or (first *command-line-args*) + (-> js/process .-env .-MASTODON_BOT_CONFIG) + "config.edn")) + +(def config (-> (find-config) fs/readFileSync str edn/read-string)) + +(def mastodon-client (or (some-> config :mastodon clj->js mastodon.) + (js/console.error "missing Mastodon client configuration!"))) (defn js->edn [data] (js->clj data :keywordize-keys true)) @@ -41,7 +45,7 @@ (post-status-with-images status-text urls [])) ([status-text [url & urls] ids] (if url - (.get https url + (.get (if (string/starts-with? url "https://") https http) url (fn [image-stream] (post-image image-stream status-text #(post-status-with-images status-text urls (conj ids %))))) (post-status status-text (not-empty ids))))) @@ -49,6 +53,12 @@ (defn get-mastodon-timeline [callback] (.then (.get mastodon-client "timelines/home" #js {}) #(-> % .-data js->edn callback))) +(defn post-items [last-post-time items] + (doseq [{:keys [text media-links]} (filter #(> (:created-at %) last-post-time) items)] + (if media-links + (post-status-with-images text media-links) + (post-status text)))) + (defn parse-tweet [{created-at :created_at text :text {:keys [media]} :extended_entities @@ -57,20 +67,43 @@ :text (str text "\n - " screen_name) :media-links (keep #(when (= (:type %) "photo") (:media_url_https %)) media)}) +(defmulti parse-tumblr-post :type) + +(defmethod parse-tumblr-post "text" [{:keys [body date short_url]}] + {:created-at (js/Date. date) + :text (str body "\n\n" short_url)}) + +(defmethod parse-tumblr-post "photo" [{:keys [caption date photos short_url] :as post}] + {:created-at (js/Date. date) + :text (string/join "\n" [(string/replace caption #"<[^>]*>" "") short_url]) + :media-links (mapv #(-> % :original_size :url) photos)}) + +(defmethod parse-tumblr-post :default [post] + (:type post)) + +(defn post-tumblrs [last-post-time] + (fn [err response] + (->> response + js->edn + :posts + (mapv parse-tumblr-post) + (post-items last-post-time)))) + (defn post-tweets [last-post-time] (fn [error tweets response] - (when tweets - (doseq [{:keys [text media-links]} (->> (js->edn tweets) - (map parse-tweet) - (filter #(> (:created-at %) last-post-time)))] - (if media-links - (post-status-with-images text media-links) - (post-status text)))))) + (->> (js->edn tweets) + (map parse-tweet) + (post-items last-post-time)))) (get-mastodon-timeline (fn [timeline] - (doseq [account twitter-accounts] - (.get twitter-client - "statuses/user_timeline" - #js {:screen_name account :include_rts false} - (-> timeline first :created_at (js/Date.) post-tweets))))) + (let [last-post-time (-> timeline first :created_at (js/Date.))] + (when-let [twitter-client (some-> config :twitter :access-keys clj->js twitter.)] + (doseq [account (-> config :twitter :accounts)] + (.get twitter-client + "statuses/user_timeline" + #js {:screen_name account :include_rts false} + (post-tweets last-post-time)))) + (when-let [tumblr-oauth (some-> config :tumblr :access-keys clj->js)] + (when-let [tumblr-client (some-> config :tumblr :accounts first (tumblr/Blog. tumblr-oauth))] + (.posts tumblr-client #js {:limit 5} (post-tumblrs last-post-time))))))) diff --git a/package.json b/package.json index 35cece9..9c94560 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "dependencies": { "lumo-cljs": "^1.8.0", "mastodon-api": "1.3.0", + "tumblr": "0.4.1", "twitter": "1.7.1" }, "scripts": {