Skip to content

Commit

Permalink
Fix Musk's mess around
Browse files Browse the repository at this point in the history
  • Loading branch information
cguess committed Jul 21, 2023
1 parent 4d915f6 commit ebe83a2
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 134 deletions.
1 change: 1 addition & 0 deletions lib/birdsong.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
require "securerandom"
require "helpers/configuration"
require "fileutils"
require "debug"

require_relative "birdsong/version"
require_relative "birdsong/tweet"
Expand Down
126 changes: 34 additions & 92 deletions lib/birdsong/tweet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,13 @@ def self.lookup(ids = [])
# Check that the ids are at least real ids
ids.each { |id| raise Birdsong::InvalidIdError if !/\A\d+\z/.match(id) }

response = self.retrieve_data_v2(ids)
raise Birdsong::AuthorizationError, "Invalid response code #{response.code}" unless response.code == 200
response = ids.map { |id| self.retrieve_data_v1(id) }

json_response = JSON.parse(response.body)
json_response = response.map { |r| JSON.parse(r.body) }
check_for_errors(json_response)

return [] if json_response["data"].nil?

json_response["data"].map do |json_tweet|
Tweet.new(json_tweet, json_response["includes"])
json_response.map do |json_tweet|
Tweet.new(json_tweet)
end
end

Expand All @@ -38,23 +35,21 @@ def self.lookup(ids = [])

private

def initialize(json_tweet, includes)
def initialize(json_tweet)
@json = json_tweet
parse(json_tweet, includes)
parse(json_tweet)
end

def parse(json_tweet, includes)
@id = json_tweet["id"]
def parse(json_tweet)
@id = json_tweet["id"].to_s
@created_at = DateTime.parse(json_tweet["created_at"])
@text = json_tweet["text"]
@text = json_tweet["full_text"]
@language = json_tweet["lang"]
@author_id = json_tweet["author_id"]
@author_id = json_tweet["user"]["id"]

# A sanity check to make sure we have media in there correctly
if includes.has_key? "media"
media_items = includes["media"].filter do |media_item|
json_tweet["attachments"]["media_keys"].include? media_item["media_key"]
end
if json_tweet["extended_entities"]&.has_key?("media")
media_items = json_tweet["extended_entities"]["media"]
else
media_items = []
end
Expand All @@ -69,8 +64,8 @@ def parse(json_tweet, includes)

# If the media is video we need to fall back to V1 of the API since V2 doesn't support
# videos yet. This is dumb, but not a big deal.
media_url = get_media_url_from_extended_entities
media_preview_url = get_media_preview_url_from_extended_entities
media_url = get_largest_variant_url(media_items)
media_preview_url = media_items.first["media_url_https"]
@video_file_type = media_item["type"]

# We're returning an array because, in the case that someday more videos are available our
Expand All @@ -80,23 +75,7 @@ def parse(json_tweet, includes)

# Look up the author given the new id.
# NOTE: This doesn't *seem* like the right place for this, but I"m not sure where else
@author = User.lookup(@author_id).first
end

# Used to extract a GIF or video URL from the extended entities object in the Twiter API response
# Assumes (as is the case right now) that a Tweet cannot have more than one GIF/video
def get_media_url_from_extended_entities
response = Tweet.retrieve_data_v1(@id)
response = JSON.parse(response.body)
get_largest_variant_url(response["extended_entities"]["media"])
end

# Used to extract a GIF or video preview URL from the extended entities object in the Twiter API response
# Assumes (as is the case right now) that a Tweet cannot have more than one GIF/video
def get_media_preview_url_from_extended_entities
response = Tweet.retrieve_data_v1(@id)
response = JSON.parse(response.body)
response["extended_entities"]["media"].first["media_url_https"]
@author = User.lookup(@author_id.to_s).first
end

def get_largest_variant_url(media_items)
Expand All @@ -118,55 +97,6 @@ def get_largest_variant_url(media_items)
largest_bitrate_variant["url"]
end

def self.retrieve_data_v2(ids)
bearer_token = Birdsong.twitter_bearer_token

tweet_lookup_url = "https://api.twitter.com/2/tweets"

# Specify the Tweet IDs that you want to lookup below (to 100 per request)
tweet_ids = ids.join(",")

# Add or remove optional parameters values from the params object below. Full list of parameters and their values can be found in the docs:
# https://developer.twitter.com/en/docs/twitter-api/tweets/lookup/api-reference
params = {
"ids": tweet_ids,
"expansions": "attachments.media_keys,author_id,referenced_tweets.id",
"tweet.fields": Birdsong.tweet_fields,
"user.fields": Birdsong.user_fields,
"media.fields": "duration_ms,height,media_key,preview_image_url,public_metrics,type,url,width",
"place.fields": "country_code",
"poll.fields": "options"
}

