module Web.Feed2Twitter
(
Config (..)
, Tweet
, GUID
, atom2twitter
, rss2twitter
, item2twitter
, feed2twitter
, trunc4tweet
, trunc4url
) where
import Web.Feed2Twitter.Twitter
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BSC
import Network.Curl.Download
import Text.Atom.Feed
import Text.Feed.Import
import Text.RSS.Syntax
import Text.Feed.Types (Feed (AtomFeed, RSSFeed))
import qualified Text.Feed.Types as Feed
import Control.Exception
import Control.Monad
import Data.Maybe
import Data.List
import Data.Ord (comparing)
import System.IO
data Config = Config
{ feedUrl :: String
, username :: String
, password :: String
, cacheFile :: FilePath
, cacheSize :: Int
, debugMode :: Bool
}
deriving Show
type Tweet = String
type GUID = String
atom2twitter :: Config -> (Entry -> Tweet) -> IO ()
atom2twitter cfg f = item2twitter cfg g
where
err = "Web.Feed2Twitter.atom2twitter: "
g (Left e) = f e
g (Right _) = error $ err ++ "Item not an Atom entry"
rss2twitter :: Config -> (RSSItem -> Tweet) -> IO ()
rss2twitter cfg f = item2twitter cfg g
where
err = "Web.Feed2Twitter.rss2twitter: "
g (Left _) = error $ err ++ "Item not a RSS item"
g (Right i) = f i
item2twitter :: Config -> (Either Entry RSSItem -> Tweet) -> IO ()
item2twitter cfg f = feed2twitter cfg g
where
err = "Web.Feed2Twitter.items2twitter: "
g (AtomFeed af) = map atom . reverse . feedEntries $ af
g (RSSFeed rf) = map rss . reverse . rssItems . rssChannel $ rf
g _ = error $ err ++ "Feed not an Atom or RSS feed"
atom entry = (entryId entry, f (Left entry))
rss ri = (guid, f (Right ri))
where
guid = maybe (filter (/='\n') $ show ri) rssGuidValue (rssItemGuid ri)
feed2twitter :: Config -> (Feed.Feed -> [(GUID, Tweet)]) -> IO ()
feed2twitter cfg f = do
feed' <- openAsFeed (feedUrl cfg)
case feed' of
Left s -> error $ err ++ s
Right feed -> do
let tweets' = f feed
touchFile (cacheFile cfg)
cfc <- BS.readFile (cacheFile cfg)
let guids = lines (BSC.unpack cfc)
let tweets = filter (pred guids) tweets'
mapM_ tweetAndCache tweets
when (not $ null tweets) cleanupCache
where
err = "Web.Feed2Twitter.feed2twitter: "
touchFile f = openFile f ReadWriteMode >>= hClose
pred guids (g, t) = not (g `elem` guids || t == "")
cleanupCache = do
guids' <- BS.readFile (cacheFile cfg)
let guids = BSC.unlines . reverse . take (cacheSize cfg) . reverse
. BSC.lines $ guids'
BS.writeFile (cacheFile cfg) guids
tweetAndCache (guid, t) =
if debugMode cfg
then putStrLn ("tweet: " ++ t)
else do
tweet (username cfg) (password cfg) t
BS.appendFile (cacheFile cfg) (BSC.pack $ guid ++ "\n")
trunc4tweet :: String -> String
trunc4tweet s = if length s <= 140
then s
else take 139 s ++ "\8230"
trunc4url :: String -> String
trunc4url s = if length s <= 119
then s ++ " "
else take 118 s ++ "\8230 "