/
Main.hs
155 lines (134 loc) · 5.72 KB
/
Main.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
module Main where
import Control.Applicative
import Control.Concurrent (threadDelay)
import Control.Lens
import Control.Monad (when,forever)
import Control.Monad.Catch (try)
import Control.Monad.IO.Class
import Control.Monad.Logger
import Control.Monad.Reader (ask)
import Control.Monad.State (modify)
import Control.Monad.Trans.Resource
import Data.Acid
import Data.SafeCopy
import Data.String.Utils
import Data.Typeable
import Language.Haskell.Interpreter (runInterpreter)
import Mueval.ArgsParse
import Mueval.Interpreter
import Web.Twitter.Conduit
import Web.Twitter.Types.Lens
import qualified Data.Conduit as C
import qualified Data.Conduit.List as CL
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Tokens
import Common
-- Strip off the first world (which is assumed to be the screenname of the
-- bot).
getHaskellExpression :: T.Text -> T.Text
getHaskellExpression t =
case T.breakOn " " $ T.strip t of
(a, "") -> a
(_, rest) -> T.strip rest
isHaskellPost :: T.Text -> Status -> Bool
isHaskellPost userName status =
(T.isPrefixOf userName $ status ^. statusText) &&
(status ^. statusUser ^. userScreenName) /= botScreenName
evalExpr :: String -> IO String
evalExpr e =
case getOptions ["--expression", e] of
Left t@(b, e) -> return $ show t
Right opts -> do
r <- runInterpreter (interpreter opts)
case r of
Left err -> return $ show err
Right (e,et,val) -> do (out, b) <- getResult val
return out
getResult :: (Functor m, MonadIO m) => String -> m (String, Bool)
getResult str = render 1024 str
statusToText :: Status -> T.Text
statusToText status = T.concat [ T.pack . show $ status ^. statusId
, ": "
, status ^. statusUser . userScreenName
, ": "
, status ^. statusText
]
evalExpression :: MonadIO m => Status -> m String
evalExpression status = do
r <- liftIO $ evalExpr $ decodeHtml $ T.unpack $ getHaskellExpression $ status ^. statusText
return $ take 140 r
{-TODO: Make this more comprehensive-}
decodeHtml :: String -> String
decodeHtml s =
replace "<" "<" $
replace ">" ">" $
replace "&" "&" $
replace """ "\"" $
replace "'" "'" $ s
-- res <- call $ update "Hello World"
reply :: Integer -> T.Text -> APIRequest StatusesUpdate Status
reply i s =
Web.Twitter.Conduit.update s & inReplyToStatusId ?~ i
postreply :: (MonadResource m, MonadLogger m) => Status -> Integer -> String -> TW m Status
postreply status i res = call (reply i $ (T.take 140 $
T.concat ["@",
status ^. statusUser ^. userScreenName,
" ",
T.pack res]))
{-Acid State database to keep track of replies-}
data TweetId = TweetId { tweetId :: Integer }
deriving (Eq, Show, Typeable)
data LambdaTwitDb = LambdaTwitDb { allReplyIds :: [TweetId] }
deriving (Typeable)
allReplies :: Query LambdaTwitDb [TweetId]
allReplies = allReplyIds <$> ask
addReply :: TweetId -> Update LambdaTwitDb ()
addReply tweetId = modify go
where go (LambdaTwitDb db) = LambdaTwitDb $ tweetId : db
{-The Acid State magic-}
deriveSafeCopy 0 'base ''TweetId
deriveSafeCopy 0 'base ''LambdaTwitDb
makeAcidic ''LambdaTwitDb ['allReplies, 'addReply]
conduitmain :: IO ()
conduitmain = do
state <- openLocalState (LambdaTwitDb [])
forever $ do
{-TODO: Use Data.Configurator to read in the oauth keys without needing a recompile:
- http://hackage.haskell.org/package/configurator-}
runNoLoggingT . runTwitterFromEnv $ do
sourceWithMaxId mentionsTimeline
C.$= CL.isolate 100
C.$$ CL.mapM_ $ \status -> do
replies <- liftIO $ query state AllReplies
if ((TweetId (status ^. statusId)) `elem` replies)
then do
liftIO $ putStrLn "Already replied to:"
liftIO $ T.putStrLn $ statusToText status
liftIO $ threadDelay $ 60 * 1000000
else do
when (isHaskellPost botScreenName status) $ do
liftIO $ T.putStrLn $ statusToText status
res <- evalExpression status
liftIO $ putStrLn res
postres <- try $ postreply status (status ^. statusId) res
case postres of
Left (FromJSONError e) -> liftIO $ print e
Left (TwitterErrorResponse s resH errs) ->
liftIO $ print errs
Left (TwitterStatusError s resH val) ->
liftIO $ print val
Right status -> liftIO $ print $ statusToText status
liftIO $ Data.Acid.update state (AddReply $ TweetId (status ^. statusId))
-- AA TODO: Better rate limiting, this probably blocks every tweet.
-- We should only wait for 60 seconds after each mentionsTimeline grab
liftIO $ threadDelay $ 60 * 1000000
main :: IO ()
main = conduitmain
{-TODO: Import lens:-}
{-https://twitter.com/relrod6/status/516785803100688384-}