packages feed

feed2lj-0.0.3: Feed2Lj.hs

-- 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