import Control.Concurrent (forkIO, threadDelay)
import Control.Monad (forever, when)
+import Data.Aeson (decode)
import Data.List ((\\))
+import Data.Monoid ((<>))
+import Data.Time.LocalTime (TimeZone, getCurrentTimeZone)
import System.Exit (ExitCode(..), exitWith)
import System.IO (hPutStrLn, stderr)
import CommandLine
-import Configuration (Cfg(..))
-import ExitCodes
-import Mail
+import Configuration (Cfg(..), default_config, merge_optional)
+import ExitCodes (exit_no_usernames)
+import qualified OptionalConfiguration as OC
+import Mail (
+ Message(..),
+ default_headers,
+ print_sendmail_result,
+ rfc822_now,
+ sendmail
+ )
import Twitter.Http
import Twitter.Status
import Twitter.User
+import Usernames (Usernames(..))
-
--- |A wrapper around threadDelay which takes seconds instead of
--- microseconds as its argument.
+-- | A wrapper around threadDelay which takes seconds instead of
+-- microseconds as its argument.
thread_sleep :: Int -> IO ()
thread_sleep seconds = do
let microseconds = seconds * (10 ^ (6 :: Int))
threadDelay microseconds
--- |Given a 'Message', 'Status', and default date, update that
--- message's body and subject with the information contained in the
--- status. Adds a /Date: / header, and returns the updated message.
-message_from_status :: Message -> String -> Status -> Message
-message_from_status message default_date status =
+-- | Given a 'Message', 'Status', and date, update that message's body
+-- and subject with the information contained in the status. Adds a
+-- /Date: / header, and returns the updated message.
+message_from_status :: Maybe TimeZone -> Message -> String -> Status -> Message
+message_from_status mtz message default_date status =
message { subject = "Twat: " ++ (screen_name (user status)),
- body = (pretty_print status),
+ body = (pretty_print mtz status),
headers = ((headers message) ++ ["Date: " ++ date])}
where
- -- Use the Status' created_at date if it can be coerced into
- -- RFC822 format.
- date = case (created_at_to_rfc822 $ created_at status) of
- Nothing -> default_date
- Just c -> c
+ date =
+ case created_at status of
+ Nothing -> default_date
+ Just c -> utc_time_to_rfc822 mtz c
-- | If the given Message is not Nothing, send a copy of it for every
-- Status in the list.
-send_messages :: Cfg -> Maybe Message -> [Status] -> IO ()
-send_messages cfg maybe_message statuses =
+send_messages :: Cfg -> Maybe TimeZone -> Maybe Message -> [Status] -> IO ()
+send_messages cfg mtz maybe_message statuses =
case maybe_message of
Nothing -> return ()
Just message -> do
default_date <- rfc822_now
- let mfs = message_from_status message (default_date)
+ let mfs = message_from_status mtz message default_date
let messages = map mfs statuses
sendmail_results <- mapM sendmail' messages
_ <- mapM print_sendmail_result sendmail_results
-- and verbose is enabled.
mention_retweets :: Cfg -> [Status] -> IO ()
mention_retweets cfg ss = do
- let retweets = filter retweet ss
+ let retweets = filter retweeted ss
when ((ignore_retweets cfg) && (verbose cfg)) $ do
let countstr = show $ length retweets
putStrLn $ "Ignoring " ++ countstr ++ " retweets."
good_statuses
where
replies = filter reply ss
- retweets = filter retweet ss
+ retweets = filter retweeted ss
- good_statuses' = case (ignore_replies cfg) of
- True -> ss \\ replies
- False -> ss
+ good_statuses' = if (ignore_replies cfg)
+ then ss \\ replies
+ else ss
- good_statuses = case (ignore_retweets cfg) of
- True -> good_statuses' \\ retweets
- False -> good_statuses'
+ good_statuses = if (ignore_retweets cfg)
+ then good_statuses' \\ retweets
+ else good_statuses'
recurse :: Cfg -> String -> Integer -> (Maybe Message) -> IO ()
recurse cfg username latest_status_id maybe_message = do
thread_sleep (heartbeat cfg)
- xmldata <- get_user_new_statuses username latest_status_id
+ timeline <- get_user_new_statuses cfg username latest_status_id
- -- Parsing an empty result can blow up. Just pretend there are
- -- no new statuses in that case.
- let new_statuses = case xmldata of
- Just xml -> parse_statuses xml
- Nothing -> []
+ -- FIXME
+ let Just new_statuses = decode timeline :: Maybe Timeline
case (length new_statuses) of
0 ->
let good_statuses = filter_statuses cfg new_statuses
- _ <- mapM (putStrLn . pretty_print) good_statuses
+ tz <- getCurrentTimeZone
+ let mtz = Just tz
+ mapM_ (putStrLn . (pretty_print mtz)) good_statuses
- send_messages cfg maybe_message good_statuses
+ send_messages cfg mtz maybe_message good_statuses
let new_latest_status_id = get_max_status_id new_statuses
do_recurse new_latest_status_id
-- | Try continually to download username's timeline, and determine the
-- latest status id to be posted once we have done so.
-get_latest_status_id :: Int -> String -> IO Integer
-get_latest_status_id delay username = do
- xmldata <- get_user_timeline username
-
- let initial_statuses = case xmldata of
- Just xml -> parse_statuses xml
- Nothing -> []
+get_latest_status_id :: Cfg -> String -> IO Integer
+get_latest_status_id cfg username = do
+ let delay = heartbeat cfg
+ timeline <- get_user_timeline cfg username
+ let Just initial_timeline = decode timeline :: Maybe Timeline
- case (length initial_statuses) of
+ case (length initial_timeline) of
0 -> do
-- If the HTTP part barfs, try again after a while.
putStrLn ("Couldn't retrieve " ++ username ++ "'s timeline. Retrying...")
thread_sleep delay
- get_latest_status_id delay username
- _ -> return (get_max_status_id initial_statuses)
+ get_latest_status_id cfg username
+ _ -> return (get_max_status_id initial_timeline)
-- should be emailed.
run_twat :: Cfg -> Maybe Message -> String -> IO ()
run_twat cfg msg username = do
- latest_status_id <- get_latest_status_id (heartbeat cfg) username
+ latest_status_id <- get_latest_status_id cfg username
recurse cfg username latest_status_id msg
return ()
-- forking, main loops forever.
main :: IO ()
main = do
- errors <- parse_errors
+ -- And a Cfg object.
+ rc_cfg <- OC.from_rc
+ cmd_cfg <- apply_args
- -- If there were errors parsing the command-line options,
- -- print them and exit.
- when (not (null errors)) $ do
- hPutStrLn stderr (concat errors)
- putStrLn help_text
- exitWith (ExitFailure exit_args_parse_failed)
+ -- Merge the config file options with the command-line ones,
+ -- prefering the command-line ones.
+ let opt_config = rc_cfg <> cmd_cfg
- -- Next, check to see if the 'help' option was passed to the
- -- program. If it was, display the help, and exit successfully.
- help <- help_set
- when (help) $ do
- putStrLn help_text
- exitWith ExitSuccess
+ -- Finally, update a default config with any options that have been
+ -- set in either the config file or on the command-line.
+ let cfg = merge_optional default_config opt_config
- -- Get the list of usernames.
- usernames <- parse_usernames
+ when (null $ get_usernames (usernames cfg)) $ do
+ hPutStrLn stderr "ERROR: no usernames supplied."
+ _ <- show_help
+ exitWith (ExitFailure exit_no_usernames)
- -- And a Cfg object.
- cfg <- get_cfg
-
-- If we have both a "To" and "From" address, we'll create a
-- message object to be passed to all of our threads.
let message = construct_message cfg
-- Execute run_twat on each username in a new thread.
let run_twat_curried = run_twat cfg message
- _ <- mapM (forkIO . run_twat_curried) usernames
+ _ <- mapM (forkIO . run_twat_curried) (get_usernames (usernames cfg))
- _ <- forever $ do
+ _ <- forever $
-- This thread (the one executing main) doesn't do anything,
-- but when it terminates, so do all the threads we forked.
-- As a result, we need to keep this thread on life support.
thread_sleep (heartbeat cfg)
return ()
-
-
--- | A debugging tool that will parse, print, and email a single
--- status (given by its id).
-twat_single_status :: Cfg -> Integer -> (Maybe Message) -> IO ()
-twat_single_status cfg the_status_id maybe_message = do
- xmldata <- get_status the_status_id
-
- -- Parsing an empty result can blow up. Just pretend there are
- -- no new statuses in that case.
- let statuses = case xmldata of
- Just xml -> parse_status xml
- Nothing -> []
-
- case (length statuses) of
- 0 -> do
- putStrLn "No statuses returned."
- return ()
- _ -> do
- _ <- mapM (putStrLn . pretty_print) statuses
-
- case maybe_message of
- Nothing -> do
- putStrLn "No message object given."
- return ()
- Just message -> do
- default_date <- rfc822_now
- let messages = map (message_from_status message (default_date)) statuses
- sendmail_results <- mapM sendmail' messages
- _ <- mapM print_sendmail_result sendmail_results
- return ()
- where
- sendmail' = sendmail (sendmail_path cfg)
\ No newline at end of file