summaryrefslogtreecommitdiff
path: root/Hsbot
diff options
context:
space:
mode:
Diffstat (limited to 'Hsbot')
-rw-r--r--Hsbot/Plugin/Quote.hs149
1 files changed, 149 insertions, 0 deletions
diff --git a/Hsbot/Plugin/Quote.hs b/Hsbot/Plugin/Quote.hs
new file mode 100644
index 0000000..92a4b13
--- /dev/null
+++ b/Hsbot/Plugin/Quote.hs
@@ -0,0 +1,149 @@
+{-# LANGUAGE TypeFamilies, DeriveDataTypeable, TemplateHaskell #-}
+-- | This module is an IRC plugin that manages quotes for posterity and legend
+module Hsbot.Plugin.Quote
+-- ( quote
+-- , theQuote
+ () where
+
+import Control.Monad.Reader
+import Control.Monad.State
+import Data.Acid
+import Data.SafeCopy
+import Data.Time
+import Data.Time.Clock.POSIX
+import Data.Typeable
+import System.Random
+import qualified Data.Map as M
+import qualified Network.IRC as IRC
+import System.Environment.XDG.BaseDir
+
+import Hsbot.Message
+import Hsbot.Types
+
+-- | A quote element
+data QuoteElt = QuoteElt
+ { eltQuotee :: IRC.UserName
+ , eltQuote :: String
+ } deriving (Show, Typeable)
+
+type QuoteID = Int
+
+-- | A quote object
+data Quote = Quote
+ { quoter :: IRC.UserName
+ , quotE :: [QuoteElt]
+ , quoteTime :: UTCTime
+ , votes :: Int
+ , voters :: M.Map IRC.UserName QuoteID
+ } deriving (Show, Typeable)
+
+emptyQuote :: Quote
+emptyQuote = Quote { quoter = ""
+ , quotE = []
+ , quoteTime = posixSecondsToUTCTime 0
+ , votes = 0
+ , voters = M.empty }
+
+-- The Quote database
+data QuoteDB = QuoteDB
+ { nextQuoteId :: QuoteID
+ , quoteBotDB :: M.Map QuoteID Quote
+ , lockedQuotes :: M.Map QuoteID (IRC.UserName, UTCTime)
+ , lastActive :: M.Map IRC.Channel (QuoteID, UTCTime)
+ } deriving (Show, Typeable)
+
+emptyQuoteDB :: QuoteDB
+emptyQuoteDB = QuoteDB { nextQuoteId = 0
+ , quoteBotDB = M.empty
+ , lockedQuotes = M.empty
+ , lastActive = M.empty }
+
+$(deriveSafeCopy 0 'base ''QuoteElt)
+$(deriveSafeCopy 0 'base ''Quote)
+$(deriveSafeCopy 0 'base ''QuoteDB)
+
+-- | Quote database transactions
+getQuote :: QuoteID -> Query QuoteDB (Maybe Quote)
+getQuote quoteId = asks quoteBotDB >>= return . M.lookup quoteId
+
+getQuoteDB :: Query QuoteDB (M.Map QuoteID Quote)
+getQuoteDB = asks quoteBotDB
+
+-- TODO : a function for cleaning locks
+
+isQuoteLockedFor :: QuoteID -> IRC.UserName -> UTCTime -> Query QuoteDB (Either String Bool)
+isQuoteLockedFor quoteId requestor now = do
+ theQuote <- asks quoteBotDB >>= return . M.lookup quoteId
+ case theQuote of
+ Just quote -> do
+ currentLock <- asks lockedQuotes >>= return . M.lookup quoteId
+ case currentLock of
+ Just (owner, lockStamp) ->
+ if owner == requestor
+ then return $ Right True
+ else return . Right $ (addUTCTime 300 lockStamp > now) -- Is the entry older than 5 min?
+ Nothing -> return $ Right True
+ Nothing -> return $ Left "QuoteId not found"
+
+lockQuoteIdFor :: QuoteID -> IRC.UserName -> UTCTime -> Update QuoteDB ()
+lockQuoteIdFor quoteId requestor now = get >>= \db -> put db { lockedQuotes = M.insert quoteId (requestor, now) (lockedQuotes db) }
+
+$(makeAcidic ''QuoteDB ['getQuote, 'getQuoteDB, 'isQuoteLockedFor, 'lockQuoteIdFor])
+
+-- | gets a random quote from the database
+getRandomQuote :: AcidState QuoteDB -> IO (Maybe Quote)
+getRandomQuote quoteDB = do
+ db <- query' quoteDB GetQuoteDB
+ if M.size db > 0
+ then getStdRandom (randomR (0, M.size db - 1)) >>= \rInt -> return . Just . snd $ M.elemAt rInt db
+ else return Nothing
+
+-- | The duck plugin identity
+quote :: PluginId
+quote = PluginId
+ { pluginName = "quote"
+ , pluginEp = theQuote }
+
+-- | An IRC plugin that generates and kills ducks
+theQuote :: Plugin (Env IO) () -- TODO : an argument for the history size
+theQuote = do
+ baseDir <- liftIO $ System.Environment.XDG.BaseDir.getUserDataDir "hsbot"
+ quoteDB <- liftIO $ openAcidStateFrom (baseDir ++ "/quoteDB/") emptyQuoteDB
+ forever $ readMsg >>= eval quoteDB
+ where
+ eval :: AcidState QuoteDB -> Message -> Plugin (Env IO) ()
+ eval quoteDB (IncomingMsg msg)
+ | IRC.msg_command msg == "PRIVMSG" = do
+ cmdArgs <- lift $ getCommand msg
+ case cmdArgs of
+ "quote":"help":"append":_ -> answerMsg msg "quote append QUOTEID QUOTEE QUOTE"
+ "quote":"help":"delete":_ -> do
+ answerMsg msg "quote delete QUOTEID [ELTID] :"
+ answerMsg msg $ concat [ " If an ELTID is provided, deletes the ELTID's line (starting from zero) "
+ , "in the quote QUOTEID. If not the whole quote is deleted." ]
+ "quote":"help":"quick":_ -> do
+ answerMsg msg "quote [quick] QUOTEE [QUOTE] :"
+ answerMsg msg $ concat [ " Begins a quote for QUOTEE. You must provide the keywork quick if the "
+ , "QUOTEE's nickname is a reserved word for this quote module. If no QUOTE is "
+ , "provided this module lookup it's conversation history and records the "
+ , "last sentence of QUOTEE." ]
+ "quote":"help":"show":_ -> answerMsg msg "quote show { QUOTEID | random [MIN_SCORE] }"
+ "quote":"help":"stat":_ -> do
+ answerMsg msg "quote stat"
+ answerMsg msg " Compute statistics about the quote database : Most quoters, most quoted "
+ "quote":"help":[] -> do
+ answerMsg msg $ concat [ "Usage: quote { [quick] QUOTEE [QUOTE] | append QUOTEID QUOTEE QUOTE | "
+ , "delete QUOTEID [ELTID] | show { QUOTEID | random [MIN_SCORE] } | stat }" ]
+ "quote":"help":_ -> answerMsg msg "Invalid help topic."
+ "quote":_ -> answerMsg msg "Invalid quote command."
+ "vote":"help":"quick":_ -> do
+ answerMsg msg "vote [quick] [QUOTEID] { +1 | -1 | ++ | -- }"
+ answerMsg msg $ concat [ " Vote for a quote. You can also vote for the last active quote on this chan "
+ , "by typing something that begins by +1, -1 or ++ or --." ]
+ "vote":"help":[] -> answerMsg msg "Usage: vote { [quick] [QUOTEID] { +1 | -1 } | show [QUOTEID] | stat }"
+ "vote":"help":_ -> answerMsg msg "Invalid help topic."
+ "vote":_ -> answerMsg msg "Invalid vote command."
+ _ -> return ()
+ | otherwise = return ()
+ eval _ _ = return ()
+