x-twitter-scraper 0.5.5 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +1 -1
  4. data/lib/x_twitter_scraper/models/compose_create_response.rb +62 -1
  5. data/lib/x_twitter_scraper/models/radar_item.rb +144 -7
  6. data/lib/x_twitter_scraper/models/x/account_connection_attempt_retrieve_params.rb +22 -0
  7. data/lib/x_twitter_scraper/models/x/account_connection_attempt_retrieve_response.rb +190 -0
  8. data/lib/x_twitter_scraper/models/x/account_create_params.rb +9 -9
  9. data/lib/x_twitter_scraper/models/x/account_create_response.rb +2 -57
  10. data/lib/x_twitter_scraper/models/x/account_reauth_params.rb +2 -2
  11. data/lib/x_twitter_scraper/models/x/x_account.rb +9 -9
  12. data/lib/x_twitter_scraper/resources/compose.rb +4 -4
  13. data/lib/x_twitter_scraper/resources/x/account_connection_attempts.rb +37 -0
  14. data/lib/x_twitter_scraper/resources/x/accounts.rb +6 -6
  15. data/lib/x_twitter_scraper/resources/x.rb +6 -0
  16. data/lib/x_twitter_scraper/version.rb +1 -1
  17. data/lib/x_twitter_scraper.rb +3 -0
  18. data/rbi/x_twitter_scraper/models/compose_create_response.rbi +142 -0
  19. data/rbi/x_twitter_scraper/models/radar_item.rbi +245 -8
  20. data/rbi/x_twitter_scraper/models/x/account_connection_attempt_retrieve_params.rbi +40 -0
  21. data/rbi/x_twitter_scraper/models/x/account_connection_attempt_retrieve_response.rbi +317 -0
  22. data/rbi/x_twitter_scraper/models/x/account_create_params.rbi +8 -11
  23. data/rbi/x_twitter_scraper/models/x/account_create_response.rbi +2 -123
  24. data/rbi/x_twitter_scraper/models/x/account_reauth_params.rbi +2 -2
  25. data/rbi/x_twitter_scraper/models/x/x_account.rbi +12 -12
  26. data/rbi/x_twitter_scraper/resources/compose.rbi +4 -4
  27. data/rbi/x_twitter_scraper/resources/x/account_connection_attempts.rbi +33 -0
  28. data/rbi/x_twitter_scraper/resources/x/accounts.rbi +5 -5
  29. data/rbi/x_twitter_scraper/resources/x.rbi +4 -0
  30. data/sig/x_twitter_scraper/models/compose_create_response.rbs +60 -0
  31. data/sig/x_twitter_scraper/models/radar_item.rbs +100 -4
  32. data/sig/x_twitter_scraper/models/x/account_connection_attempt_retrieve_params.rbs +25 -0
  33. data/sig/x_twitter_scraper/models/x/account_connection_attempt_retrieve_response.rbs +182 -0
  34. data/sig/x_twitter_scraper/models/x/account_create_params.rbs +6 -8
  35. data/sig/x_twitter_scraper/models/x/account_create_response.rbs +1 -62
  36. data/sig/x_twitter_scraper/resources/x/account_connection_attempts.rbs +14 -0
  37. data/sig/x_twitter_scraper/resources/x/accounts.rbs +2 -2
  38. data/sig/x_twitter_scraper/resources/x.rbs +2 -0
  39. metadata +11 -2
@@ -19,11 +19,11 @@ module XTwitterScraper
19
19
  required :created_at, Time, api_name: :createdAt
20
20
 
21
21
  # @!attribute health
22
- # Derived connection health. `healthy` = session active. `needsReauth` = user must
22
+ # Derived connection health. `healthy` = ready to use. `needsReauth` = user must
23
23
  # submit fresh credentials. `locked` = X locked the account; unlock on x.com
24
- # first. `suspended` = X banned the account. `recovering` = past cooldown, will
25
- # auto-retry on next use. `temporaryIssue` = temporary connection problem; retry
26
- # shortly.
24
+ # first. `suspended` = X banned the account. `recovering` = cooldown ended; the
25
+ # account can reconnect on its next use. `temporaryIssue` = temporary connection
26
+ # problem; wait before the next use.
27
27
  #
28
28
  # @return [Symbol, XTwitterScraper::Models::X::XAccount::Health]
29
29
  required :health, enum: -> { XTwitterScraper::X::XAccount::Health }
