REFACTOR: move API types, requests and tests to their own 'api_types_spaces' files
This commit is contained in:
parent
f07da7880c
commit
f7d383adf3
@ -207,6 +207,7 @@ func (api *API) update_csrf_token() {
|
|||||||
|
|
||||||
panic("No CSRF Token Found")
|
panic("No CSRF Token Found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) do_http_POST(url string, body string, result interface{}) error {
|
func (api *API) do_http_POST(url string, body string, result interface{}) error {
|
||||||
req, err := http.NewRequest("POST", url, strings.NewReader(body))
|
req, err := http.NewRequest("POST", url, strings.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -387,18 +388,6 @@ func (api API) GetMoreTweetsFromFeed(user_id UserID, response *TweetResponse, mi
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api API) GetSpace(id SpaceID) (SpaceResponse, error) {
|
|
||||||
// TODO: break up this URL into params so it's readable
|
|
||||||
url, err := url.Parse("https://twitter.com/i/api/graphql/Ha9BKBF0uAz9d4-lz0jnYA/AudioSpaceById?variables=%7B%22id%22%3A%22" + string(id) + "%22%2C%22isMetatagsQuery%22%3Afalse%2C%22withSuperFollowsUserFields%22%3Atrue%2C%22withDownvotePerspective%22%3Afalse%2C%22withReactionsMetadata%22%3Afalse%2C%22withReactionsPerspective%22%3Afalse%2C%22withSuperFollowsTweetFields%22%3Atrue%2C%22withReplays%22%3Atrue%7D&features=%7B%22spaces_2022_h2_clipping%22%3Atrue%2C%22spaces_2022_h2_spaces_communities%22%3Atrue%2C%22responsive_web_twitter_blue_verified_badge_is_enabled%22%3Atrue%2C%22verified_phone_label_enabled%22%3Afalse%2C%22tweetypie_unmention_optimization_enabled%22%3Atrue%2C%22responsive_web_uc_gql_enabled%22%3Atrue%2C%22vibe_api_enabled%22%3Atrue%2C%22responsive_web_edit_tweet_api_enabled%22%3Atrue%2C%22graphql_is_translatable_rweb_tweet_is_translatable_enabled%22%3Atrue%2C%22standardized_nudges_misinfo%22%3Atrue%2C%22tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled%22%3Afalse%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue%2C%22interactive_text_enabled%22%3Atrue%2C%22responsive_web_text_conversations_enabled%22%3Afalse%2C%22responsive_web_enhance_cards_enabled%22%3Atrue%7D") //nolint:lll // It's a URL, come on
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var result SpaceResponse
|
|
||||||
err = api.do_http(url.String(), "", &result)
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) GetTweet(id TweetID, cursor string) (TweetResponse, error) {
|
func (api *API) GetTweet(id TweetID, cursor string) (TweetResponse, error) {
|
||||||
url, err := url.Parse(fmt.Sprintf("%s%d.json", API_CONVERSATION_BASE_PATH, id))
|
url, err := url.Parse(fmt.Sprintf("%s%d.json", API_CONVERSATION_BASE_PATH, id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
105
scraper/api_types_spaces.go
Normal file
105
scraper/api_types_spaces.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package scraper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SpaceResponse struct {
|
||||||
|
Data struct {
|
||||||
|
AudioSpace struct {
|
||||||
|
Metadata struct {
|
||||||
|
RestId string `json:"rest_id"`
|
||||||
|
State string
|
||||||
|
Title string
|
||||||
|
MediaKey string `json:"media_key"`
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
StartedAt int64 `json:"started_at"`
|
||||||
|
EndedAt int64 `json:"ended_at,string"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
|
DisallowJoin bool `json:"disallow_join"`
|
||||||
|
NarrowCastSpaceType int64 `json:"narrow_cast_space_type"`
|
||||||
|
IsEmployeeOnly bool `json:"is_employee_only"`
|
||||||
|
IsLocked bool `json:"is_locked"`
|
||||||
|
IsSpaceAvailableForReplay bool `json:"is_space_available_for_replay"`
|
||||||
|
IsSpaceAvailableForClipping bool `json:"is_space_available_for_clipping"`
|
||||||
|
ConversationControls int64 `json:"conversation_controls"`
|
||||||
|
TotalReplayWatched int64 `json:"total_replay_watched"`
|
||||||
|
TotalLiveListeners int64 `json:"total_live_listeners"`
|
||||||
|
CreatorResults struct {
|
||||||
|
Result struct {
|
||||||
|
ID int64 `json:"rest_id,string"`
|
||||||
|
Legacy APIUser `json:"legacy"`
|
||||||
|
} `json:"result"`
|
||||||
|
} `json:"creator_results"`
|
||||||
|
}
|
||||||
|
Participants struct {
|
||||||
|
Total int
|
||||||
|
Admins []struct {
|
||||||
|
Start int
|
||||||
|
User struct {
|
||||||
|
RestId int64 `json:"rest_id,string"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Speakers []struct {
|
||||||
|
User struct {
|
||||||
|
RestId int64 `json:"rest_id,string"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r SpaceResponse) ToTweetTrove() TweetTrove {
|
||||||
|
data := r.Data.AudioSpace
|
||||||
|
|
||||||
|
ret := NewTweetTrove()
|
||||||
|
space := Space{}
|
||||||
|
space.ID = SpaceID(data.Metadata.RestId)
|
||||||
|
if space.ID == "" {
|
||||||
|
// The response is empty. Abort processing
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
space.Title = data.Metadata.Title
|
||||||
|
space.State = data.Metadata.State
|
||||||
|
space.CreatedById = UserID(data.Metadata.CreatorResults.Result.ID)
|
||||||
|
space.CreatedAt = TimestampFromUnix(data.Metadata.CreatedAt)
|
||||||
|
space.StartedAt = TimestampFromUnix(data.Metadata.StartedAt)
|
||||||
|
space.EndedAt = TimestampFromUnix(data.Metadata.EndedAt)
|
||||||
|
space.UpdatedAt = TimestampFromUnix(data.Metadata.UpdatedAt)
|
||||||
|
space.IsAvailableForReplay = data.Metadata.IsSpaceAvailableForReplay
|
||||||
|
space.ReplayWatchCount = data.Metadata.TotalReplayWatched
|
||||||
|
space.LiveListenersCount = data.Metadata.TotalLiveListeners
|
||||||
|
space.IsDetailsFetched = true
|
||||||
|
|
||||||
|
for _, admin := range data.Participants.Admins {
|
||||||
|
space.ParticipantIds = append(space.ParticipantIds, UserID(admin.User.RestId))
|
||||||
|
}
|
||||||
|
for _, speaker := range data.Participants.Speakers {
|
||||||
|
space.ParticipantIds = append(space.ParticipantIds, UserID(speaker.User.RestId))
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.Spaces[space.ID] = space
|
||||||
|
|
||||||
|
creator, err := ParseSingleUser(data.Metadata.CreatorResults.Result.Legacy)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
creator.ID = space.CreatedById
|
||||||
|
ret.Users[creator.ID] = creator
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api API) GetSpace(id SpaceID) (SpaceResponse, error) {
|
||||||
|
// TODO: break up this URL into params so it's readable
|
||||||
|
url, err := url.Parse("https://twitter.com/i/api/graphql/Ha9BKBF0uAz9d4-lz0jnYA/AudioSpaceById?variables=%7B%22id%22%3A%22" + string(id) + "%22%2C%22isMetatagsQuery%22%3Afalse%2C%22withSuperFollowsUserFields%22%3Atrue%2C%22withDownvotePerspective%22%3Afalse%2C%22withReactionsMetadata%22%3Afalse%2C%22withReactionsPerspective%22%3Afalse%2C%22withSuperFollowsTweetFields%22%3Atrue%2C%22withReplays%22%3Atrue%7D&features=%7B%22spaces_2022_h2_clipping%22%3Atrue%2C%22spaces_2022_h2_spaces_communities%22%3Atrue%2C%22responsive_web_twitter_blue_verified_badge_is_enabled%22%3Atrue%2C%22verified_phone_label_enabled%22%3Afalse%2C%22tweetypie_unmention_optimization_enabled%22%3Atrue%2C%22responsive_web_uc_gql_enabled%22%3Atrue%2C%22vibe_api_enabled%22%3Atrue%2C%22responsive_web_edit_tweet_api_enabled%22%3Atrue%2C%22graphql_is_translatable_rweb_tweet_is_translatable_enabled%22%3Atrue%2C%22standardized_nudges_misinfo%22%3Atrue%2C%22tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled%22%3Afalse%2C%22responsive_web_graphql_timeline_navigation_enabled%22%3Atrue%2C%22interactive_text_enabled%22%3Atrue%2C%22responsive_web_text_conversations_enabled%22%3Afalse%2C%22responsive_web_enhance_cards_enabled%22%3Atrue%7D") //nolint:lll // It's a URL, come on
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var result SpaceResponse
|
||||||
|
err = api.do_http(url.String(), "", &result)
|
||||||
|
return result, err
|
||||||
|
}
|
63
scraper/api_types_spaces_test.go
Normal file
63
scraper/api_types_spaces_test.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package scraper_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
. "offline_twitter/scraper"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseSpaceResponse(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
require := require.New(t)
|
||||||
|
data, err := os.ReadFile("test_responses/tweet_content/space_object.json")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var response SpaceResponse
|
||||||
|
err = json.Unmarshal(data, &response)
|
||||||
|
assert.NoError(err)
|
||||||
|
|
||||||
|
trove := response.ToTweetTrove()
|
||||||
|
require.Len(trove.Spaces, 1)
|
||||||
|
space := trove.Spaces["1BdxYypQzBgxX"]
|
||||||
|
assert.Equal(space.Title, "dreary weather 🌧️☔🌬️")
|
||||||
|
assert.Equal(space.CreatedById, UserID(1356335022815539201))
|
||||||
|
assert.Equal(int64(1665884387263), space.CreatedAt.Time.Unix())
|
||||||
|
assert.Equal(int64(1665884388222), space.StartedAt.Time.Unix())
|
||||||
|
assert.Equal(int64(1665887491804), space.EndedAt.Time.Unix())
|
||||||
|
assert.Equal(int64(1665887492705), space.UpdatedAt.Time.Unix())
|
||||||
|
assert.False(space.IsAvailableForReplay)
|
||||||
|
assert.Equal(int64(4), space.ReplayWatchCount)
|
||||||
|
assert.Equal(int64(1), space.LiveListenersCount)
|
||||||
|
|
||||||
|
assert.True(space.IsDetailsFetched)
|
||||||
|
|
||||||
|
assert.Len(space.ParticipantIds, 2)
|
||||||
|
assert.Equal(UserID(1356335022815539201), space.ParticipantIds[0])
|
||||||
|
assert.Equal(UserID(1523838615377350656), space.ParticipantIds[1])
|
||||||
|
|
||||||
|
require.Len(trove.Users, 1)
|
||||||
|
user := trove.Users[1356335022815539201]
|
||||||
|
assert.Equal(847, user.FollowersCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseEmptySpaceResponse(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
data, err := os.ReadFile("test_responses/tweet_content/space_object_empty.json")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var response SpaceResponse
|
||||||
|
err = json.Unmarshal(data, &response)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
trove := response.ToTweetTrove()
|
||||||
|
require.Len(trove.Spaces, 0)
|
||||||
|
}
|
@ -491,91 +491,3 @@ func (api *API) GetMoreTweetsFromGraphqlFeed(user_id UserID, response *APIV2Resp
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type SpaceResponse struct {
|
|
||||||
Data struct {
|
|
||||||
AudioSpace struct {
|
|
||||||
Metadata struct {
|
|
||||||
RestId string `json:"rest_id"`
|
|
||||||
State string
|
|
||||||
Title string
|
|
||||||
MediaKey string `json:"media_key"`
|
|
||||||
CreatedAt int64 `json:"created_at"`
|
|
||||||
StartedAt int64 `json:"started_at"`
|
|
||||||
EndedAt int64 `json:"ended_at,string"`
|
|
||||||
UpdatedAt int64 `json:"updated_at"`
|
|
||||||
DisallowJoin bool `json:"disallow_join"`
|
|
||||||
NarrowCastSpaceType int64 `json:"narrow_cast_space_type"`
|
|
||||||
IsEmployeeOnly bool `json:"is_employee_only"`
|
|
||||||
IsLocked bool `json:"is_locked"`
|
|
||||||
IsSpaceAvailableForReplay bool `json:"is_space_available_for_replay"`
|
|
||||||
IsSpaceAvailableForClipping bool `json:"is_space_available_for_clipping"`
|
|
||||||
ConversationControls int64 `json:"conversation_controls"`
|
|
||||||
TotalReplayWatched int64 `json:"total_replay_watched"`
|
|
||||||
TotalLiveListeners int64 `json:"total_live_listeners"`
|
|
||||||
CreatorResults struct {
|
|
||||||
Result struct {
|
|
||||||
ID int64 `json:"rest_id,string"`
|
|
||||||
Legacy APIUser `json:"legacy"`
|
|
||||||
} `json:"result"`
|
|
||||||
} `json:"creator_results"`
|
|
||||||
}
|
|
||||||
Participants struct {
|
|
||||||
Total int
|
|
||||||
Admins []struct {
|
|
||||||
Start int
|
|
||||||
User struct {
|
|
||||||
RestId int64 `json:"rest_id,string"`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Speakers []struct {
|
|
||||||
User struct {
|
|
||||||
RestId int64 `json:"rest_id,string"`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r SpaceResponse) ToTweetTrove() TweetTrove {
|
|
||||||
data := r.Data.AudioSpace
|
|
||||||
|
|
||||||
ret := NewTweetTrove()
|
|
||||||
space := Space{}
|
|
||||||
space.ID = SpaceID(data.Metadata.RestId)
|
|
||||||
if space.ID == "" {
|
|
||||||
// The response is empty. Abort processing
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
space.Title = data.Metadata.Title
|
|
||||||
space.State = data.Metadata.State
|
|
||||||
space.CreatedById = UserID(data.Metadata.CreatorResults.Result.ID)
|
|
||||||
space.CreatedAt = TimestampFromUnix(data.Metadata.CreatedAt)
|
|
||||||
space.StartedAt = TimestampFromUnix(data.Metadata.StartedAt)
|
|
||||||
space.EndedAt = TimestampFromUnix(data.Metadata.EndedAt)
|
|
||||||
space.UpdatedAt = TimestampFromUnix(data.Metadata.UpdatedAt)
|
|
||||||
space.IsAvailableForReplay = data.Metadata.IsSpaceAvailableForReplay
|
|
||||||
space.ReplayWatchCount = data.Metadata.TotalReplayWatched
|
|
||||||
space.LiveListenersCount = data.Metadata.TotalLiveListeners
|
|
||||||
space.IsDetailsFetched = true
|
|
||||||
|
|
||||||
for _, admin := range data.Participants.Admins {
|
|
||||||
space.ParticipantIds = append(space.ParticipantIds, UserID(admin.User.RestId))
|
|
||||||
}
|
|
||||||
for _, speaker := range data.Participants.Speakers {
|
|
||||||
space.ParticipantIds = append(space.ParticipantIds, UserID(speaker.User.RestId))
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.Spaces[space.ID] = space
|
|
||||||
|
|
||||||
creator, err := ParseSingleUser(data.Metadata.CreatorResults.Result.Legacy)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
creator.ID = space.CreatedById
|
|
||||||
ret.Users[creator.ID] = creator
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
@ -471,57 +471,6 @@ func TestAPIV2ParseTweetWithSpace(t *testing.T) {
|
|||||||
assert.False(s.IsDetailsFetched)
|
assert.False(s.IsDetailsFetched)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseSpaceResponse(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
require := require.New(t)
|
|
||||||
data, err := os.ReadFile("test_responses/tweet_content/space_object.json")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var response SpaceResponse
|
|
||||||
err = json.Unmarshal(data, &response)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
trove := response.ToTweetTrove()
|
|
||||||
require.Len(trove.Spaces, 1)
|
|
||||||
space := trove.Spaces["1BdxYypQzBgxX"]
|
|
||||||
assert.Equal(space.Title, "dreary weather 🌧️☔🌬️")
|
|
||||||
assert.Equal(space.CreatedById, UserID(1356335022815539201))
|
|
||||||
assert.Equal(int64(1665884387263), space.CreatedAt.Time.Unix())
|
|
||||||
assert.Equal(int64(1665884388222), space.StartedAt.Time.Unix())
|
|
||||||
assert.Equal(int64(1665887491804), space.EndedAt.Time.Unix())
|
|
||||||
assert.Equal(int64(1665887492705), space.UpdatedAt.Time.Unix())
|
|
||||||
assert.False(space.IsAvailableForReplay)
|
|
||||||
assert.Equal(int64(4), space.ReplayWatchCount)
|
|
||||||
assert.Equal(int64(1), space.LiveListenersCount)
|
|
||||||
|
|
||||||
assert.True(space.IsDetailsFetched)
|
|
||||||
|
|
||||||
assert.Len(space.ParticipantIds, 2)
|
|
||||||
assert.Equal(UserID(1356335022815539201), space.ParticipantIds[0])
|
|
||||||
assert.Equal(UserID(1523838615377350656), space.ParticipantIds[1])
|
|
||||||
|
|
||||||
require.Len(trove.Users, 1)
|
|
||||||
user := trove.Users[1356335022815539201]
|
|
||||||
assert.Equal(847, user.FollowersCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseEmptySpaceResponse(t *testing.T) {
|
|
||||||
require := require.New(t)
|
|
||||||
data, err := os.ReadFile("test_responses/tweet_content/space_object_empty.json")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var response SpaceResponse
|
|
||||||
err = json.Unmarshal(data, &response)
|
|
||||||
require.NoError(err)
|
|
||||||
|
|
||||||
trove := response.ToTweetTrove()
|
|
||||||
require.Len(trove.Spaces, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseAPIV2UserFeed(t *testing.T) {
|
func TestParseAPIV2UserFeed(t *testing.T) {
|
||||||
data, err := os.ReadFile("test_responses/api_v2/user_feed_apiv2.json")
|
data, err := os.ReadFile("test_responses/api_v2/user_feed_apiv2.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user