Add parsing of Likes feed
This commit is contained in:
parent
3d7166c4aa
commit
79a4b87f3a
@ -506,6 +506,11 @@ type APIV2Response struct {
|
|||||||
Data struct {
|
Data struct {
|
||||||
User struct {
|
User struct {
|
||||||
Result struct {
|
Result struct {
|
||||||
|
TimelineV2 struct { // "Likes" feed calls this "timeline_v2" for some reason
|
||||||
|
Timeline struct {
|
||||||
|
Instructions []APIV2Instruction `json:"instructions"`
|
||||||
|
} `json:"timeline"`
|
||||||
|
} `json:"timeline_v2"`
|
||||||
Timeline struct {
|
Timeline struct {
|
||||||
Timeline struct {
|
Timeline struct {
|
||||||
Instructions []APIV2Instruction `json:"instructions"`
|
Instructions []APIV2Instruction `json:"instructions"`
|
||||||
@ -526,6 +531,12 @@ func (api_response APIV2Response) GetMainInstruction() *APIV2Instruction {
|
|||||||
return &instructions[i]
|
return &instructions[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
instructions = api_response.Data.User.Result.TimelineV2.Timeline.Instructions
|
||||||
|
for i := range instructions {
|
||||||
|
if instructions[i].Type == "TimelineAddEntries" {
|
||||||
|
return &instructions[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
instructions = api_response.Data.ThreadedConversationWithInjectionsV2.Instructions
|
instructions = api_response.Data.ThreadedConversationWithInjectionsV2.Instructions
|
||||||
for i := range instructions {
|
for i := range instructions {
|
||||||
if instructions[i].Type == "TimelineAddEntries" {
|
if instructions[i].Type == "TimelineAddEntries" {
|
||||||
@ -677,6 +688,39 @@ func (api_response APIV2Response) ToTweetTrove() (TweetTrove, error) {
|
|||||||
return ret, nil // TODO: This doesn't need to return an error, it's always nil
|
return ret, nil // TODO: This doesn't need to return an error, it's always nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r APIV2Response) ToTweetTroveAsLikes() (TweetTrove, error) {
|
||||||
|
ret, err := r.ToTweetTrove()
|
||||||
|
if err != nil {
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post-process tweets as Likes
|
||||||
|
for _, entry := range r.GetMainInstruction().Entries {
|
||||||
|
// Skip cursors
|
||||||
|
if entry.Content.EntryType == "TimelineTimelineCursor" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Assume it's not a TimelineModule or a Tombstone
|
||||||
|
if entry.Content.EntryType != "TimelineTimelineItem" {
|
||||||
|
panic(fmt.Sprintf("Unknown Like entry type: %s", entry.Content.EntryType))
|
||||||
|
}
|
||||||
|
if entry.Content.ItemContent.ItemType == "TimelineTombstone" {
|
||||||
|
panic(fmt.Sprintf("Liked tweet is a tombstone: %#v", entry))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a "Like" from the entry
|
||||||
|
tweet, is_ok := ret.Tweets[TweetID(entry.Content.ItemContent.TweetResults.Result._Result.ID)]
|
||||||
|
if !is_ok {
|
||||||
|
panic(entry)
|
||||||
|
}
|
||||||
|
ret.Likes[LikeSortID(entry.SortIndex)] = Like{
|
||||||
|
SortID: LikeSortID(entry.SortIndex),
|
||||||
|
TweetID: tweet.ID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
|
||||||
// Get a User feed using the new GraphQL twitter api
|
// Get a User feed using the new GraphQL twitter api
|
||||||
func (api *API) GetGraphqlFeedFor(user_id UserID, cursor string) (APIV2Response, error) {
|
func (api *API) GetGraphqlFeedFor(user_id UserID, cursor string) (APIV2Response, error) {
|
||||||
url, err := url.Parse(GraphqlURL{
|
url, err := url.Parse(GraphqlURL{
|
||||||
|
@ -866,3 +866,23 @@ func TestTweetDetailWithUnjoinedNontombstoneTweet(t *testing.T) {
|
|||||||
assert.False(t3.IsStub)
|
assert.False(t3.IsStub)
|
||||||
assert.Equal(t2.ID, t3.InReplyToID)
|
assert.Equal(t2.ID, t3.InReplyToID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseResultAsLikes(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
data, err := os.ReadFile("test_responses/api_v2/likes_feed.json")
|
||||||
|
require.NoError(err)
|
||||||
|
var response_result APIV2Response
|
||||||
|
err = json.Unmarshal(data, &response_result)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
trove, err := response_result.ToTweetTroveAsLikes()
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
assert.Len(trove.Retweets, 0)
|
||||||
|
assert.True(len(trove.Likes) == 20)
|
||||||
|
for _, l := range trove.Likes {
|
||||||
|
_, is_ok := trove.Tweets[l.TweetID]
|
||||||
|
assert.True(is_ok, "Like (%#v) didn't have its Tweet in the trove", l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1
scraper/test_responses/api_v2/likes_feed.json
Normal file
1
scraper/test_responses/api_v2/likes_feed.json
Normal file
File diff suppressed because one or more lines are too long
@ -12,6 +12,7 @@ type TweetTrove struct {
|
|||||||
Users map[UserID]User
|
Users map[UserID]User
|
||||||
Retweets map[TweetID]Retweet
|
Retweets map[TweetID]Retweet
|
||||||
Spaces map[SpaceID]Space
|
Spaces map[SpaceID]Space
|
||||||
|
Likes map[LikeSortID]Like
|
||||||
|
|
||||||
TombstoneUsers []UserHandle
|
TombstoneUsers []UserHandle
|
||||||
}
|
}
|
||||||
@ -22,6 +23,7 @@ func NewTweetTrove() TweetTrove {
|
|||||||
ret.Users = make(map[UserID]User)
|
ret.Users = make(map[UserID]User)
|
||||||
ret.Retweets = make(map[TweetID]Retweet)
|
ret.Retweets = make(map[TweetID]Retweet)
|
||||||
ret.Spaces = make(map[SpaceID]Space)
|
ret.Spaces = make(map[SpaceID]Space)
|
||||||
|
ret.Likes = make(map[LikeSortID]Like)
|
||||||
ret.TombstoneUsers = []UserHandle{}
|
ret.TombstoneUsers = []UserHandle{}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
@ -54,6 +56,9 @@ func (t1 *TweetTrove) MergeWith(t2 TweetTrove) {
|
|||||||
for id, val := range t2.Spaces {
|
for id, val := range t2.Spaces {
|
||||||
t1.Spaces[id] = val
|
t1.Spaces[id] = val
|
||||||
}
|
}
|
||||||
|
for id, val := range t2.Likes {
|
||||||
|
t1.Likes[id] = val
|
||||||
|
}
|
||||||
|
|
||||||
t1.TombstoneUsers = append(t1.TombstoneUsers, t2.TombstoneUsers...)
|
t1.TombstoneUsers = append(t1.TombstoneUsers, t2.TombstoneUsers...)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user