@@ -63,7 +63,7 @@ module XTwitterScraper
63
63
  #
64
64
  # @param created_at [Time]
65
65
  #
66
- # @param health [Symbol, XTwitterScraper::Models::X::XAccount::Health] Derived connection health. `healthy` = session active. `needsReauth` = user must
66
+ # @param health [Symbol, XTwitterScraper::Models::X::XAccount::Health] Derived connection health. `healthy` = ready to use. `needsReauth` = user must s
67
67
  #
68
68
  # @param status [String]
69
69
  #
@@ -75,11 +75,11 @@ module XTwitterScraper
75
75
  #
76
76
  # @param cookies_obtained_at [Time]
77
77
 
78
- # Derived connection health. `healthy` = session active. `needsReauth` = user must
78
+ # Derived connection health. `healthy` = ready to use. `needsReauth` = user must
79
79
  # submit fresh credentials. `locked` = X locked the account; unlock on x.com
80
- # first. `suspended` = X banned the account. `recovering` = past cooldown, will
81
- # auto-retry on next use. `temporaryIssue` = temporary connection problem; retry
82
- # shortly.
80
+ # first. `suspended` = X banned the account. `recovering` = cooldown ended; the
81
+ # account can reconnect on its next use. `temporaryIssue` = temporary connection
82
+ # problem; wait before the next use.
83
83
  #
84
84
  # @see XTwitterScraper::Models::X::XAccount#health
85
85
  module Health
@@ -8,10 +8,10 @@ module XTwitterScraper
8
8
  module Resources
9
9
  # AI tweet composition, drafts, writing styles, and radar
10
10
  class Compose
11
- # Run one step of Xquik's three-step writing workflow. Compose returns questions
12
- # and editorial rules. Refine returns goal-specific guidance. Score applies
13
- # deterministic text checks. It does not predict reach or expose X ranking
14
- # weights.
11
+ # Run one step of Xquik's three-step writing workflow. Compose returns questions,
12
+ # editorial rules, and source-specific Radar recommendations. Refine returns
13
+ # goal-specific guidance. Score applies deterministic text checks. It does not
14
+ # predict reach or expose X ranking weights.
15
15
  #
16
16
  # @overload create(body:, request_options: {})
17
17
  #
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XTwitterScraper
4
+ module Resources
5
+ class X
6
+ # Connected X account management
7
+ class AccountConnectionAttempts
8
+ # Get X account connection status
9
+ #
10
+ # @overload retrieve(id, request_options: {})
11
+ #
12
+ # @param id [String] Connection attempt ID returned by `POST /x/accounts`.
13
+ #
14
+ # @param request_options [XTwitterScraper::RequestOptions, Hash{Symbol=>Object}, nil]
15
+ #
16
+ # @return [XTwitterScraper::Models::X::AccountConnectionAttemptRetrieveResponse::XAccountConnectionAttemptPending, XTwitterScraper::Models::X::AccountConnectionAttemptRetrieveResponse::XAccountConnectionAttemptSuccess, XTwitterScraper::Models::X::AccountConnectionAttemptRetrieveResponse::XAccountConnectionAttemptFailed, XTwitterScraper::Models::X::AccountConnectionAttemptRetrieveResponse::XAccountConnectionChallenge]
17
+ #
18
+ # @see XTwitterScraper::Models::X::AccountConnectionAttemptRetrieveParams
19
+ def retrieve(id, params = {})
20
+ @client.request(
21
+ method: :get,
22
+ path: ["x/account-connection-attempts/%1$s", id],
23
+ model: XTwitterScraper::Models::X::AccountConnectionAttemptRetrieveResponse,
24
+ options: params[:request_options]
25
+ )
26
+ end
27
+
28
+ # @api private
29
+ #
30
+ # @param client [XTwitterScraper::Client]
31
+ def initialize(client:)
32
+ @client = client
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -11,19 +11,19 @@ module XTwitterScraper
11
11
  class Accounts
12
12
  # Connect X account
13
13
  #
14
- # @overload create(email:, password:, username:, totp_secret: nil, request_options: {})
14
+ # @overload create(email:, password:, totp_secret:, username:, request_options: {})
15
15
  #
16
16
  # @param email [String] Account email
17
17
  #
18
18
  # @param password [String] Account password
19
19
  #
20
- # @param username [String] X username
20
+ # @param totp_secret [String] Authenticator App TOTP secret required for durable login
21
21
  #
