diff --git a/README.md b/README.md index f3052b7..8cd7ff8 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,27 @@ func main() { It appears you can ask for up to 50 tweets (limit ~3200 tweets). +### Get single tweet + +```golang +package main + +import ( + "fmt" + + twitterscraper "github.com/n0madic/twitter-scraper" +) + +func main() { + scraper := twitterscraper.New() + tweet, err := scraper.GetTweet("1328684389388185600") + if err != nil { + panic(err) + } + fmt.Println(tweet.Text) +} +``` + ### Search tweets by query standard operators Tweets containing “twitter” and “scraper” and “data“, filtering out retweets: diff --git a/tweets.go b/tweets.go index 03eceaa..58cca2f 100644 --- a/tweets.go +++ b/tweets.go @@ -2,6 +2,7 @@ package twitterscraper import ( "context" + "fmt" "strconv" ) @@ -48,3 +49,25 @@ func (s *Scraper) FetchTweets(user string, maxTweetsNbr int, cursor string) ([]* tweets, nextCursor := parseTimeline(&timeline) return tweets, nextCursor, nil } + +// GetTweet get a single tweet by ID. +func (s *Scraper) GetTweet(id string) (*Tweet, error) { + req, err := s.newRequest("GET", "https://twitter.com/i/api/2/timeline/conversation/"+id+".json") + if err != nil { + return nil, err + } + + var timeline timeline + err = s.RequestAPI(req, &timeline) + if err != nil { + return nil, err + } + + tweets, _ := parseTimeline(&timeline) + for _, tweet := range tweets { + if tweet.ID == id { + return tweet, nil + } + } + return nil, fmt.Errorf("tweet with ID %s not found", id) +} diff --git a/tweets_test.go b/tweets_test.go index 4897eca..fe66cf8 100644 --- a/tweets_test.go +++ b/tweets_test.go @@ -3,6 +3,10 @@ package twitterscraper import ( "context" "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" ) func TestGetTweets(t *testing.T) { @@ -58,3 +62,35 @@ func TestGetTweets(t *testing.T) { t.Errorf("Expected tweets count=%v, got: %v", maxTweetsNbr, count) } } + +func TestGetTweet(t *testing.T) { + sample := Tweet{ + HTML: "That thing you didn’t Tweet but wanted to but didn’t but got so close but then were like nah.

We have a place for that now—Fleets!

Rolling out to everyone starting today.
", + ID: "1328684389388185600", + PermanentURL: "https://twitter.com/Twitter/status/1328684389388185600", + Photos: []string{"https://pbs.twimg.com/amplify_video_thumb/1328684333599756289/img/cP5KwbIXbGunNSBy.jpg"}, + Text: "That thing you didn’t Tweet but wanted to but didn’t but got so close but then were like nah. \n\nWe have a place for that now—Fleets! \n\nRolling out to everyone starting today. https://t.co/auQAHXZMfH", + TimeParsed: time.Date(2020, 11, 17, 13, 0, 18, 0, time.FixedZone("UTC", 0)), + Timestamp: 1605618018, + UserID: "783214", + Username: "Twitter", + Videos: []Video{{ + ID: "1328684333599756289", + Preview: "https://pbs.twimg.com/amplify_video_thumb/1328684333599756289/img/cP5KwbIXbGunNSBy.jpg", + URL: "https://video.twimg.com/amplify_video/1328684333599756289/vid/480x360/Qh70ELAcq-N2RYmZ.mp4?tag=13", + }}, + } + tweet, err := defaultScraper.GetTweet("1328684389388185600") + if err != nil { + t.Error(err) + } else { + cmpOptions := cmp.Options{ + cmpopts.IgnoreFields(Tweet{}, "Likes"), + cmpopts.IgnoreFields(Tweet{}, "Replies"), + cmpopts.IgnoreFields(Tweet{}, "Retweets"), + } + if diff := cmp.Diff(sample, *tweet, cmpOptions...); diff != "" { + t.Error("Resulting tweet does not match the sample", diff) + } + } +}