packages feed

hs-twitter 0.2.1 → 0.2.2

raw patch · 5 files changed

+341/−29 lines, 5 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Web.Twitter.Types.Import: readJSONs :: (JSON a) => JSValue -> Result [a]
+ Web.Twitter: SearchContext :: String -> Int -> Int -> StatusId -> String -> Bool -> String -> String -> UserName -> UserName -> UserName -> [String] -> [String] -> [String] -> String -> String -> SearchContext
+ Web.Twitter: addSearchFilter :: SearchContext -> IO SearchId
+ Web.Twitter: data SearchContext
+ Web.Twitter: dropSearch :: SearchId -> IO ()
+ Web.Twitter: getTrends :: TM Trends
+ Web.Twitter: search :: SearchContext -> TM [SearchResult]
+ Web.Twitter: searchAllWords :: SearchContext -> [String]
+ Web.Twitter: searchAnyWords :: SearchContext -> [String]
+ Web.Twitter: searchFor :: SearchContext
+ Web.Twitter: searchFromUser :: SearchContext -> UserName
+ Web.Twitter: searchGeocode :: SearchContext -> String
+ Web.Twitter: searchHashTag :: SearchContext -> String
+ Web.Twitter: searchLang :: SearchContext -> String
+ Web.Twitter: searchNear :: SearchContext -> String
+ Web.Twitter: searchNoneWords :: SearchContext -> [String]
+ Web.Twitter: searchPage :: SearchContext -> Int
+ Web.Twitter: searchPhrase :: SearchContext -> String
+ Web.Twitter: searchQuery :: SearchContext -> String
+ Web.Twitter: searchRPP :: SearchContext -> Int
+ Web.Twitter: searchReferring :: SearchContext -> UserName
+ Web.Twitter: searchShowUser :: SearchContext -> Bool
+ Web.Twitter: searchSinceId :: SearchContext -> StatusId
+ Web.Twitter: searchToUser :: SearchContext -> UserName
+ Web.Twitter.Monad: search_base_url :: URLString
+ Web.Twitter.Monad: strArg :: String -> String -> [(String, String)] -> [(String, String)]
+ Web.Twitter.Types: SearchResult :: String -> StatusId -> UserName -> UserId -> DateString -> Maybe String -> Maybe UserName -> Maybe UserId -> SearchResult
+ Web.Twitter.Types: Trends :: DateString -> [(String, URLString)] -> Trends
+ Web.Twitter.Types: data SearchResult
+ Web.Twitter.Types: data Trends
+ Web.Twitter.Types: nullSearchResult :: SearchResult
+ Web.Twitter.Types: nullTrends :: Trends
+ Web.Twitter.Types: searchResultAt :: SearchResult -> DateString
+ Web.Twitter.Types: searchResultFromUser :: SearchResult -> UserName
+ Web.Twitter.Types: searchResultFromUserId :: SearchResult -> UserId
+ Web.Twitter.Types: searchResultId :: SearchResult -> StatusId
+ Web.Twitter.Types: searchResultLanguage :: SearchResult -> Maybe String
+ Web.Twitter.Types: searchResultText :: SearchResult -> String
+ Web.Twitter.Types: searchResultToUser :: SearchResult -> Maybe UserName
+ Web.Twitter.Types: searchResultToUserId :: SearchResult -> Maybe UserId
+ Web.Twitter.Types: trendsAsOf :: Trends -> DateString
+ Web.Twitter.Types: trendsInfo :: Trends -> [(String, URLString)]
+ Web.Twitter.Types: type StatusId = String
+ Web.Twitter.Types.Import: arr :: [JSValue] -> JSValue
+ Web.Twitter.Types.Import: getArr :: String -> JM [JSValue]
+ Web.Twitter.Types.Import: instance JSON SearchResult
+ Web.Twitter.Types.Import: instance JSON Trends
+ Web.Twitter.Types.Import: obj :: [(String, JSValue)] -> JSValue
+ Web.Twitter.Types.Import: readSearchResult :: JM SearchResult
+ Web.Twitter.Types.Import: readTrends :: JM Trends
+ Web.Twitter.Types.Import: showSearchResult :: SearchResult -> [(String, JSValue)]
+ Web.Twitter.Types.Import: showTrends :: Trends -> [(String, JSValue)]
- Web.Twitter.Types: DirectMessage :: Maybe User -> Maybe User -> UserId -> UserName -> UserId -> UserId -> String -> UserId -> DateString -> DirectMessage
+ Web.Twitter.Types: DirectMessage :: Maybe User -> Maybe User -> UserId -> UserName -> UserId -> UserId -> String -> StatusId -> DateString -> DirectMessage
- Web.Twitter.Types: Status :: DateString -> String -> String -> String -> Bool -> Maybe String -> Maybe UserId -> Maybe Bool -> User -> Status
+ Web.Twitter.Types: Status :: DateString -> StatusId -> String -> String -> Bool -> Maybe StatusId -> Maybe UserId -> Maybe Bool -> User -> Status
- Web.Twitter.Types: directId :: DirectMessage -> UserId
+ Web.Twitter.Types: directId :: DirectMessage -> StatusId
- Web.Twitter.Types: statusId :: Status -> String
+ Web.Twitter.Types: statusId :: Status -> StatusId
- Web.Twitter.Types: statusInReplyTo :: Status -> Maybe String
+ Web.Twitter.Types: statusInReplyTo :: Status -> Maybe StatusId
- Web.Twitter.Types.Import: readJS :: String -> a -> (JM a) -> JSValue -> Result a
+ Web.Twitter.Types.Import: readJS :: String -> String -> a -> (JM a) -> JSValue -> Result a

