/
Main.hs
124 lines (104 loc) · 3.87 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
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}
module Main where
import Config
import IRC
import Log
import Plugins
import Plugins.Commands
import Plugins.Commands.Tell
import Plugins.Karma
import Plugins.Leaked
import Plugins.NixRepl
import Plugins.Pr
import Plugins.Unreg
import Types
import Control.Concurrent.STM
import Control.Concurrent.STM.TMQueue
import Control.Monad.Reader
import Data.Aeson
import Data.Monoid ((<>))
import qualified Data.Set as Set
import Frontend.AMQP
import Frontend.Types
import System.Directory
import System.FilePath
withSharedState :: Config -> (TVar SharedState -> IO a) -> IO a
withSharedState cfg action = do
let path = configStateDir cfg </> "new/shared"
exists <- doesFileExist path
value <- if exists then eitherDecodeFileStrict path >>= \case
Left err -> fail $ "Error decoding shared state file (" ++ path ++ "): " ++ err
Right value -> return value
else return $ SharedState Set.empty
var <- newTVarIO value
result <- action var
finalValue <- readTVarIO var
encodeFile path finalValue
putStrLn "Saved shared state"
return result
main :: IO ()
main = do
config' <- getConfig
logQueue' <- newTMQueueIO
withSharedState config' $ \sharedState' -> do
frontend' <- initFrontend
let env = Env config' logQueue' sharedState' frontend'
flip runReaderT env $ withLogging $
runFrontend onInput
onInput :: Input -> App ()
onInput input = do
traceUser input
ps <- plugins (inputSender input)
_ <- runPlugins ps input
return ()
traceUser :: Input -> App ()
traceUser (inputUser -> user) = do
var <- asks sharedState
new <- liftIO $ atomically $ do
s <- readTVar var
writeTVar var $ s { knownUsers = Set.insert user (knownUsers s) }
return $ not $ Set.member user $ knownUsers s
when new $ logMsg $ "Recorded new user: " <> user
examplePlugin :: Plugin
examplePlugin = Plugin
{ pluginName = "example"
, pluginCatcher = Catched True
, pluginHandler = \Input { inputSender } ->
case inputSender of
Left "infinisil" -> privMsg "infinisil" "I have received your message"
Right ("bottest", user) -> chanMsg "bottest" $ user <> ": I have received your message"
_ -> return ()
}
developFilter :: Plugin
developFilter = Plugin
{ pluginName = "develop-filter"
, pluginCatcher = \Input { inputSender } -> case inputSender of
Left "infinisil" -> PassedOn
Left "gchristensen" -> PassedOn
Right ("bottest", _) -> PassedOn
_ -> Catched True ()
, pluginHandler = const (return ())
}
plugins :: (MonadIO m, MonadReader Env m) => Either User (Channel, User) -> m [Plugin]
plugins sender = do
pluginConfig <- asks (pluginConfigForSender sender . config)
debug <- asks (configDebugMode . config)
let selectedPlugins =
[ developFilter | debug ] ++
[ unregPlugin | enableUnreg pluginConfig ] ++
[ tellSnooper | enableCommands pluginConfig ] ++
[ leakedPlugin | enableLeaked pluginConfig ] ++
[ commandsPlugin' | enableCommands pluginConfig ] ++
[ nixreplPlugin | enableNixrepl pluginConfig ] ++
[ karmaPlugin | enableKarma pluginConfig ] ++
[ prPlugin (configPr pluginConfig) | enablePr pluginConfig ]
--liftIO $ putStrLn $ "For sender " ++ show sender ++ " using plugins " ++ show (map pluginName selectedPlugins)
return selectedPlugins