irc bot for announcing rss/atom feeds (http://hackage.haskell.org/package/rss2irc)

root / rss2irc.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
#!/usr/bin/env runhaskell
{-# LANGUAGE PatternGuards, BangPatterns, DeriveDataTypeable, OverloadedStrings, ScopedTypeVariables #-}
--------------------------------------------------------------------
{- |
rss2irc - watches an RSS/Atom feed and writes it to an IRC channel.

Copyright (c) Don Stewart 2008-2009, Simon Michael 2009-2014
License: BSD3.
-}
--------------------------------------------------------------------

module Main where

import Control.Concurrent
import Control.Concurrent.STM
import Control.Exception
import Control.Monad (when,unless)
import Data.Maybe
import Data.Time.Clock (getCurrentTime)
import Prelude hiding (log)
import Network (withSocketsDo)
import Network.HTTP.Client (defaultManagerSettings)
import Network.HTTP.Conduit (newManager)
import System.Console.CmdArgs
import System.Exit (ExitCode(ExitSuccess), exitFailure, exitSuccess)
import System.IO
import System.IO.Storage
import Text.Printf (printf)

import Base
import Utils
import Feed
import Irc


-- | Get parsed and checked options and a reader and bot ready to connect.
getRss2IrcArgs :: IO (Opts, Reader, Bot)
getRss2IrcArgs = do
  opts <- cmdArgs defopts >>= \opts -> do
    unless (interval opts > 0 || (maybe False (<=10) $ num_iterations opts)) $
      opterror "--interval 0 requires --num-iterations 10 or less"
    seq (applyReplacements opts "") $ return () -- report any bad --replace regexp
    return opts
  q <- atomically $ newTChan
  t <- getCurrentTime
  let reader = Reader{httpManager=Nothing
                     ,iterationsleft=num_iterations opts
                     }
      bot = Bot{socket        = stdout
               ,server        = ""
               ,port          = defport
               ,channel       = ""
               ,botnick       = ""
               ,announcequeue = q
               ,batchindex    = 0
               ,lastmsgtime   = t
               }
      bot' = case ircAddressFromOpts opts of
              Nothing -> bot
              Just (IrcAddress s p c n) ->
                bot{server  = s
                   ,port    = fromMaybe defport p
                   ,channel = c
                   ,botnick = n
                   }
  return (opts, reader, bot')

-- | Process arguments, join the irc channel, start worker threads,
-- disconnect and report when there is a problem.
main :: IO ()
main =
 withStore "globals" $ do -- for readFeedFile
  -- http-conduit stuff
  withSocketsDo $ do -- for ms windows
   manager <- io $ newManager defaultManagerSettings
   (opts,reader,bot) <- getRss2IrcArgs
   let app = App{aOpts=opts,aReader=reader{httpManager=Just manager},aBot=bot}
   when (delay opts > 0) $ threadDelay $ (delay opts) * minutes
   runThreads app

runThreads :: App -> IO ()
runThreads app@App{aOpts=opts} = do
  -- catch any termination or error in sub-threads and handle it here
  r <- try $ bracket (connect app) disconnect $ \a -> do
    appvar <- newSharedVar a
    -- 1. the feed reader thread polls forever or until it reaches max
    -- iterations or raises a non-transient exception
    _ <- forkMonitoredIO $ feedReader appvar
    -- 2. the irc announcer runs until it raises an exception or is killed
    _ <- forkMonitoredIO $ ircAnnouncer appvar
    -- 3. the main thread becomes the irc responder, keeping the
    -- connection alive until it raises or receives an exception
    ircResponder appvar
  -- exit after an exception
  preExitDelay
  case r of
    Right _                                      -> unless (quiet opts) (log "normal termination.") >> exitSuccess -- shouldn't happen
    Left e | Just ExitSuccess <- fromException e -> unless (quiet opts) (log "normal termination") >> exitSuccess
    Left e                                       -> unless (quiet opts) (putStr "\n" >> log (show e)) >> exitFailure

-- | Spawn a thread which will throw any exception or termination (as
-- ExitSuccess) to us. Also log error exceptions.
forkMonitoredIO :: IO () -> IO ThreadId
forkMonitoredIO action = do
  me <- myThreadId
  forkIO $ do
    ex <- action >> return (toException ExitSuccess)
          `Control.Exception.catch` \e ->
            case fromException e of
              Just ExitSuccess -> return e
              _ -> log (printf "Error: %s" (show (e::SomeException))) >> return e
    throwTo me ex

-- hack: give announcer a chance to announce items from final poll
preExitDelay :: IO ()
preExitDelay = threadDelay $ 500000