Files

Web/Twitter.hs view
@@ -65,12 +65,19 @@        , createBlock     -- :: UserId -> TM User
        , destroyBlock    -- :: UserId -> TM User
 
-       , testCall                -- :: TM String
+       , testCall        -- :: TM String
        
+       , search          -- :: String -> Maybe SearchContext -> TM [Status]
+       , getTrends       -- :: TM [Status]
+       , SearchContext(..)
+       , searchFor
+       
        , setUpdateInterval
        , setTwitterUser
        , tweet
        , stopUpdates
+       , addSearchFilter
+       , dropSearch
 
        ) where
 
@@ -98,6 +105,12 @@ twitter_update_info :: MVar (Maybe Int,Maybe ThreadId)
 twitter_update_info = unsafePerformIO (newMVar (Nothing,Nothing))
 
+twitter_get_action :: MVar (Maybe String -> TM [Status])
+twitter_get_action = unsafePerformIO (newMVar (\ _ -> return []))
+
+twitter_searches :: MVar (SearchId, [(SearchId,SearchContext)],Maybe ThreadId)
+twitter_searches = unsafePerformIO (newMVar (0,[],Nothing))
+
 setUpdateInterval :: IO ()
 setUpdateInterval = do
   putStr "Check updates every X mins: "
@@ -109,6 +122,8 @@        case b of
          Nothing -> return ()
 	 Just t  -> catch (killThread t) (\ _ -> return ())
+       modifyMVar_ twitter_get_action 
+                   (\ _ -> return (\ x -> getFriendsTimeline x Nothing))
        t <- forkIO (updateChecker v Nothing)
        modifyMVar_ twitter_update_info (\ _ -> return (Just v, Just t)) 
     _ -> putStrLn ("Unable to parse minute: "  ++ show l)
@@ -119,12 +134,77 @@     case x of
       Nothing -> updateChecker everyMins mbSince
       Just au -> do
-	 n  <- nowDateString
-         ls <- runTM au (getFriendsTimeline mbSince Nothing)
+	 n   <- nowDateString
+	 upd <- readMVar twitter_get_action
+         ls  <- runTM au (upd mbSince)
 	 when (not $ null ls) (putStrLn "")
 	 mapM_ (\ s -> putStrLn (userScreenName (statusUser s) ++ ": " ++ statusText s)) ls
 	 updateChecker everyMins (Just n)
 
