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:
Simon Brooke 2019-02-18 23:43:27 +00:00
parent cafecd0296
commit 593640e426
6 changed files with 178 additions and 7 deletions

2
.gitignore vendored
View file

@ -18,3 +18,5 @@ profiles.clj
[0-9a-f]*-init\.clj
*.log
env/prod/resources/config\.edn

View file

@ -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"}

View file

@ -1,3 +1,8 @@
{:prod true
: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"}

View file

@ -18,6 +18,7 @@
[cprop "0.1.13"]
[day8.re-frame/http-fx "0.1.6"]
[funcool/struct "1.3.0"]
[http.async.client "1.3.0"]
[com.github.jai-imageio/jai-imageio-core "1.4.0"]
[luminus-immutant "0.2.5"]
[luminus-transit "0.1.1"]
@ -27,6 +28,7 @@
[metosin/muuntaja "0.6.3"]
[metosin/ring-http-response "0.9.1"]
[mount "0.1.16"]
[net.sourceforge.tess4j/tess4j "4.3.1"]
[nrepl "0.6.0"]
[org.clojure/clojure "1.10.0"]
[org.clojure/clojurescript "1.10.520" :scope "provided"]
@ -42,7 +44,8 @@
[ring/ring-core "1.7.1"]
[ring/ring-defaults "0.3.2"]
[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"

View 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))))

View file

@ -34,10 +34,6 @@
;;; 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]
(let [t (Tesseract.)]
(.setDatapath t data-path)