22
- # @param totp_secret [String] TOTP secret for 2FA
22
+ # @param username [String] X username
23
23
  #
24
24
  # @param request_options [XTwitterScraper::RequestOptions, Hash{Symbol=>Object}, nil]
25
25
  #
26
- # @return [XTwitterScraper::Models::X::AccountCreateResponse]
26
+ # @return [Object]
27
27
  #
28
28
  # @see XTwitterScraper::Models::X::AccountCreateParams
29
29
  def create(params)
@@ -32,7 +32,7 @@ module XTwitterScraper
32
32
  method: :post,
33
33
  path: "x/accounts",
34
34
  body: parsed,
35
- model: XTwitterScraper::Models::X::AccountCreateResponse,
35
+ model: XTwitterScraper::Internal::Type::Unknown,
36
36
  options: options
37
37
  )
38
38
  end
@@ -124,7 +124,7 @@ module XTwitterScraper
124
124
  #
125
125
  # @param email [String] Email for the X account (updates stored email)
126
126
  #
127
- # @param totp_secret [String] TOTP secret for 2FA re-authentication
127
+ # @param totp_secret [String] Replacement Authenticator App TOTP secret. Omit it to reuse the saved secret.
128
128
  #
129
129
  # @param request_options [XTwitterScraper::RequestOptions, Hash{Symbol=>Object}, nil]
130
130
  #
@@ -42,6 +42,10 @@ module XTwitterScraper
42
42
  # @return [XTwitterScraper::Resources::X::AccountConnectionChallenges]
43
43
  attr_reader :account_connection_challenges
44
44
 
45
+ # Connected X account management
46
+ # @return [XTwitterScraper::Resources::X::AccountConnectionAttempts]
47
+ attr_reader :account_connection_attempts
48
+
45
49
  # Look up, search, and analyze individual tweets
46
50
  # @return [XTwitterScraper::Resources::X::Bookmarks]
47
51
  attr_reader :bookmarks
@@ -165,6 +169,8 @@ module XTwitterScraper
165
169
  @accounts = XTwitterScraper::Resources::X::Accounts.new(client: client)
166
170
  @account_connection_challenges =
167
171
  XTwitterScraper::Resources::X::AccountConnectionChallenges.new(client: client)
172
+ @account_connection_attempts =
173
+ XTwitterScraper::Resources::X::AccountConnectionAttempts.new(client: client)
168
174
  @bookmarks = XTwitterScraper::Resources::X::Bookmarks.new(client: client)
169
175
  @lists = XTwitterScraper::Resources::X::Lists.new(client: client)
170
176
  end
@@ -5,5 +5,5 @@
5
5
  # frozen_string_literal: true
6
6
 
7
7
  module XTwitterScraper
8
- VERSION = "0.5.5"
8
+ VERSION = "0.6.0"
9
9
  end
@@ -184,6 +184,8 @@ require_relative "x_twitter_scraper/models/webhook_update_params"
184
184
  require_relative "x_twitter_scraper/models/winner"
185
185
  require_relative "x_twitter_scraper/models/x/account_bulk_retry_params"
186
186
  require_relative "x_twitter_scraper/models/x/account_bulk_retry_response"
187
+ require_relative "x_twitter_scraper/models/x/account_connection_attempt_retrieve_params"
188
+ require_relative "x_twitter_scraper/models/x/account_connection_attempt_retrieve_response"
187
189
  require_relative "x_twitter_scraper/models/x/account_connection_challenge_submit_params"
188
190
  require_relative "x_twitter_scraper/models/x/account_connection_challenge_submit_response"
189
191
  require_relative "x_twitter_scraper/models/x/account_create_params"
@@ -304,6 +306,7 @@ require_relative "x_twitter_scraper/resources/support/tickets"
304
306
  require_relative "x_twitter_scraper/resources/trends"
305
307
  require_relative "x_twitter_scraper/resources/webhooks"
306
308
  require_relative "x_twitter_scraper/resources/x"
309
+ require_relative "x_twitter_scraper/resources/x/account_connection_attempts"
307
310
  require_relative "x_twitter_scraper/resources/x/account_connection_challenges"
308
311
  require_relative "x_twitter_scraper/resources/x/accounts"
309
312
  require_relative "x_twitter_scraper/resources/x/bookmarks"