+type SearchId = Int
+
+addSearchFilter :: SearchContext -> IO SearchId
+addSearchFilter sc = do
+  st <- readMVar twitter_searches
+  t  <- 
+    case st of
+     (_,_,Just t) -> return t
+     (_,_,Nothing) -> do
+          putStr "Perform tracking/searches every X mins: "
+          l <- getLine
+	  let readIt x = case reads x of { ((v,_):_) -> v ; _ -> 1} 
+	  forkIO (searchBot (readIt l) Nothing)
+  (a,b,_) <- takeMVar twitter_searches
+  putMVar twitter_searches (a+1,(a,sc):b,Just t)
+  return (a+1)
+ where
+  searchBot p mbSinceId = do
+    threadDelay (p * 1000000 * 60)
+    x <- readMVar twitter_searches
+    u <- readMVar twitter_user
+    case (u,x) of
+      (Nothing,_) -> searchBot p mbSinceId
+      (_, (_,[],_)) -> searchBot p mbSinceId
+      (Just au, (_,ls,_)) -> do
+	 n   <- nowDateString
+	 let doOneSearch st@(sid,s) = do
+	        ss <- runTM au (search s)
+		case ss of
+		  [] -> return st
+		  _  -> do
+		    let ifn "" c = c
+		        ifn c  _ = c
+		    let label = searchQuery s   `ifn` 
+		                searchPhrase s  `ifn` 
+				searchHashTag s `ifn` 
+				  ("<"++shows sid ">")
+
+		    putStrLn ("Search results for: " ++ label)
+		    mapM_ ( \ r -> putStrLn (searchResultFromUser r ++ ": " ++ 
+		                             searchResultText r ++ " @ " ++ 
+					     searchResultAt r))
+			  ss
+                       -- update the sinceId so as to not repeat entries next time around..
+		    return (sid,s{searchSinceId=searchResultId (head ss)})
+         ls1 <- mapM doOneSearch ls
+	 let updateSC y@(s,_) =
+		   -- replace with new sinceId
+	       maybe y (\ v -> (s,v)) (lookup s ls1)
+	 modifyMVar_ twitter_searches (\ (a,xs,b) -> return (a,map updateSC xs,b))
+	 searchBot p (Just n)
+
+dropSearch :: SearchId -> IO ()
+dropSearch s = do
+  (a,ls,b) <- takeMVar twitter_searches
+  ls1 <- 
+    case break(\ x -> fst x == s) ls of
+      (_,[]) -> do
+         putStrLn ("Unknown search ID; ignoring")
+         return ls
+      (as,_:bs) -> 
+         return (as++bs)
+  putMVar twitter_searches (a,ls1,b)
+
 stopUpdates :: IO ()
 stopUpdates = do
        (a,b) <- readMVar twitter_update_info
@@ -147,7 +227,7 @@    r <- readMVar twitter_user
    case r of
      Nothing -> do
-       putStrLn "Unable to tweet, no user set - run 'setTwitterUser'"
+       putStrLn "Unable to tweet, no user set - run 'setTwitterUser'" -- "
        return ()
      Just au -> do
        runTM au (update s Nothing)
@@ -462,10 +542,10 @@ updateProfile :: ProfileInfo -> TM ()
 updateProfile pin = withBase acc_base_url $ withAuth True $ postMethod $
   restCall acc 
