packages feed

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