commit 75b679a5331ec08062b4adec342771b26fc8915a Author: dmitri.sotnikov@gmail.com Date: Sun Mar 11 17:05:25 2018 -0400 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..55ab5df --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +config.edn +package-lock.json +/node_modules diff --git a/README.md b/README.md new file mode 100644 index 0000000..d16e6fb --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +### description + +the bot will read the timeline from the specified Twitter accounts, +and post it to Mastodon + +### installation + +1. install [Node.js](https://nodejs.org/en/) +2. install [Lumo](https://github.com/anmonteiro/lumo): `npm install -g lumo-cljs` +3. run `npm install` to install Node modules + +### usage + +* 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 file called `config.edn` with the following contents: + +```clojure +{: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 + :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` +* to poll at intervals setup a cron job such as: + + */30 * * * * mastodon-bot.cljs /path/to/config.edn > /dev/null 2>&1 diff --git a/mastodon-bot.cljs b/mastodon-bot.cljs new file mode 100755 index 0000000..8ae4871 --- /dev/null +++ b/mastodon-bot.cljs @@ -0,0 +1,76 @@ +#!/usr/bin/env lumo +(ns mastodon-bot.core + (:require + [cljs.core :refer [*command-line-args*]] + [cljs.reader :as edn] + ["fs" :as fs] + ["https" :as https] + ["mastodon-api" :as mastodon] + ["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 js->edn [data] + (js->clj data :keywordize-keys true)) + +(defn delete-status [status] + (.delete mastodon-client (str "statuses/" status) #js {})) + +(defn post-status + ([status-text] + (post-status status-text nil)) + ([status-text media-ids] + (.post mastodon-client "statuses" + (clj->js (merge {:status status-text} + (when media-ids {:media_ids media-ids})))))) + +(defn post-image [image-stream description callback] + (-> (.post mastodon-client "media" #js {:file image-stream :description description}) + (.then #(-> % .-data .-id callback)))) + +(defn post-status-with-images + ([status-text urls] + (post-status-with-images status-text urls [])) + ([status-text [url & urls] ids] + (if url + (.get https 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))))) + +(defn get-mastodon-timeline [callback] + (.then (.get mastodon-client "timelines/home" #js {}) #(-> % .-data js->edn callback))) + +(defn parse-tweet [{created-at :created_at + text :text + {:keys [media]} :extended_entities + {:keys [screen_name]} :user :as tweet}] + {:created-at (js/Date. created-at) + :text (str text "\n - " screen_name) + :media-links (keep #(when (= (:type %) "photo") (:media_url_https %)) media)}) + +(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)))))) + +(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))))) diff --git a/package.json b/package.json new file mode 100644 index 0000000..23529e3 --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "name": "mastodon-bot", + "version": "0.0.1", + "repository": "https://github.com/yogthos/mastodon-bot", + "license": "MIT", + "dependencies": { + "mastodon-api": "1.3.0", + "twitter": "1.7.1" + } +}