packages feed

hog (empty) → 0.1

raw patch · 4 files changed

+204/−0 lines, 4 filesdep +basedep +cmdargsdep +filepathsetup-changed

Dependencies added: base, cmdargs, filepath, irc, network, old-locale, time, unix

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c)2011, Chris Done++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Chris Done nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ hog.cabal view
@@ -0,0 +1,25 @@+Name:                hog+Version:             0.1+Synopsis:            Simple IRC logger bot.+Description:         Simple IRC logger bot, connects to given channels and+                     logs lines to files of the corresponding channel names.+License:             BSD3+License-file:        LICENSE+Author:              Chris Done+Maintainer:          chrisdone@gmail.com+Copyright:           2010 Chris Done+Category:            Network+Build-type:          Simple+Cabal-version:       >=1.2++Executable hog+  Main-is:           Main.hs+  Hs-source-dirs:    src+  Build-depends:     base >4 && <5,+                     unix >= 2.4 && <2.5,+                     cmdargs >= 0.6 && <0.7,+                     network >= 2.2 && <2.3,+                     irc >= 0.4 && <0.5,+                     filepath >= 1.1 && <1.2,+                     time >= 1.1 && <1.2,+                     old-locale >= 1 && <2
+ src/Main.hs view
@@ -0,0 +1,147 @@+{-# LANGUAGE DeriveDataTypeable, RecordWildCards, ScopedTypeVariables,+  ViewPatterns #-} +{-# OPTIONS -Wall -fno-warn-missing-signatures -fno-warn-name-shadowing #-}++-- | Main entry point, just connect to the given IRC server and join+-- the given channels and log all messages received, to the given+-- file(s).++module Main where++import Control.Concurrent.Delay+import Control.Monad+import Control.Monad.Fix+import Data.Char+import Data.List+import Data.Time+import Network+import Network.IRC+import System.Console.CmdArgs+import System.FilePath+import System.IO+import System.Locale+import System.Posix++-- | Options for the executable.+data Options = Options+  { host      :: String       -- ^ The IRC server host/IP.+  , port      :: Int          -- ^ The remote port to connect to.+  , channels  :: String       -- ^ The channels to join (comma or+                              -- space sep'd).+  , logpath   :: FilePath     -- ^ Which directory to log to.+  , pass      :: Maybe String -- ^ Maybe a *server* password.+  , nick      :: String       -- ^ The nick.+  , user      :: String       -- ^ User (not real name) to use.+  , delay     :: Integer      -- ^ Reconnect delay (secs).+  } deriving (Show,Data,Typeable)++-- | Options for the executable.+options :: Options+options  = Options+  { host = def &= opt "irc.freenode.net" &= help "The IRC server."+  , port = def &= opt (6667::Int) &= help "The remote port."+  , channels = def &= opt "#hog" &= help "The channels to join."+  , logpath = def &= opt "." &= help "The directory to save log files."+  , pass = Nothing+  , nick = def &= opt "hog" &= help "The nickname to use."+  , user = def &= opt "hog" &= help "The user name to use."+  , delay = def &= opt (30::Integer) &= help "Reconnect delay (secs)."+  }+  &= summary "Hog IRC logger (C) Chris Done 2011"+  &= help "Simple IRC logger bot."++-- | Main entry point.+main :: IO ()+main = withSocketsDo $ do+  _ <- installHandler sigPIPE Ignore Nothing+  cmdArgs options >>= start++-- | Connect to the IRC server and start logging.+start :: Options -> IO ()+start options@Options{..} = do+  hSetBuffering stdout NoBuffering+  h <- connectTo host (PortNumber (fromIntegral port))+  hSetBuffering h NoBuffering+  register h options+  fix $ \repeat -> do+    line <- catch (Just `fmap` hGetLine h) $ \_e -> do+      delaySeconds delay+      start options+      return Nothing+    flip (maybe (return ())) line $ \line -> do+       putStrLn $ "<- " ++ line+       handleLine options h line+       repeat++-- | Register to the server by sending user/nick/pass/etc.+register :: Handle -> Options -> IO ()+register h Options{..} = do+  let send = sendLine h+  maybe (return ()) (send . ("PASS "++)) pass+  send $ "USER " ++ user ++ " * * *"+  send $ "NICK " ++ nick++-- | Handle incoming lines; ping/pong, privmsg, etc.+handleLine :: Options -> Handle -> String -> IO ()+handleLine options handle line = +  case decode line of+    Nothing -> putStrLn $ "Unable to decode line " ++ show line+    Just msg -> handleMsg options handle msg+    +-- | Handle an IRC message.+handleMsg :: Options -> Handle -> Message -> IO ()+handleMsg options h msg =+  case msg_command msg of+    "PING" -> reply $ msg {msg_command="PONG"}+    "376"  -> joinChannels options h+    "PRIVMSG" -> logMsg options msg+    _ -> return ()+    +  where reply = sendLine h . encode+  +-- | Log a privmsg of a given channel to the right file.+logMsg :: Options -> Message -> IO ()+logMsg options@Options{..} msg@Message{..} = do+  case msg_params of+    [('#':chan),msg] -> logLine options chan from msg+    _ -> putStrLn $ "Bogus message: " ++ show msg+    +    where from = case msg_prefix of+                   Just (NickName str _ _) -> "<" ++ str ++ "> "+                   _ -> "unknown"++-- | Log a line of a channel.+logLine :: Options -> String -> String -> String -> IO ()+logLine Options{..} (sanitize -> chan) from line =+  when (not (null chan)) $ do+    now <- fmap format getCurrentTime+    appendFile (logpath </> chan) $ +      now ++ " " ++ from ++ line ++ "\n"+      +  where format = formatTime defaultTimeLocale "%Y-%m-%d %H:%M:%S %Z"++-- | Sanitize a channel name.+sanitize :: String -> String+sanitize = filter ok where+  ok c = isDigit c || elem (toLower c) ['a'..'z']++-- | Join the requested channels.+joinChannels :: Options -> Handle -> IO ()+joinChannels Options{..} h = do+  let chans = words $ replace ',' ' ' channels+  putStrLn $ "Joining channels: " ++ show chans+  sendLine h $ "JOIN :" ++ intercalate "," chans++-- | Replace x with y in xs.+replace :: Eq a => a -> a -> [a] -> [a]+replace c y = go where+    go [] = []+    go (c':cs) | c'==c     = y : go cs+               | otherwise = c' : go cs++-- | Send a line on a handle, ignoring errors (like, if the socket's+-- closed.)+sendLine :: Handle -> String -> IO ()+sendLine h line = do+  catch (do hPutStrLn h line; putStrLn $ "-> " ++ line) $+    \_e -> return ()