Added responder bot
This compiles, but I can't test it yet because I don't have an API key.
This commit is contained in:
parent
cafecd0296
commit
593640e426
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -18,3 +18,5 @@ profiles.clj
|
||||||
[0-9a-f]*-init\.clj
|
[0-9a-f]*-init\.clj
|
||||||
|
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
env/prod/resources/config\.edn
|
||||||
|
|
7
env/dev/resources/config.edn
vendored
7
env/dev/resources/config.edn
vendored
|
@ -1 +1,6 @@
|
||||||
{:tess-data "/usr/local/Cellar/tesseract/4.0.0_1/share/tessdata/"}
|
{:tess-data "/usr/local/Cellar/tesseract/4.0.0_1/share/tessdata/"
|
||||||
|
:app_key "applicationkey"
|
||||||
|
:app_secret "applicationsecret"
|
||||||
|
:user_token "simon-brooke-ireadit"
|
||||||
|
:user_secret "somesortofnonsense"
|
||||||
|
:bot_account "IReadIt"}
|
||||||
|
|
7
env/prod/resources/config.edn
vendored
7
env/prod/resources/config.edn
vendored
|
@ -1,3 +1,8 @@
|
||||||
{:prod true
|
{:prod true
|
||||||
:port 8889
|
:port 8889
|
||||||
:tess-data "/usr/share/tesseract-ocr/tessdata"}
|
:tess-data "/usr/share/tesseract-ocr/tessdata"
|
||||||
|
:app_key "applicationkey"
|
||||||
|
:app_secret "applicationsecret"
|
||||||
|
:user_token "simon-brooke-ireadit"
|
||||||
|
:user_secret "somesortofnonsense"
|
||||||
|
:bot_account "IReadIt"}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
[cprop "0.1.13"]
|
[cprop "0.1.13"]
|
||||||
[day8.re-frame/http-fx "0.1.6"]
|
[day8.re-frame/http-fx "0.1.6"]
|
||||||
[funcool/struct "1.3.0"]
|
[funcool/struct "1.3.0"]
|
||||||
|
[http.async.client "1.3.0"]
|
||||||
[com.github.jai-imageio/jai-imageio-core "1.4.0"]
|
[com.github.jai-imageio/jai-imageio-core "1.4.0"]
|
||||||
[luminus-immutant "0.2.5"]
|
[luminus-immutant "0.2.5"]
|
||||||
[luminus-transit "0.1.1"]
|
[luminus-transit "0.1.1"]
|
||||||
|
@ -27,6 +28,7 @@
|
||||||
[metosin/muuntaja "0.6.3"]
|
[metosin/muuntaja "0.6.3"]
|
||||||
[metosin/ring-http-response "0.9.1"]
|
[metosin/ring-http-response "0.9.1"]
|
||||||
[mount "0.1.16"]
|
[mount "0.1.16"]
|
||||||
|
[net.sourceforge.tess4j/tess4j "4.3.1"]
|
||||||
[nrepl "0.6.0"]
|
[nrepl "0.6.0"]
|
||||||
[org.clojure/clojure "1.10.0"]
|
[org.clojure/clojure "1.10.0"]
|
||||||
[org.clojure/clojurescript "1.10.520" :scope "provided"]
|
[org.clojure/clojurescript "1.10.520" :scope "provided"]
|
||||||
|
@ -42,7 +44,8 @@
|
||||||
[ring/ring-core "1.7.1"]
|
[ring/ring-core "1.7.1"]
|
||||||
[ring/ring-defaults "0.3.2"]
|
[ring/ring-defaults "0.3.2"]
|
||||||
[selmer "1.12.6"]
|
[selmer "1.12.6"]
|
||||||
[net.sourceforge.tess4j/tess4j "4.3.1"]]
|
[twitter-api "1.8.0"]
|
||||||
|
[twitter-streaming-client "0.3.3"]]
|
||||||
|
|
||||||
:min-lein-version "2.0.0"
|
:min-lein-version "2.0.0"
|
||||||
|
|
||||||
|
|
160
src/clj/ireadit/bot/responder.clj
Normal file
160
src/clj/ireadit/bot/responder.clj
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
(ns ^{:doc "Meme transcriber: Twitter bot"
|
||||||
|
:author "Simon Brooke"}
|
||||||
|
ireadit.bot.responder
|
||||||
|
(:require [clojure.data.json :as json]
|
||||||
|
[http.async.client :as ac]
|
||||||
|
[clojure.edn :as edn]
|
||||||
|
[clojure.java.io :as io]
|
||||||
|
[clojure.tools.logging :as log]
|
||||||
|
[ireadit.config :refer [env]]
|
||||||
|
[ireadit.tesseractor :as tess]
|
||||||
|
[twitter.api.restful :as tar]
|
||||||
|
;; [twitter.api.streaming :as tas]
|
||||||
|
;; [twitter.callbacks :as tc]
|
||||||
|
;; [twitter.callbacks.handlers :as tch]
|
||||||
|
[twitter.oauth :as to]
|
||||||
|
[twitter-streaming-client.core :as client]
|
||||||
|
;; )
|
||||||
|
;; (:import
|
||||||
|
;; ;; (twitter.callbacks.protocols SyncSingleCallback)
|
||||||
|
;; (twitter.callbacks.protocols SyncStreamingCallback)))
|
||||||
|
))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;;;;
|
||||||
|
;;;; ireadit.bot.responder: Twitter bot.
|
||||||
|
;;;;
|
||||||
|
;;;; This program is free software; you can redistribute it and/or
|
||||||
|
;;;; modify it under the terms of the GNU General Public License
|
||||||
|
;;;; as published by the Free Software Foundation; either version 2
|
||||||
|
;;;; of the License, or (at your option) any later version.
|
||||||
|
;;;;
|
||||||
|
;;;; This program is distributed in the hope that it will be useful,
|
||||||
|
;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
;;;; GNU General Public License for more details.
|
||||||
|
;;;;
|
||||||
|
;;;; You should have received a copy of the GNU General Public License
|
||||||
|
;;;; along with this program; if not, write to the Free Software
|
||||||
|
;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
||||||
|
;;;; USA.
|
||||||
|
;;;;
|
||||||
|
;;;; Copyright (C) 2016 Simon Brooke for Radical Independence Campaign
|
||||||
|
;;;;
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
;;; partially cribbed from https://github.com/kelseyq/clojure-twitter-bot
|
||||||
|
|
||||||
|
(def credentials
|
||||||
|
(to/make-oauth-creds (:app_key env)
|
||||||
|
(:app_secret env)
|
||||||
|
(:user_token env)
|
||||||
|
(:user_secret env)))
|
||||||
|
|
||||||
|
(def mentions-stream
|
||||||
|
(client/create-twitter-stream twitter.api.streaming/user-stream
|
||||||
|
:oauth-creds credentials :params {:with "user"}))
|
||||||
|
|
||||||
|
(defn do-every
|
||||||
|
[ms callback]
|
||||||
|
(loop []
|
||||||
|
(do
|
||||||
|
(Thread/sleep ms)
|
||||||
|
(try (callback)
|
||||||
|
(catch Exception e (log/error e (str "caught exception: " (.getMessage e))))))
|
||||||
|
(recur)))
|
||||||
|
|
||||||
|
(defn is-legit-mention?
|
||||||
|
[tweetMap]
|
||||||
|
(not (or (empty? tweetMap)
|
||||||
|
(= (get-in tweetMap [:user :screen_name]) (:bot-account env))
|
||||||
|
(and (= (get-in tweetMap [:retweeted_status :user :screen_name]) (:bot-account env))
|
||||||
|
(.startsWith (get-in tweetMap [:retweeted_status :text]) "@")))))
|
||||||
|
|
||||||
|
;; if the mention is in response to a tweet, then it will have a top level
|
||||||
|
;; `in_reply_to_status_id_str` attribute. It is the tweet whose id is the
|
||||||
|
;; value of this attribute which is the one which probably contains the image.
|
||||||
|
(defn tweet-replied-to
|
||||||
|
"Given this `tweet`, assumed to be a map representing a JSON representation
|
||||||
|
of a tweet, return an equivalent representation of the tweet to which it was
|
||||||
|
a reply. If `tweet` was not a reply, returns `nil`."
|
||||||
|
[tweet]
|
||||||
|
(let [parent-id (:in_reply_to_status_id_str tweet)]
|
||||||
|
(if
|
||||||
|
parent-id
|
||||||
|
(tar/statuses-show :oauth-creds credentials
|
||||||
|
:params {:id parent-id
|
||||||
|
:include-entities true}))))
|
||||||
|
|
||||||
|
(defn image-from-tweet
|
||||||
|
"Return the url of a media entity from this `tweet`, assumed to be a map
|
||||||
|
representing a JSON representation of a tweet,; if `index` is not
|
||||||
|
specified, the first media entity; else the indexth. If no such entity
|
||||||
|
exists, returns `nil`."
|
||||||
|
([tweet]
|
||||||
|
(image-from-tweet tweet 0))
|
||||||
|
([tweet index]
|
||||||
|
(:media-url (get-in tweet (nth [:entities :media] index)))))
|
||||||
|
|
||||||
|
(defn truncate
|
||||||
|
"If string `s` is longer than `n` characters, return a string like `s`
|
||||||
|
truncated to `n` characters and with an added trailing ellipsis."
|
||||||
|
[s n]
|
||||||
|
(if (> (count s) n)
|
||||||
|
(str (subs s 0 (min (count s) n)) "…")
|
||||||
|
s))
|
||||||
|
|
||||||
|
(defn reply-to-mention
|
||||||
|
"From this `mention`, assumed to be a map representing a JSON representation
|
||||||
|
of a tweet, extract the tweet replied to, and, from that,
|
||||||
|
extract the first media entity if present; pass the entity to the
|
||||||
|
tesseractor, and post a reply based on its response."
|
||||||
|
[mention]
|
||||||
|
(let [screen-name (str "@" (get-in mention [:user :screen_name]))
|
||||||
|
image (image-from-tweet (tweet-replied-to [mention]))
|
||||||
|
trascription (if image
|
||||||
|
(try
|
||||||
|
(str "Hi, "
|
||||||
|
screen-name
|
||||||
|
", I read it as '"
|
||||||
|
(tess/ocr image)
|
||||||
|
"'.")
|
||||||
|
(catch Exception e
|
||||||
|
(log/error
|
||||||
|
e
|
||||||
|
(str "error transribing image " image))
|
||||||
|
(str "I'm sorry, "
|
||||||
|
screen-name
|
||||||
|
", I'm afraid I can't do that.")))
|
||||||
|
"I'm sorry, I didn't find an image in that tweet.")]
|
||||||
|
(log/debug (str "replying to mention from " name))
|
||||||
|
(try
|
||||||
|
(statuses-update :oauth-creds credentials
|
||||||
|
:params {:status (truncate transcription 279)
|
||||||
|
:in_reply_to_status_id (:id_str mention)},
|
||||||
|
:callbacks (SyncSingleCallback. response-return-body
|
||||||
|
response-throw-error
|
||||||
|
exception-rethrow))
|
||||||
|
(catch Exception e (log/error e (str "error replying to mention from " (get-in mention [:user :screen_name])))))))
|
||||||
|
|
||||||
|
(defn reply-to-mentions
|
||||||
|
[user-mentions]
|
||||||
|
(when-let [mentions (seq (->> user-mentions
|
||||||
|
(filter is-legit-mention?)))]
|
||||||
|
(doseq [m mentions] (reply-to-mention m))))
|
||||||
|
|
||||||
|
(defn handle-user-stream
|
||||||
|
[]
|
||||||
|
(let [stream (client/retrieve-queues mentions-stream)
|
||||||
|
user-events (:unknown stream)
|
||||||
|
mentions (:tweet stream)]
|
||||||
|
(reply-to-mentions mentions)))
|
||||||
|
|
||||||
|
(defn bot []
|
||||||
|
(let [state (atom (assoc empty-state :minutes-since-update 30))
|
||||||
|
previous (atom #{})]
|
||||||
|
|
||||||
|
(start-streams)
|
||||||
|
|
||||||
|
(future (log/debug "STARTING USER STREAM")
|
||||||
|
(do-every 60500 handle-user-stream))))
|
|
@ -34,10 +34,6 @@
|
||||||
|
|
||||||
;;; Cribbed partly from https://github.com/hugoArregui/tesseract-clojure
|
;;; Cribbed partly from https://github.com/hugoArregui/tesseract-clojure
|
||||||
|
|
||||||
;; (def tesseract-data-dir "/usr/share/tessdata")
|
|
||||||
;; (def language "eng")
|
|
||||||
;; (def test-file "eurotext.png")
|
|
||||||
|
|
||||||
(defn prepare-tesseract [data-path]
|
(defn prepare-tesseract [data-path]
|
||||||
(let [t (Tesseract.)]
|
(let [t (Tesseract.)]
|
||||||
(.setDatapath t data-path)
|
(.setDatapath t data-path)
|
||||||
|
|
Loading…
Reference in a new issue