@@ -61,6 +61,16 @@ module XTwitterScraper
61
61
  sig { returns(String) }
62
62
  attr_accessor :next_step
63
63
 
64
+ # Sources and guidance for researching a fresh post angle.
65
+ sig do
66
+ returns(
67
+ T::Array[
68
+ XTwitterScraper::Models::ComposeCreateResponse::ComposePrepareResult::RadarRecommendation
69
+ ]
70
+ )
71
+ end
72
+ attr_accessor :radar_recommendations
73
+
64
74
  # Published signal names with unpublished weights as null.
65
75
  sig do
66
76
  returns(
@@ -129,6 +139,10 @@ module XTwitterScraper
129
139
  follow_up_questions: T::Array[String],
130
140
  intent_url: String,
131
141
  next_step: String,
142
+ radar_recommendations:
143
+ T::Array[
144
+ XTwitterScraper::Models::ComposeCreateResponse::ComposePrepareResult::RadarRecommendation::OrHash
145
+ ],
132
146
  scorer_weights:
133
147
  T::Array[
134
148
  XTwitterScraper::Models::ComposeCreateResponse::ComposePrepareResult::ScorerWeight::OrHash
@@ -154,6 +168,8 @@ module XTwitterScraper
154
168
  # X post intent seeded with the topic.
155
169
  intent_url:,
156
170
  next_step:,
171
+ # Sources and guidance for researching a fresh post angle.
172
+ radar_recommendations:,
157
173
  # Published signal names with unpublished weights as null.
158
174
  scorer_weights:,
159
175
  # Signal source and evidence limits.
@@ -184,6 +200,10 @@ module XTwitterScraper
184
200
  follow_up_questions: T::Array[String],
185
201
  intent_url: String,
186
202
  next_step: String,
203
+ radar_recommendations:
204
+ T::Array[
205
+ XTwitterScraper::Models::ComposeCreateResponse::ComposePrepareResult::RadarRecommendation
206
+ ],
187
207
  scorer_weights:
188
208
  T::Array[
189
209
  XTwitterScraper::Models::ComposeCreateResponse::ComposePrepareResult::ScorerWeight
@@ -254,6 +274,128 @@ module XTwitterScraper
254
274
  end
255
275
  end
256
276
 
277
+ class RadarRecommendation < XTwitterScraper::Internal::Type::BaseModel
278
+ OrHash =
279
+ T.type_alias do
280
+ T.any(
281
+ XTwitterScraper::Models::ComposeCreateResponse::ComposePrepareResult::RadarRecommendation,
282
+ XTwitterScraper::Internal::AnyHash
283
+ )
284
+ end
285
+
286
+ # Radar endpoint for this source.
287
+ sig { returns(String) }
288
+ attr_accessor :endpoint
289
+
290
+ # Source-specific drafting guidance.
291
+ sig { returns(String) }
292
+ attr_accessor :guidance
293
+
294
+ sig do
295
+ returns(
296
+ XTwitterScraper::Models::ComposeCreateResponse::ComposePrepareResult::RadarRecommendation::Source::TaggedSymbol
297
+ )
298
+ end
299
+ attr_accessor :source
300
+
301
+ # Current-topic research this source supports.
302
+ sig { returns(String) }
303
+ attr_accessor :use_for
304
+
305
+ sig do
306
+ params(
307
+ endpoint: String,
308
+ guidance: String,
309
+ source:
310
+ XTwitterScraper::Models::ComposeCreateResponse::ComposePrepareResult::RadarRecommendation::Source::OrSymbol,
311
+ use_for: String
312
+ ).returns(T.attached_class)
313
+ end
314
+ def self.new(
315
+ # Radar endpoint for this source.
316
+ endpoint:,
317
+ # Source-specific drafting guidance.
318
+ guidance:,
319
+ source:,
320
+ # Current-topic research this source supports.
321
+ use_for:
322
+ )
323
+ end
324
+
325
+ sig do
326
+ override.returns(
327
+ {
328
+ endpoint: String,
329
+ guidance: String,
330
+ source:
331
+ XTwitterScraper::Models::ComposeCreateResponse::ComposePrepareResult::RadarRecommendation::Source::TaggedSymbol,
332
+ use_for: String
333
+ }
334
+ )
335
+ end
336
+ def to_hash
337
+ end
338
+
339
+ module Source
340
+ extend XTwitterScraper::Internal::Type::Enum
341
+
342
+ TaggedSymbol =
343
+ T.type_alias do
344
+ T.all(
345
+ Symbol,
346
+ XTwitterScraper::Models::ComposeCreateResponse::ComposePrepareResult::RadarRecommendation::Source
347
+ )
348
+ end
349
+ OrSymbol = T.type_alias { T.any(Symbol, String) }
350
+
351
+ REDDIT =
352
+ T.let(
353
+ :reddit,
354
+ XTwitterScraper::Models::ComposeCreateResponse::ComposePrepareResult::RadarRecommendation::Source::TaggedSymbol
355
+ )
356
+ GITHUB =
357
+ T.let(
358
+ :github,
359
+ XTwitterScraper::Models::ComposeCreateResponse::ComposePrepareResult::RadarRecommendation::Source::TaggedSymbol
360
+ )
361
+ TRUSTMRR =
362
+ T.let(
363
+ :trustmrr,
364
+ XTwitterScraper::Models::ComposeCreateResponse::ComposePrepareResult::RadarRecommendation::Source::TaggedSymbol
365
+ )
366
+ HACKER_NEWS =
367
+ T.let(
368
+ :hacker_news,
369
+ XTwitterScraper::Models::ComposeCreateResponse::ComposePrepareResult::RadarRecommendation::Source::TaggedSymbol
370
+ )
371
+ GOOGLE_TRENDS =
372
+ T.let(
373
+ :google_trends,
374
+ XTwitterScraper::Models::ComposeCreateResponse::ComposePrepareResult::RadarRecommendation::Source::TaggedSymbol
375
+ )
376
+ WIKIPEDIA =
377
+ T.let(
378
+ :wikipedia,
379
+ XTwitterScraper::Models::ComposeCreateResponse::ComposePrepareResult::RadarRecommendation::Source::TaggedSymbol
380
+ )
381
+ POLYMARKET =
382
+ T.let(
383
+ :polymarket,
384
+ XTwitterScraper::Models::ComposeCreateResponse::ComposePrepareResult::RadarRecommendation::Source::TaggedSymbol
385
+ )
386
+
387
+ sig do
388
+ override.returns(
389
+ T::Array[
390
+ XTwitterScraper::Models::ComposeCreateResponse::ComposePrepareResult::RadarRecommendation::Source::TaggedSymbol
391
+ ]
392
+ )
393
+ end
394
+ def self.values
395
+ end
396
+ end
397
+ end
398
+
257
399
  class ScorerWeight < XTwitterScraper::Internal::Type::BaseModel
258
400
  OrHash =
259
401
  T.type_alias do
@@ -22,12 +22,24 @@ module XTwitterScraper
22
22
  sig { returns(Time) }
23
23
  attr_accessor :created_at
24
24
 
25
+ # BCP-47 language code. und means the source did not identify a language.
25
26
  sig { returns(String) }
26
27
  attr_accessor :language
27
28
 
28
29
  # Source-specific fields. Shape varies per source:
29
30
  #
30
- # - reddit: { subreddit: string, author: string }
31
+ # - reddit: { author, authorId?, subreddit, subredditId?, subredditSubscribers?,
32
+ # sourceFormat, score?, upvoteRatio?, estimatedUpvotes?, estimatedDownvotes?,
33
+ # numberComments?, numberCrossposts?, selftext?, contentUrl?, domain?,
34
+ # postHint?, linkFlairText?, distinguished?, totalAwardsReceived?, viewCount?,
35
+ # editedAt?, galleryImageUrls?, redditVideo?, archived?, contestMode?,
36
+ # isCrosspostable?, isMeta?, isNsfw?, isOriginalContent?, isRobotIndexable?,
37
+ # isSelf?, isSpoiler?, isVideo?, locked?, stickied? }. `score` is Reddit's
38
+ # public net score. Exact public upvote and downvote counts are not available.
39
+ # Estimated counts derive from the public score and upvote ratio, which Reddit
40
+ # may fuzz. Comment bodies are not included. Current items combine public
41
+ # listing discovery with server-rendered post data and use `sourceFormat: html`;
42
+ # `json` and `rss` remain for legacy rows.
31
43
  # - github: { starsToday: number }
32
44
  # - hacker_news: { points: number, numberComments: number }
33
45
  # - google_trends: { approxTraffic: number }
@@ -35,9 +47,19 @@ module XTwitterScraper
35
47
  # - wikipedia: { views: number }
36
48
  # - trustmrr: { mrr, growthPercent, last30Days, total, customers,
37
49
  # activeSubscriptions, onSale, xHandle?, category?, askingPrice?, country?,
38
- # growthMrrPercent?, multiple?, paymentProvider?, rank? }
39
- sig { returns(T::Hash[Symbol, T.anything]) }
40
- attr_accessor :metadata
50
+ # foundedDate?, googleSearchImpressionsLast30Days?, growthMrrPercent?,
51
+ # multiple?, paymentProvider?, profitMarginLast30Days?, rank?,
52
+ # revenuePerVisitor?, targetAudience?, visitorsLast30Days? } For the startup
53
+ # growth source, xHandle is the founder's X username without @. The rank field
54
+ # is the source's revenue rank. Result order represents reported 30-day
55
+ # revenue-growth rank.
56
+ sig { returns(XTwitterScraper::RadarItem::Metadata) }
57
+ attr_reader :metadata
58
+
59
+ sig do
60
+ params(metadata: XTwitterScraper::RadarItem::Metadata::OrHash).void
61
+ end
62
+ attr_writer :metadata
41
63
 
42
64
  sig { returns(Time) }
43
65
  attr_accessor :published_at
@@ -64,6 +86,7 @@ module XTwitterScraper
64
86
  sig { params(description: String).void }
65
87
  attr_writer :description
66
88
 
89
+ # Source image. Startup growth items return the logo here.
67
90
  sig { returns(T.nilable(String)) }
68
91
  attr_reader :image_url
69
92
 
@@ -84,7 +107,7 @@ module XTwitterScraper
84
107
  category: XTwitterScraper::RadarItem::Category::OrSymbol,
85
108
  created_at: Time,
86
109
  language: String,
87
- metadata: T::Hash[Symbol, T.anything],
110
+ metadata: XTwitterScraper::RadarItem::Metadata::OrHash,
88
111
  published_at: Time,
89
112
  region: String,
90
113
  score: Float,
@@ -101,10 +124,22 @@ module XTwitterScraper
101
124
  id:,
102
125
  category:,
103
126
  created_at:,
127
+ # BCP-47 language code. und means the source did not identify a language.
104
128
  language:,
105
129
  # Source-specific fields. Shape varies per source:
106
130
  #
107
- # - reddit: { subreddit: string, author: string }
131
+ # - reddit: { author, authorId?, subreddit, subredditId?, subredditSubscribers?,
132
+ # sourceFormat, score?, upvoteRatio?, estimatedUpvotes?, estimatedDownvotes?,
133
+ # numberComments?, numberCrossposts?, selftext?, contentUrl?, domain?,
134
+ # postHint?, linkFlairText?, distinguished?, totalAwardsReceived?, viewCount?,
135
+ # editedAt?, galleryImageUrls?, redditVideo?, archived?, contestMode?,
136
+ # isCrosspostable?, isMeta?, isNsfw?, isOriginalContent?, isRobotIndexable?,
137
+ # isSelf?, isSpoiler?, isVideo?, locked?, stickied? }. `score` is Reddit's
138
+ # public net score. Exact public upvote and downvote counts are not available.
139
+ # Estimated counts derive from the public score and upvote ratio, which Reddit
140
+ # may fuzz. Comment bodies are not included. Current items combine public
141
+ # listing discovery with server-rendered post data and use `sourceFormat: html`;
142
+ # `json` and `rss` remain for legacy rows.
108
143
  # - github: { starsToday: number }
109
144
  # - hacker_news: { points: number, numberComments: number }
110
145
  # - google_trends: { approxTraffic: number }
@@ -112,7 +147,12 @@ module XTwitterScraper
112
147
  # - wikipedia: { views: number }
113
148
  # - trustmrr: { mrr, growthPercent, last30Days, total, customers,
114
149
  # activeSubscriptions, onSale, xHandle?, category?, askingPrice?, country?,
115
- # growthMrrPercent?, multiple?, paymentProvider?, rank? }
150
+ # foundedDate?, googleSearchImpressionsLast30Days?, growthMrrPercent?,
151
+ # multiple?, paymentProvider?, profitMarginLast30Days?, rank?,
152
+ # revenuePerVisitor?, targetAudience?, visitorsLast30Days? } For the startup
153
+ # growth source, xHandle is the founder's X username without @. The rank field
154
+ # is the source's revenue rank. Result order represents reported 30-day
155
+ # revenue-growth rank.
116
156
  metadata:,
117
157
  published_at:,
118
158
  region:,
@@ -122,6 +162,7 @@ module XTwitterScraper
122
162
  source_id:,
123
163
  title:,
124
164
  description: nil,
165
+ # Source image. Startup growth items return the logo here.
125
166
  image_url: nil,
126
167
  url: nil
127
168
  )
@@ -134,7 +175,7 @@ module XTwitterScraper
134
175
  category: XTwitterScraper::RadarItem::Category::TaggedSymbol,
135
176
  created_at: Time,
136
177
  language: String,
137
- metadata: T::Hash[Symbol, T.anything],
178
+ metadata: XTwitterScraper::RadarItem::Metadata,
138
179
  published_at: Time,
139
180
  region: String,
140
181
  score: Float,
@@ -184,6 +225,202 @@ module XTwitterScraper
184
225
  end
185
226
  end
186
227
 
228
+ class Metadata < XTwitterScraper::Internal::Type::BaseModel
229
+ OrHash =
230
+ T.type_alias do
231
+ T.any(
232
+ XTwitterScraper::RadarItem::Metadata,
233
+ XTwitterScraper::Internal::AnyHash
234
+ )
235
+ end
236
+
237
+ sig { returns(T.nilable(String)) }
238
+ attr_reader :author
239
+
240
+ sig { params(author: String).void }
241
+ attr_writer :author
242
+
243
+ sig { returns(T.nilable(String)) }
244
+ attr_reader :content_url
245
+
246
+ sig { params(content_url: String).void }
247
+ attr_writer :content_url
248
+
249
+ sig { returns(T.nilable(Integer)) }
250
+ attr_reader :estimated_downvotes
251
+
252
+ sig { params(estimated_downvotes: Integer).void }
253
+ attr_writer :estimated_downvotes
254
+
255
+ sig { returns(T.nilable(Integer)) }
256
+ attr_reader :estimated_upvotes
257
+
258
+ sig { params(estimated_upvotes: Integer).void }
259
+ attr_writer :estimated_upvotes
260
+
261
+ sig { returns(T.nilable(Integer)) }
262
+ attr_reader :number_comments
263
+
264
+ sig { params(number_comments: Integer).void }
265
+ attr_writer :number_comments
266
+
267
+ sig { returns(T.nilable(Integer)) }
268
+ attr_reader :score
269
+
270
+ sig { params(score: Integer).void }
271
+ attr_writer :score
272
+
273
+ sig { returns(T.nilable(String)) }
274
+ attr_reader :selftext
275
+
276
+ sig { params(selftext: String).void }
277
+ attr_writer :selftext
278
+
279
+ # Current items use html. json and rss are retained for legacy rows.
280
+ sig do
281
+ returns(
282
+ T.nilable(
283
+ XTwitterScraper::RadarItem::Metadata::SourceFormat::TaggedSymbol
284
+ )
285
+ )
286
+ end
287
+ attr_reader :source_format
288
+
289
+ sig do
290
+ params(
291
+ source_format:
292
+ XTwitterScraper::RadarItem::Metadata::SourceFormat::OrSymbol
293
+ ).void
294
+ end
295
+ attr_writer :source_format
296
+
297
+ sig { returns(T.nilable(String)) }
298
+ attr_reader :subreddit
299
+
300
+ sig { params(subreddit: String).void }
301
+ attr_writer :subreddit
302
+
303
+ sig { returns(T.nilable(Float)) }
304
+ attr_reader :upvote_ratio
305
+
306
+ sig { params(upvote_ratio: Float).void }
307
+ attr_writer :upvote_ratio
308
+
309
+ # Source-specific fields. Shape varies per source:
310
+ #
311
+ # - reddit: { author, authorId?, subreddit, subredditId?, subredditSubscribers?,
312
+ # sourceFormat, score?, upvoteRatio?, estimatedUpvotes?, estimatedDownvotes?,
313
+ # numberComments?, numberCrossposts?, selftext?, contentUrl?, domain?,
314
+ # postHint?, linkFlairText?, distinguished?, totalAwardsReceived?, viewCount?,
315
+ # editedAt?, galleryImageUrls?, redditVideo?, archived?, contestMode?,
316
+ # isCrosspostable?, isMeta?, isNsfw?, isOriginalContent?, isRobotIndexable?,
317
+ # isSelf?, isSpoiler?, isVideo?, locked?, stickied? }. `score` is Reddit's
318
+ # public net score. Exact public upvote and downvote counts are not available.
319
+ # Estimated counts derive from the public score and upvote ratio, which Reddit
320
+ # may fuzz. Comment bodies are not included. Current items combine public
321
+ # listing discovery with server-rendered post data and use `sourceFormat: html`;
322
+ # `json` and `rss` remain for legacy rows.
323
+ # - github: { starsToday: number }
324
+ # - hacker_news: { points: number, numberComments: number }
325
+ # - google_trends: { approxTraffic: number }
326
+ # - polymarket: { volume24hr: number }
327
+ # - wikipedia: { views: number }
328
+ # - trustmrr: { mrr, growthPercent, last30Days, total, customers,
329
+ # activeSubscriptions, onSale, xHandle?, category?, askingPrice?, country?,
330
+ # foundedDate?, googleSearchImpressionsLast30Days?, growthMrrPercent?,
331
+ # multiple?, paymentProvider?, profitMarginLast30Days?, rank?,
332
+ # revenuePerVisitor?, targetAudience?, visitorsLast30Days? } For the startup
333
+ # growth source, xHandle is the founder's X username without @. The rank field
334
+ # is the source's revenue rank. Result order represents reported 30-day
335
+ # revenue-growth rank.
336
+ sig do
337
+ params(
338
+ author: String,
339
+ content_url: String,
340
+ estimated_downvotes: Integer,
341
+ estimated_upvotes: Integer,
342
+ number_comments: Integer,
343
+ score: Integer,
344
+ selftext: String,
345
+ source_format:
346
+ XTwitterScraper::RadarItem::Metadata::SourceFormat::OrSymbol,
347
+ subreddit: String,
348
+ upvote_ratio: Float
349
+ ).returns(T.attached_class)
350
+ end
351
+ def self.new(
352
+ author: nil,
353
+ content_url: nil,
354
+ estimated_downvotes: nil,
355
+ estimated_upvotes: nil,
356
+ number_comments: nil,
357
+ score: nil,
358
+ selftext: nil,
359
+ # Current items use html. json and rss are retained for legacy rows.
360
+ source_format: nil,
361
+ subreddit: nil,
362
+ upvote_ratio: nil
363
+ )
364
+ end
365
+
366
+ sig do
367
+ override.returns(
368
+ {
369
+ author: String,
370
+ content_url: String,
371
+ estimated_downvotes: Integer,
372
+ estimated_upvotes: Integer,
373
+ number_comments: Integer,
374
+ score: Integer,
375
+ selftext: String,
376
+ source_format:
377
+ XTwitterScraper::RadarItem::Metadata::SourceFormat::TaggedSymbol,
378
+ subreddit: String,
379
+ upvote_ratio: Float
380
+ }
381
+ )
382
+ end
383
+ def to_hash
384
+ end
385
+
386
+ # Current items use html. json and rss are retained for legacy rows.
387
+ module SourceFormat
388
+ extend XTwitterScraper::Internal::Type::Enum
389
+
390
+ TaggedSymbol =
391
+ T.type_alias do
392
+ T.all(Symbol, XTwitterScraper::RadarItem::Metadata::SourceFormat)
393
+ end
394
+ OrSymbol = T.type_alias { T.any(Symbol, String) }
395
+
396
+ HTML =
397
+ T.let(
398
+ :html,
399
+ XTwitterScraper::RadarItem::Metadata::SourceFormat::TaggedSymbol
400
+ )
401
+ JSON =
402
+ T.let(
403
+ :json,
404
+ XTwitterScraper::RadarItem::Metadata::SourceFormat::TaggedSymbol
405
+ )
406
+ RSS =
407
+ T.let(
408
+ :rss,
409
+ XTwitterScraper::RadarItem::Metadata::SourceFormat::TaggedSymbol
410
+ )
411
+
412
+ sig do
413
+ override.returns(
414
+ T::Array[
415
+ XTwitterScraper::RadarItem::Metadata::SourceFormat::TaggedSymbol
416
+ ]
417
+ )
418
+ end
419
+ def self.values
420
+ end
421
+ end
422
+ end
423
+
187
424
  module Source
188
425
  extend XTwitterScraper::Internal::Type::Enum
189
426