response = tweet_lookup_v2(tweet_lookup_url, bearer_token, params)
raise Birdsong::AuthorizationError, "Invalid response code #{response.code}" unless response.code === 200

response
end

def self.tweet_lookup_v2(url, bearer_token, params)
options = {
method: "get",
headers: {
"User-Agent": "v2TweetLookupRuby",
"Authorization": "Bearer #{bearer_token}"
},
params: params
}

request = Typhoeus::Request.new(url, options)
response = request.run

raise Birdsong::RateLimitExceeded.new(
response.headers["x-rate-limit-limit"],
response.headers["x-rate-limit-remaining"],
response.headers["x-rate-limit-reset"]
) if response.code === 429
raise Birdsong::AuthorizationError, "Invalid response code #{response.code}" unless response.code === 200

response
end

# Note that unlike the V2 this only supports one url at a time
def self.retrieve_data_v1(id)
bearer_token = Birdsong.twitter_bearer_token
Expand Down Expand Up @@ -204,20 +134,32 @@ def self.tweet_lookup_v1(url, bearer_token)
response.headers["x-rate-limit-remaining"],
response.headers["x-rate-limit-reset"]
) if response.code === 429

raise Birdsong::NoTweetFoundError, "Tweet with id #{url} not found" if response.code === 404
if response.code === 403
json = JSON.parse(response.body)
if json.has_key?("errors")
json["errors"].each do |error|
raise Birdsong::NoTweetFoundError, "User with id #{url} suspended" if error["code"] == 63
end
end
end
raise Birdsong::AuthorizationError, "Invalid response code #{response.code}" unless response.code === 200

response
end


def self.check_for_errors(parsed_json)
return false unless parsed_json.key?("errors")
return false if parsed_json["errors"].empty?

parsed_json["errors"].each do |error|
# If the tweet is removed, or if the user is suspended you get an Authorization Error
if error["title"] == "Not Found Error" || error["title"] == "Authorization Error"
raise Birdsong::NoTweetFoundError, "Tweet with id #{error["value"]} not found"
parsed_json.each do |json|
next unless json.key?("errors")
next if json["errors"].empty?

json["errors"].each do |error|
# If the tweet is removed, or if the user is suspended you get an Authorization Error
if error["title"] == "Not Found Error" || error["title"] == "Authorization Error"
raise Birdsong::NoTweetFoundError, "Tweet with id #{error["value"]} not found"
end
end
end
false
Expand Down
61 changes: 29 additions & 32 deletions lib/birdsong/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ def initialize(json_user)
end

def parse(json_user)
@id = json_user["id"]
@id = json_user["id"].to_s
@name = json_user["name"]
@username = json_user["username"]
@username = json_user["screen_name"]
@created_at = DateTime.parse(json_user["created_at"])
@location = json_user["location"]

Expand All @@ -55,52 +55,48 @@ def parse(json_user)
@description = json_user["description"]
@url = json_user["url"]
@url = "https://www.twitter.com/#{@username}" if @url.nil?
@followers_count = json_user["public_metrics"]["followers_count"]
@following_count = json_user["public_metrics"]["following_count"]
@tweet_count = json_user["public_metrics"]["tweet_count"]
@listed_count = json_user["public_metrics"]["listed_count"]
@verified = json_user["verified"]

@followers_count = json_user["followers_count"]
@following_count = json_user["friends_count"]
@tweet_count = json_user["statuses_count"]
@listed_count = json_user["listed_count"]
@verified = json_user["verified"] # this will always be `false` but we're keeping it here for compatibility
@profile_image_file_name = Birdsong.retrieve_media(@profile_image_url)
end

def self.lookup_primative(usernames: nil, ids: nil)
raise Birdsong::InvalidIdError if usernames.nil? && ids.nil? # can't pass in nothing
raise Birdsong::InvalidIdError if usernames.nil? == false && ids.nil? == false # don't pass in both

response = self.retrieve_data(ids: ids, usernames: usernames)
def self.lookup_primative(usernames: [], ids: [])
raise Birdsong::InvalidIdError if usernames.empty? && ids.empty? # can't pass in nothing

raise Birdsong::AuthorizationError, "Invalid response code #{response.code}" unless response.code == 200
if usernames.empty? == false
response = usernames.map { |username| self.retrieve_data(username: username) }
elsif ids.empty? == false
response = ids.map { |id| self.retrieve_data(id: id) }
else
raise Birdsong::InvalidIdError
end

json_response = JSON.parse(response.body)
return [] if json_response["data"].nil?
json_response = response.map { |r| JSON.parse(r.body) }

json_response["data"].map do |json_user|
json_response.map do |json_user|
User.new(json_user)
end
end

def self.retrieve_data(usernames: nil, ids: nil)
def self.retrieve_data(username: nil, id: nil)
bearer_token = Birdsong.twitter_bearer_token