-           (mbArg "name" (profileInfoName pin) $
+           (mbArg "name"        (profileInfoName pin) $
 	    mbArg "email"       (profileInfoEmail pin) $
-	    mbArg "url"       (profileInfoURL pin) $
-	    mbArg "location" (profileInfoLocation pin) $
+	    mbArg "url"         (profileInfoURL pin) $
+	    mbArg "location"    (profileInfoLocation pin) $
 	    mbArg "description" (profileInfoDescription pin) $
 	    []) >> return ()
  where
@@ -533,4 +613,86 @@ -- | @testCall@ returns the string "ok" in the requested format 
 -- with a 200 OK HTTP status code.
 testCall :: TM String
-testCall = withBase top_base_url $ withAuth False $ restCall "help/test.json" [] >>= readResult "testCall"
+testCall = withBase top_base_url $ withAuth False $ 
+   restCall "help/test.json" [] >>= readResult "testCall"
+
+-- The "Search API"
+--
+
+-- | @testCall@ returns the string "ok" in the requested format 
+-- with a 200 OK HTTP status code.
+getTrends :: TM Trends
+getTrends = withBase search_base_url $ withAuth False $ do
+   restCall "trends.json" [] >>= readResult "getTrends"
+
+data SearchContext
+ = SearchContext
+       { searchLang      :: String
+       , searchRPP       :: Int
+       , searchPage      :: Int
+       , searchSinceId   :: StatusId
+       , searchGeocode   :: String
+       , searchShowUser  :: Bool
+       , searchQuery     :: String
+       , searchHashTag   :: String
+
+       , searchFromUser  :: UserName
+       , searchToUser    :: UserName
+       , searchReferring :: UserName
+       , searchAllWords  :: [String]
+       , searchAnyWords  :: [String]
+       , searchNoneWords :: [String]
+       , searchPhrase    :: String
+       , searchNear      :: String -- location name
+       }
+
+searchFor :: SearchContext
+searchFor = SearchContext
+       { searchLang     = "all"
+       , searchRPP      = 15
+       , searchPage     = 1
+       , searchSinceId  = ""
+       , searchGeocode  = ""
+       , searchShowUser = True
+       , searchQuery    = ""
+       , searchHashTag  = ""
+
+       , searchFromUser  = ""
+       , searchToUser    = ""
+       , searchReferring = ""
+       , searchAllWords  = []
+       , searchAnyWords  = []
+       , searchNoneWords = []
+       , searchPhrase    = ""
+       , searchNear      = ""
+       }
+
+search :: SearchContext
+       -> TM [SearchResult]
+search scon = withBase search_base_url $ 
+  restCall acc (searchArgs scon []) >>= readResult "search"
+ where
+  acc = "search.json"
+
+  searchArgs sc ls = 
+     strArg "lang" (searchLang sc) $
+     strArg "rpp"  (show $ searchRPP sc) $
+     strArg "page" (show $ searchPage sc) $
+     strArg "since_id" (searchSinceId sc) $
+     strArg "geocode"  (searchGeocode sc) $ 
+     strArg "show_user" (searchSinceId sc) $
+     strArg "tag"       (searchHashTag sc) $
+     strArg "q"    (searchQuery sc) $
+     strArg "from" (searchFromUser sc) $
+     strArg "to"   (searchToUser sc) $
+     strArg "ref"  (searchReferring sc) $
+     strArg "ands" (unwords $ searchAllWords sc) $
+     strArg "ors"  (unwords $ searchAnyWords sc) $
+     strArg "nots" (unwords $ searchNoneWords sc) $
+     strArg "phrase" (searchPhrase sc) $
+        -- this doesn't appear to work as expected via the .json interface
+     strArg "near"   (searchNear sc) $
+     strArg "within" ((\ x -> if x == "" then "" else "15") $ searchNear sc) $
+     strArg "units"  ((\ x -> if x == "" then "" else "mi") $ searchNear sc) $
+      ls
+	    
Web/Twitter/Monad.hs view
@@ -27,13 +27,15 @@        , user_base_url
        , top_base_url
        , acc_base_url
---       , buildUrl
+       , search_base_url
        
        , Result(..)
        , decodeStrict
        
        , mbArg
        , arg
+       , strArg
+
        , restCall
        , postCall
        , readResult
@@ -61,6 +63,9 @@ acc_base_url :: URLString
 acc_base_url = "http://www.twitter.com/account/"
 
+search_base_url :: URLString
+search_base_url = "http://search.twitter.com/"
+
 {-
 buildUrl :: (URLString -> IO a) -> URLString -> TM a
 buildUrl f u = do
@@ -74,6 +79,11 @@ 
 arg :: String -> String -> [(String,String)] -> [(String,String)]
 arg f x xs = (f,x):xs
+
+strArg :: String -> String -> [(String,String)] -> [(String,String)]
+strArg _ "" xs = xs
+strArg f  x xs = (f,x):xs
+
 
 restCall :: String -> [(String,String)] -> TM String
 restCall u args = do
Web/Twitter/Types.hs view
@@ -1,3 +1,19 @@+--------------------------------------------------------------------
+-- |
+-- Module      : Web.Twitter.Types
+-- Description : Main types introduced by the Twitter API
+-- Copyright   : (c) Sigbjorn Finne, 2008
+-- License     : BSD3
+--
+-- Maintainer: Sigbjorn Finne <sof@forkIO.com>
+-- Stability : provisional
+-- Portability: portable
+--
+-- This module defines the main types that the Haskell API binding
+-- for Twitter introduces. Most importantly, if that's the word,
+-- the representation of tweets\/status updates.
+-- 
+--------------------------------------------------------------------
 module Web.Twitter.Types where
 
 type ColorString = String
@@ -6,17 +22,20 @@ type UserId      = String
 type URLString   = String
 type UserName    = String
+type StatusId    = String
 
 data Format = FormatXML | FormatJSON | FormatRSS | FormatAtom
 
+-- | @Status@ is the record type used to represent a ''tweet'',
+-- a status update by a user.
 data Status
  = Status
      { statusCreated       :: DateString
-     , statusId            :: String
+     , statusId            :: StatusId
      , statusText          :: String
      , statusSource        :: String
      , statusTruncated     :: Bool
-     , statusInReplyTo     :: Maybe String
+     , statusInReplyTo     :: Maybe StatusId
      , statusInReplyToUser :: Maybe UserId
      , statusFavorite      :: Maybe Bool
      , statusUser          :: User
@@ -122,7 +141,7 @@      , directRecipientId   :: UserId
      , directRecipientName :: UserId
      , directText          :: String
-     , directId            :: UserId -- mis-named, but works.
+     , directId            :: StatusId -- mis-named, but works.
      , directCreated       :: DateString
      }
 
@@ -139,6 +158,8 @@      , directCreated       = ""
      }
 
+-- | @RateLimit@ bundles up the data that the 'getRateLimit'
+-- Twitter API call returns regarding API call limits.
 data RateLimit
  = RateLimit
      { rateLimitResetSecs :: Integer
@@ -153,4 +174,44 @@      , rateLimitResetTime = ""
      , rateLimitRemHits   = 0
      , rateLimitHourlyLimit = 0
+     }
+
+-- | @Trends@ bundles up the data that the 'trends'
+-- Twitter Search API call returns regarding what's
+-- being majorly tweeted about.
+data Trends
+ = Trends
+     { trendsAsOf :: DateString
+     , trendsInfo :: [(String{-name-}, URLString)]
+     }
+
+nullTrends :: Trends
+nullTrends = Trends
+     { trendsAsOf = ""
+     , trendsInfo = []
+     }
+
+-- | @SearchResult@ bundles up the data that the Search API returns.
+data SearchResult
+ = SearchResult
+     { searchResultText       :: String
+     , searchResultId         :: StatusId
+     , searchResultFromUser   :: UserName
+     , searchResultFromUserId :: UserId
+     , searchResultAt         :: DateString
+     , searchResultLanguage   :: Maybe String
+     , searchResultToUser     :: Maybe UserName
+     , searchResultToUserId   :: Maybe UserId
+     }
+
+nullSearchResult :: SearchResult
+nullSearchResult = SearchResult
+     { searchResultText       = ""
+     , searchResultId         = ""
+     , searchResultFromUser   = ""
+     , searchResultFromUserId = ""
+     , searchResultAt         = ""
+     , searchResultLanguage   = Nothing
+     , searchResultToUser     = Nothing
+     , searchResultToUserId   = Nothing
      }
Web/Twitter/Types/Import.hs view
@@ -9,7 +9,7 @@ import Data.Maybe
 import Control.Monad
 
-import Debug.Trace
+--import Debug.Trace
 
 data JM a = JM (String -> [(String,JSValue)] -> Result a)
 
@@ -43,7 +43,7 @@   m   <- getMb k
   case m of
     Just (JSString s)  -> return (fromJSString s)
-    Just js -> return (showJSValue js "")
+    Just jso -> return (showJSValue jso "")
     Nothing -> do
        loc <- getLoc
        fail (loc ++ ": missing value for key " ++ show k)
@@ -87,6 +87,14 @@        loc <- getLoc
        fail (loc ++ ": unable to locate expected JSON field " ++ show k)
 
+getArr :: String -> JM [JSValue]
+getArr k = do
+  v <- getMb k
+  case v of
+    Just (JSArray as) -> return as
+    _      -> do
+       loc <- getLoc
+       fail (loc ++ ": unable to locate expected JSON (array) field " ++ show k)
 
 getMbB :: String -> JM (Maybe Bool)
 getMbB k = do
@@ -122,36 +130,47 @@ showJS :: (a -> [(String, JSValue)]) -> a -> JSValue
 showJS f x = JSObject (toJSObject (f x))
 
-readJS :: String -> a -> (JM a) -> JSValue -> Result a
-readJS _ n _ (JSArray []) = return n
-readJS m n f (JSArray [x]) = readJS m n f x
-readJS m _ f (JSObject (JSONObject pairs)) = runJM m pairs f
-readJS m _ _ v = fail (m ++ ": unexpected JSON value " ++ show v)
-
-readJSONs :: JSON a => JSValue -> Result [a]
-readJSONs ls@JSArray{} = readJSON ls
-readJSONs x = readJSON (JSArray [x])
+readJS :: String -> String -> a -> (JM a) -> JSValue -> Result a
+readJS _ _ n _ (JSArray []) = return n
+readJS m q n f (JSArray [x]) = readJS m q n f x
+readJS loc nm n f (JSObject (JSONObject [(x,ls@JSArray{})])) 
+  | nm == x = readJS loc nm n f ls
+readJS m _ _ f (JSObject (JSONObject pairs)) = runJM m pairs f
+readJS m _ _ _ v = fail (m ++ ": unexpected JSON value " ++ show v)
 
 instance JSON User where
     showJSON u = showJS showUser u
-    readJSON m = readJS "Web.Twitter.Types.User" nullUser readUser m
+    readJSON m = readJS "Web.Twitter.Types.User" "" nullUser readUser m
     
 instance JSON Status where
     showJSON u = showJS showStatus u
-    readJSON m = readJS "Web.Twitter.Types.Status" nullStatus readStatus m
+    readJSON m = readJS "Web.Twitter.Types.Status" "" nullStatus readStatus m
     
 instance JSON UserInfo where
     showJSON u = showJS showUserInfo u
-    readJSON m = readJS "Web.Twitter.Types.UserInfo" nullUserInfo readUserInfo m
+    readJSON m = readJS "Web.Twitter.Types.UserInfo" "" nullUserInfo readUserInfo m
     
 instance JSON DirectMessage where
     showJSON u = showJS showDM u
-    readJSON m = readJS "Web.Twitter.Types.DirectMessage" nullDirectMessage readDM m
+    readJSON m = readJS "Web.Twitter.Types.DirectMessage" "" nullDirectMessage readDM m
     
 instance JSON RateLimit where
     showJSON u = showJS showRateLimit u
-    readJSON m = readJS "Web.Twitter.Types.RateLimit" nullRateLimit readRateLimit m
+    readJSON m = readJS "Web.Twitter.Types.RateLimit" "" nullRateLimit readRateLimit m
     
+instance JSON Trends where
+    showJSON u = showJS showTrends u
+    readJSON m = readJS "Web.Twitter.Types.Trends" "" nullTrends readTrends m
+
+instance JSON SearchResult where
+    showJSON u = showJS showSearchResult u
+    readJSON m = readJS "Web.Twitter.Types.SearchResult" "results" nullSearchResult readSearchResult m
+
+    readJSONs (JSObject (JSONObject (("results", JSArray xs):_))) = do
+       mapM (\ x -> readJS "Web.Twitter.Types.SearchResult" "results" nullSearchResult readSearchResult x) xs
+
+    readJSONs _  = Error ("Unable to read search results")
+
 showUser :: User -> [(String, JSValue)]
 showUser u = 
   [ "id"           -=> str  $ userId u
@@ -175,6 +194,12 @@ bool :: Bool -> JSValue
 bool f = showJSON (JSBool f)
 
+arr :: [JSValue] -> JSValue
+arr s  = showJSON (JSArray s)
+
+obj :: [(String,JSValue)] -> JSValue
+obj s  = showJSON (JSObject (JSONObject s))
+
 js :: JSON a => a -> JSValue
 js = showJSON
 
@@ -388,3 +413,57 @@       , rateLimitHourlyLimit = rl
       }
 
+
+showTrends :: Trends -> [(String, JSValue)]
+showTrends t = 
+  [ "as_of"   -=> str  $ trendsAsOf t
+  , "trends"  -=> arr  $ (map (\ (a,b) -> obj [("name", showJSON a), ("url", showJSON b)])
+                              (trendsInfo t))
+  ]
+
+readTrends :: JM Trends
+readTrends = do
+  rs <- get "as_of"
+  as <- getArr "trends"
+  ls <- mapM readEntry as
+  return nullTrends
+      { trendsAsOf = rs
+      , trendsInfo = ls
+      }
+  where 
+   readEntry (JSObject (JSONObject os)) = addToEnv os $ do
+      a <- get "name"
+      b <- get "url"
+      return (a,b)
+   readEntry _ = return ("","")
+      
+
+showSearchResult :: SearchResult -> [(String,JSValue)]
+showSearchResult s = 
+  [ "text"  -=> str  $ searchResultText s
+  , "id"    -=> str  $ searchResultId s
+  , "from_user" -=> str $ searchResultFromUser s
+  , "from_user_id" -=> str $ searchResultFromUserId s
+  , "created_at"    -=> str $ searchResultAt s
+  ] ++ catMaybes
+   [  mb "language_code" str (searchResultLanguage s)
+   ,  mb "to_user_id"    str (searchResultToUserId s)
+   ,  mb "to_user"       str (searchResultToUser s)
+   ]
+
+readSearchResult :: JM SearchResult
+readSearchResult = do
+  [tx,i,fu,fui,ca] <- mapM get  ["text", "id", "from_user", "from_user_id", "created_at"]
+  l   <- getMbS "language_code"
+  tu  <- getMbS "to_user"
+  tui <- getMbS "to_user_id"
+  return nullSearchResult
+     { searchResultText = tx
+     , searchResultId   = i
+     , searchResultFromUser = fu
+     , searchResultFromUserId = fui
+     , searchResultAt = ca
+     , searchResultLanguage = l
+     , searchResultToUser   = tu
+     , searchResultToUserId = tui
+     }
hs-twitter.cabal view
@@ -1,5 +1,5 @@ name: hs-twitter
-version: 0.2.1
+version: 0.2.2
 synopsis: Haskell binding to the Twitter API
 description:
    The hs-twitter API binding lets you access twitter.com's