tweet-hs 0.6.1.3 → 1.0.0.0
raw patch · 10 files changed
+248/−39 lines, 10 filesdep +QuickCheckdep +htomldep +unordered-containersdep ~megaparsecPVP ok
version bump matches the API change (PVP)
Dependencies added: QuickCheck, htoml, unordered-containers
Dependency ranges changed: megaparsec
API changes (from Hackage documentation)
- Web.Tweet: block :: String -> FilePath -> IO ()
- Web.Tweet: blockUserRaw :: String -> FilePath -> IO ByteString
- Web.Tweet: deleteTweet :: Integer -> FilePath -> IO ()
- Web.Tweet: deleteTweetRaw :: Integer -> FilePath -> IO ByteString
- Web.Tweet: deleteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
- Web.Tweet: favoriteTweet :: Integer -> FilePath -> IO ()
- Web.Tweet: favoriteTweetRaw :: Integer -> FilePath -> IO ByteString
- Web.Tweet: favoriteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
- Web.Tweet: follow :: String -> FilePath -> IO ()
- Web.Tweet: followUserRaw :: String -> FilePath -> IO ByteString
- Web.Tweet: getAll :: String -> Maybe Int -> FilePath -> IO Timeline
- Web.Tweet: getDMs :: Int -> FilePath -> IO ByteString
- Web.Tweet: getDMsRaw :: Show t => t -> FilePath -> IO ByteString
- Web.Tweet: getMarkov :: String -> Maybe Int -> FilePath -> IO [String]
- Web.Tweet: getProfile :: String -> Int -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
- Web.Tweet: getProfileMax :: String -> Int -> FilePath -> Maybe Int -> IO (Either (ParseError Char Dec) Timeline)
- Web.Tweet: getProfileRaw :: String -> Int -> FilePath -> Maybe Int -> IO ByteString
- Web.Tweet: getTimeline :: Int -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
- Web.Tweet: getTimelineRaw :: Int -> FilePath -> IO ByteString
- Web.Tweet: mentions :: Int -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
- Web.Tweet: mentionsRaw :: Int -> FilePath -> IO ByteString
- Web.Tweet: mute :: String -> FilePath -> IO ()
- Web.Tweet: muteUserRaw :: String -> FilePath -> IO ByteString
- Web.Tweet: retweetTweet :: Integer -> FilePath -> IO ()
- Web.Tweet: retweetTweetRaw :: Integer -> FilePath -> IO ByteString
- Web.Tweet: retweetTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
- Web.Tweet: sendDMRaw :: String -> [Char] -> FilePath -> IO ByteString
- Web.Tweet: tweetData :: Tweet -> FilePath -> IO Int
- Web.Tweet: unblock :: String -> FilePath -> IO ()
- Web.Tweet: unblockUserRaw :: String -> FilePath -> IO ByteString
- Web.Tweet: unfavoriteTweet :: Integer -> FilePath -> IO ()
- Web.Tweet: unfavoriteTweetRaw :: Integer -> FilePath -> IO ByteString
- Web.Tweet: unfavoriteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
- Web.Tweet: unfollow :: String -> FilePath -> IO ()
- Web.Tweet: unfollowUserRaw :: String -> FilePath -> IO ByteString
- Web.Tweet: unmute :: String -> FilePath -> IO ()
- Web.Tweet: unmuteUserRaw :: String -> FilePath -> IO ByteString
- Web.Tweet: unretweetTweet :: Integer -> FilePath -> IO ()
- Web.Tweet: unretweetTweetRaw :: Integer -> FilePath -> IO ByteString
- Web.Tweet: unretweetTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: block :: String -> FilePath -> IO ()
+ Web.Tweet.API: blockMem :: String -> Config -> IO ()
+ Web.Tweet.API: blockUserRaw :: String -> FilePath -> IO ByteString
+ Web.Tweet.API: blockUserRawMem :: String -> Config -> IO ByteString
+ Web.Tweet.API: deleteTweet :: Integer -> FilePath -> IO ()
+ Web.Tweet.API: deleteTweetMem :: Integer -> Config -> IO ()
+ Web.Tweet.API: deleteTweetRaw :: Integer -> FilePath -> IO ByteString
+ Web.Tweet.API: deleteTweetRawMem :: Integer -> Config -> IO ByteString
+ Web.Tweet.API: deleteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: deleteTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: favoriteTweet :: Integer -> FilePath -> IO ()
+ Web.Tweet.API: favoriteTweetMem :: Integer -> Config -> IO ()
+ Web.Tweet.API: favoriteTweetRaw :: Integer -> FilePath -> IO ByteString
+ Web.Tweet.API: favoriteTweetRawMem :: Integer -> Config -> IO ByteString
+ Web.Tweet.API: favoriteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: follow :: String -> FilePath -> IO ()
+ Web.Tweet.API: followMem :: String -> Config -> IO ()
+ Web.Tweet.API: followUserRaw :: String -> FilePath -> IO ByteString
+ Web.Tweet.API: followUserRawMem :: String -> Config -> IO ByteString
+ Web.Tweet.API: getAll :: String -> Maybe Int -> FilePath -> IO Timeline
+ Web.Tweet.API: getDMMem :: Int -> Config -> IO ByteString
+ Web.Tweet.API: getDMs :: Int -> FilePath -> IO ByteString
+ Web.Tweet.API: getDMsRaw :: Show t => t -> FilePath -> IO ByteString
+ Web.Tweet.API: getMarkov :: String -> Maybe Int -> FilePath -> IO [String]
+ Web.Tweet.API: getProfile :: String -> Int -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: getProfileMax :: String -> Int -> FilePath -> Maybe Int -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: getProfileMaxMem :: String -> Int -> Config -> Maybe Int -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: getProfileRaw :: String -> Int -> FilePath -> Maybe Int -> IO ByteString
+ Web.Tweet.API: getProfileRawMem :: String -> Int -> Config -> Maybe Int -> IO ByteString
+ Web.Tweet.API: getTimeline :: Int -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: getTimelineMem :: Int -> Config -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: getTimelineRaw :: Int -> FilePath -> IO ByteString
+ Web.Tweet.API: getTimelineRawMem :: Int -> Config -> IO ByteString
+ Web.Tweet.API: mentions :: Int -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: mentionsMem :: Int -> Config -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: mentionsRaw :: Int -> FilePath -> IO ByteString
+ Web.Tweet.API: mentionsRawMem :: Int -> Config -> IO ByteString
+ Web.Tweet.API: mute :: String -> FilePath -> IO ()
+ Web.Tweet.API: muteMem :: String -> Config -> IO ()
+ Web.Tweet.API: muteUserRaw :: String -> FilePath -> IO ByteString
+ Web.Tweet.API: muteUserRawMem :: String -> Config -> IO ByteString
+ Web.Tweet.API: retweetTweet :: Integer -> FilePath -> IO ()
+ Web.Tweet.API: retweetTweetMem :: Integer -> Config -> IO ()
+ Web.Tweet.API: retweetTweetRaw :: Integer -> FilePath -> IO ByteString
+ Web.Tweet.API: retweetTweetRawMem :: Integer -> Config -> IO ByteString
+ Web.Tweet.API: retweetTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: retweetTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: sendDMRaw :: String -> [Char] -> FilePath -> IO ByteString
+ Web.Tweet.API: tweetData :: Tweet -> FilePath -> IO Int
+ Web.Tweet.API: tweetDataMem :: Tweet -> Config -> IO Int
+ Web.Tweet.API: unblock :: String -> FilePath -> IO ()
+ Web.Tweet.API: unblockMem :: String -> Config -> IO ()
+ Web.Tweet.API: unblockUserRaw :: String -> FilePath -> IO ByteString
+ Web.Tweet.API: unblockUserRawMem :: String -> Config -> IO ByteString
+ Web.Tweet.API: unfavoriteTweet :: Integer -> FilePath -> IO ()
+ Web.Tweet.API: unfavoriteTweetMem :: Integer -> Config -> IO ()
+ Web.Tweet.API: unfavoriteTweetRaw :: Integer -> FilePath -> IO ByteString
+ Web.Tweet.API: unfavoriteTweetRawMem :: Integer -> Config -> IO ByteString
+ Web.Tweet.API: unfavoriteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: unfavoriteTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: unfollow :: String -> FilePath -> IO ()
+ Web.Tweet.API: unfollowMem :: String -> Config -> IO ()
+ Web.Tweet.API: unfollowUserRaw :: String -> FilePath -> IO ByteString
+ Web.Tweet.API: unfollowUserRawMem :: String -> Config -> IO ByteString
+ Web.Tweet.API: unmute :: String -> FilePath -> IO ()
+ Web.Tweet.API: unmuteMem :: String -> Config -> IO ()
+ Web.Tweet.API: unmuteUserRaw :: String -> FilePath -> IO ByteString
+ Web.Tweet.API: unmuteUserRawMem :: String -> Config -> IO ByteString
+ Web.Tweet.API: unretweetTweet :: Integer -> FilePath -> IO ()
+ Web.Tweet.API: unretweetTweetMem :: Integer -> Config -> IO ()
+ Web.Tweet.API: unretweetTweetRaw :: Integer -> FilePath -> IO ByteString
+ Web.Tweet.API: unretweetTweetRawMem :: Integer -> Config -> IO ByteString
+ Web.Tweet.API: unretweetTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.API: unretweetTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Dec) Timeline)
+ Web.Tweet.Sign: mkConfig :: FilePath -> IO Config
+ Web.Tweet.Sign: mkConfigToml :: FilePath -> IO Config
+ Web.Tweet.Sign: signRequest :: FilePath -> Request -> IO Request
+ Web.Tweet.Sign: signRequestMem :: Config -> Request -> IO Request
Files
- README.md +6/−5
- bench/Bench.hs +7/−2
- src/Web/Tweet.hs +2/−0
- src/Web/Tweet/API.hs +147/−23
- src/Web/Tweet/API/Internal.hs +30/−0
- src/Web/Tweet/Exec.hs +2/−2
- src/Web/Tweet/Sign.hs +34/−2
- src/Web/Tweet/Utils/API.hs +4/−2
- test/Spec.hs +8/−0
- tweet-hs.cabal +8/−3
README.md view
@@ -37,13 +37,14 @@ ## Config Generate a token to authorize access to your twitter account by following the guide [here](https://dev.twitter.com/oauth/overview/application-owner-access-tokens) -Then place your API keys and OAuth tokens in a file `~/.cred`, separated by a line break:+Then place your API keys and OAuth tokens in a file `~/.cred.toml`, as in the+following example: ```-api-key: API_KEY_HERE-api-sec: API_SECRET_HERE-tok: OAUTH_TOKEN_HERE-tok-sec: TOKEN_SECRET_HERE+api-key = "API_KEY_HERE"+api-sec = "API_SECRET_HERE"+tok = "OAUTH_TOKEN_HERE"+tok-sec = "TOKEN_SECRET_HERE" ``` ## Installation
bench/Bench.hs view
@@ -3,15 +3,20 @@ import Criterion.Main import qualified Data.ByteString as BS import Web.Tweet.Parser.FastParser-+import Web.Tweet.Parser+import Text.Megaparsec setupEnv = BS.readFile "test/data"+setupEnv' = readFile "test/data" fast = fmap (fmap fromFast) . fastParse main = do defaultMain [ env setupEnv $ \ ~file ->- bgroup "fastParser"+ bgroup "aeson parser" [ bench "226" $ whnf fast file ]+ , env setupEnv' $ \ ~file ->+ bgroup "handrolled parser"+ [ bench "226" $ whnf (parse parseTweet) file ] ]
src/Web/Tweet.hs view
@@ -23,6 +23,7 @@ , module Web.Tweet.Types -- * Various API calls , module Web.Tweet.API+ , module Web.Tweet.API.Internal -- * Functions to sign API requests , signRequest -- * Functions to generate a URL string from a `Tweet`@@ -33,6 +34,7 @@ import Web.Tweet.Sign import Web.Tweet.API+import Web.Tweet.API.Internal import Web.Tweet.Utils.API import Web.Tweet.Utils import Web.Tweet.Types
src/Web/Tweet/API.hs view
@@ -1,6 +1,6 @@ {-# LANGUAGE OverloadedStrings #-} --- | Module containing the functions directly dealing with twitter's API+-- | Module containing the functions directly dealing with twitter's API. Most functions in this module have two versions - one which takes a path to a TOML file containing api keys/secrets and tokens/secrets, the other takes api keys/secrets and tokens/secrets as an argument. module Web.Tweet.API where import qualified Data.ByteString.Lazy.Char8 as BSL@@ -14,7 +14,7 @@ -- | Get tweets (text only) for some user getMarkov :: String -> Maybe Int -> FilePath -> IO [String]-getMarkov = (fmap (map (view text))) .** getAll +getMarkov = (fmap (map (view text))) .** getAll -- | Get all tweets by some user getAll :: String -> Maybe Int -> FilePath -> IO Timeline@@ -29,6 +29,14 @@ next <- getAll screenName (Just lastId) filepath pure (tweets ++ next) +-- | tweet, given a `Tweet` and a `Config` containing necessary data to sign the request.+tweetDataMem :: Tweet -> Config -> IO Int+tweetDataMem tweet config = do+ let requestString = urlString tweet+ bytes <- postRequestMem ("https://api.twitter.com/1.1/statuses/update.json" ++ requestString) config+ putStrLn $ displayTimelineColor . either (error "failed to parse tweet") id . getTweets . BSL.toStrict $ bytes+ pure . (view tweetId) . head . either (error "failed to parse tweet") id . getTweets . BSL.toStrict $ bytes+ -- | tweet, given a `Tweet` and path to credentials. Return id of posted tweet. tweetData :: Tweet -> FilePath -> IO Int tweetData tweet filepath = do@@ -42,61 +50,76 @@ getProfileMax = fmap (getTweets . BSL.toStrict) .*** getProfileRaw -- | Gets user profile with max_id set.+getProfileMaxMem :: String -> Int -> Config -> Maybe Int -> IO (Either (ParseError Char Dec) Timeline)+getProfileMaxMem = fmap (getTweets . BSL.toStrict) .*** getProfileRawMem++-- | Gets user profile with max_id set. getProfileRaw :: String -> Int -> FilePath -> Maybe Int -> IO BSL.ByteString getProfileRaw screenName count filepath maxId = getRequest ("https://api.twitter.com/1.1/statuses/user_timeline.json" ++ requestString) filepath where requestString = case maxId of { (Just id) -> "?screen_name=" ++ screenName ++ "&count=" ++ (show count) ++ "&max_id=" ++ (show id) ; Nothing -> "?screen_name=" ++ screenName ++ "&count=" ++ (show count) } +-- | Gets user profile with max_id set+getProfileRawMem :: String -> Int -> Config -> Maybe Int -> IO BSL.ByteString+getProfileRawMem screenName count config maxId = getRequestMem ("https://api.twitter.com/1.1/statuses/user_timeline.json" ++ requestString) config+ where requestString = case maxId of {+ (Just id) -> "?screen_name=" ++ screenName ++ "&count=" ++ (show count) ++ "&max_id=" ++ (show id) ;+ Nothing -> "?screen_name=" ++ screenName ++ "&count=" ++ (show count) }+ -- | Get mentions and parse response as a list of tweets mentions :: Int -> FilePath -> IO (Either (ParseError Char Dec) Timeline) mentions = fmap (getTweets . BSL.toStrict) .* mentionsRaw +-- | Get mentions and parse response as a list of tweets+mentionsMem :: Int -> Config -> IO (Either (ParseError Char Dec) Timeline)+mentionsMem = fmap (getTweets . BSL.toStrict) .* mentionsRawMem+ -- | Gets mentions mentionsRaw :: Int -> FilePath -> IO BSL.ByteString mentionsRaw count = getRequest ("https://api.twitter.com/1.1/statuses/mentions_timeline.json" ++ requestString) where requestString = "?count=" ++ (show count) +-- | Gets mentions+mentionsRawMem :: Int -> Config -> IO BSL.ByteString+mentionsRawMem count = getRequestMem ("https://api.twitter.com/1.1/statuses/mentions_timeline.json" ++ requestString)+ where requestString = "?count=" ++ (show count)+ -- | Get user profile given screen name and how many tweets to return getProfile :: String -> Int -> FilePath -> IO (Either (ParseError Char Dec) Timeline) getProfile screenName count filepath = getProfileMax screenName count filepath Nothing --- | Show a user profile given screen name, how many tweets to return, --- and whether to print them in color.-showProfile :: String -> Int -> Bool -> FilePath -> IO String-showProfile screenName count color = fmap (showTweets color) . getProfile screenName count---- | Show the most successful tweets by a given user, given their screen name. -showBest :: String -> Int -> Bool -> FilePath -> IO String-showBest screenName n color = fmap (showTweets color . pure . (take n . hits)) . getAll screenName Nothing---- | Show the most successful tweets by a given user, given their screen name. Additionally filter out replies.-showBest' :: String -> Int -> Bool -> FilePath -> IO String-showBest' screenName n color = fmap (showTweets color . pure . (take n . hits')) . getAll screenName Nothing- -- | Mute a user given their screen name mute :: String -> FilePath -> IO () mute = (fmap void) . muteUserRaw +-- | Mute a user given their screen name+muteMem :: String -> Config -> IO ()+muteMem = (fmap void) . muteUserRawMem+ -- | Unmute a user given their screen name unmute :: String -> FilePath -> IO () unmute = (fmap void) . unmuteUserRaw +-- | Unmute a user given their screen name+unmuteMem :: String -> Config -> IO ()+unmuteMem = (fmap void) . unmuteUserRawMem+ -- | Mute a user given their screen name muteUserRaw :: String -> FilePath -> IO BSL.ByteString muteUserRaw screenName = postRequest ("https://api.twitter.com/1.1/mutes/users/create.json?screen_name=" ++ screenName) +-- | Mute a user given their screen name+muteUserRawMem :: String -> Config -> IO BSL.ByteString+muteUserRawMem screenName = postRequestMem ("https://api.twitter.com/1.1/mutes/users/create.json?screen_name=" ++ screenName)+ -- | Unmute a user given their screen name unmuteUserRaw :: String -> FilePath -> IO BSL.ByteString unmuteUserRaw screenName = postRequest ("https://api.twitter.com/1.1/mutes/users/destroy.json?screen_name=" ++ screenName) --- | Display user timeline-showTimeline :: Int -> Bool -> FilePath -> IO String-showTimeline count color = (fmap (showTweets color)) . getTimeline count ---- | Display user timeline in color, as appropriate-showTweets :: Bool -> Either (ParseError Char Dec) Timeline -> String-showTweets color = (either show id) . (fmap (if color then displayTimelineColor else displayTimeline))+-- | Unmute a user given their screen name+unmuteUserRawMem :: String -> Config -> IO BSL.ByteString+unmuteUserRawMem screenName = postRequestMem ("https://api.twitter.com/1.1/mutes/users/destroy.json?screen_name=" ++ screenName) -- | Get user's DMs. getDMsRaw count = getRequest ("https://api.twitter.com/1.1/direct_messages.json" ++ requestString)@@ -106,23 +129,44 @@ getTimeline :: Int -> FilePath -> IO (Either (ParseError Char Dec) Timeline) getTimeline = (fmap (getTweets . BSL.toStrict)) .* getTimelineRaw +-- | Get a timeline+getTimelineMem :: Int -> Config -> IO (Either (ParseError Char Dec) Timeline)+getTimelineMem = (fmap (getTweets . BSL.toStrict)) .* getTimelineRawMem+ -- | Get a user's timeline and return response as a bytestring getTimelineRaw :: Int -> FilePath -> IO BSL.ByteString getTimelineRaw count = getRequest ("https://api.twitter.com/1.1/statuses/home_timeline.json" ++ requestString) where requestString = "?count=" ++ (show count) +-- | Get a user's timeline and return response as a bytestring+getTimelineRawMem :: Int -> Config -> IO BSL.ByteString+getTimelineRawMem count = getRequestMem ("https://api.twitter.com/1.1/statuses/home_timeline.json" ++ requestString)+ where requestString = "?count=" ++ (show count)+ -- | Delete a tweet given its id deleteTweet :: Integer -> FilePath -> IO () deleteTweet = (fmap void) . deleteTweetRaw +-- | Delete a tweet given its id+deleteTweetMem :: Integer -> Config -> IO ()+deleteTweetMem = (fmap void) . deleteTweetRawMem+ -- | Get response, i.e. the tweet deleted deleteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline) deleteTweetResponse = fmap (getTweets . BSL.toStrict) .* deleteTweetRaw +-- | Get response, i.e. the tweet deleted+deleteTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Dec) Timeline)+deleteTweetResponseMem = fmap (getTweets . BSL.toStrict) .* deleteTweetRawMem+ -- | Favorite a tweet given its id favoriteTweet :: Integer -> FilePath -> IO () favoriteTweet = (fmap void) . favoriteTweetRaw +-- | Favorite a tweet given its id+favoriteTweetMem :: Integer -> Config -> IO ()+favoriteTweetMem = (fmap void) . favoriteTweetRawMem+ -- | Favorite a tweet and returned the (parsed) response favoriteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline) favoriteTweetResponse = fmap (getTweets . BSL.toStrict) .* favoriteTweetRaw@@ -131,50 +175,98 @@ unfavoriteTweet :: Integer -> FilePath -> IO () unfavoriteTweet = (fmap void) . unfavoriteTweetRaw +-- | Unfavorite a tweet given its id+unfavoriteTweetMem :: Integer -> Config -> IO ()+unfavoriteTweetMem = (fmap void) . unfavoriteTweetRawMem+ -- | Unfavorite a tweet and returned the (parsed) response unfavoriteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline) unfavoriteTweetResponse = fmap (getTweets . BSL.toStrict) .* unfavoriteTweetRaw +-- | Unfavorite a tweet and returned the (parsed) response+unfavoriteTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Dec) Timeline)+unfavoriteTweetResponseMem = fmap (getTweets . BSL.toStrict) .* unfavoriteTweetRawMem+ -- | Unretweet a tweet given its id unretweetTweet :: Integer -> FilePath -> IO () unretweetTweet = (fmap void) . unretweetTweetRaw +-- | Unretweet a tweet given its id+unretweetTweetMem :: Integer -> Config -> IO ()+unretweetTweetMem = (fmap void) . unretweetTweetRawMem+ -- | Unretweet a tweet and returned the (parsed) response unretweetTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline) unretweetTweetResponse = fmap (getTweets . BSL.toStrict) .* unretweetTweetRaw +-- | Unretweet a tweet and returned the (parsed) response+unretweetTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Dec) Timeline)+unretweetTweetResponseMem = fmap (getTweets . BSL.toStrict) .* unretweetTweetRawMem+ -- | Unfollow a user given their screen name unfollow :: String -> FilePath -> IO () unfollow = (fmap void) . unfollowUserRaw +-- | Unfollow a user given their screen name+unfollowMem :: String -> Config -> IO ()+unfollowMem = (fmap void) . unfollowUserRawMem+ -- | Follow a user given their screen name follow :: String -> FilePath -> IO () follow = (fmap void) . followUserRaw +-- | Follow a user given their screen name+followMem :: String -> Config -> IO ()+followMem = (fmap void) . followUserRawMem+ -- | Block a user given their screen name block :: String -> FilePath -> IO () block = (fmap void) . blockUserRaw +-- | Block a user given their screen name+blockMem :: String -> Config -> IO ()+blockMem = (fmap void) . blockUserRawMem+ -- | Unblock a user given their screen name unblock :: String -> FilePath -> IO () unblock = (fmap void) . unblockUserRaw +-- | Unblock a user given their screen name+unblockMem :: String -> Config -> IO ()+unblockMem = (fmap void) . unblockUserRawMem+ -- | Retweet a tweet given its id retweetTweet :: Integer -> FilePath -> IO () retweetTweet = (fmap void) . retweetTweetRaw +-- | Retweet a tweet given its id+retweetTweetMem :: Integer -> Config -> IO ()+retweetTweetMem = (fmap void) . retweetTweetRawMem+ -- | Retweet a tweet and returned the (parsed) response retweetTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline) retweetTweetResponse = fmap (getTweets . BSL.toStrict) .* retweetTweetRaw +-- | Retweet a tweet and returned the (parsed) response+retweetTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char Dec) Timeline)+retweetTweetResponseMem = fmap (getTweets . BSL.toStrict) .* retweetTweetRawMem+ -- | Favorite a tweet given its id; return bytestring response favoriteTweetRaw :: Integer -> FilePath -> IO BSL.ByteString favoriteTweetRaw id = postRequest ("https://api.twitter.com/1.1/favorites/create.json?id=" ++ (show id)) +-- | Favorite a tweet given its id; return bytestring response+favoriteTweetRawMem :: Integer -> Config -> IO BSL.ByteString+favoriteTweetRawMem id = postRequestMem ("https://api.twitter.com/1.1/favorites/create.json?id=" ++ (show id))+ -- | Retweet a tweet given its id; return bytestring response retweetTweetRaw :: Integer -> FilePath -> IO BSL.ByteString retweetTweetRaw id = postRequest ("https://api.twitter.com/1.1/statuses/retweet/" ++ (show id) ++ ".json") +-- | Retweet a tweet given its id; return bytestring response+retweetTweetRawMem :: Integer -> Config -> IO BSL.ByteString+retweetTweetRawMem id = postRequestMem ("https://api.twitter.com/1.1/statuses/retweet/" ++ (show id) ++ ".json")+ -- | Send a DM given text, screen name of recipient. sendDMRaw txt screenName = postRequest ("https://api.twitter.com/1.1/direct_messages/new.json?text=" ++ encoded ++ "&screen_name" ++ screenName ++ ".json") where encoded = strEncode txt@@ -183,30 +275,62 @@ getDMs :: Int -> FilePath -> IO BSL.ByteString getDMs count = getRequest ("https://dev.twitter.com/rest/reference/get/direct_messages.json?count=" ++ (show count)) +-- | Get DMs, return bytestring of response+getDMMem :: Int -> Config -> IO BSL.ByteString+getDMMem count = getRequestMem ("https://dev.twitter.com/rest/reference/get/direct_messages.json?count=" ++ (show count))+ -- | Follow a user given their screen name followUserRaw :: String -> FilePath -> IO BSL.ByteString followUserRaw screenName = postRequest ("https://api.twitter.com/1.1/friendships/create.json?screen_name=" ++ screenName) +-- | Follow a user given their screen name+followUserRawMem :: String -> Config -> IO BSL.ByteString+followUserRawMem screenName = postRequestMem ("https://api.twitter.com/1.1/friendships/create.json?screen_name=" ++ screenName)+ -- | Block a user given their screen name blockUserRaw :: String -> FilePath -> IO BSL.ByteString blockUserRaw screenName = postRequest ("https://api.twitter.com/1.1/blocks/create.json?screen_name=" ++ screenName) +-- | Block a user given their screen name+blockUserRawMem :: String -> Config -> IO BSL.ByteString+blockUserRawMem screenName = postRequestMem ("https://api.twitter.com/1.1/blocks/create.json?screen_name=" ++ screenName)+ -- | Unblock a user given their screen name unblockUserRaw :: String -> FilePath -> IO BSL.ByteString unblockUserRaw screenName = postRequest ("https://api.twitter.com/1.1/blocks/destroy.json?screen_name=" ++ screenName) +-- | Unblock a user given their screen name+unblockUserRawMem :: String -> Config -> IO BSL.ByteString+unblockUserRawMem screenName = postRequestMem ("https://api.twitter.com/1.1/blocks/destroy.json?screen_name=" ++ screenName)+ -- | Follow a user given their screen name unfollowUserRaw :: String -> FilePath -> IO BSL.ByteString unfollowUserRaw screenName = postRequest ("https://api.twitter.com/1.1/friendships/destroy.json?screen_name=" ++ screenName) +-- | Follow a user given their screen name+unfollowUserRawMem :: String -> Config -> IO BSL.ByteString+unfollowUserRawMem screenName = postRequestMem ("https://api.twitter.com/1.1/friendships/destroy.json?screen_name=" ++ screenName)+ -- | Unretweet a tweet given its id; return bytestring response unretweetTweetRaw :: Integer -> FilePath -> IO BSL.ByteString unretweetTweetRaw id = postRequest ("https://api.twitter.com/1.1/statuses/unretweet/" ++ (show id) ++ ".json") --- | Favorite a tweet given its id; return bytestring response+-- | Unretweet a tweet given its id; return bytestring response+unretweetTweetRawMem :: Integer -> Config -> IO BSL.ByteString+unretweetTweetRawMem id = postRequestMem ("https://api.twitter.com/1.1/statuses/unretweet/" ++ (show id) ++ ".json")++-- | Unfavorite a tweet given its id; return bytestring response unfavoriteTweetRaw :: Integer -> FilePath -> IO BSL.ByteString unfavoriteTweetRaw id = postRequest ("https://api.twitter.com/1.1/favorites/destroy.json?id=" ++ (show id)) +-- | Unfavorite a tweet given its id; return bytestring response+unfavoriteTweetRawMem :: Integer -> Config -> IO BSL.ByteString+unfavoriteTweetRawMem id = postRequestMem ("https://api.twitter.com/1.1/favorites/destroy.json?id=" ++ (show id))+ -- | Delete a tweet given its id; return bytestring response deleteTweetRaw :: Integer -> FilePath -> IO BSL.ByteString deleteTweetRaw id = postRequest ("https://api.twitter.com/1.1/statuses/destroy/" ++ (show id) ++ ".json")++-- | Delete a tweet given its id; return bytestring response+deleteTweetRawMem :: Integer -> Config -> IO BSL.ByteString+deleteTweetRawMem id = postRequestMem ("https://api.twitter.com/1.1/statuses/destroy/" ++ (show id) ++ ".json")
+ src/Web/Tweet/API/Internal.hs view
@@ -0,0 +1,30 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Helper functions for the command line tool.+module Web.Tweet.API.Internal where++import Web.Tweet.API+import Web.Tweet.Types+import Web.Tweet.Utils+import Text.Megaparsec.Error++-- | Show a user profile given screen name, how many tweets to return, +-- and whether to print them in color.+showProfile :: String -> Int -> Bool -> FilePath -> IO String+showProfile screenName count color = fmap (showTweets color) . getProfile screenName count++-- | Show the most successful tweets by a given user, given their screen name. +showBest :: String -> Int -> Bool -> FilePath -> IO String+showBest screenName n color = fmap (showTweets color . pure . (take n . hits)) . getAll screenName Nothing++-- | Show the most successful tweets by a given user, given their screen name. Additionally filter out replies.+showBest' :: String -> Int -> Bool -> FilePath -> IO String+showBest' screenName n color = fmap (showTweets color . pure . (take n . hits')) . getAll screenName Nothing++-- | Display user timeline+showTimeline :: Int -> Bool -> FilePath -> IO String+showTimeline count color = (fmap (showTweets color)) . getTimeline count ++-- | Display user timeline in color, as appropriate+showTweets :: Bool -> Either (ParseError Char Dec) Timeline -> String+showTweets color = (either show id) . (fmap (if color then displayTimelineColor else displayTimeline))
src/Web/Tweet/Exec.hs view
@@ -65,7 +65,7 @@ select :: Program -> IO () select (Program com maybeFile color) = case maybeFile of (Just file) -> selectCommand com (not color) file- _ -> selectCommand com (not color) =<< (++ "/.cred") <$> getHomeDirectory+ _ -> selectCommand com (not color) =<< (++ "/.cred.toml") <$> getHomeDirectory -- | Executes subcommand given subcommand + filepath to configuration file selectCommand :: Command -> Bool -> FilePath -> IO ()@@ -145,7 +145,7 @@ (long "cred" <> short 'c' <> metavar "CREDENTIALS"- <> completer (bashCompleter "file -o plusdirs")+ <> completer (bashCompleter "file -X '!*.toml' -o plusdirs") <> help "path to credentials")) <*> switch (long "color"
src/Web/Tweet/Sign.hs view
@@ -3,12 +3,22 @@ -- | Functions to sign HTTP requests with oAuth module Web.Tweet.Sign ( signRequest , signRequestMem- , mkConfig ) where+ , mkConfig+ , mkConfigToml ) where +import Prelude hiding (lookup) import Web.Tweet.Utils import Web.Authenticate.OAuth import Network.HTTP.Client import Web.Tweet.Types+import Text.Toml+import Text.Toml.Types+import Data.HashMap.Lazy+import qualified Data.Text as T+import qualified Data.Text.IO as TIO+import Data.Text.Encoding (encodeUtf8)+import Data.Monoid+import Data.ByteString as BS -- | Sign a request using your OAuth dev token, as stored in a config file. -- Uses the IO monad because signatures require a timestamp@@ -25,8 +35,30 @@ secret <- (lineByKey "api-sec") <$> getConfigData filepath key <- (lineByKey "api-key") <$> getConfigData filepath let url = "api.twitter.com"- return newOAuth { oauthConsumerKey = key , oauthConsumerSecret = secret , oauthServerName = url }+ pure newOAuth { oauthConsumerKey = key , oauthConsumerSecret = secret , oauthServerName = url } +getKey :: HashMap T.Text Node -> T.Text -> BS.ByteString+getKey hm key = case lookup key hm of+ (Just (VString key)) -> encodeUtf8 key+ (Just _) -> error $ "Key: " <> T.unpack key <> " found in the config file, but it is not a string."+ Nothing -> error $ "Key: " <> T.unpack key <> " not found in config file."++mkConfigToml :: FilePath -> IO Config+mkConfigToml filepath = do+ t <- TIO.readFile filepath+ let hm = case parseTomlDoc ("failed to read .toml at: " <> filepath) t of+ Right tab -> tab+ Left e -> error (show e)+ secret = getKey hm "api-sec"+ key = getKey hm "api-key"+ tok = getKey hm "tok"+ tokSecret = getKey hm "tok-sec"+ url = "api.twitter.com"+ o = newOAuth { oauthConsumerKey = key , oauthConsumerSecret = secret , oauthServerName = url }+ c = newCredential tok tokSecret+ pure (o, c)+ +-- | Given a filepath, parse the contents of the file and return a configuration. mkConfig :: FilePath -> IO Config mkConfig filepath = do o <- oAuth filepath
src/Web/Tweet/Utils/API.hs view
@@ -4,6 +4,8 @@ module Web.Tweet.Utils.API ( getRequest , postRequest+ , getRequestMem+ , postRequestMem , urlString , strEncode ) where @@ -30,11 +32,11 @@ -- | Make a GET request to twitter given a request string getRequest :: String -> FilePath -> IO BSL.ByteString-getRequest = flip ((. getRequestMem) . (>>=) . mkConfig)+getRequest = flip ((. getRequestMem) . (>>=) . mkConfigToml) -- | Make a POST request to twitter given a request string postRequest :: String -> FilePath -> IO BSL.ByteString-postRequest = flip ((. postRequestMem) . (>>=) . mkConfig)+postRequest = flip ((. postRequestMem) . (>>=) . mkConfigToml) -- | Make a POST request to twitter given a request string postRequestMem :: String -> Config -> IO BSL.ByteString
test/Spec.hs view
@@ -3,10 +3,18 @@ import qualified Data.ByteString as BS import Test.Hspec import Web.Tweet.Parser.FastParser+import Web.Tweet.Sign+import Data.Monoid+import System.Environment+import Test.QuickCheck main :: IO () main = hspec $ do describe "fastParse" $ do file <- runIO $ BS.readFile "test/data"+ config <- runIO $ (<> "/.cred") <$> getEnv "HOME"+ configToml <- runIO $ (<> "/.cred.toml") <$> getEnv "HOME" parallel $ it "parses sample tweets wrong" $ do fastParse "" `shouldBe` Left "Error in $: not enough input"+ parallel $ it "parses a config file the same way with the toml parser" $+ ((==) <$> mkConfigToml configToml <*> mkConfig config) >>= (`shouldBe` True)
tweet-hs.cabal view
@@ -1,5 +1,5 @@ name: tweet-hs-version: 0.6.1.3+version: 1.0.0.0 synopsis: Command-line tool for twitter description: a Command Line Interface Tweeter homepage: https://github.com/vmchale/command-line-tweeter#readme@@ -40,11 +40,12 @@ , Web.Tweet.Exec , Web.Tweet.Parser.FastParser , Web.Tweet.Parser+ , Web.Tweet.Sign+ , Web.Tweet.API other-modules: Web.Tweet.Types , Web.Tweet.Utils , Web.Tweet.Utils.Colors- , Web.Tweet.Sign- , Web.Tweet.API+ , Web.Tweet.API.Internal , Web.Tweet.Utils.API , Paths_tweet_hs build-depends: base >= 4.7 && < 5@@ -57,6 +58,8 @@ , split , optparse-applicative , lens+ , unordered-containers+ , htoml , data-default , text , containers@@ -99,6 +102,7 @@ , criterion , tweet-hs , bytestring+ , megaparsec if flag(llvm-fast) ghc-options: -fllvm -optlo-O3 -O3 if flag(gold) @@ -114,6 +118,7 @@ build-depends: base , tweet-hs , hspec+ , QuickCheck , bytestring if flag(gold) ghc-options: -optl-fuse-ld=gold