diff --git a/README.md b/README.md index cd57430b07..929ca184cd 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) @@ -291,13 +292,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..7ba3aae4d0 --- /dev/null +++ b/doc/default/x.md @@ -0,0 +1,228 @@ +`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 (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. + +## 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: "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" + }] + } + }] +} +``` + +With additional fields: + +```ruby +Faker::X.tweet(include_media: true, include_user: true) => +{ + 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: [{ + 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: { + 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" + }] + } +} +``` + +## Faker::X.screen_name + +Produces a random screen_name: + +```ruby +Faker::X.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 7ea67b1fad..02c5d3f0f6 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) + 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') profile_image_url = Faker::Avatar.image(slug: user_id, size: '48x48') @@ -81,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, @@ -123,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 @@ -204,4 +212,190 @@ 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: user_entities, + 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 = {} + 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_object[:attachments] = { media_keys: [media_key] } + end + + includes[:users] = user[:includes][:users] if include_user + + tweet[:data] = [ + tweet_object + ] + + 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).to_time.utc.iso8601(3) + end + + def url + "https://t.co/#{Faker::Lorem.characters(number: 10)}" + end + + def user_entities + entities = tweet_entities + + { + url: { + urls: entities[:urls] + }, + description: { + 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') + + [{ + 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), + 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 diff --git a/test/faker/default/test_twitter.rb b/test/faker/default/test_twitter.rb index 8273ee870e..a17127ffc9 100644 --- a/test/faker/default/test_twitter.rb +++ b/test/faker/default/test_twitter.rb @@ -59,4 +59,60 @@ def test_status_with_photo assert_equal(1, status[:entities][:media].count) assert_equal(10, status[:entities][:media].first.keys.count) end + + 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 + + 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 + + def test_screen_name + assert_kind_of String, @tester.screen_name + end end