From a080fcc4512182f4603e48ba520bacea96cb779d Mon Sep 17 00:00:00 2001 From: Stefanni Brasil Date: Fri, 7 Nov 2025 16:47:31 -0700 Subject: [PATCH 1/4] Add deprecation warning for Faker::Twitter Besides renaming the generator to Faker::X, the generator is vastly outdated. Twitter, aka X API, has changed a lot (it's now v2), and most of the attributes being returned in the user have been deprecated: https://docs.x.com/x-api/enterprise-gnip-2.0/fundamentals/data-dictionary#no-longer-supported-deprecated-attributes To maintain backwards compatibility, users still using Faker::Twitter will keep using it but be notified of the upcoming changes. Initially, the goal was to only update the `user` generator but then I realized it wouldn't make sense to leave `status` as it is. --- lib/faker/default/twitter.rb | 3 +++ test/faker/default/test_twitter.rb | 41 ++++++++++++++++++++---------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/lib/faker/default/twitter.rb b/lib/faker/default/twitter.rb index 7ea67b1fad..c7adf42637 100644 --- a/lib/faker/default/twitter.rb +++ b/lib/faker/default/twitter.rb @@ -17,6 +17,9 @@ class << self # # @faker.version 1.7.3 def user(include_status: true, include_email: false) + $stdout.puts('DEPRECATION WARNING: Faker::Internet is deprecated. Use Faker::X instead. Some return attributes \ + will be removed, check the CHANGELOG for more details.') + user_id = id background_image_url = Faker::LoremFlickr.image(size: '600x400') profile_image_url = Faker::Avatar.image(slug: user_id, size: '48x48') diff --git a/test/faker/default/test_twitter.rb b/test/faker/default/test_twitter.rb index 8273ee870e..eca3d8db59 100644 --- a/test/faker/default/test_twitter.rb +++ b/test/faker/default/test_twitter.rb @@ -8,28 +8,34 @@ def setup end def test_user - user = @tester.user + assert_deprecated do + user = @tester.user - assert_kind_of Hash, user - assert_equal(41, user.keys.count) - assert_kind_of Hash, user[:status] - assert_nil user[:status][:user] + assert_kind_of Hash, user + assert_equal(41, user.keys.count) + assert_kind_of Hash, user[:status] + assert_nil user[:status][:user] + end end def test_user_with_email - user = @tester.user(include_email: true) + assert_deprecated do + user = @tester.user(include_email: true) - assert_kind_of Hash, user - assert_equal(42, user.keys.count) - assert_kind_of String, user[:email] + assert_kind_of Hash, user + assert_equal(42, user.keys.count) + assert_kind_of String, user[:email] + end end def test_user_without_status - user = @tester.user(include_status: false) + assert_deprecated do + user = @tester.user(include_status: false) - assert_kind_of Hash, user - assert_equal(40, user.keys.count) - assert_nil user[:status] + assert_kind_of Hash, user + assert_equal(40, user.keys.count) + assert_nil user[:status] + end end def test_status @@ -59,4 +65,13 @@ def test_status_with_photo assert_equal(1, status[:entities][:media].count) assert_equal(10, status[:entities][:media].first.keys.count) end + + def assert_deprecated(&block) + warning = with_captured_stdout(&block) + result = yield block + + refute_predicate warning, :empty?, 'Expected a deprecation warning within the block but received none' + + result + end end From f2aa39540c6bc3c942fed506a237fcecd77fe9a8 Mon Sep 17 00:00:00 2001 From: Stefanni Brasil Date: Fri, 7 Nov 2025 22:47:36 -0700 Subject: [PATCH 2/4] Deprecate Faker::Twitter and update status to tweet After working on the user generator, it made sense to also deprecate the `status` one. The API migration docs don't mention changing status to tweet, but after inspecting what `status` returns, I realized the new API calls is `tweet`. There were lots of changes for both user and tweet objects. Most notably, the shape of the response, additional fields, and removal of deprecated attributes. Used resources as references: - https://docs.x.com/x-api/migrate/data-format-migration#user-object - https://docs.x.com/x-api/fundamentals/data-dictionary#tweet - https://docs.x.com/x-api/migrate/data-format-migration#entities-and-expanded-entities-objects --- README.md | 3 +- doc/default/twitter.md | 29 ----- doc/default/x.md | 189 +++++++++++++++++++++++++++++ lib/faker/default/twitter.rb | 182 ++++++++++++++++++++++++++- test/faker/default/test_twitter.rb | 91 ++++++++++---- 5 files changed, 437 insertions(+), 57 deletions(-) delete mode 100644 doc/default/twitter.md create mode 100644 doc/default/x.md diff --git a/README.md b/README.md index 2f42ed4383..6bbb289b49 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ and it was the original impetus for the creation of this gem. - [Faker](#faker) - [Quick links](#quick-links) + - [In the media](#in-the-media) - [Table of Contents](#table-of-contents) - [Notes](#notes) - [Getting Started](#getting-started) @@ -292,13 +293,13 @@ gem 'faker', :git => 'https://github.com/faker-ruby/faker.git', :branch => 'main - [Faker::Team](doc/default/team.md) - [Faker::Theater](doc/default/theater.md) - [Faker::Time](doc/default/time.md) - - [Faker::Twitter](doc/default/twitter.md) - [Faker::Types](doc/default/types.md) - [Faker::University](doc/default/university.md) - [Faker::Vehicle](doc/default/vehicle.md) - [Faker::Verbs](doc/default/verbs.md) - [Faker::VulnerabilityIdentifier](doc/default/vulnerability_identifier.md) - [Faker::WorldCup](doc/default/world_cup.md) + - [Faker::X](doc/default/x.md)
diff --git a/doc/default/twitter.md b/doc/default/twitter.md deleted file mode 100644 index 4531daf142..0000000000 --- a/doc/default/twitter.md +++ /dev/null @@ -1,29 +0,0 @@ -# Faker::Twitter - -Available since version 1.7.3. - -Generate realistic Twitter user and status objects similar to what you would get back from the API. - -```json -{ - "created_at": "Mon Dec 10 00:00:00 +0000 2012", - "id": 8821452687517076614, - "id_str": "8821452687517076614", - "text": "Ea et laboriosam vel non.", - // ... -} -``` - -```ruby -# Keyword arguments: include_status, include_email -Faker::Twitter.user #=> {:id=>8821452687517076614, :name=>"Lincoln Paucek", :screen_name=>"cody"... -Faker::Twitter.user(include_status: false) # Just get a user object with no embed status -Faker::Twitter.user(include_email: true) # Simulate an authenticated user with the email permission - -# Keyword arguments: include_user, include_photo -Faker::Twitter.status #=> {:id=>8821452687517076614, :text=>"Ea et laboriosam vel non."... -Faker::Twitter.status(include_user: false) # Just get a status object with no embed user -Faker::Twitter.status(include_photo: true) # Includes entities for an attached image - -Faker::Twitter.screen_name #=> "audreanne_hackett" -``` diff --git a/doc/default/x.md b/doc/default/x.md new file mode 100644 index 0000000000..fc9965161d --- /dev/null +++ b/doc/default/x.md @@ -0,0 +1,189 @@ +`Faker::Twitter` has been deprecated. While it's still maintained, its docs can be found at [Faker::Twitter](#fakertwitter). + +# Faker::X + +Generates approximate X (previous Twitter) user and tweet objects, based on X's API v2 responses. + +The generators are not a comprehensive match of the API. However, they are enough to create a demo app using X’s user or tweet data, for example. + +## Faker::X.user + +Produces a random X user based on X's v2 API: + +```ruby +Faker::X.user => { + data: [ + { + author_id: "5688134805624042468", + id: "2007502004337242257", + text: "Qui sint magni vel." + } + ], + includes: { + users: [ + { + public_metrics: { + followers_count: 1000, + following_count: 77, + tweet_count: 4642, + listed_count: 704 + }, + username: "lilian", + pinned_tweet_id: "1702549793917523469", + entities: { + url: { + urls: [ { url: "https://t.co/0iz5wx1ysh", expanded_url: "http://example.com/stuart", display_url: "example.com/stuart" }] + }, + description: { hashtags: [{tag: "Adipisci"}] } + }, + description: "Est est laborum dolores.", + name: "Kimberli Ullrich Jr.", + verified: false, + location: "104.82.135.3", + id: "5688134805624042468", + protected: false, + url: "https://t.co/lqsqf67cx5", + profile_image_url: "https://robohash.org/990174365255127568.png?size=48x48&set=set1", + created_at: "2018-07-11T00:00:00+00:00" + } + ] + } +} +``` + +## Faker::X.tweet + +Produces a random X tweet with default attributes. Available extensions can be returned with `include_media` and `include_user`: + +```ruby +# Keyword arguments: include_user, include_media +Faker::Twitter.tweet #=> { data: [{:id=>"8821452687517076614", :text=>"Ea et laboriosam vel non.",... +Faker::Twitter.tweet(include_user: true) # Includes user attributes +Faker::Twitter.tweet(include_media: true) # Includes media (photo) attributes +``` + +Example outputs: + +```ruby +Faker::X.tweet => +{ + data: [ + { + id: "7999525033982409544", + text: "Molestias non possimus voluptatem.", + lang: "VA", + conversation_id: "7999525033982409544", + created_at: "2011-03-16T00:00:00+00:00", + public_metrics: {retweet_count: 69, reply_count: 17, like_count: 21, quote_count: 8}, + possibly_sensitive: false, + entities: { + url: { urls: [{url: "https://t.co/gz4z8dvybe", expanded_url: "http://example.com/salley.grant", display_url: "example.com/salley.grant"}] }, + description: { hashtags: [{tag: "Veritatis"}] } + }, + in_reply_to_user_id: false + } + ] +} +``` + +With additional fields: + +```ruby +Faker::X.tweet(include_media: true, include_user: true) => + +{ + data: [ + { + id: "8194812886422142201", + text: "Eos sed quibusdam aperiam.", + lang: "AU", + conversation_id: "8194812886422142201", + created_at: "2017-10-24T00:00:00+00:00", + public_metrics: {retweet_count: 87, reply_count: 19, like_count: 14, quote_count: 4}, + possibly_sensitive: false, + entities: { + url: { urls: [{url: "https://t.co/h9pi5f7q7j", expanded_url: "http://example.com/angelica_moore", display_url: "example.com/angelica_moore"}] }, + description: { hashtags: [{tag: "Saepe"}] } + }, + in_reply_to_user_id: false}, + { attachments: {media_keys: ["466995304449852781"]} + } + ], + includes: { + media: [ + { + type: "photo", + indices: [103, 126], + height: 446, + media_key: "466995304449852781", + preview_image_url: "https://loremflickr.com/1064/600", + width: 1321, + url: "https://t.co/s3j25etwdj", + expanded_url: "https://loremflickr.com/1064/600", + display_url: "loremflickr.com/1064/600", + alt_text: "Et possimus repudiandae tenetur." + } + ], + users: [ + { + public_metrics: {followers_count: 488, following_count: 180, tweet_count: 6368, listed_count: 209}, + username: "shante", + pinned_tweet_id: "7350697183426089357", + entities: { + url: { urls: [{url: "https://t.co/cxwehs7i1z", expanded_url: "http://example.com/kirstie.reilly", display_url: "example.com/kirstie.reilly"}] }, + description: { hashtags: [{tag: "Rem"}] } + }, + description: "Ipsa nihil velit adipisci.", + name: "Jerome Heathcote III", + verified: false, + location: "222.70.154.120", + id: "5679269981284870916", + protected: false, + url: "https://t.co/5xyjlv6ckv", + profile_image_url: "https://robohash.org/4106878141532070549.png?size=48x48&set=set1", + created_at: "2019-01-27T00:00:00+00:00" + } + ] + } +} +``` + +## Faker::X.screen_name + +Produces a random screen_name: + +```ruby +Faker::Twitter.screen_name #=> "audreanne_hackett" +``` + +# Faker::Twitter + +This generator has been deprecated. Please use `Faker::X` instead. Note that some attributes have been deprecated by [X's API](https://docs.x.com/x-api/migrate/data-format-migration). + +Available since version 1.7.3. + +Generate realistic Twitter user and status objects similar to what you would get back from the API. + +```json +{ + "created_at": "Mon Dec 10 00:00:00 +0000 2012", + "id": 8821452687517076614, + "id_str": "8821452687517076614", + "text": "Ea et laboriosam vel non.", + // ... +} +``` + +```ruby +# Keyword arguments: include_status, include_email +Faker::Twitter.user #=> {:id=>8821452687517076614, :name=>"Lincoln Paucek", :screen_name=>"cody"... +Faker::Twitter.user(include_status: false) # Just get a user object with no embed status +Faker::Twitter.user(include_email: true) # Simulate an authenticated user with the email permission + +# Keyword arguments: include_user, include_photo +Faker::Twitter.status #=> {:id=>8821452687517076614, :text=>"Ea et laboriosam vel non."... +Faker::Twitter.status(include_user: false) # Just get a status object with no embed user +Faker::Twitter.status(include_photo: true) # Includes entities for an attached image + +Faker::Twitter.screen_name #=> "audreanne_hackett" +``` diff --git a/lib/faker/default/twitter.rb b/lib/faker/default/twitter.rb index c7adf42637..551b5749a7 100644 --- a/lib/faker/default/twitter.rb +++ b/lib/faker/default/twitter.rb @@ -17,8 +17,8 @@ class << self # # @faker.version 1.7.3 def user(include_status: true, include_email: false) - $stdout.puts('DEPRECATION WARNING: Faker::Internet is deprecated. Use Faker::X instead. Some return attributes \ - will be removed, check the CHANGELOG for more details.') + warn('DEPRECATION WARNING: Faker::Twitter is deprecated. Use Faker::X instead. Some return attributes \ + will be removed, check the docs for more details.') user_id = id background_image_url = Faker::LoremFlickr.image(size: '600x400') @@ -84,6 +84,9 @@ def user(include_status: true, include_email: false) # # @faker.version 1.7.3 def status(include_user: true, include_photo: false) + warn('DEPRECATION WARNING: Faker::Twitter is deprecated. Use Faker::X instead. Some return attributes \ + will be removed, check the docs for more details.') + status_id = id status = { id: status_id, @@ -126,6 +129,8 @@ def status(include_user: true, include_photo: false) # # @faker.version 1.7.3 def screen_name + warn('DEPRECATION WARNING: Faker::Twitter is deprecated. Use Faker::X instead.') + Faker::Internet.username(specifier: nil, separators: ['_'])[0...20] end @@ -207,4 +212,177 @@ def photo_entity end end end + + class X < Base + class << self + ## + # Produces a random X user based on X's v2 API. + # + # @return [Hash] + # + # @example + # Faker::X.user #=> { data: [{:id=>"8821452687517076614", :name=>"Lincoln Paucek", :screen_name=>"cody"... + # + # @faker.version 1.7.3 + def user + author_id = Faker::Number.between(from: 1, to: 9_223_372_036_854_775_807) + + { + data: [ + { + author_id: author_id.to_s, + id: id.to_s, + text: Faker::Lorem.sentence + } + ], + includes: { + users: [ + { + public_metrics: { + followers_count: Faker::Number.between(to: 1, from: 1_000), + following_count: Faker::Number.between(to: 1, from: 200), + tweet_count: Faker::Number.between(to: 1, from: 10_000), + listed_count: Faker::Number.between(to: 1, from: 1_000) + }, + username: screen_name, + pinned_tweet_id: Faker::Number.between(from: 1, to: 9_223_372_036_854_775_807).to_s, + entities: entities(url: url), + description: Faker::Lorem.sentence, + name: Faker::Name.name, + verified: Faker::Boolean.boolean(true_ratio: 0.1), + location: Faker::Internet.public_ip_v4_address, + id: author_id.to_s, + protected: Faker::Boolean.boolean(true_ratio: 0.1), + url: url, + profile_image_url: Faker::Avatar.image(slug: id, size: '48x48'), + created_at: created_at + } + ] + } + } + end + + ## + # Produces a random X tweet with default attributes. + # + # @param include_user [Boolean] Include user details + # @param include_media [Boolean] Include user media (photo) details + # @return [Hash] + # + # @example + # Faker::X.tweet #=> { data: [{:id=>"8821452687517076614", :text=>"Ea et laboriosam vel non."... + # Faker::X.tweet(include_media: true) # Get a tweet object with a photo media attachment + # Faker::X.tweet(include_user: true) # Get a tweet object with an user details + # + # @faker.version 1.7.3 + def tweet(include_media: false, include_user: false) + tweet = {} + conversation_id = id.to_s + + data = [ + { + id: conversation_id, + text: Faker::Lorem.sentence, + lang: Faker::Address.country_code, + conversation_id: conversation_id, + created_at: created_at, + public_metrics: { + retweet_count: Faker::Number.between(to: 1, from: 100), + reply_count: Faker::Number.between(to: 1, from: 20), + like_count: Faker::Number.between(to: 1, from: 25), + quote_count: Faker::Number.between(to: 1, from: 10) + }, + possibly_sensitive: Faker::Boolean.boolean(true_ratio: 0.1), + entities: entities(url: url), + in_reply_to_user_id: Faker::Boolean.boolean(true_ratio: 0.1) + } + ] + + tweet[:data] = data + includes = {} + + if include_media + media_key = Faker::Number.between(from: 1, to: 9_223_372_036_854_775_807).to_s + + includes[:media] = media(media_key) + tweet[:data] << { attachments: { media_keys: [media_key] } } + end + + includes[:users] = Faker::X.user[:includes][:users] if include_user + + unless includes.empty? + tweet[:includes] = includes + end + + tweet + end + + ## + # Produces a random screen_name. + # + # @return [String] + # + # @example + # Faker::X.screen_name #=> "audreanne_hackett" + # + # @faker.version 1.7.3 + def screen_name + Faker::Internet.username(specifier: nil, separators: ['_'])[0...20] + end + + private + + def id + Faker::Number.between(from: 1, to: 9_223_372_036_854_775_807) + end + + def created_at + Faker::Date.between(from: '2006-03-21', to: ::Date.today).strftime('%Y-%m-%dT%H:%M:%S%:z') + end + + def url + "https://t.co/#{Faker::Lorem.characters(number: 10)}" + end + + def entities(url:) + display_url = Faker::Internet.url(host: 'example.com') + + { + url: { + urls: [ + { + url: url, + expanded_url: display_url, + display_url: display_url.sub('http://', '') + } + ] + }, + description: { + hashtags: [ + { + tag: Faker::Lorem.word.capitalize + } + ] + } + } + end + + def media(media_key) + expanded_url = Faker::LoremFlickr.image(size: '1064x600') + + [{ + type: 'photo', + indices: [103, 126], + height: Faker::Number.between(to: 1, from: 1000), + media_key: media_key, + preview_image_url: expanded_url, + width: Faker::Number.between(to: 1, from: 2000), + url: "https://t.co/#{Faker::Lorem.characters(number: 10)}", + expanded_url: expanded_url, + display_url: expanded_url.sub('https://', ''), + alt_text: Faker::Lorem.sentence + }] + end + end + end end diff --git a/test/faker/default/test_twitter.rb b/test/faker/default/test_twitter.rb index eca3d8db59..a17127ffc9 100644 --- a/test/faker/default/test_twitter.rb +++ b/test/faker/default/test_twitter.rb @@ -8,34 +8,28 @@ def setup end def test_user - assert_deprecated do - user = @tester.user - - assert_kind_of Hash, user - assert_equal(41, user.keys.count) - assert_kind_of Hash, user[:status] - assert_nil user[:status][:user] - end + user = @tester.user + + assert_kind_of Hash, user + assert_equal(41, user.keys.count) + assert_kind_of Hash, user[:status] + assert_nil user[:status][:user] end def test_user_with_email - assert_deprecated do - user = @tester.user(include_email: true) + user = @tester.user(include_email: true) - assert_kind_of Hash, user - assert_equal(42, user.keys.count) - assert_kind_of String, user[:email] - end + assert_kind_of Hash, user + assert_equal(42, user.keys.count) + assert_kind_of String, user[:email] end def test_user_without_status - assert_deprecated do - user = @tester.user(include_status: false) + user = @tester.user(include_status: false) - assert_kind_of Hash, user - assert_equal(40, user.keys.count) - assert_nil user[:status] - end + assert_kind_of Hash, user + assert_equal(40, user.keys.count) + assert_nil user[:status] end def test_status @@ -66,12 +60,59 @@ def test_status_with_photo assert_equal(10, status[:entities][:media].first.keys.count) end - def assert_deprecated(&block) - warning = with_captured_stdout(&block) - result = yield block + def test_screen_name + assert_kind_of String, @tester.screen_name + end +end + +class TestFakerX < Test::Unit::TestCase + def setup + @tester = Faker::X + end + + def test_user + user = @tester.user + + assert_kind_of Hash, user + assert_kind_of Array, user[:data] + assert_kind_of Hash, user[:includes] + assert_kind_of Array, user[:includes][:users] + end + + def test_tweet + tweet = @tester.tweet - refute_predicate warning, :empty?, 'Expected a deprecation warning within the block but received none' + assert_kind_of Hash, tweet + assert_kind_of Array, tweet[:data] + assert_nil tweet[:includes] + end + + def test_tweet_with_include_media + tweet = @tester.tweet(include_media: true) + + assert_kind_of Hash, tweet + assert_kind_of Array, tweet[:data] + assert_kind_of Array, tweet[:includes][:media] + end + + def test_tweet_with_include_user + tweet = @tester.tweet(include_user: true) + + assert_kind_of Hash, tweet + assert_kind_of Array, tweet[:data] + assert_kind_of Array, tweet[:includes][:users] + end + + def test_tweet_with_include_user_and_include_media + tweet = @tester.tweet(include_user: true, include_media: true) + + assert_kind_of Hash, tweet + assert_kind_of Array, tweet[:data] + assert_kind_of Array, tweet[:includes][:users] + assert_kind_of Array, tweet[:includes][:media] + end - result + def test_screen_name + assert_kind_of String, @tester.screen_name end end From 9b52e234fa17dd7d08993d02400d388a0b91e2b1 Mon Sep 17 00:00:00 2001 From: Stefanni Brasil Date: Sat, 22 Nov 2025 11:58:55 -0700 Subject: [PATCH 3/4] Address review comments --- doc/default/x.md | 4 +- lib/faker/default/twitter.rb | 103 ++++++++++++++++++++--------------- 2 files changed, 60 insertions(+), 47 deletions(-) diff --git a/doc/default/x.md b/doc/default/x.md index fc9965161d..149fc65acd 100644 --- a/doc/default/x.md +++ b/doc/default/x.md @@ -1,8 +1,8 @@ -`Faker::Twitter` has been deprecated. While it's still maintained, its docs can be found at [Faker::Twitter](#fakertwitter). +`Faker::Twitter` has been deprecated in favor of `Faker::X`. While it's still maintained, its docs can be found at [Faker::Twitter](#fakertwitter). # Faker::X -Generates approximate X (previous Twitter) user and tweet objects, based on X's API v2 responses. +Generates approximate X (previously Twitter) user and tweet objects, based on X's API v2 responses. The generators are not a comprehensive match of the API. However, they are enough to create a demo app using X’s user or tweet data, for example. diff --git a/lib/faker/default/twitter.rb b/lib/faker/default/twitter.rb index 551b5749a7..02c5d3f0f6 100644 --- a/lib/faker/default/twitter.rb +++ b/lib/faker/default/twitter.rb @@ -246,7 +246,7 @@ def user }, username: screen_name, pinned_tweet_id: Faker::Number.between(from: 1, to: 9_223_372_036_854_775_807).to_s, - entities: entities(url: url), + entities: user_entities, description: Faker::Lorem.sentence, name: Faker::Name.name, verified: Faker::Boolean.boolean(true_ratio: 0.1), @@ -277,38 +277,21 @@ def user # @faker.version 1.7.3 def tweet(include_media: false, include_user: false) tweet = {} - conversation_id = id.to_s - - data = [ - { - id: conversation_id, - text: Faker::Lorem.sentence, - lang: Faker::Address.country_code, - conversation_id: conversation_id, - created_at: created_at, - public_metrics: { - retweet_count: Faker::Number.between(to: 1, from: 100), - reply_count: Faker::Number.between(to: 1, from: 20), - like_count: Faker::Number.between(to: 1, from: 25), - quote_count: Faker::Number.between(to: 1, from: 10) - }, - possibly_sensitive: Faker::Boolean.boolean(true_ratio: 0.1), - entities: entities(url: url), - in_reply_to_user_id: Faker::Boolean.boolean(true_ratio: 0.1) - } - ] - - tweet[:data] = data + tweet_object = tweet_item includes = {} if include_media media_key = Faker::Number.between(from: 1, to: 9_223_372_036_854_775_807).to_s includes[:media] = media(media_key) - tweet[:data] << { attachments: { media_keys: [media_key] } } + tweet_object[:attachments] = { media_keys: [media_key] } end - includes[:users] = Faker::X.user[:includes][:users] if include_user + includes[:users] = user[:includes][:users] if include_user + + tweet[:data] = [ + tweet_object + ] unless includes.empty? tweet[:includes] = includes @@ -337,52 +320,82 @@ def id end def created_at - Faker::Date.between(from: '2006-03-21', to: ::Date.today).strftime('%Y-%m-%dT%H:%M:%S%:z') + Faker::Date.between(from: '2006-03-21', to: ::Date.today).to_time.utc.iso8601(3) end def url "https://t.co/#{Faker::Lorem.characters(number: 10)}" end - def entities(url:) - display_url = Faker::Internet.url(host: 'example.com') + def user_entities + entities = tweet_entities { url: { - urls: [ - { - url: url, - expanded_url: display_url, - display_url: display_url.sub('http://', '') - } - ] + urls: entities[:urls] }, description: { - hashtags: [ - { - tag: Faker::Lorem.word.capitalize - } - ] + hashtags: entities[:hashtags] } } end + def tweet_entities + display_url = Faker::Internet.url(host: 'example.com') + + { + urls: [ + { + start: 0, + end: 5, + url: url, + expanded_url: display_url, + display_url: display_url.sub('http://', '') + } + ], + hashtags: [ + { + start: 0, + end: 5, + tag: Faker::Lorem.word.capitalize + } + ] + } + end + def media(media_key) expanded_url = Faker::LoremFlickr.image(size: '1064x600') [{ - type: 'photo', - indices: [103, 126], height: Faker::Number.between(to: 1, from: 1000), media_key: media_key, + type: 'photo', preview_image_url: expanded_url, width: Faker::Number.between(to: 1, from: 2000), - url: "https://t.co/#{Faker::Lorem.characters(number: 10)}", - expanded_url: expanded_url, - display_url: expanded_url.sub('https://', ''), alt_text: Faker::Lorem.sentence }] end + + def tweet_item + conversation_id = id.to_s + + { + id: conversation_id, + text: Faker::Lorem.sentence, + lang: sample(%w[en fr es pt it ja]), + conversation_id: conversation_id, + created_at: created_at, + author_id: Faker::Number.between(from: 1, to: 9_223_372_036_854_775_807).to_s, + public_metrics: { + retweet_count: Faker::Number.between(to: 1, from: 100), + reply_count: Faker::Number.between(to: 1, from: 20), + like_count: Faker::Number.between(to: 1, from: 25), + quote_count: Faker::Number.between(to: 1, from: 10) + }, + possibly_sensitive: Faker::Boolean.boolean(true_ratio: 0.1), + entities: tweet_entities + } + end end end end From 0cbd925a5b34a2f14d18a4ba4f6a5a95f0c23ca7 Mon Sep 17 00:00:00 2001 From: Stefanni Brasil Date: Sat, 22 Nov 2025 12:36:12 -0700 Subject: [PATCH 4/4] Update docs --- doc/default/x.md | 175 +++++++++++++++++++++++++++++------------------ 1 file changed, 107 insertions(+), 68 deletions(-) diff --git a/doc/default/x.md b/doc/default/x.md index 149fc65acd..7ba3aae4d0 100644 --- a/doc/default/x.md +++ b/doc/default/x.md @@ -11,7 +11,8 @@ The generators are not a comprehensive match of the API. However, they are enoug Produces a random X user based on X's v2 API: ```ruby -Faker::X.user => { +Faker::X.user => +{ data: [ { author_id: "5688134805624042468", @@ -67,22 +68,35 @@ Example outputs: ```ruby Faker::X.tweet => { - data: [ - { - id: "7999525033982409544", - text: "Molestias non possimus voluptatem.", - lang: "VA", - conversation_id: "7999525033982409544", - created_at: "2011-03-16T00:00:00+00:00", - public_metrics: {retweet_count: 69, reply_count: 17, like_count: 21, quote_count: 8}, - possibly_sensitive: false, - entities: { - url: { urls: [{url: "https://t.co/gz4z8dvybe", expanded_url: "http://example.com/salley.grant", display_url: "example.com/salley.grant"}] }, - description: { hashtags: [{tag: "Veritatis"}] } - }, - in_reply_to_user_id: false + data: [{ + id: "5530076569335337477", + text: "Omnis facere ullam velit.", + lang: "ja", + conversation_id: "5530076569335337477", + created_at: "2009-02-21T07:00:00.000Z", + author_id: "2788144046134446176", + public_metrics: { + retweet_count: 95, + reply_count: 3, + like_count: 10, + quote_count: 3 + }, + possibly_sensitive: false, + entities: { + urls: [{ + start: 0, + end: 5, + url: "https://t.co/t6o3lav9z1", + expanded_url: "http://example.com/errol.upton", + display_url: "example.com/errol.upton" + }], + hashtags: [{ + start: 0, + end: 5, + tag: "Odit" + }] } - ] + }] } ``` @@ -90,60 +104,85 @@ With additional fields: ```ruby Faker::X.tweet(include_media: true, include_user: true) => - { - data: [ - { - id: "8194812886422142201", - text: "Eos sed quibusdam aperiam.", - lang: "AU", - conversation_id: "8194812886422142201", - created_at: "2017-10-24T00:00:00+00:00", - public_metrics: {retweet_count: 87, reply_count: 19, like_count: 14, quote_count: 4}, - possibly_sensitive: false, - entities: { - url: { urls: [{url: "https://t.co/h9pi5f7q7j", expanded_url: "http://example.com/angelica_moore", display_url: "example.com/angelica_moore"}] }, - description: { hashtags: [{tag: "Saepe"}] } - }, - in_reply_to_user_id: false}, - { attachments: {media_keys: ["466995304449852781"]} + data: [{ + id: "5340086698567112794", + text: "Esse nulla minus qui.", + lang: "en", + conversation_id: "5340086698567112794", + created_at: "2009-07-04T06:00:00.000Z", + author_id: "5156189524741091965", + public_metrics: { + retweet_count: 56, + reply_count: 2, + like_count: 23, + quote_count: 1 + }, + possibly_sensitive: false, + entities: { + urls: [{ + start: 0, + end: 5, + url: "https://t.co/mqplf9rhpn", + expanded_url: "http://example.com/mohamed_koelpin", + display_url: "example.com/mohamed_koelpin" + }], + hashtags: [{ + start: 0, + end: 5, + tag: "Atque" + }] + }, + attachments: { + media_keys: ["6992225089295851582"] } - ], - includes: { - media: [ - { - type: "photo", - indices: [103, 126], - height: 446, - media_key: "466995304449852781", - preview_image_url: "https://loremflickr.com/1064/600", - width: 1321, - url: "https://t.co/s3j25etwdj", - expanded_url: "https://loremflickr.com/1064/600", - display_url: "loremflickr.com/1064/600", - alt_text: "Et possimus repudiandae tenetur." - } - ], - users: [ - { - public_metrics: {followers_count: 488, following_count: 180, tweet_count: 6368, listed_count: 209}, - username: "shante", - pinned_tweet_id: "7350697183426089357", - entities: { - url: { urls: [{url: "https://t.co/cxwehs7i1z", expanded_url: "http://example.com/kirstie.reilly", display_url: "example.com/kirstie.reilly"}] }, - description: { hashtags: [{tag: "Rem"}] } + }], + includes: { + media: [{ + height: 526, + media_key: "6992225089295851582", + type: "photo", + preview_image_url: "https://loremflickr.com/1064/600", + width: 1571, + alt_text: "Qui ratione magnam et." + }], + users: [{ + public_metrics: { + followers_count: 467, + following_count: 3, + tweet_count: 9006, + listed_count: 984 + }, + username: "gayle", + pinned_tweet_id: "2282479924658708548", + entities: { + url: { + urls: [{ + start: 0, + end: 5, + url: "https://t.co/69eytnuwwu", + expanded_url: "http://example.com/werner", + display_url: "example.com/werner" + }] }, - description: "Ipsa nihil velit adipisci.", - name: "Jerome Heathcote III", - verified: false, - location: "222.70.154.120", - id: "5679269981284870916", - protected: false, - url: "https://t.co/5xyjlv6ckv", - profile_image_url: "https://robohash.org/4106878141532070549.png?size=48x48&set=set1", - created_at: "2019-01-27T00:00:00+00:00" - } - ] + description: { + hashtags: [{ + start: 0, + end: 5, + tag: "Soluta" + }] + } + }, + description: "Esse harum voluptatem voluptate.", + name: "Elva Spinka", + verified: false, + location: "34.230.131.77", + id: "2365736908578621112", + protected: false, + url: "https://t.co/pyuqky3gdl", + profile_image_url: "https://robohash.org/2204799175591912732.png?size=48x48&set=set1", + created_at: "2025-01-30T07:00:00.000Z" + }] } } ``` @@ -153,7 +192,7 @@ Faker::X.tweet(include_media: true, include_user: true) => Produces a random screen_name: ```ruby -Faker::Twitter.screen_name #=> "audreanne_hackett" +Faker::X.screen_name #=> "audreanne_hackett" ``` # Faker::Twitter