summaryrefslogtreecommitdiffstats
path: root/Hirc/Bot.hs
blob: 9cdeeaf19f71304d4380962de91e88b4c2b3ce81 (plain)
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
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}

module Hirc.Bot where

import Prelude hiding (read)
import Network
import System.IO
import Control.Concurrent.STM
import Control.Monad.Reader
import Control.Exception
import Hirc.Parser as P
import Hirc.Types
import Text.Parsec (parse)

runBot :: Config -> Hooks -> IO ()
runBot Config{..} Hooks{..} =
    bracket (connect config_server) disconnect run
  where
    connect Server{..} = do
      socket <- connectTo hostname (PortNumber (fromIntegral port))
      nick <- atomically $ newTVar config_nick
      chan <- atomically $ newTVar config_chan
      hSetBuffering socket NoBuffering
      return Bot {
        bot_server = config_server,
        bot_nick = nick,
        bot_chan = chan,
        bot_socket = socket
      }

    disconnect bot@Bot{bot_socket=h} = do
        hClose h
        runReaderT hooks_onDisconnect bot

    run bot = do
      runReaderT (hooks_onConnect >> receive hooks_onMessage hooks_onError) bot

    receive :: (Message -> Net ()) -> (Error -> Net ()) -> Net ()
    receive onMessage onError = do
      server <- asks bot_server
      socket <- asks bot_socket
      forever $ do
        s <- init <$> liftIO (hGetLine socket)
        case parse P.message (show server) s of
          Right m -> do
              case m of
                  Message _ "PING" [x] -> do
                      h <- asks bot_socket
                      liftIO $ hPutStr h $ "PONG :" ++ x ++ "\r\n"
                  _ -> return ()
              onMessage m
          e -> onError $ BadMessage $ show e -- TODO

atomic :: (Bot -> a) -> (a -> STM b) -> Net b
atomic v f = asks v >>= liftIO . atomically . f

read :: (Bot -> TVar a) -> Net a
read v = atomic v readTVar

write :: (Bot -> TVar a) -> a -> Net ()
write v = atomic v . flip writeTVar


bumpNick :: Net String
bumpNick =
    atomic bot_nick $ flip updateTVar $ \nick ->
      case parse P.nickNum "bumpNick" nick of
        Right (n,i) -> n ++ show (i+1)
        _ -> nick ++ "_"


-- Like modifyTVar but returns the new value.
updateTVar :: TVar a -> (a -> a) -> STM a
updateTVar v f = do
    x <- readTVar v
    let x' = f x
    writeTVar v x'
    return x'