twitter-feed (empty) → 0.1.0.0
raw patch · 5 files changed
+263/−0 lines, 5 filesdep +HUnitdep +aesondep +authenticate-oauthsetup-changed
Dependencies added: HUnit, aeson, authenticate-oauth, base, containers, http-conduit, json, test-framework, test-framework-hunit
Files
- LICENSE +22/−0
- Setup.hs +2/−0
- src/Web/Twitter/Feed.hs +183/−0
- test/Suite.hs +9/−0
- twitter-feed.cabal +47/−0
+ LICENSE view
@@ -0,0 +1,22 @@+Copyright (c) 2014 Justin Leitgeb++MIT License++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be+included in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ src/Web/Twitter/Feed.hs view
@@ -0,0 +1,183 @@+{-# LANGUAGE OverloadedStrings, DeriveGeneric #-}++module Web.Twitter.Feed+ ( Tweet+ , Link (..)+ , URLEntity (..)+ , UserEntity (..)+ , MediaEntity (..)+ , timeline+ , addLink+ , timelineUrl+ , sortLinks+ )+where++import Network.HTTP.Conduit+import Web.Authenticate.OAuth+import Data.Aeson+import GHC.Generics (Generic)+import Control.Applicative+import Data.List (sortBy)+import Control.Monad (mzero)+import Web.Authenticate.OAuth++type BoundingIndices = [Int]++data SimpleTweet = SimpleTweet+ { body :: String+ , tweetId :: String+ } deriving (Show, Generic)++data Tweet = Tweet+ { text :: String+ , createdAt :: String+ , idStr :: String+ , entities :: Entities+ } deriving (Show, Generic)++data UserEntity = UserEntity+ { screenName :: String+ , userIndices :: BoundingIndices+ } deriving (Show, Generic)++data URLEntity = URLEntity+ { urlMessage :: String+ , urlIndices :: BoundingIndices+ , displayUrl :: String+ } deriving (Show, Generic)++data MediaEntity = MediaEntity+ { mediaUrl :: String+ , mediaIndices :: BoundingIndices+ , displayMediaUrl :: String+ } deriving (Show, Generic)++data Entities = Entities+ { userEntities :: [UserEntity]+ , urlEntities :: [URLEntity]+ , mediaEntities :: [MediaEntity]+ } deriving (Show, Generic)++data Link = Link+ { startIndex :: Int+ , endIndex :: Int+ , newHtml :: String+ } deriving (Show, Eq)++instance ToJSON SimpleTweet++instance ToJSON Tweet+instance FromJSON Tweet+ where parseJSON (Object v) = Tweet+ <$> v .: "text"+ <*> v .: "created_at"+ <*> v .: "id_str"+ <*> v .: "entities"+ parseJSON _ = mzero++instance ToJSON UserEntity+instance FromJSON UserEntity+ where parseJSON (Object v) = UserEntity+ <$> v .: "screen_name"+ <*> v .: "indices" .!= []+ parseJSON _ = mzero++instance ToJSON URLEntity+instance FromJSON URLEntity+ where parseJSON (Object v) = URLEntity+ <$> v .: "url"+ <*> v .: "indices" .!= []+ <*> v .: "display_url"+ parseJSON _ = mzero++instance ToJSON MediaEntity+instance FromJSON MediaEntity+ where parseJSON (Object v) = MediaEntity+ <$> v .: "url"+ <*> v .: "indices" .!= []+ <*> v .: "display_url"+ parseJSON _ = mzero++instance ToJSON Entities+instance FromJSON Entities+ where parseJSON (Object v) = Entities+ <$> v .:? "user_mentions" .!= []+ <*> v .:? "urls" .!= []+ <*> v .:? "media" .!= []+ parseJSON _ = mzero++timeline :: String -> OAuth -> Credential -> Int ->+ IO (Either String [SimpleTweet])+timeline twitterUsername oauth credential tweets = do+ req <- parseUrl $ timelineUrl twitterUsername tweets+ res <- withManager $ \m -> do+ signedreq <- signOAuth oauth credential req+ httpLbs signedreq m++ let decoded = decode $ responseBody res++ case decoded of+ Nothing -> return $ Left "Unable to retrieve tweets!"+ Just ts -> return $ Right $ map (simplifyTweet . linkifyTweet) ts++timelineUrl :: String -> Int -> String+timelineUrl user count =+ "https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=" +++ user ++ "&count=" ++ show count++linkifyTweet :: Tweet -> Tweet+linkifyTweet tweet = Tweet (processText (text tweet)+ (userEntities $ entities tweet)+ (urlEntities $ entities tweet)+ (mediaEntities $ entities tweet))+ (createdAt tweet)+ (idStr tweet)+ (entities tweet)++processText :: String -> [UserEntity] -> [URLEntity] -> [MediaEntity] -> String+processText message users urls medias = foldr addLink message+ (sortLinks urls users medias)++sortLinks :: [URLEntity] -> [UserEntity] -> [MediaEntity] -> [Link]+sortLinks urls users medias = sortBy sortDesc (map makeURLLink urls +++ map makeUserLink users +++ map makeMediaLink medias)+ where sortDesc a b+ | startIndex a < startIndex b = LT+ | otherwise = GT++makeURLLink :: URLEntity -> Link+makeURLLink urlEntity = Link x y url+ where x = head (urlIndices urlEntity)+ y = urlIndices urlEntity !! 1+ urlText = displayUrl urlEntity+ href = urlMessage urlEntity+ url = "<a target=\"_blank\" href=\"" ++ href ++ "\">"+ ++ urlText ++ "</a>"++makeMediaLink :: MediaEntity -> Link+makeMediaLink mediaEntity = Link x y url+ where x = head (mediaIndices mediaEntity)+ y = mediaIndices mediaEntity !! 1+ urlText = displayMediaUrl mediaEntity+ href = mediaUrl mediaEntity+ url = "<a target=\"_blank\" href=\"" ++ href ++ "\">"+ ++ urlText ++ "</a>"++makeUserLink :: UserEntity -> Link+makeUserLink userEntity = Link x y mention+ where x = head (userIndices userEntity)+ y = userIndices userEntity !! 1+ username = screenName userEntity+ mention = "<a target=\"_blank\" href=\"//twitter.com/" ++ username+ ++ "\">@" ++ username ++ "</a>"++simplifyTweet :: Tweet -> SimpleTweet+simplifyTweet tweet =+ SimpleTweet { body = text tweet, tweetId = idStr tweet }++addLink :: Link -> String -> String+addLink link tweet = before ++ newHtml link ++ after+ where before = fst (splitAt (startIndex link) tweet)+ after = snd (splitAt (endIndex link) tweet)
+ test/Suite.hs view
@@ -0,0 +1,9 @@+module Main where++import Test.Framework (defaultMain)++import qualified Web.Twitter.Feed.Tests++main :: IO ()+main = defaultMain+ [ Web.Twitter.Feed.Tests.tests ]
+ twitter-feed.cabal view
@@ -0,0 +1,47 @@+name: twitter-feed+version: 0.1.0.0+synopsis: Client for fetching Twitter timeline via Oauth+description: Fetches a user timeline from Twitter, and optionally linkifies the results using the Twitter entity API.+homepage: https://github.com/stackbuilders/twitter-feed+license: MIT+license-file: LICENSE+author: Justin Leitgeb, Andrés Torres+maintainer: justin@stackbuilders.com+category: Web+build-type: Simple+extra-source-files:+cabal-version: >=1.10++source-repository head+ type: git+ location: https://github.com/stackbuilders/twitter-feed.git++library+ exposed-modules: Web.Twitter.Feed+ build-depends: base >=4.6 && <4.7+ , json+ , authenticate-oauth+ , aeson+ , http-conduit++ hs-source-dirs: src+ default-language: Haskell2010++test-suite twitter-library+ type: exitcode-stdio-1.0+ hs-source-dirs: test, src+ main-is: Suite.hs+ build-depends:+ base >=4.6 && <4.7+ , test-framework+ , HUnit+ , test-framework-hunit+ , containers++ -- Copied from the main build+ , aeson+ , json+ , authenticate-oauth+ , http-conduit++ default-language: Haskell2010