1 -- | Functions and data for working with Twitter statuses.
5 import Control.Applicative ((<$>), (<*>))
6 import Control.Monad (liftM)
7 import Data.Aeson ((.:), FromJSON(..), Value(Object))
8 import Data.Maybe (catMaybes, isJust)
9 import Data.Monoid (mempty)
10 import Data.String.Utils (join, splitWs)
11 import Data.Text (pack)
12 import Data.Time (formatTime)
13 import Data.Time.Clock (UTCTime)
14 import Data.Time.Format (parseTime)
15 import Data.Time.LocalTime (TimeZone, utcToZonedTime)
16 import System.Locale (defaultTimeLocale, rfc822DateFormat)
18 import Text.Regex (matchRegex, mkRegex)
20 import StringUtils (listify)
23 data Status = Status {
24 created_at :: Maybe UTCTime,
32 type Timeline = [Status]
34 instance FromJSON Status where
35 parseJSON (Object t) =
37 liftM parse_status_time (t .: created_at_field) <*>
39 liftM isJustInt (t .: in_reply_to_status_id_field) <*>
40 (t .: retweeted_field) <*>
44 -- The typechecker flips out without this.
45 isJustInt :: Maybe Int -> Bool
48 created_at_field = pack "created_at"
50 in_reply_to_status_id_field = pack "in_reply_to_status_id"
51 retweeted_field = pack "retweeted"
52 text_field = pack "text"
53 user_field = pack "user"
58 parse_status_time :: String -> Maybe UTCTime
60 parseTime defaultTimeLocale status_format
62 -- | Should match e.g. "Sun Oct 24 18:21:41 +0000 2010"
63 status_format :: String
64 status_format = "%a %b %d %H:%M:%S %z %Y"
66 utc_time_to_rfc822 :: Maybe TimeZone -> UTCTime -> String
67 utc_time_to_rfc822 mtz utc =
70 Just tz -> foo $ utcToZonedTime tz utc
72 foo = formatTime defaultTimeLocale rfc822DateFormat
75 show_created_at :: Maybe TimeZone -> Status -> String
77 (maybe "" id) . (fmap $ utc_time_to_rfc822 mtz) . created_at
79 -- | Returns a nicely-formatted String representing the given 'Status'
81 pretty_print :: Maybe TimeZone -> Status -> String
82 pretty_print mtz status =
87 replicate bar_length '-',
91 join "\n" user_timeline_urls,
94 sca = show_created_at mtz status
95 name = screen_name (user status)
96 user_timeline_urls = listify (make_user_timeline_urls status)
97 bar_length = (length name) + 3 + (length sca)
100 -- | Given a list of statuses, returns the greatest status_id
101 -- belonging to one of the statuses in the list.
102 get_max_status_id :: Timeline -> Integer
103 get_max_status_id statuses =
106 status_ids = map status_id statuses
109 -- | Parse one username from a word.
110 parse_username :: String -> Maybe String
111 parse_username word =
115 Just (first_match:_) -> Just first_match
117 username_regex = mkRegex "@([a-zA-Z0-9_]+)"
118 matches = matchRegex username_regex word
121 -- | Parse all usernames of the form \@username from a status.
122 parse_usernames_from_status :: Status -> [String]
123 parse_usernames_from_status status =
124 catMaybes (map parse_username status_words)
126 status_words = splitWs (text status)
128 -- | Get all referenced users' timeline URLs.
129 make_user_timeline_urls :: Status -> [String]
130 make_user_timeline_urls status =
131 map screen_name_to_timeline_url usernames
133 usernames = parse_usernames_from_status status
136 status_tests :: [Test]
137 status_tests = [ test_parse_usernames ]
140 test_parse_usernames :: Test
141 test_parse_usernames =
144 "All usernames are parsed."
148 dummy_user = User { screen_name = "nobody" }
149 dummy_status = Status { status_id = 1,
150 created_at = Nothing,
151 text = "Hypothesis: @donsbot and @bonus500 are two personalities belonging to the same person.",
157 actual_usernames = parse_usernames_from_status dummy_status
158 expected_usernames = ["donsbot", "bonus500"]