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)
+ }
+ }
+}