-- vim: ts=2 sw=2 expandtab si ai : import LjPost import System.Environment (getArgs) import System.Directory (renameFile, doesFileExist) import System (exitWith, ExitCode(..)) import Control.Monad (liftM, mapM,ap) import Maybe (fromJust, fromMaybe, isNothing, isJust, maybe) import List ((\\)) import Network.HTTP (urlEncode) import Text.Regex.Posix ((=~)) import Network.Curl (curlGetString) import Text.Feed.Import (parseFeedString) import Text.Feed.Types (Feed, Item) import Text.Feed.Query import Text.HTML.TagSoup import IO import Codec.Binary.UTF8.String (encodeString) --import Debug.Trace (trace) --debug x = trace (show x) x -- take first n sentences from string s takeSentences n s | n > 0 = let (s',r) = takeSentence s in s' ++ takeSentences (n-1) r | otherwise = "" takeSentence s = let ends = ".?!;" (first,rest) = break (`elem` ends) s in if not (null rest) then (first ++ [head rest],tail rest) else (first,[]) -- remove all html/xml tags, use tag text and replace images with their ALT text eatTags :: String -> String eatTags [] = [] eatTags s = let tags = parseTags s toString t = if (isTagOpenName "img" t) then (fromAttrib "alt" t)++" " else if isTagText t then fromTagText t else "" in concat $ map toString tags -- mini template engine renderTemplate _ [] = [] renderTemplate alist s = let (b,t,a) = s =~ "%[a-z0-9]*%" :: (String,String,String) tagval t | t == "%%" = Just "%" | otherwise = let inner = take (length t - 2) $ drop 1 t in lookup inner alist val = tagval t in if isJust val then b ++ (fromJust val) ++ renderTemplate alist a else b ++ t ++ renderTemplate alist a renderItem :: (Maybe Int) -> String -> Item -> String renderItem n t i = let title = ( fromJust . getItemTitle ) i link = ( fromJust . getItemLink ) i guid = ( fromJust . getItemId ) i justFirst Nothing = id justFirst (Just n) = takeSentences n summary = ( justFirst n . eatTags . fromJust . getItemSummary) i tags = zip [ "title","link","text" ] [ title, urlEncode link,summary ] in renderTemplate tags t isNotSent sent i = ((snd . fromJust . getItemId) i) `notElem` sent postItem u p t n i = do let message = renderItem n t i let subj = fromJust $ getItemTitle i r <- postToLj u p subj message if isSuccess r then putLjKey "url" r else putLjKey "errmsg" r printItem t n i = do let message = renderItem n t i let subj = fromJust $ getItemTitle i putStrLn $ "Subject: " ++ subj putStrLn message putStrLn "" readFileIfExists f = do t <- doesFileExist f if t then readFile f else return "" readMaybeInt = maybe Nothing (\s -> Just (read s::Int)) usage = "usage: feed2lj [--help] [--dry-run] http://example.com/rss.xml" printUsageAndExitIf c status = if c then do { putStrLn usage ; exitWith status ; } else return () main = do args <- getArgs let opts = [ "--help", "--dry-run" ] let dryrun = "--dry-run" `elem` args printUsageAndExitIf ("--help" `elem` args) ExitSuccess let args' = args \\ opts printUsageAndExitIf (length args' == 0) $ ExitFailure 1 let url= head $ args \\ opts (_,rawfeed) <- curlGetString url [] ljuser <- return fromJust `ap` readLjSetting "username" ljpass <- return fromJust `ap` readLjSetting "password" sentfile <- return fromJust `ap` readLjSetting "sentfile" t <- return fromJust `ap` readLjSetting "template" nsentences <- return readMaybeInt `ap` readLjSetting "nsentences" sent_ids <- (return . lines) =<< readFileIfExists sentfile let feed = fromJust $ parseFeedString rawfeed let items = feedItems feed let newitems = reverse $ filter (isNotSent sent_ids) items let new_ids = map ( snd . fromJust . getItemId) newitems if not dryrun then do mapM_ (postItem ljuser ljpass t nsentences) newitems renameFile sentfile (sentfile ++ "~") writeFile sentfile $ unlines (sent_ids ++ new_ids) else do -- debug script and cross-post preview mapM_ (printItem t nsentences) newitems