tweet.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. package anaconda
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "time"
  6. )
  7. type Tweet struct {
  8. Contributors []int64 `json:"contributors"`
  9. Coordinates *Coordinates `json:"coordinates"`
  10. CreatedAt string `json:"created_at"`
  11. DisplayTextRange []int `json:"display_text_range"`
  12. Entities Entities `json:"entities"`
  13. ExtendedEntities Entities `json:"extended_entities"`
  14. ExtendedTweet ExtendedTweet `json:"extended_tweet"`
  15. FavoriteCount int `json:"favorite_count"`
  16. Favorited bool `json:"favorited"`
  17. FilterLevel string `json:"filter_level"`
  18. FullText string `json:"full_text"`
  19. HasExtendedProfile bool `json:"has_extended_profile"`
  20. Id int64 `json:"id"`
  21. IdStr string `json:"id_str"`
  22. InReplyToScreenName string `json:"in_reply_to_screen_name"`
  23. InReplyToStatusID int64 `json:"in_reply_to_status_id"`
  24. InReplyToStatusIdStr string `json:"in_reply_to_status_id_str"`
  25. InReplyToUserID int64 `json:"in_reply_to_user_id"`
  26. InReplyToUserIdStr string `json:"in_reply_to_user_id_str"`
  27. IsTranslationEnabled bool `json:"is_translation_enabled"`
  28. Lang string `json:"lang"`
  29. Place Place `json:"place"`
  30. QuotedStatusID int64 `json:"quoted_status_id"`
  31. QuotedStatusIdStr string `json:"quoted_status_id_str"`
  32. QuotedStatus *Tweet `json:"quoted_status"`
  33. PossiblySensitive bool `json:"possibly_sensitive"`
  34. PossiblySensitiveAppealable bool `json:"possibly_sensitive_appealable"`
  35. RetweetCount int `json:"retweet_count"`
  36. Retweeted bool `json:"retweeted"`
  37. RetweetedStatus *Tweet `json:"retweeted_status"`
  38. Source string `json:"source"`
  39. Scopes map[string]interface{} `json:"scopes"`
  40. Text string `json:"text"`
  41. User User `json:"user"`
  42. WithheldCopyright bool `json:"withheld_copyright"`
  43. WithheldInCountries []string `json:"withheld_in_countries"`
  44. WithheldScope string `json:"withheld_scope"`
  45. //Geo is deprecated
  46. //Geo interface{} `json:"geo"`
  47. }
  48. // CreatedAtTime is a convenience wrapper that returns the Created_at time, parsed as a time.Time struct
  49. func (t Tweet) CreatedAtTime() (time.Time, error) {
  50. return time.Parse(time.RubyDate, t.CreatedAt)
  51. }
  52. // It may be worth placing these in an additional source file(s)
  53. // Could also use User, since the fields match, but only these fields are possible in Contributor
  54. type Contributor struct {
  55. Id int64 `json:"id"`
  56. IdStr string `json:"id_str"`
  57. ScreenName string `json:"screen_name"`
  58. }
  59. type Coordinates struct {
  60. Coordinates [2]float64 `json:"coordinates"` // Coordinate always has to have exactly 2 values
  61. Type string `json:"type"`
  62. }
  63. type ExtendedTweet struct {
  64. FullText string `json:"full_text"`
  65. DisplayTextRange []int `json:"display_text_range"`
  66. Entities Entities `json:"entities"`
  67. ExtendedEntities Entities `json:"extended_entities"`
  68. }
  69. // HasCoordinates is a helper function to easily determine if a Tweet has coordinates associated with it
  70. func (t Tweet) HasCoordinates() bool {
  71. if t.Coordinates != nil {
  72. if t.Coordinates.Type == "Point" {
  73. return true
  74. }
  75. }
  76. return false
  77. }
  78. // The following provide convenience and eliviate confusion about the order of coordinates in the Tweet
  79. // Latitude is a convenience wrapper that returns the latitude easily
  80. func (t Tweet) Latitude() (float64, error) {
  81. if t.HasCoordinates() {
  82. return t.Coordinates.Coordinates[1], nil
  83. }
  84. return 0, fmt.Errorf("No Coordinates in this Tweet")
  85. }
  86. // Longitude is a convenience wrapper that returns the longitude easily
  87. func (t Tweet) Longitude() (float64, error) {
  88. if t.HasCoordinates() {
  89. return t.Coordinates.Coordinates[0], nil
  90. }
  91. return 0, fmt.Errorf("No Coordinates in this Tweet")
  92. }
  93. // X is a convenience wrapper which returns the X (Longitude) coordinate easily
  94. func (t Tweet) X() (float64, error) {
  95. return t.Longitude()
  96. }
  97. // Y is a convenience wrapper which return the Y (Lattitude) corrdinate easily
  98. func (t Tweet) Y() (float64, error) {
  99. return t.Latitude()
  100. }
  101. func (t *Tweet) extractExtendedTweet() {
  102. // if the TruncatedText is set, the API does not return an extended tweet
  103. // we need to manually set the Text in this case
  104. if len(t.Text) > 0 && len(t.FullText) == 0 {
  105. t.FullText = t.Text
  106. }
  107. if len(t.ExtendedTweet.FullText) > 0 {
  108. t.DisplayTextRange = t.ExtendedTweet.DisplayTextRange
  109. t.Entities = t.ExtendedTweet.Entities
  110. t.ExtendedEntities = t.ExtendedTweet.ExtendedEntities
  111. t.FullText = t.ExtendedTweet.FullText
  112. }
  113. // if the API supplied us with information how to extract the shortened
  114. // text, extract it
  115. if len(t.Text) == 0 && len(t.DisplayTextRange) == 2 {
  116. t.Text = t.FullText[t.DisplayTextRange[0]:t.DisplayTextRange[1]]
  117. }
  118. // if the truncated text is still empty then full & truncated text are equal
  119. if len(t.Text) == 0 {
  120. t.Text = t.FullText
  121. }
  122. }
  123. func (t *Tweet) UnmarshalJSON(data []byte) error {
  124. type Alias Tweet
  125. aux := &struct {
  126. *Alias
  127. }{
  128. Alias: (*Alias)(t),
  129. }
  130. if err := json.Unmarshal(data, &aux); err != nil {
  131. return err
  132. }
  133. t.extractExtendedTweet()
  134. return nil
  135. }