{-# LANGUAGE OverloadedStrings #-} module XMPP ( xmppPlugins, xmppPlugin ) where -- base import Data.Char (isControl) import Control.Monad import Data.List.Split import Data.List -- Hackage import Control.Concurrent.Lifted (fork) import Control.Exception.Lifted as E (SomeException(..), throwIO, catch) import Control.Monad.Trans (lift) import Network (PortID(..)) import qualified Data.Text as T import Network.TLS ( ClientParams(..), ClientHooks(..), defaultParamsClient, Supported(..) ) import qualified Network.TLS.Extra as CI import Data.Default (def) import Network.Xmpp ( SessionConfiguration(sessionStreamConfiguration) , StreamConfiguration(tlsParams) , parseJid, getJid, resourcepart, Session, session, plain , Presence(presenceFrom, presenceTo, presencePayload) , sendPresence, getMessage, messageFrom, messageTo, messagePayload , sendMessage, Message(..), MessageType(..) ) import Data.XML.Types ( nameLocalName, elementName, elementText , Element(Element), Name(Name), Content(ContentText), Node(..) ) import System.Timeout.Lifted (timeout) import qualified Data.X509.Validation as XV -- Lambdabot import Lambdabot.IRC import Lambdabot.Logging import Lambdabot.Monad import Lambdabot.Plugin import Lambdabot.Util ------ type XMPP = ModuleT () LB data XMPPConfig = XMPPConfig { xmppHost :: String, xmppPort :: PortID, xmppUser :: String, xmppNick :: String, xmppPass :: String, xmppRoom :: String } ---- xmppPlugins :: [String] xmppPlugins = ["xmpp"] xmppPlugin :: Module () xmppPlugin = newModule { moduleCmds = return [ (command "xmpp-connect") { aliases = [] , help = say "xmpp-connect . connect to an xmpp server" , process = \rest -> case splitOn " " rest of tag:hostn:portn:usern:nick:passw:room -> do pn <- (PortNumber . fromInteger) `fmap` readM portn let xmppconf = XMPPConfig hostn pn usern nick passw (intercalate " " room) lift (online tag xmppconf) _ -> say "XMPP: Not enough parameters!" } ] } sendMessage' :: Session -> XMPPConfig -> IrcMessage -> XMPP () sendMessage' sess xmppconf ircmsg = do let msg = Data.List.last (ircMsgParams ircmsg) let msg' = filtermsg $ T.filter (not . isControl) (T.drop 1 (T.pack msg)) let name = Name "body" (Just "jabber:client") Nothing node = NodeContent (ContentText msg') let payload = Element name [] [node] let m = Message{ messageFrom = Nothing , messageID = Nothing , messageTo = Just (parseJid (xmppRoom xmppconf)) , messageType = GroupChat , messagePayload = [payload] , messageLangTag = Nothing , messageAttributes = [] } io $ sendMessage m sess >> return () where filtermsg :: T.Text -> T.Text filtermsg m = case (T.isPrefixOf "ACTION " m) of True -> T.replace "ACTION" "/me" m False -> T.stripStart m online :: String -> XMPPConfig -> XMPP () online tag xmppconf = do sess <- io $ xmppListen xmppconf E.catch (registerServer tag (sendMessage' sess xmppconf)) (\err@SomeException{} -> E.throwIO err) void . fork $ E.catch (lb (readerLoop tag sess xmppconf)) (\e@SomeException{} -> do errorM (show e) unregisterServer tag) readerLoop :: String -> Session -> XMPPConfig -> LB () readerLoop tag sess xmppconf = forever $ do mes <- io $ getMessage sess let from = maybe "(anybody)" T.unpack (resourcepart =<< messageFrom mes) let to = maybe "(anybody)" T.unpack (resourcepart =<< messageTo mes) let bodyElems = elems "body" mes let delayElems = elems "delay" mes when ((/=) from (xmppNick xmppconf) && null delayElems && (not . null) bodyElems) $ do let body = head $ elementText (head bodyElems) void . fork . void . timeout 15000000 . received $ IrcMessage { ircMsgServer = tag , ircMsgLBName = xmppNick xmppconf , ircMsgPrefix = from , ircMsgCommand = "PRIVMSG" , ircMsgParams = [to, ':' : T.unpack body] } return () (readerLoop tag sess xmppconf) where elems tagname mes = filter ((== tagname) . nameLocalName . elementName) $ (messagePayload mes) xmppListen :: XMPPConfig -> IO Session xmppListen xmppconf = do result <- session (xmppHost xmppconf) (Just (\_ -> [plain (T.pack . xmppUser $ xmppconf) Nothing (T.pack . xmppPass $ xmppconf)], Nothing)) def sess <- case result of Right s -> return s Left e -> error $ "XmppFailure: " ++ (show e) sendMUCPresence xmppconf sess return sess sendMUCPresence :: XMPPConfig -> Session -> IO () sendMUCPresence xmppconf sess = do jid <- getJid $ sess _ <- sendPresence (def { presenceFrom = jid , presenceTo = Just . parseJid $ (xmppRoom xmppconf) ++ '/' : (xmppNick xmppconf) , presencePayload = [Element "x" [(Name "xmlns" Nothing Nothing, [ContentText "http://jabber.org/protocol/muc"])] []] }) sess return ()