raise Birdsong::InvalidIdError if usernames.nil? && ids.nil? # can't pass in nothing
raise Birdsong::InvalidIdError if usernames.nil? == false && ids.nil? == false # don't pass in both
raise Birdsong::InvalidIdError if username.nil? && id.nil? # can't pass in nothing
raise Birdsong::InvalidIdError if username.nil? == false && id.nil? == false # don't pass in both

# Add or remove optional parameters values from the params object below. Full list of parameters and their values can be found in the docs:
# https://developer.twitter.com/en/docs/twitter-api/tweets/lookup/api-reference
params = {
"expansions": "pinned_tweet_id",
"tweet.fields": Birdsong.tweet_fields,
"user.fields": Birdsong.user_fields,
}
user_lookup_url = "https://api.twitter.com/1.1/users/show.json"

if usernames.nil? == false
user_lookup_url = "https://api.twitter.com/2/users/by"
params = {}
if username.nil? == false
# Specify the Usernames that you want to lookup below (to 100 per request)
params["usernames"] = usernames.join(",")
elsif ids.nil? == false
user_lookup_url = "https://api.twitter.com/2/users"
params["screen_name"] = username
elsif id.nil? == false
# Specify the User IDs that you want to lookup below (to 100 per request)
params["ids"] = ids.join(",")
params["user_id"] = id
end

response = self.user_lookup(user_lookup_url, bearer_token, params)
Expand All @@ -110,6 +106,7 @@ def self.retrieve_data(usernames: nil, ids: nil)
response.headers["x-rate-limit-remaining"],
response.headers["x-rate-limit-reset"]
) if response.code === 429
raise Birdsong::NoTweetFoundError, "User with id #{id} or username #{username} not found" if response.code === 404
raise Birdsong::AuthorizationError, "Invalid response code #{response.code}" unless response.code == 200

response
Expand Down
12 changes: 7 additions & 5 deletions test/tweet_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ def teardown

def test_that_a_tweet_is_created_with_id
tweets = Birdsong::Tweet.lookup("1378268627615543296")
assert_not_nil tweets
assert_equal 1, tweets.count
tweets.each { |tweet| assert_instance_of Birdsong::Tweet, tweet }
end

Expand All @@ -29,11 +31,11 @@ def test_that_a_tweet_raises_exception_with_invalid_id

def test_that_a_tweet_has_correct_attributes
tweet = Birdsong::Tweet.lookup("1378268627615543296").first
assert_equal tweet.id, "1378268627615543296"
assert_equal tweet.created_at, DateTime.parse("2021-04-03T08:50:22.000Z")
assert_equal tweet.text, "Five years ago... #PanamaPapers #OnThisDay @ICIJorg @SZ https://t.co/hLMVuYOk3D https://t.co/8uJkbb6Pko"
assert_equal tweet.language, "en"
assert_equal tweet.author.name, "Frederik Obermaier"
assert_equal "1378268627615543296", tweet.id
assert_equal DateTime.parse("2021-04-03T08:50:22.000Z"), tweet.created_at
assert_equal "Five years ago... #PanamaPapers #OnThisDay @ICIJorg @SZ https://t.co/hLMVuYOk3D https://t.co/8uJkbb6Pko", tweet.text
assert_equal "en", tweet.language
assert_equal "Frederik Obermaier", tweet.author.name
end

def test_that_a_tweet_cant_be_found_works
Expand Down
11 changes: 6 additions & 5 deletions test/user_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def test_that_a_tweet_has_correct_attributes
user = Birdsong::User.lookup("404661154").first
assert_equal user.id, "404661154"
assert_equal user.created_at, DateTime.parse("2011-11-04T07:18:35.000Z")
assert_equal user.profile_image_url, "https://pbs.twimg.com/profile_images/1140973306889277440/q3P0CIh6.jpg"
assert_equal user.profile_image_url, "http://pbs.twimg.com/profile_images/1140973306889277440/q3P0CIh6.jpg"
assert_equal user.name, "Frederik Obermaier"
assert_equal user.username, "f_obermaier"
assert_equal user.location, "Threema FPN4FKZE | PGP"
Expand All @@ -42,8 +42,9 @@ def test_that_a_tweet_has_correct_attributes
assert File.exist?(user.profile_image_file_name) && File.file?(user.profile_image_file_name)
end

def test_that_a_verified_user_is_probably_marked
user = Birdsong::User.lookup_by_usernames("JulieWillbanks1").first
assert user.verified
end
# `verified` is always false since the weird Musk decisions, so we're going to comment this out
# def test_that_a_verified_user_is_probably_marked
# user = Birdsong::User.lookup_by_usernames("Alphafox78").first
# assert user.verified
# end
end

0 comments on commit ebe83a2

Please sign in to comment.