packages feed

twidge 1.0.6.1 → 1.1.0

raw patch · 15 files changed

+333/−471 lines, 15 filesdep +aesondep +old-localedep +textdep −HaXmldep ~hoauth

Dependencies added: aeson, old-locale, text, time

Dependencies removed: HaXml

Dependency ranges changed: hoauth

Files

Commands.hs view
@@ -33,7 +33,6 @@ import Text.Printf import Utils import Types-import Config  import qualified Commands.FollowBlock import qualified Commands.Ls@@ -54,9 +53,8 @@      Commands.Ls.lsfollowing,      Commands.Ls.lsrecent,      Commands.Ls.lsreplies,-     Commands.Ls.lsrt,-     Commands.Ls.lsrtarchive,      Commands.Ls.lsrtreplies,+     Commands.Ls.status,      Commands.Setup.setup,      Commands.FollowBlock.unblock,      Commands.FollowBlock.unfollow,
Commands/FollowBlock.hs view
@@ -19,7 +19,6 @@ module Commands.FollowBlock(follow, unfollow, block, unblock) where import Utils import System.Log.Logger-import Data.List import Download import Network.OAuth.Http.Request @@ -28,31 +27,34 @@ follow = simpleCmd "follow" "Start following someone"              follow_help              [] follow_worker-follow_worker = generic_worker POST "/friendships/create/" "follow"+follow_worker = generic_worker POST "/friendships/create.json" "follow" follow_help = generic_add_help "follow"  unfollow = simpleCmd "unfollow" "Stop following someone"              unfollow_help              [] unfollow_worker-unfollow_worker = generic_worker POST "/friendships/destroy/" "unfollow"+unfollow_worker = generic_worker POST "/friendships/destroy.json" "unfollow" unfollow_help = generic_rm_help "follow"  block = simpleCmd "block" "Start blocking someone"         block_help [] block_worker-block_worker = generic_worker POST "/blocks/create/" "block"+block_worker = generic_worker POST "/blocks/create.json" "block" block_help = generic_add_help "block"  unblock = simpleCmd "unblock" "Stop blocking someone"           unblock_help [] unblock_worker-unblock_worker = generic_worker POST "/blocks/destroy/" "unblock"+unblock_worker = generic_worker POST "/blocks/destroy.json" "unblock" unblock_help = generic_rm_help "block" -generic_worker method urlbase cmdname _ cp ([], [user]) =-    do xmlstr <- sendAuthRequest method cp (urlbase ++ user ++ ".xml") [] [("id", user)]-       debugM cmdname $ "Got doc: " ++ xmlstr-       -- let doc = getContent . xmlParse "follow" . stripUnicodeBOM $ xmlstr+generic_worker method url cmdname _ cp ([], [user_string]) =+    do let user = strip_at user_string+       json <- sendAuthRequest method cp url [] [("screen_name", user)]+       debugM cmdname $ "Got doc: " ++ show json        -- return ()-       ++       where strip_at ('@':u) = u+             strip_at u = u+ generic_worker _ _ cmdname _ _ _ =     permFail $ "follow: syntax error; see twidge " ++ cmdname ++ " --help" 
Commands/Ls.hs view
@@ -18,16 +18,13 @@  module Commands.Ls(lsrecent, lsreplies, lsblocking,                    lsfollowing, lsfollowers, lsarchive, -                   lsrt, lsrtreplies, lsrtarchive, lsdm, lsdmarchive) where+                   lsrtreplies, status, lsdm, lsdmarchive) where import Utils import System.Log.Logger import Types import Text.Printf import System.Console.GetOpt-import Data.List-import Text.XML.HaXml hiding (when) import Download-import FeedParser import Data.ConfigFile import Data.String.Utils(strip) import Config@@ -36,8 +33,11 @@ import HSH import System.Console.GetOpt.Utils import Network.URI-import Data.Maybe (isJust)+import Data.Maybe (isJust, isNothing) import Network.OAuth.Http.Request+import Data.Time.Format (formatTime, parseTime)+import Data.Time.LocalTime (ZonedTime)+import System.Locale (defaultTimeLocale, rfc822DateFormat)  i = infoM "ls" @@ -50,10 +50,12 @@            Option "l" ["long"] (NoArg ("l", ""))                        "Long format output -- more info and \                       \tab-separated columns",-           Option "w" ["width"] (ReqArg (stdRequired "w") "WIDTH")+           widthopt]++widthopt = Option "w" ["width"] (ReqArg (stdRequired "w") "WIDTH")                       ("Set the margin at which word-wrapping occurs.\n\                        \Ignored in long format mode. Default is "-                       ++ show defaultWidth ++ ".")]+                       ++ show defaultWidth ++ ".")  sinceopts = [            Option "e" ["exec"] (ReqArg (stdRequired "e") "COMMAND")@@ -101,8 +103,9 @@           isRight _  (Left _)   = False           isRight v1 (Right v2) = v1 == v2 -sinceArgs section cp args =-    case (lookup "u" args, get cp section "lastid") of+sinceArgs section cp args maxId = from ++ since where+    from = maybe [] (\i -> [("max_id", show i)]) maxId+    since = case (lookup "u" args, get cp section "lastid") of       (Nothing, _) -> []       (_, Left _) -> []       (_, Right a) -> @@ -117,15 +120,15 @@ -- lsrecent & friends -------------------------------------------------- -lsrecent = simpleCmd "lsrecent" "List recent updates from those you follow"+lsrecent = simpleCmd "lsrecent" "List recent updates from your home timeline"              lsrecent_help              (stdopts ++ sinceopts) (paginated (statuses_worker "lsrecent"-                                                "/statuses/friends_timeline"))+                                                "/statuses/home_timeline")) -lsreplies = simpleCmd "lsreplies" "List recent replies to you"+lsreplies = simpleCmd "lsreplies" "List recent messages mentioning you"             lsreplies_help             (stdopts ++ sinceopts) (paginated (statuses_worker "lsreplies" -                                               "/statuses/replies"))+                                               "/statuses/mentions_timeline"))  lsarchive = simpleCmd "lsarchive" "List recent status updates you posted yourself"             lsarchive_help@@ -139,8 +142,9 @@                   \those of the given username."                   ] --+status = simpleCmd "status" "Retrieve a single status"+         status_help+         [widthopt] status_worker  lsdm = simpleCmd "lsdm" "List recent direct messages to you"        lsdm_help@@ -152,38 +156,45 @@               (stdopts ++ sinceopts) (paginated (dm_worker "lsdmarchive"                                                  "/direct_messages/sent")) -lsrt = simpleCmd "lsrt" "List recent retweets from those you follow"-       lsrt_help-       (stdopts ++ sinceopts) (paginated (statuses_worker "lsrt"-                                          "/statuses/retweeted_to_me"))-       -lsrtarchive = simpleCmd "lsrtarchive" "List recent retweets you made yourself"-              lsrtarchive_help-              (stdopts ++ sinceopts) (paginated (statuses_worker "lsrtarchive"-                                                 "/statuses/retweeted_by_me"))- lsrtreplies = simpleCmd "lsrtreplies" "List others' retweets of your statuses"               lsrtreplies_help               (stdopts ++ sinceopts) (paginated (statuses_worker "lsrtreplies"                                                  "/statuses/retweets_of_me")) -statuses_worker = generic_worker handleStatus+statuses_worker = generic_worker handleStatuses dm_worker = generic_worker handleDM -generic_worker procfunc section command cpath cp (args, _) page =-    do xmlstr <- sendAuthRequest GET cp (command ++ ".xml")-                 (("page", show page) : sinceArgs section cp args-                  ++ screenNameArgs args)-                 []-       debugM section $ "Got doc: " ++ xmlstr-       results <- procfunc section cp args xmlstr-       when (page == 1) $-            maybeSaveList section cpath cp args (map sId results)-       return results+status_worker _ cp (args, [statusId]) = do+  json <- sendAuthRequest GET cp "/statuses/show.json"+            [("id", statusId)] []+  debugM "status" $ "Got doc: " ++ show json+  let TimelineMessage status = decode_json json+  printStatus "status" cp args status+status_worker _ _ _ = error "Invalid args to status; see twidge status --help" ++handleStatuses = handleGeneric (map fromTimeline) printStatus++generic_worker procfunc section command cpath cp (args, _) maxId = do+  json <- sendAuthRequest GET cp (command ++ ".json")+            (sinceArgs section cp args maxId+             ++ screenNameArgs args) []+  debugM section $ "Got doc: " ++ show json+  results <- procfunc section cp args json+  when (isNothing maxId) $+       maybeSaveList section cpath cp args (map sId results)+  return (results, nextMaxId results)++nextMaxId :: [Message] -> Maybe Integer+nextMaxId [] = Nothing+nextMaxId r = Just ((minimum $ map (read . sId) r) - 1)++status_help =+ "Usage: twidge status [options] status-id\n"+ lsrecent_help =  "Usage: twidge lsrecent [options]\n\n\- \You can see the 20 most recent items from your friends with:\n\n\+ \You can see the 20 most recent items from your timeline with:\n\n\  \   twidge lsrecent\n\n\  \To see items that you haven't seen yet, and remember this for the future,\n\  \use:\n\n\@@ -194,9 +205,9 @@  lsreplies_help =  "Usage: twidge lsreplies [options]\n\n\- \You can see the 20 most recent @replies from others to you with:\n\n\+ \You can see the 20 most recent @mentions from others of you with:\n\n\  \   twidge lsreplies\n\n\- \For more examples, including how to see only unseen replies, please\n\+ \For more examples, including how to see only unseen mentions, please\n\  \refer to the examples under twidge lsrecent --help, which also pertain\n\  \to lsreplies.\n" @@ -208,14 +219,6 @@  \refer to the examples under twidge lsrecent --help, which also pertain\n\  \to lsarchive.\n" -lsrt_help = - "Usage: twidge lsrt [options]\n\n\- \You can see the 20 most recent retweets posted by those you follow with:\n\n\- \   twidge lsrt\n\n\- \For more examples, including how to see only unseen retweets, please\n\- \refer to the examples under twidge lsrecent --help, which also pertain\n\- \to lsreplies.\n"-  lsrtreplies_help =   "Usage: twidge lsrtreplies [options]\n\n\  \You can see the 20 most retweets made of your statuses with:\n\n\@@ -224,15 +227,7 @@  \refer to the examples under twidge lsrecent --help, which also pertain\n\  \to lsreplies.\n" -lsrtarchive_help = - "Usage: twidge lsrt [options]\n\n\- \You can see the 20 most recent retweets you made:\n\n\- \   twidge lsrtarchive\n\n\- \For more examples, including how to see only unseen retweets, please\n\- \refer to the examples under twidge lsrecent --help, which also pertain\n\- \to lsreplies.\n" - lsdm_help =  "Usage: twidge lsdm [options]\n\n\  \You can see the 20 most recent direct messages to you with:\n\n\@@ -249,36 +244,13 @@  \refer to the examples under twidge lsrecent --help, which also pertain\n\  \to lsdmarchive.\n" -handleStatus = handleGeneric (map procStatuses . getStatuses) printStatus-handleDM = handleGeneric (map procDM . getDMs) printDM+handleDM = handleGeneric (map fromDM) printDM -handleGeneric pfunc printfunc section cp args xmlstr = -    let doc = getContent . xmlParse section . stripUnicodeBOM $ xmlstr-        statuses = pfunc doc+handleGeneric pfunc printfunc section cp args json =+    let statuses = pfunc $ decode_json json     in do mapM_ (printfunc section cp args) statuses           return statuses -procStatuses :: Content -> Message-procStatuses item = -    Message {sId = s (tag "id") item,-             sSender = s (tag "user" /> tag "screen_name") item,-             sRecipient = "",-             sText = unEsc $ s (tag "text") item,-             sDate = s (tag "created_at") item}--s f item = sanitize $ contentToString (keep /> f /> txt $ item)--procDM :: Content -> Message-procDM item =-    Message {sId = s (tag "id") item,-             sSender = s (tag "sender_screen_name") item,-             sRecipient = s (tag "recipient_screen_name") item,-             sText = unEsc $ s (tag "text") item,-             sDate = s (tag "created_at") item}--getStatuses = tag "statuses" /> tag "status"-getDMs = tag "direct-messages" /> tag "direct_message"- longStatus :: Message -> String longStatus m = printf "%s\t%s\t%s\t%s\t%s\t\n"                (sId m) (sSender m) (sRecipient m) (sText m) (sDate m)@@ -315,7 +287,6 @@                                                             Nothing -> defaultWidth) m)             (Nothing, Just _) -> putStr (longfunc m)       Just recipient -> mailto section cp args m recipient-    where msgid = genMsgId section m cp  mailto section cp args m recipient =     runIO $ echo (message ++ "\n") -|- (sendmail, ["-t"])@@ -328,6 +299,10 @@                       Right x -> ["From: " ++ (sSender m) ++ " <" ++ x ++ ">",                                   "Subject: " ++ subject]                     ) ++ +                    (case twitterToRFC822 (sDate m) of+                      Just d -> [ "Date: " ++ d ]+                      Nothing -> []+                    ) ++                     ["Message-ID: " ++ msgid,                      "X-Twidge-urlbase: " ++ forceEither (get cp "DEFAULT" "urlbase"),                      "X-Twidge-server-base: " ++ serverHost cp,@@ -349,6 +324,11 @@                      escapeURIString isUnreserved (sSender m)                     ,"User home: http://twitter.com/" ++ sSender m                     ]+          twitterToRFC822 d =+            formatTime defaultTimeLocale rfc822DateFormat `fmap` time+              where+                time :: Maybe ZonedTime+                time = parseTime defaultTimeLocale "%a %b %e %H:%M:%S %Z %Y" d  ---------------------------------------------------------------------- -- Follow/block type commands@@ -361,7 +341,7 @@ lsfollowing = simpleCmd "lsfollowing" "List people you are following"              lsfollowing_help              stdopts (paginated lsfollowing_worker)-lsfollowing_worker = genericfb_worker "lsfollowing" "/statuses/friends"+lsfollowing_worker = genericfb_worker "lsfollowing" "/friends/list.json" lsfollowing_help =  "Usage: twidge lsfollowing [options] [username]\n\n\  \If username is given, list the twitter accounts that user is following.\n\@@ -374,7 +354,7 @@ lsfollowers = simpleCmd "lsfollowers" "List people that follow you"              lsfollowers_help              stdopts (paginated lsfollowers_worker)-lsfollowers_worker = genericfb_worker "lsfollowers" "/statuses/followers"+lsfollowers_worker = genericfb_worker "lsfollowers" "/followers/list.json" lsfollowers_help =  "Usage: twidge lsfollowers [options] [username]\n\n\  \If username is given, list the twitter accounts that follow the user.\n\@@ -387,27 +367,27 @@ lsblocking = simpleCmd "lsblocking" "List people you are blocking"              lsblocking_help              stdopts (paginated lsblocking_worker)-lsblocking_worker = genericfb_worker "lsblocking" "/blocks/blocking"+lsblocking_worker = genericfb_worker "lsblocking" "/blocks/list.json" lsblocking_help =  "Usage: twidge lsblocking [options]\n\n\  \List the twitter accounts that your account is blocking.\n"  ------------------------------------------------------------ -- Generic follow/block support--- urlbase should be "/statuses/followers" or "/statuses/friends" ------------------------------------------------------------ -genericfb_worker cmdname urlbase _ cp (args, user) page =-    do xmlstr <- sendAuthRequest GET cp url [("page", show page)] []-       debugM cmdname $ "Got doc: " ++ xmlstr-       let doc = getContent . xmlParse cmdname . stripUnicodeBOM $ xmlstr-       let users = map procUsers . getUsers $ doc+genericfb_worker cmdname url _ cp (args, user) page =+    do json <- sendAuthRequest GET cp url params []+       debugM cmdname $ "Got doc: " ++ show json+       let UserList doc nextPage = decode_json json+       let users = map fromListedUser doc        mapM_ (printUser args) users-       return users-       -    where url = case user of-                  [] -> urlbase ++ ".xml"-                  [x] -> urlbase ++ "/" ++ x ++ ".xml"+       return (users, nextPage)+    where params = pageParams ++ targetParams+          pageParams = maybe [] (\c -> [("cursor", c)]) page+          targetParams = case user of+                  [] -> []+                  [x] -> [("screen_name", x)]                   _ -> error $ "Invalid args to " ++ cmdname ++                         "; see twidge " ++ cmdname ++ " --help"           printUser args (name, userid) = @@ -415,13 +395,6 @@                 Nothing -> putStrLn name                 Just _ -> printf "%s\t%s\n" userid name -          getUsers = tag "users" /> tag "user"--          procUsers :: Content -> (String, String)-          procUsers item =-              (sanitize $ contentToString (keep /> tag "screen_name" /> txt $ item),-               sanitize $ contentToString (keep /> tag "id" /> txt $ item))- ---------------------------------------------------------------------- -- Generic Utilities ----------------------------------------------------------------------@@ -435,20 +408,11 @@ returned. -} paginated workerfunc cppath cp (args, remainder)     | lookup "a" args == Nothing = -        do workerfunc cppath cp (args, remainder) 1+        do workerfunc cppath cp (args, remainder) Nothing            return ()-    | otherwise = paginateloop 1+    | otherwise = paginateloop Nothing     where paginateloop page =-              do r <- workerfunc cppath cp (args, remainder) page+              do (r, nextPage) <- workerfunc cppath cp (args, remainder) page                  if null r                      then return ()-                     else paginateloop (page + 1)--{- | Twitter has an additional level of escaping for &lt; and &gt; only. -Sigh. -}-unEsc :: String -> String-unEsc [] = []-unEsc x -  | "&lt;" `isPrefixOf` x = '<' : unEsc (drop 4 x)-  | "&gt;" `isPrefixOf` x = '>' : unEsc (drop 4 x)-  | otherwise = head x : unEsc (tail x)+                     else paginateloop nextPage
Commands/Setup.hs view
@@ -1,6 +1,6 @@ {-# LANGUAGE CPP #-} {--Copyright (C) 2010 John Goerzen <jgoerzen@complete.org>+Copyright (C) 2010-2013 John Goerzen <jgoerzen@complete.org>  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by@@ -35,6 +35,7 @@ import Data.Binary(encode) import Control.Monad.Trans import qualified Control.Monad.State.Class as M+import Download(twidgeCurlClient)  i = infoM "setup" d = debugM "setup"@@ -67,47 +68,34 @@      let authUrl = ((authUrlBase ++ "?oauth_token=") ++ ) .                     findWithDefault ("oauth_token", "") .                    oauthParams-     -     let CurlM resp = -           runOAuth $ -           do ignite app-              liftIO $ d "ignite done.  trying request 1."-              reqres1 <- tryRequest reqUrl-              case reqres1 of-#if MIN_VERSION_hoauth(0,2,4)-                AccessToken _ _ -> return ()-                _ -> -- hack around hoauth bug for identica-#else-                Left x -> -- hack around hoauth bug for identica-#endif-                  do liftIO $ d "request 1 failed.  attempting workaround."-                     putToken $ AccessToken {application = app,-                                             oauthParams = empty}-                     reqres2 <- tryRequest reqUrl-                     case reqres2 of -#if MIN_VERSION_hoauth(0,2,4)-                       AccessToken _ _ -> return ()-                       _ -> fail $ "Error from oauthRequest."-#else-                       Left x -> fail $ "Error from oauthRequest: " ++ show x-                       Right _ -> return ()-                Right _ -> return ()-#endif++     let resp = +           runOAuthM (fromApplication app) $ +           do liftIO $ d "Trying first signRq2"+              reqres1 <- signRq2 HMACSHA1 Nothing reqUrl+              liftIO $ d $ "First signRq2 result: " ++ (show reqres1)+              oauthRequest twidgeCurlClient reqres1+                             twidgeAskAuthorization authUrl-              oauthRequest HMACSHA1 Nothing accUrl+              +              liftIO $ d "Trying second signRq2"+              reqres2 <- signRq2 HMACSHA1 Nothing accUrl+              liftIO $ d $ "Second signRq2 result: " ++ show reqres2+              oauthRequest twidgeCurlClient reqres2+              +               tok <- getToken-              return (twoLegged tok, threeLegged tok, tok)-     (leg2, leg3, response) <- resp-     -- on successful auth, leg3 is True. Otherwise, it is False.-     -- leg1 is always false and r appears to not matter.-     d $ show (leg2, leg3, oauthParams response)-     if leg3 -       then do let newcp = forceEither $ set cp "DEFAULT" "oauthdata" .-                           esc . show . fixIdentica . toList . oauthParams $ response-               writeCP cpath newcp-               putStrLn $ "Successfully authenticated!" -               putStrLn "Twidge has now been configured for you and is ready to use."-       else putStrLn "Authentication failed; please try again"+              return tok+     tok <- resp+     d $ "Got token: " ++ show tok+     case tok of+       AccessToken _ _ -> do+         let newcp = forceEither $ set cp "DEFAULT" "oauthdata" .+                 esc . show . toList . oauthParams $ tok+         writeCP cpath newcp+         putStrLn $ "Successfully authenticated!" +         putStrLn "Twidge has now been configured for you and is ready to use."+       _ -> putStrLn "Authentication failed; please try again"     where confirmSetup =               do putStrLn "\nIt looks like you have already authenticated twidge."                  putStrLn "If we continue, I may remove your existing"@@ -117,30 +105,14 @@                  if (map toLower c) == "yes"                     then return ()                     else permFail "Aborting setup at user request."-          tryRequest reqUrl = -            do reqres <- oauthRequest HMACSHA1 Nothing reqUrl-#if MIN_VERSION_hoauth(0,2,4)-               liftIO $ d $ "reqres params: " ++ (show (oauthParams reqres))-#else-               liftIO $ d $ "reqres params: " ++ case reqres of-                 Left x -> " error " ++ x-                 Right y -> show (oauthParams y)-#endif-               return reqres-          -- Work around a hoauth bug - identica doesn't return-          -- oauth_callback_confirmed-          fixIdentica :: [(String, String)] -> [(String, String)]-          fixIdentica inp =-            case lookup "oauth_callback_confirmed" inp of-              Nothing -> ("oauth_callback_confirmed", "true") : inp-              Just _ -> inp           esc x = concatMap fix x           fix '%' = "%%"           fix x = [x] -twidgeAskAuthorization :: MonadIO m => (Token -> String) -> OAuthMonad m ()++twidgeAskAuthorization :: MonadIO m => (Token -> String) -> OAuthMonadT m () twidgeAskAuthorization getUrl = -  do token <- M.get+  do token <- getToken      answer <- liftIO $ do putStrLn "OK, next I need you to authorize Twidge to access your account."                            putStrLn "Please cut and paste this URL and open it in a web browser:\n"                            putStrLn (getUrl token)@@ -150,7 +122,7 @@                            putStrLn "if you don't get one.)\n"                            putStr   "Authorization key: "                            getLine-     M.put (injectOAuthVerifier answer token)+     putToken (injectOAuthVerifier answer token)  setup_help =   "Usage: twidge setup\n\n"
Commands/Update.hs view
@@ -23,7 +23,6 @@ import Types import System.Console.GetOpt import System.Console.GetOpt.Utils-import Data.List import Download import Control.Monad(when) import Text.Regex.Posix@@ -34,6 +33,7 @@ #ifdef USE_BITLY import Network.Bitly (Account(..),bitlyAccount,jmpAccount,shorten) #endif+import qualified Codec.Binary.UTF8.String as UTF8  i = infoM "update" d = debugM "update"@@ -46,10 +46,11 @@               "Indicate this message is in reply to MSGID"              ]              update_worker_wrapper-             + update_worker_wrapper x cp args =   do d $ "Running update_worker with: " ++ show (x, args)-     update_worker x cp args+     update_worker x cp (newargs args)+  where newargs (opts, status) = (opts, map UTF8.decodeString status)  update_worker x cp ([("m", "")], []) =     do d "Reading mail message"@@ -70,10 +71,9 @@                                        else []                  status = body              in do poststatus <- procStatus cp "update" status-                   xmlstr <- sendAuthRequest POST cp "/statuses/update.xml" []-                             ([("source", "twidge"), ("status", poststatus)] ++-                              irt)-                   debugM "update" $ "Got doc: " ++ xmlstr+                   json <- sendAuthRequest POST cp "/statuses/update.json" []+                             ([("status", poststatus)] ++ irt)+                   debugM "update" $ "Got doc: " ++ show json  update_worker x cp ([], []) =     do d "No args reading line"@@ -88,24 +88,27 @@ update_worker _ cp ([("i", id)], [status]) =     do d "-i have line"        poststatus <- procStatus cp "update" status-       xmlstr <- sendAuthRequest POST cp "/statuses/update.xml" [] -                 [("source", "Twidge"), ("status", poststatus), ("in_reply_to_status_id", id)]-       debugM "update" $ "Got doc: " ++ xmlstr+       json <- sendAuthRequest POST cp "/statuses/update.json" []+                 [("status", poststatus), ("in_reply_to_status_id", id)]+       debugM "update" $ "Got doc: " ++ show json  update_worker _ cp ([], [status]) =     do d "no args have line"        poststatus <- procStatus cp "update" status-       xmlstr <- sendAuthRequest POST cp "/statuses/update.xml" [] -                 [("source", "Twidge"), ("status", poststatus)]-       debugM "update" $ "Got doc: " ++ xmlstr+       json <- sendAuthRequest POST cp "/statuses/update.json" []+                 [("status", poststatus)]+       debugM "update" $ "Got doc: " ++ show json update_worker _ _ _ =     permFail "update: syntax error; see twidge update --help"  procStatus cp section status =     do poststatus <- case get cp section "shortenurls" of-                       Right True | length status > 140-                                  -> shortenUrls cp status-                       _ -> return status+                       Right True -> case get cp section "shortenall" of+                                       Right True -> shortenUrls cp status+                                       _ | length status > 140+                                                  -> shortenUrls cp status+                                       _          -> return status+                       _          -> return status        when (length poststatus > 140)                 (permFail $ "Your status update was " ++                            show (length poststatus) ++@@ -122,10 +125,9 @@        dmsend_worker x cp ([], [r, l]) dmsend_worker x cp ([], [recipient, status]) =     do poststatus <- procStatus cp "dmsend" status-       xmlstr <- sendAuthRequest POST cp "/direct_messages/new.xml" []-                 [("source", "Twidge"), -                  ("text", poststatus), ("user", recipient)]-       debugM "dmsend" $ "Got doc: " ++ xmlstr+       json <- sendAuthRequest POST cp "/direct_messages/new.json" []+                 [("text", poststatus), ("screen_name", recipient)]+       debugM "dmsend" $ "Got doc: " ++ show json dmsend_worker _ _ _ = permFail "Syntax error; see twidge dmsend --help"  shortenUrls _ "" = return ""@@ -142,7 +144,7 @@                                     then tiny else match)                              ++ rest     where (before, match, after) = status =~ pat-          pat = "(http|https|ftp)\\://[a-zA-Z0-9\\-\\.]+(:[a-zA-Z0-9]*)?/?([-a-zA-Z0-9:\\._\\?\\,\\'/\\\\\\+&%\\$#\\=~])*"+          pat = "(http|https|ftp)\\://[a-zA-Z0-9\\-\\.]+(:[a-zA-Z0-9]*)?/?([-a-zA-Z0-9:()\\._\\?\\,\\'/\\\\\\+&%\\$#\\=~])*"  #ifdef USE_BITLY chooseShortener cp = do
Config.hs view
@@ -41,12 +41,13 @@ getDefaultCP =     do return $ forceEither $                do cp <- set startingcp "DEFAULT" "serverbase" "https://api.twitter.com"-                 cp <- set cp "DEFAULT" "urlbase" "%(serverbase)s/1"+                 cp <- set cp "DEFAULT" "urlbase" "%(serverbase)s/1.1"                  cp <- set cp "DEFAULT" "oauthrequesttoken" "%(serverbase)s/oauth/request_token"                  cp <- set cp "DEFAULT" "oauthaccesstoken" "%(serverbase)s/oauth/access_token"                  cp <- set cp "DEFAULT" "oauthauthorize" "%(serverbase)s/oauth/authorize"                  cp <- set cp "DEFAULT" "sendmail" "/usr/sbin/sendmail"                  cp <- set cp "DEFAULT" "shortenurls" "yes"+                 cp <- set cp "DEFAULT" "shortenall" "no"                  return cp  startingcp = emptyCP {accessfunc = interpolatingAccess 10}
Download.hs view
@@ -1,5 +1,5 @@ {- hpodder component-Copyright (C) 2006-2010 John Goerzen <jgoerzen@complete.org>+Copyright (C) 2006-2013 John Goerzen <jgoerzen@complete.org>  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by@@ -18,7 +18,7 @@  {- |    Module     : Download-   Copyright  : Copyright (C) 2006-2010 John Goerzen+   Copyright  : Copyright (C) 2006-2013 John Goerzen    License    : GNU GPL, version 2 or above     Maintainer : John Goerzen <jgoerzen@complete.org>@@ -29,7 +29,8 @@  -} -module Download(sendAuthRequest, simpleDownload) where+module Download(sendAuthRequest, simpleDownload, twidgeCurlClient) where+import Control.Monad(unless) import System.Log.Logger import Data.ConfigFile import Data.List@@ -38,23 +39,44 @@ import Data.Maybe import Network.OAuth.Http.Request import Network.OAuth.Http.Response+import Network.OAuth.Http.CurlHttpClient+import Network.OAuth.Http.HttpClient+import Network.Curl import Network.OAuth.Consumer-import Network.OAuth.Http.HttpClient(request)-import TwidgeHttpClient import OAuth+import Data.ByteString.Lazy(ByteString) import Data.ByteString.Lazy.UTF8(toString)  d = debugM "download" i = infoM "download" +twidgeCurlClient :: CurlClient+twidgeCurlClient = OptionsCurlClient+                   [CurlLowSpeedTime 60+                   ,CurlLowSpeed 1+                   ,CurlUserAgent "twidge v1.1.0; Haskell. GHC"+                   ,CurlFollowLocation True  -- follow redirects+                   ,CurlFailOnError True     -- fail on server errors +                   ]++{- | Download a webpage without using oauth. -} simpleDownload :: String -> IO String simpleDownload url =   do r <- resp      d $ "simpleDownload response from URL " ++ show url ++ ": " ++ show r      return . toString . rspPayload $ r-  where CurlM resp = request (fromJust $ parseURL url)+  where resp = (runClient_ twidgeCurlClient) (fromJust $ parseURL url) -sendAuthRequest :: Method -> ConfigParser -> String -> [(String, String)] -> [(String, String)] -> IO String+needsUpgrade :: String+needsUpgrade = +  unlines $ +  ["Your configuration needs to be updated to work with changes at Twitter."+  ,"Please edit your configuration file and fix the urlbase option.  In most"+  ,"cases, you can set it like this:"+  ,""+  ,"urlbase: %(serverbase)s/1.1"]++sendAuthRequest :: Method -> ConfigParser -> String -> [(String, String)] -> [(String, String)] -> IO ByteString sendAuthRequest mth cp url getopts postoptlist =     do app <- case getApp cp of                Nothing -> fail $ "Error: auth not set up for this host"@@ -64,6 +86,8 @@                    ++ show x          Right y -> return y        +       unless (urlbase /= "https://api.twitter.com/1") $+         fail $ needsUpgrade        let parsedUrl = fromJust . parseURL $ urlbase ++ url ++ optstr                -- add to the request the GET/POST headers@@ -72,17 +96,13 @@                                                     postoptlist)                                ,method = mth                                }-       -       let CurlM resp = runOAuth $ -                        do ignite app-                           putToken $ AccessToken -                                       {application = app,-                                        oauthParams = fromList (read oauthdata)-                                       }-                           serviceRequest HMACSHA1 Nothing request+       let token = AccessToken app (fromList (read oauthdata))+       let resp = runOAuthM token $ +                  do r <- signRq2 HMACSHA1 (Just $ Realm "realm") request+                     serviceRequest twidgeCurlClient r        r <- resp        d $ "response: " ++ show r-       return . toString . rspPayload $ r+       return . rspPayload $ r     where urlbase = forceEither $ get cp "DEFAULT" "urlbase"           optstr = case getopts of                      [] -> ""
− FeedParser.hs
@@ -1,70 +0,0 @@-{- -Copyright (C) 2006-2008 John Goerzen <jgoerzen@complete.org>--This program is free software; you can redistribute it and/or modify-it under the terms of the GNU General Public License as published by-the Free Software Foundation; either version 2 of the License, or-(at your option) any later version.--This program is distributed in the hope that it will be useful,-but WITHOUT ANY WARRANTY; without even the implied warranty of-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the-GNU General Public License for more details.--You should have received a copy of the GNU General Public License-along with this program; if not, write to the Free Software-Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA--}--{- |-   Module     : FeedParser-   Copyright  : Copyright (C) 2006-2008 John Goerzen-   License    : GNU GPL, version 2 or above--   Maintainer : John Goerzen <jgoerzen@complete.org>-   Stability  : provisional-   Portability: portable--Written by John Goerzen, jgoerzen\@complete.org---}-module FeedParser where--import Text.XML.HaXml-import Data.Char-import Data.List-import Data.String.Utils(strip)--{- | Convert [Content] to a printable string, taking care to unescape it.--An implementation without unescaping would simply be:--> contentToString = concatMap (show . content)--Because HaXml's unescaping only works on Elements, we must make sure that-whatever Content we have is wrapped in an Element, then use txt to-pull the insides back out. -}-contentToString :: [Content] -> String-contentToString = -    concatMap procContent-    where procContent x = -              verbatim $ keep /> txt $ CElem (unesc (fakeElem x))--          fakeElem :: Content -> Element-          fakeElem x = Elem "fake" [] [x]--          unesc :: Element -> Element-          unesc = xmlUnEscape stdXmlEscaper--stripUnicodeBOM :: String -> String-stripUnicodeBOM ('\xef':'\xbb':'\xbf':x) = x-stripUnicodeBOM x = x--{- | Removes potentially problematic or malicious stuff -}-sanitize :: String -> String-sanitize = strip . map sanitizer-    where sanitizer c -              | c `elem` "\n\r\0\t" = ' '-              | otherwise = c--getContent (Document _ _ e _) = CElem e
MailParser.hs view
@@ -19,7 +19,7 @@ module MailParser where import Text.ParserCombinators.Parsec import Data.String.Utils-import FeedParser(sanitize)+import Types(sanitize)  eol = (string "\r\n") <|> string "\n" 
OAuth.hs view
@@ -48,6 +48,14 @@ twitterDeObfuscator :: String -> String twitterDeObfuscator = reverse . map rot13 +instance Show Application where+  show (Application a b c) = "Application " ++ show a ++ " " ++ show b ++ " " ++ show c++instance Show Token where+  show (TwoLegg app oauthp) = "TwoLegg " ++ show app ++ " " ++ show oauthp+  show (ReqToken app oauthp) = "ReqToken " ++ show app ++ " " ++ show oauthp+  show (AccessToken app oauthp) = "AccessToken " ++ show app ++ " " ++ show oauthp+ rot13 :: Char -> Char rot13 x =      case lookup x trans of
− TwidgeHttpClient.hs
@@ -1,110 +0,0 @@-{-# LANGUAGE GeneralizedNewtypeDeriving, CPP #-}---- Copyright (c) 2009, Diego Souza--- All rights reserved.--- --- Redistribution and use in source and binary forms, with or without--- modification, are permitted provided that the following conditions are met:--- ---   * Redistributions of source code must retain the above copyright notice,---     this list of conditions and the following disclaimer.---   * Redistributions in binary form must reproduce the above copyright notice,---     this list of conditions and the following disclaimer in the documentation---     and/or other materials provided with the distribution.---   * Neither the name of the <ORGANIZATION> nor the names of its contributors---     may be used to endorse or promote products derived from this software---     without specific prior written permission.--- --- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND--- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED--- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE--- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE--- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL--- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR--- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER--- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,--- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE--- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.---- | A type class that is able to perform HTTP requests.-module TwidgeHttpClient (CurlM(..)-                                     ) where--import Network.Curl-import Control.Monad.Fix-import Network.OAuth.Http.Request-import Network.OAuth.Http.Response-import qualified Network.OAuth.Http.HttpClient-import Control.Monad.Trans-import Data.Char (chr,ord)-import System.Log.Logger-import Network.URI-import qualified Data.ByteString.Lazy as B--d = debugM "TwidgeHttpClient"---- | The libcurl backend-newtype CurlM a = CurlM { unCurlM :: IO a }-  deriving (Monad,MonadIO,MonadFix,Functor)--instance Network.OAuth.Http.HttpClient.HttpClient CurlM where-#if MIN_VERSION_hoauth(0,2,4)-  unpack = unCurlM-#else-  unlift = unCurlM-#endif--  request req = CurlM $ withCurlDo $ do c <- initialize-                                        setopts c opts-                                        d $ "Sending request: " ++ show req-                                        rsp <- perform_with_response_ c-                                        d $ "Got response: " ++ show -                                          (respStatus rsp, respStatusLine rsp,-                                           respHeaders rsp, respBody rsp)-                                        if respStatus rsp < 200 || respStatus rsp >= 300-                                          then fail $ "Bad response: " ++ show (respStatus rsp)-                                          else return () -                                        return $ RspHttp (respStatus rsp)-                                                         (respStatusLine rsp)-                                                         (fromList.respHeaders $ rsp)-                                                         (B.pack.map (fromIntegral.ord).respBody $ rsp)-    where httpVersion = case (version req)-                        of Http10 -> HttpVersion10-                           Http11 -> HttpVersion11-                           -          url = case method req of-            POST -> showURL (req {qString = fromList []})-            _ -> showURL req-          curlPostData = case method req of-            POST -> [CurlPostFields (map postopt . toList . qString $ req)]-            _ -> []-          postopt (k, v) = escapeURIString isUnreserved k ++ "=" ++-                           escapeURIString isUnreserved v-          -          curlMethod = case (method req)-                       of GET   -> [CurlHttpGet True]-                          POST  -> [CurlPost True]-                          PUT   -> [CurlPut True]-                          HEAD  -> [CurlNoBody True,CurlCustomRequest "HEAD"]-                          other -> if (B.null.reqPayload $ req)-                                   then [CurlHttpGet True,CurlCustomRequest (show other)]-                                   else [CurlPost True,CurlCustomRequest (show other)]-          curlHeaders = let headers = (map (\(k,v) -> k++": "++v).toList.reqHeaders $ req)-                        in [CurlHttpHeaders $"Expect: " -                                            :("Content-Length: " ++ (show.B.length.reqPayload $ req))-                                            :headers-                           ]--          opts = [CurlURL (showURL req)-                 ,CurlHttpVersion httpVersion-                 ,CurlFollowLocation True  -- follow redirects-                 ,CurlFailOnError True     -- fail on server errors-                 ,CurlLowSpeedTime 60-                 ,CurlLowSpeed 1-                 ,CurlUserAgent "twidge v1.0.0; Haskell; GHC"-                 ,CurlHeader False-                 ] ++ curlHeaders-                   ++ curlMethod -                   ++ curlPostData-          --- vim:sts=2:sw=2:ts=2:et
Types.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE OverloadedStrings, CPP #-} {- hpodder component Copyright (C) 2006-2007 John Goerzen <jgoerzen@complete.org> @@ -29,8 +30,34 @@  -} module Types where+import Control.Applicative+import Control.Monad (mzero) import Data.ConfigFile+import Data.Aeson+import Data.ByteString.Lazy (ByteString)+import Data.List (isPrefixOf)+import Data.String.Utils (strip)+import Data.Text (unpack) +decode_json :: FromJSON a => ByteString -> a+decode_json = either error id . eitherDecode++{- | Removes potentially problematic or malicious stuff -}+sanitize :: String -> String+sanitize = strip . map sanitizer+    where sanitizer c+              | c `elem` "\n\r\0\t" = ' '+              | otherwise = c++{- | Twitter has an additional level of escaping for &lt; and &gt; only.+Sigh. -}+unEsc :: String -> String+unEsc [] = []+unEsc x+  | "&lt;" `isPrefixOf` x = '<' : unEsc (drop 4 x)+  | "&gt;" `isPrefixOf` x = '>' : unEsc (drop 4 x)+  | otherwise = head x : unEsc (tail x)+ data Command =     Command {cmdname :: String,              cmddescrip :: String,@@ -44,3 +71,55 @@       sDate :: String     } deriving (Eq, Read, Show, Ord) ++newtype TimelineMessage = TimelineMessage { fromTimeline :: Message }++instance FromJSON TimelineMessage where+  parseJSON j = TimelineMessage <$> parseTimelineMessage j++parseTimelineMessage (Object v) = Message <$>+    s v "id_str" <*>+    (v .: "user" >>= extractScreenName) <*>+    pure "" <*>+    retweetOrText v <*>+    s v "created_at"+parseTimelineMessage _ = mzero++newtype DirectMessage = DirectMessage { fromDM :: Message }++instance FromJSON DirectMessage where+  parseJSON j = DirectMessage <$> parseDirectMessage j++parseDirectMessage (Object v) = Message <$>+    s v "id_str" <*>+    s v "sender_screen_name" <*>+    s v "recipient_screen_name" <*>+    (unEsc <$> s v "text") <*>+    s v "created_at"+parseDirectMessage _ = mzero++extractScreenName (Object v) = s v "screen_name"+extractScreenName _ = mzero++retweetOrText v = unEsc <$> ((retweet v) <|> (unpack <$> v .: "text")) where+  retweet v = do+    rt <- v .: "retweeted_status"+    user <- rt .: "user" >>= extractScreenName+    text <- rt .: "text"+    return $ "RT @" ++ user ++ ": " ++ text++s v name = sanitize <$> v .: name++data UserList = UserList [ListedUser] (Maybe String)++newtype ListedUser = ListedUser { fromListedUser :: (String, String) }++instance FromJSON UserList where+  parseJSON (Object v) = UserList <$> v .: "users" <*> v .:? "next_cursor_str"+  parseJSON _ = mzero++instance FromJSON ListedUser where+  parseJSON (Object v) = (ListedUser .) . (,) <$>+    v .: "screen_name" <*>+    v .: "id_str"+  parseJSON _ = mzero
doc/twidge-manpage.sgml view
@@ -264,10 +264,9 @@     <para>       Several commands can display updates from yourself or others.       They all share a common syntax and common set of options.  The-      commands are <command>lsarchive</command>,+      commands are       <command>lsdm</command>, <command>lsdmarchive</command>,       <command>lsrecent</command>, <command>lsreplies</command>,-      <command>lsrt</command>, <command>lsrtarchive</command>,       and <command>lsrtreplies</command>.       Here are the common options:     </para>@@ -404,26 +403,6 @@       are undefined if you use such options simultaneously.     </para> -    <refsect2 id="twidge.commands.lsarchive">-      <title>lsarchive</title>-      <para>-        Lists your own posts.  With the one-        <literal>lsarchive</literal> option, you can also list the-        posts of a specific other user:-      </para>-      <variablelist>-        <varlistentry>-          <term>-U <replaceable>USERNAME</replaceable></term>-          <term>--username=<replaceable>USERNAME</replaceable></term>-          <listitem><para>-              Instead of showing your own updates, instead show those of-              a differerent user.-            </para>-          </listitem>-        </varlistentry>-      </variablelist>-    </refsect2>-     <refsect2 id="twidge.commands.lsdm">       <title>lsdm</title>       <para>@@ -457,28 +436,6 @@       </para>     </refsect2> -    <refsect2 id ="twidge.commands.lsrt">-      <title>lsrt</title>-      <para>-        Lists new-style retweets made by those that you follow. -      </para>-      <para>-        Note: identi.ca doesn't support new-style retweets and will-        return an error on this command.-      </para>-    </refsect2>--    <refsect2 id="twidge.commands.lsrtarchive">-      <title>lsrtarchive</title>-      <para>-        Lists the new-style retweets you made yourself.-      </para>-      <para>-        Note: identi.ca doesn't support new-style retweets and will-        return an error on this command.-      </para>-    </refsect2>-     <refsect2 id="twidge.commands.lsrtreplies">       <title>lsrtreplies</title>       <para>@@ -559,10 +516,21 @@       </para>       <para>         By default, Twidge will attempt to shorten URLs in your-        updates via the is.gd service.  You can disable this by-        setting shortenurls = no in the [DEFAULT] or [dmsend] section of your-        configuration file.+        updates via an URL shortening service, but only if your+        update's length exceeds 140 characters. This means long+        URLs shorter than 140 characters WILL NOT be shortened.       </para>+      <para>+        If you want all your URLs to be shortened all the time+        regardless of their length, then set shortenall = yes+        in the [DEFAULT] or [dmsend] section of your configuration+        file.+      </para>+      <para>+        You can disable URL shortening altogether by setting+        shortenurls = no in the [DEFAULT] or [dmsend] section of+        your configuration file.+      </para>     </refsect2>      <refsect2 id="twidge.commands.follow">@@ -626,11 +594,22 @@       </para>       <para>         By default, Twidge will attempt to shorten URLs in your-        updates via the TinyURL.com service.  You can disable this by-        setting shortenurls = no in the [DEFAULT] or [update] section of your-        configuration file.+        updates via an URL shortening service, but only if your+        update's length exceeds 140 characters. This means long+        URLs shorter than 140 characters WILL NOT be shortened.       </para>       <para>+        If you want all your URLs to be shortened all the time+        regardless of their length, then set shortenall = yes+        in the [DEFAULT] or [dmsend] section of your configuration+        file.+      </para>+      <para>+        You can disable URL shortening altogether by setting+        shortenurls = no in the [DEFAULT] or [dmsend] section of+        your configuration file.+      </para>+      <para>         When -r is given, &twidge; expects to read an RFC2822 email         message in stdin.  The body of the message will be used as the         content of the update.  The References header of the message@@ -709,9 +688,6 @@  ; Last IDs seen by different commands.  Written to with -s. -[lsarchive]-lastid = 917383569- [lsrecent] lastid = 914881241 ]]></programlisting>@@ -855,17 +831,34 @@               You may specify <literal>shortenurls</literal> in the               <literal>[update]</literal> section.  It defaults to yes.  If               set to no, will cause &twidge; to omit its attempt to shorten-              URLs in your updates via <literal>tinyurl.com</literal>.+              URLs in your updates. If set to yes, will cause &twidge; to+              shorten URLs in your updates, only if the latter's length+              exceeds 140 characters. See <literal>shortenall</literal>             </para>           </listitem>         </varlistentry> +        <varlistentry><term>shortenall</term>+          <listitem>+            <para>+              You may specify <literal>shortenall</literal> in the+              <literal>[update]</literal> section.  It defaults to no.  If+              set to yes, will cause &twidge; to shorten all URLs in+              your updates, all the time. It only makes sense if+              <literal>shortenurls</literal> is set to yes.+            </para>+          </listitem>+        </varlistentry>+       </variablelist>      </refsect2>      <refsect2 id="twidge.config.shortening">       <title>URL Shortening Options</title>+      <para>+        &twidge; uses is.gd as its default URL shortening service.+      </para>       <para>         To enable the bit.ly or j.ump URL shorteners, you must add a         <literal>[bitly]</literal> or <literal>[jmp]</literal> section
twidge.bash_completion view
@@ -4,7 +4,9 @@ # Copyright 2010 Ernesto Hernández-Novich <emhn@itverx.com.ve> # Licensed under the GNU General Public License, version 2 -have twidge &&+: ${have=$(command -v twidge)}++test -n "$have" && _twidge() {     local cur@@ -22,10 +24,10 @@     while [ $((--i)) -ge 0 ]; do       COMPREPLY[$i]=`printf %q "${COMPREPLY[$i]}"` -      COMPREPLY[$i]=${COMPREPLY[$i]#"$colonprefixes"} +      COMPREPLY[$i]=${COMPREPLY[$i]#"$colonprefixes"}     done     return 0  }-[ "$have" ] && complete -F _twidge -o default twidge+test -n "$have" && complete -F _twidge -o default twidge 
twidge.cabal view
@@ -1,10 +1,10 @@ Name: twidge-Version: 1.0.6.1+Version: 1.1.0 License: GPL Maintainer: John Goerzen <jgoerzen@complete.org> Author: John Goerzen Stability: Stable-Copyright: Copyright (c) 2006-2010 John Goerzen+Copyright: Copyright (c) 2006-2013 John Goerzen license-file: COPYRIGHT extra-source-files: COPYING, INSTALL, doc/SConstruct,   doc/twidge-manpage.sgml, doc/twidge.sgml, doc/local.dsl,@@ -48,9 +48,10 @@  Executable twidge   Build-Depends: network, unix, parsec, MissingH>=1.0.0,-   mtl, base >= 4 && < 5, HaXml>=1.13.2, HaXml<1.19, hslogger, hoauth>=0.2.3 && <0.2.4,+   mtl, base >= 4 && < 5, hslogger, hoauth>=0.3.4 && <0.4,    ConfigFile, directory, HSH, regex-posix, utf8-string, binary,-   bytestring, curl+   bytestring, curl, old-locale, time, aeson>=0.6.1.0 && <0.7,+   text>=0.11.2.0 && <0.12    if flag(withBitly)     Build-Depends: Bitly@@ -58,8 +59,8 @@    Main-Is: twidge.hs   Other-Modules: Commands, Commands.FollowBlock, Commands.Ls,-        Commands.Setup, Commands.Update, Config, Download, FeedParser,-        MailParser, Types, Utils, OAuth, TwidgeHttpClient+        Commands.Setup, Commands.Update, Config, Download,+        MailParser, Types, Utils, OAuth   GHC-Options: -O2   Extensions: ExistentialQuantification, OverlappingInstances,     UndecidableInstances