t 4.1.1 → 5.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +820 -0
- data/LICENSE.md +1 -1
- data/README.md +3 -3
- data/bin/t +7 -16
- data/lib/t/cli.rb +317 -310
- data/lib/t/collectable.rb +4 -4
- data/lib/t/delete.rb +28 -35
- data/lib/t/list.rb +17 -18
- data/lib/t/printable/messaging.rb +54 -0
- data/lib/t/printable/rendering.rb +100 -0
- data/lib/t/printable.rb +58 -181
- data/lib/t/rcfile.rb +9 -1
- data/lib/t/requestable.rb +13 -7
- data/lib/t/requestable_api/account_endpoints.rb +93 -0
- data/lib/t/requestable_api/dm_endpoints.rb +41 -0
- data/lib/t/requestable_api/dm_helpers.rb +107 -0
- data/lib/t/requestable_api/dm_parsing.rb +76 -0
- data/lib/t/requestable_api/helpers.rb +86 -0
- data/lib/t/requestable_api/http.rb +113 -0
- data/lib/t/requestable_api/list_endpoints.rb +70 -0
- data/lib/t/requestable_api/list_normalization.rb +74 -0
- data/lib/t/requestable_api/mutations.rb +88 -0
- data/lib/t/requestable_api/resolution.rb +108 -0
- data/lib/t/requestable_api/tweet_endpoints.rb +85 -0
- data/lib/t/requestable_api/tweet_normalization.rb +87 -0
- data/lib/t/requestable_api/user_endpoints.rb +82 -0
- data/lib/t/requestable_api/user_normalization.rb +68 -0
- data/lib/t/requestable_api.rb +69 -0
- data/lib/t/search.rb +43 -58
- data/lib/t/set.rb +8 -9
- data/lib/t/stream.rb +91 -131
- data/lib/t/utils.rb +55 -52
- data/lib/t/version.rb +3 -3
- data/t.gemspec +16 -4
- metadata +46 -12
- data/lib/t/core_ext/kernel.rb +0 -13
- data/lib/t/core_ext/string.rb +0 -15
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module T
|
|
2
|
+
module RequestableAPI
|
|
3
|
+
module TweetNormalization
|
|
4
|
+
private
|
|
5
|
+
|
|
6
|
+
def extract_tweets(value)
|
|
7
|
+
return value if value.is_a?(Array)
|
|
8
|
+
return value.fetch("statuses", []) if value["statuses"].is_a?(Array)
|
|
9
|
+
|
|
10
|
+
users_by_id = index_items_by_id(value.dig("includes", "users"))
|
|
11
|
+
places_by_id = index_items_by_id(value.dig("includes", "places"))
|
|
12
|
+
return value["data"].map { |tweet| normalize_v2_tweet(tweet, users_by_id, places_by_id) } if value["data"].is_a?(Array)
|
|
13
|
+
return [normalize_v2_tweet(value["data"], users_by_id, places_by_id)] if value["data"].is_a?(Hash)
|
|
14
|
+
|
|
15
|
+
[]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def normalize_v2_tweet(tweet, users_by_id, places_by_id)
|
|
19
|
+
return tweet if tweet.is_a?(Hash) && tweet.key?("user")
|
|
20
|
+
|
|
21
|
+
object = build_v2_tweet_core(tweet)
|
|
22
|
+
apply_v2_tweet_entities(object, tweet)
|
|
23
|
+
apply_v2_tweet_metrics(object, tweet)
|
|
24
|
+
apply_v2_tweet_author(object, tweet, users_by_id)
|
|
25
|
+
apply_v2_tweet_geo(object, tweet, places_by_id)
|
|
26
|
+
object
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def build_v2_tweet_core(tweet)
|
|
30
|
+
object = {}
|
|
31
|
+
apply_v2_id(object, tweet)
|
|
32
|
+
text = tweet["full_text"] || tweet["text"]
|
|
33
|
+
if text
|
|
34
|
+
object["text"] = text
|
|
35
|
+
object["full_text"] = text
|
|
36
|
+
end
|
|
37
|
+
object["created_at"] = tweet["created_at"] if tweet["created_at"]
|
|
38
|
+
object["source"] = tweet["source"] if tweet["source"]
|
|
39
|
+
object
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def apply_v2_tweet_entities(object, tweet)
|
|
43
|
+
return unless tweet["entities"]
|
|
44
|
+
|
|
45
|
+
object["entities"] = tweet["entities"]
|
|
46
|
+
object["uris"] = tweet.dig("entities", "urls") if tweet.dig("entities", "urls")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def apply_v2_tweet_metrics(object, tweet)
|
|
50
|
+
return unless tweet["public_metrics"].is_a?(Hash)
|
|
51
|
+
|
|
52
|
+
metrics = tweet["public_metrics"]
|
|
53
|
+
object["retweet_count"] = metrics["retweet_count"] if metrics.key?("retweet_count")
|
|
54
|
+
object["favorite_count"] = metrics["like_count"] if metrics.key?("like_count")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def apply_v2_tweet_author(object, tweet, users_by_id)
|
|
58
|
+
author_id = tweet["author_id"]
|
|
59
|
+
object["user"] = normalize_v2_user(users_by_id[author_id] || {"id" => author_id, "username" => author_id}) if author_id
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def apply_v2_tweet_geo(object, tweet, places_by_id)
|
|
63
|
+
geo = tweet["geo"]
|
|
64
|
+
return unless geo.is_a?(Hash)
|
|
65
|
+
|
|
66
|
+
place_id = geo["place_id"]
|
|
67
|
+
object["place"] = places_by_id[place_id] if place_id && places_by_id[place_id]
|
|
68
|
+
coords = geo.dig("coordinates", "coordinates")
|
|
69
|
+
object["geo"] = {"type" => "Point", "coordinates" => coords} if coords.is_a?(Array) && coords.size == 2 && !place_id
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def extract_ids(response)
|
|
73
|
+
data = response["data"]
|
|
74
|
+
return [] unless data.is_a?(Array)
|
|
75
|
+
|
|
76
|
+
data.filter_map { |entry| value_id(entry) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def index_items_by_id(values)
|
|
80
|
+
Array(values).each_with_object({}) do |entry, memo|
|
|
81
|
+
id = value_id(entry)
|
|
82
|
+
memo[id] = entry if id
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module T
|
|
2
|
+
module RequestableAPI
|
|
3
|
+
module UserEndpoints
|
|
4
|
+
def x_verify_credentials
|
|
5
|
+
extract_users(t_get_v2("users/me", user_lookup_params)).first || {}
|
|
6
|
+
rescue X::Error
|
|
7
|
+
t_get_v1("account/verify_credentials.json")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def x_user(user = nil, _opts = {}, &)
|
|
11
|
+
if block_given? && user.nil?
|
|
12
|
+
@requestable_api_before_request&.call
|
|
13
|
+
x_home_timeline(count: 100).each(&)
|
|
14
|
+
return
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
fetch_single_user(user)
|
|
18
|
+
rescue X::ServiceUnavailable
|
|
19
|
+
t_get_v1("users/show.json", screen_name: strip_at(user.to_s))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def x_users(users)
|
|
23
|
+
users = Array(users).flatten.compact
|
|
24
|
+
return [] if users.empty?
|
|
25
|
+
|
|
26
|
+
ids, names = users.partition { |entry| numeric_identifier?(entry) }
|
|
27
|
+
results = []
|
|
28
|
+
ids.each_slice(100) do |chunk|
|
|
29
|
+
results.concat(extract_users(t_get_v2("users", user_lookup_params.merge(ids: chunk.join(",")))))
|
|
30
|
+
end
|
|
31
|
+
names.each_slice(100) do |chunk|
|
|
32
|
+
results.concat(extract_users(t_get_v2("users/by", user_lookup_params.merge(usernames: chunk.map { |name| strip_at(name) }.join(",")))))
|
|
33
|
+
end
|
|
34
|
+
results
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def x_user_search(query, page:)
|
|
38
|
+
page = page.to_i
|
|
39
|
+
return [] if page > 1 && @requestable_api_user_search_tokens[[query, page - 1]].to_s.empty?
|
|
40
|
+
|
|
41
|
+
params = {
|
|
42
|
+
query: query.to_s,
|
|
43
|
+
max_results: "100",
|
|
44
|
+
"user.fields": V2_USER_FIELDS,
|
|
45
|
+
expansions: V2_USER_EXPANSIONS,
|
|
46
|
+
"tweet.fields": V2_TWEET_FIELDS,
|
|
47
|
+
}
|
|
48
|
+
params[:next_token] = @requestable_api_user_search_tokens[[query, page - 1]] if page > 1
|
|
49
|
+
response = t_get_v2("users/search", params)
|
|
50
|
+
@requestable_api_user_search_tokens[[query, page]] = response.dig("meta", "next_token")
|
|
51
|
+
extract_users(response)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def x_friendship?(user1, user2)
|
|
55
|
+
user1_id = resolve_user_id(user1)
|
|
56
|
+
user2_id = resolve_user_id(user2)
|
|
57
|
+
fetch_relationship_ids(user1_id, "following").include?(user2_id.to_s)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def x_friend_ids(user = nil)
|
|
61
|
+
user_id = user.nil? ? current_user_id : resolve_user_id(user)
|
|
62
|
+
fetch_relationship_ids(user_id, "following")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def x_follower_ids(user = nil)
|
|
66
|
+
user_id = user.nil? ? current_user_id : resolve_user_id(user)
|
|
67
|
+
fetch_relationship_ids(user_id, "followers")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def fetch_single_user(user)
|
|
73
|
+
response = if numeric_identifier?(user)
|
|
74
|
+
t_get_v2("users/#{user}", user_lookup_params)
|
|
75
|
+
else
|
|
76
|
+
t_get_v2("users/by/username/#{strip_at(user)}", user_lookup_params)
|
|
77
|
+
end
|
|
78
|
+
extract_users(response).first || {}
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module T
|
|
2
|
+
module RequestableAPI
|
|
3
|
+
module UserNormalization
|
|
4
|
+
private
|
|
5
|
+
|
|
6
|
+
def extract_users(value)
|
|
7
|
+
return value if value.is_a?(Array)
|
|
8
|
+
return value.fetch("users", []) if value["users"].is_a?(Array)
|
|
9
|
+
|
|
10
|
+
users = value["data"]
|
|
11
|
+
includes_tweets = index_items_by_id(value.dig("includes", "tweets"))
|
|
12
|
+
return users.map { |user| normalize_user_with_pinned_status(user, includes_tweets) } if users.is_a?(Array)
|
|
13
|
+
return [normalize_user_with_pinned_status(users, includes_tweets)] if users.is_a?(Hash)
|
|
14
|
+
|
|
15
|
+
extract_bare_v1_user(value)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def extract_bare_v1_user(value)
|
|
19
|
+
return [value] if value.is_a?(Hash) && (value.key?("screen_name") || value.key?("id"))
|
|
20
|
+
|
|
21
|
+
[]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def normalize_user_with_pinned_status(user, includes_tweets)
|
|
25
|
+
normalized = normalize_v2_user(user || {})
|
|
26
|
+
pinned_id = user["pinned_tweet_id"]
|
|
27
|
+
normalized["status"] = normalize_v2_tweet(includes_tweets[pinned_id], {}, {}) if pinned_id && includes_tweets[pinned_id]
|
|
28
|
+
normalized
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def normalize_v2_user(user)
|
|
32
|
+
return user if user.is_a?(Hash) && user.key?("screen_name")
|
|
33
|
+
|
|
34
|
+
object = build_v2_user_core(user)
|
|
35
|
+
apply_v2_user_metrics(object, user)
|
|
36
|
+
object
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def build_v2_user_core(user)
|
|
40
|
+
object = {}
|
|
41
|
+
apply_v2_id(object, user)
|
|
42
|
+
username = user["username"] || user["screen_name"]
|
|
43
|
+
if username
|
|
44
|
+
object["screen_name"] = username
|
|
45
|
+
object["username"] = username
|
|
46
|
+
end
|
|
47
|
+
%w[created_at name verified protected description location url].each do |field|
|
|
48
|
+
object[field] = user[field] if user.key?(field)
|
|
49
|
+
end
|
|
50
|
+
object
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def apply_v2_user_metrics(object, user)
|
|
54
|
+
return unless user["public_metrics"].is_a?(Hash)
|
|
55
|
+
|
|
56
|
+
metrics = user["public_metrics"]
|
|
57
|
+
object["statuses_count"] = metrics["tweet_count"] if metrics.key?("tweet_count")
|
|
58
|
+
if metrics.key?("like_count")
|
|
59
|
+
object["favourites_count"] = metrics["like_count"]
|
|
60
|
+
object["favorites_count"] = metrics["like_count"]
|
|
61
|
+
end
|
|
62
|
+
object["listed_count"] = metrics["listed_count"] if metrics.key?("listed_count")
|
|
63
|
+
object["friends_count"] = metrics["following_count"] if metrics.key?("following_count")
|
|
64
|
+
object["followers_count"] = metrics["followers_count"] if metrics.key?("followers_count")
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
require "json"
|
|
5
|
+
require "time"
|
|
6
|
+
require "uri"
|
|
7
|
+
|
|
8
|
+
require_relative "requestable_api/helpers"
|
|
9
|
+
require_relative "requestable_api/http"
|
|
10
|
+
require_relative "requestable_api/tweet_normalization"
|
|
11
|
+
require_relative "requestable_api/user_normalization"
|
|
12
|
+
require_relative "requestable_api/list_normalization"
|
|
13
|
+
require_relative "requestable_api/resolution"
|
|
14
|
+
require_relative "requestable_api/dm_parsing"
|
|
15
|
+
require_relative "requestable_api/dm_helpers"
|
|
16
|
+
require_relative "requestable_api/user_endpoints"
|
|
17
|
+
require_relative "requestable_api/tweet_endpoints"
|
|
18
|
+
require_relative "requestable_api/mutations"
|
|
19
|
+
require_relative "requestable_api/dm_endpoints"
|
|
20
|
+
require_relative "requestable_api/list_endpoints"
|
|
21
|
+
require_relative "requestable_api/account_endpoints"
|
|
22
|
+
|
|
23
|
+
module T
|
|
24
|
+
module RequestableAPI
|
|
25
|
+
include Helpers
|
|
26
|
+
include HTTP
|
|
27
|
+
include TweetNormalization
|
|
28
|
+
include UserNormalization
|
|
29
|
+
include ListNormalization
|
|
30
|
+
include Resolution
|
|
31
|
+
include DMParsing
|
|
32
|
+
include DMHelpers
|
|
33
|
+
include UserEndpoints
|
|
34
|
+
include TweetEndpoints
|
|
35
|
+
include Mutations
|
|
36
|
+
include DMEndpoints
|
|
37
|
+
include ListEndpoints
|
|
38
|
+
include AccountEndpoints
|
|
39
|
+
|
|
40
|
+
BASE_URL = "https://api.twitter.com"
|
|
41
|
+
BASE_URL_V1 = "#{BASE_URL}/1.1/".freeze
|
|
42
|
+
BASE_URL_UPLOAD = "https://upload.twitter.com/1.1/"
|
|
43
|
+
|
|
44
|
+
DEFAULT_NUM_RESULTS = 20
|
|
45
|
+
MAX_SEARCH_RESULTS = 100
|
|
46
|
+
MAX_PAGE = 51
|
|
47
|
+
|
|
48
|
+
V2_TWEET_FIELDS = "author_id,created_at,entities,geo,id,in_reply_to_user_id,public_metrics,source,text"
|
|
49
|
+
V2_USER_FIELDS = "created_at,description,id,location,name,protected,public_metrics,url,username,verified"
|
|
50
|
+
V2_LIST_FIELDS = "created_at,description,follower_count,id,member_count,name,owner_id,private"
|
|
51
|
+
V2_TWEET_EXPANSIONS = "author_id,geo.place_id"
|
|
52
|
+
V2_USER_EXPANSIONS = "pinned_tweet_id"
|
|
53
|
+
V2_PLACE_FIELDS = "contained_within,country,country_code,full_name,geo,id,name,place_type"
|
|
54
|
+
|
|
55
|
+
FORM_HEADERS = {"Content-Type" => "application/x-www-form-urlencoded; charset=utf-8"}.freeze
|
|
56
|
+
JSON_HEADERS = {"Content-Type" => "application/json; charset=utf-8"}.freeze
|
|
57
|
+
|
|
58
|
+
def setup_requestable_api!(credentials)
|
|
59
|
+
return if defined?(@requestable_api_setup) && @requestable_api_setup
|
|
60
|
+
|
|
61
|
+
@requestable_api_setup = true
|
|
62
|
+
@requestable_api_credentials = credentials
|
|
63
|
+
@v1_client = X::Client.new(**credentials, base_url: BASE_URL_V1)
|
|
64
|
+
@upload_client = X::Client.new(**credentials, base_url: BASE_URL_UPLOAD)
|
|
65
|
+
@requestable_api_user_search_tokens = {}
|
|
66
|
+
@requestable_api_before_request = nil
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
data/lib/t/search.rb
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
require "thor"
|
|
2
|
-
require "twitter"
|
|
3
2
|
require "t/collectable"
|
|
4
3
|
require "t/printable"
|
|
5
4
|
require "t/rcfile"
|
|
@@ -32,28 +31,10 @@ module T
|
|
|
32
31
|
method_option "relative_dates", aliases: "-a", type: :boolean, desc: "Show relative dates."
|
|
33
32
|
def all(query)
|
|
34
33
|
count = options["number"] || DEFAULT_NUM_RESULTS
|
|
35
|
-
opts = {count: MAX_SEARCH_RESULTS}
|
|
36
|
-
|
|
37
|
-
tweets = client.search(query, opts).take(count)
|
|
34
|
+
opts = {count: MAX_SEARCH_RESULTS, include_entities: !!options["decode_uris"]}
|
|
35
|
+
tweets = x_search(query, opts).take(count)
|
|
38
36
|
tweets.reverse! if options["reverse"]
|
|
39
|
-
|
|
40
|
-
require "csv"
|
|
41
|
-
say TWEET_HEADINGS.to_csv unless tweets.empty?
|
|
42
|
-
tweets.each do |tweet|
|
|
43
|
-
say [tweet.id, csv_formatted_time(tweet), tweet.user.screen_name, decode_full_text(tweet, options["decode_uris"])].to_csv
|
|
44
|
-
end
|
|
45
|
-
elsif options["long"]
|
|
46
|
-
array = tweets.collect do |tweet|
|
|
47
|
-
[tweet.id, ls_formatted_time(tweet), "@#{tweet.user.screen_name}", decode_full_text(tweet, options["decode_uris"]).gsub(/\n+/, " ")]
|
|
48
|
-
end
|
|
49
|
-
format = options["format"] || Array.new(TWEET_HEADINGS.size) { "%s" }
|
|
50
|
-
print_table_with_headings(array, TWEET_HEADINGS, format)
|
|
51
|
-
else
|
|
52
|
-
say unless tweets.empty?
|
|
53
|
-
tweets.each do |tweet|
|
|
54
|
-
print_message(tweet.user.screen_name, decode_full_text(tweet, options["decode_uris"]))
|
|
55
|
-
end
|
|
56
|
-
end
|
|
37
|
+
print_search_results(tweets)
|
|
57
38
|
end
|
|
58
39
|
|
|
59
40
|
desc "favorites [USER] QUERY", "Returns Tweets you've favorited that match the specified query."
|
|
@@ -68,20 +49,19 @@ module T
|
|
|
68
49
|
opts = {count: MAX_NUM_RESULTS}
|
|
69
50
|
opts[:include_entities] = !!options["decode_uris"]
|
|
70
51
|
if user
|
|
71
|
-
|
|
72
|
-
user = options["id"] ? user.to_i : user.strip_ats
|
|
52
|
+
user = options["id"] ? user.to_i : user.tr("@", "")
|
|
73
53
|
tweets = collect_with_max_id do |max_id|
|
|
74
54
|
opts[:max_id] = max_id unless max_id.nil?
|
|
75
|
-
|
|
55
|
+
x_favorites(user, opts)
|
|
76
56
|
end
|
|
77
57
|
else
|
|
78
58
|
tweets = collect_with_max_id do |max_id|
|
|
79
59
|
opts[:max_id] = max_id unless max_id.nil?
|
|
80
|
-
|
|
60
|
+
x_favorites(opts)
|
|
81
61
|
end
|
|
82
62
|
end
|
|
83
63
|
tweets = tweets.select do |tweet|
|
|
84
|
-
/#{query}/i.match(tweet
|
|
64
|
+
/#{query}/i.match(tweet["full_text"])
|
|
85
65
|
end
|
|
86
66
|
print_tweets(tweets)
|
|
87
67
|
end
|
|
@@ -99,10 +79,10 @@ module T
|
|
|
99
79
|
opts[:include_entities] = !!options["decode_uris"]
|
|
100
80
|
tweets = collect_with_max_id do |max_id|
|
|
101
81
|
opts[:max_id] = max_id unless max_id.nil?
|
|
102
|
-
|
|
82
|
+
x_list_timeline(owner, list_name, opts)
|
|
103
83
|
end
|
|
104
84
|
tweets = tweets.select do |tweet|
|
|
105
|
-
/#{query}/i.match(tweet
|
|
85
|
+
/#{query}/i.match(tweet["full_text"])
|
|
106
86
|
end
|
|
107
87
|
print_tweets(tweets)
|
|
108
88
|
end
|
|
@@ -117,10 +97,10 @@ module T
|
|
|
117
97
|
opts[:include_entities] = !!options["decode_uris"]
|
|
118
98
|
tweets = collect_with_max_id do |max_id|
|
|
119
99
|
opts[:max_id] = max_id unless max_id.nil?
|
|
120
|
-
|
|
100
|
+
x_mentions(opts)
|
|
121
101
|
end
|
|
122
102
|
tweets = tweets.select do |tweet|
|
|
123
|
-
/#{query}/i.match(tweet
|
|
103
|
+
/#{query}/i.match(tweet["full_text"])
|
|
124
104
|
end
|
|
125
105
|
print_tweets(tweets)
|
|
126
106
|
end
|
|
@@ -138,20 +118,19 @@ module T
|
|
|
138
118
|
opts = {count: MAX_NUM_RESULTS}
|
|
139
119
|
opts[:include_entities] = !!options["decode_uris"]
|
|
140
120
|
if user
|
|
141
|
-
|
|
142
|
-
user = options["id"] ? user.to_i : user.strip_ats
|
|
121
|
+
user = options["id"] ? user.to_i : user.tr("@", "")
|
|
143
122
|
tweets = collect_with_max_id do |max_id|
|
|
144
123
|
opts[:max_id] = max_id unless max_id.nil?
|
|
145
|
-
|
|
124
|
+
x_retweeted_by_user(user, opts)
|
|
146
125
|
end
|
|
147
126
|
else
|
|
148
127
|
tweets = collect_with_max_id do |max_id|
|
|
149
128
|
opts[:max_id] = max_id unless max_id.nil?
|
|
150
|
-
|
|
129
|
+
x_retweeted_by_me(opts)
|
|
151
130
|
end
|
|
152
131
|
end
|
|
153
132
|
tweets = tweets.select do |tweet|
|
|
154
|
-
/#{query}/i.match(tweet
|
|
133
|
+
/#{query}/i.match(tweet["full_text"])
|
|
155
134
|
end
|
|
156
135
|
print_tweets(tweets)
|
|
157
136
|
end
|
|
@@ -169,28 +148,13 @@ module T
|
|
|
169
148
|
def timeline(*args)
|
|
170
149
|
query = args.pop
|
|
171
150
|
user = args.pop
|
|
172
|
-
opts =
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
opts[:since_id] = options["since_id"] if options["since_id"]
|
|
178
|
-
if user
|
|
179
|
-
require "t/core_ext/string"
|
|
180
|
-
user = options["id"] ? user.to_i : user.strip_ats
|
|
181
|
-
tweets = collect_with_max_id do |max_id|
|
|
182
|
-
opts[:max_id] = max_id unless max_id.nil?
|
|
183
|
-
client.user_timeline(user, opts)
|
|
184
|
-
end
|
|
185
|
-
else
|
|
186
|
-
tweets = collect_with_max_id do |max_id|
|
|
187
|
-
opts[:max_id] = max_id unless max_id.nil?
|
|
188
|
-
client.home_timeline(opts)
|
|
189
|
-
end
|
|
190
|
-
end
|
|
191
|
-
tweets = tweets.select do |tweet|
|
|
192
|
-
/#{query}/i.match(tweet.full_text)
|
|
151
|
+
opts = build_timeline_opts.merge(count: MAX_NUM_RESULTS)
|
|
152
|
+
user = resolve_user_input(user) if user
|
|
153
|
+
tweets = collect_with_max_id do |max_id|
|
|
154
|
+
opts[:max_id] = max_id unless max_id.nil?
|
|
155
|
+
user ? x_user_timeline(user, opts) : x_home_timeline(opts)
|
|
193
156
|
end
|
|
157
|
+
tweets = tweets.select { |tweet| /#{query}/i.match(tweet["full_text"]) }
|
|
194
158
|
print_tweets(tweets)
|
|
195
159
|
end
|
|
196
160
|
map %w[tl] => :timeline
|
|
@@ -204,9 +168,30 @@ module T
|
|
|
204
168
|
method_option "unsorted", aliases: "-u", type: :boolean, desc: "Output is not sorted."
|
|
205
169
|
def users(query)
|
|
206
170
|
users = collect_with_page do |page|
|
|
207
|
-
|
|
171
|
+
x_user_search(query, page:)
|
|
208
172
|
end
|
|
209
173
|
print_users(users)
|
|
210
174
|
end
|
|
175
|
+
|
|
176
|
+
private
|
|
177
|
+
|
|
178
|
+
def print_search_results(tweets)
|
|
179
|
+
if options["csv"]
|
|
180
|
+
require "csv"
|
|
181
|
+
say TWEET_HEADINGS.to_csv unless tweets.empty?
|
|
182
|
+
tweets.each { |tweet| print_csv_tweet(tweet) }
|
|
183
|
+
elsif options["long"]
|
|
184
|
+
array = tweets.collect { |tweet| build_long_tweet(tweet) }
|
|
185
|
+
format = options["format"] || Array.new(TWEET_HEADINGS.size) { "%s" }
|
|
186
|
+
print_table_with_headings(array, TWEET_HEADINGS, format)
|
|
187
|
+
else
|
|
188
|
+
print_search_messages(tweets)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def print_search_messages(tweets)
|
|
193
|
+
say unless tweets.empty?
|
|
194
|
+
tweets.each { |tweet| print_message(tweet["user"]["screen_name"], decode_full_text(tweet, decode_full_uris: options["decode_uris"])) }
|
|
195
|
+
end
|
|
211
196
|
end
|
|
212
197
|
end
|
data/lib/t/set.rb
CHANGED
|
@@ -15,8 +15,7 @@ module T
|
|
|
15
15
|
|
|
16
16
|
desc "active SCREEN_NAME [CONSUMER_KEY]", "Set your active account."
|
|
17
17
|
def active(screen_name, consumer_key = nil)
|
|
18
|
-
|
|
19
|
-
screen_name = screen_name.strip_ats
|
|
18
|
+
screen_name = screen_name.tr("@", "")
|
|
20
19
|
@rcfile.path = options["profile"] if options["profile"]
|
|
21
20
|
consumer_key = @rcfile[screen_name].keys.last if consumer_key.nil?
|
|
22
21
|
@rcfile.active_profile = {"username" => @rcfile[screen_name][consumer_key]["username"], "consumer_key" => consumer_key}
|
|
@@ -26,46 +25,46 @@ module T
|
|
|
26
25
|
|
|
27
26
|
desc "bio DESCRIPTION", "Edits your Bio information on your Twitter profile."
|
|
28
27
|
def bio(description)
|
|
29
|
-
|
|
28
|
+
x_update_profile(description:)
|
|
30
29
|
say "@#{@rcfile.active_profile[0]}'s bio has been updated."
|
|
31
30
|
end
|
|
32
31
|
|
|
33
32
|
desc "language LANGUAGE_NAME", "Selects the language you'd like to receive notifications in."
|
|
34
33
|
def language(language_name)
|
|
35
|
-
|
|
34
|
+
x_settings(lang: language_name)
|
|
36
35
|
say "@#{@rcfile.active_profile[0]}'s language has been updated."
|
|
37
36
|
end
|
|
38
37
|
|
|
39
38
|
desc "location PLACE_NAME", "Updates the location field in your profile."
|
|
40
39
|
def location(place_name)
|
|
41
|
-
|
|
40
|
+
x_update_profile(location: place_name)
|
|
42
41
|
say "@#{@rcfile.active_profile[0]}'s location has been updated."
|
|
43
42
|
end
|
|
44
43
|
|
|
45
44
|
desc "name NAME", "Sets the name field on your Twitter profile."
|
|
46
45
|
def name(name)
|
|
47
|
-
|
|
46
|
+
x_update_profile(name:)
|
|
48
47
|
say "@#{@rcfile.active_profile[0]}'s name has been updated."
|
|
49
48
|
end
|
|
50
49
|
|
|
51
50
|
desc "profile_background_image FILE", "Sets the background image on your Twitter profile."
|
|
52
51
|
method_option "tile", aliases: "-t", type: :boolean, desc: "Whether or not to tile the background image."
|
|
53
52
|
def profile_background_image(file)
|
|
54
|
-
|
|
53
|
+
x_update_profile_background_image(File.new(File.expand_path(file)), tile: options["tile"], skip_status: true)
|
|
55
54
|
say "@#{@rcfile.active_profile[0]}'s background image has been updated."
|
|
56
55
|
end
|
|
57
56
|
map %w[background background_image] => :profile_background_image
|
|
58
57
|
|
|
59
58
|
desc "profile_image FILE", "Sets the image on your Twitter profile."
|
|
60
59
|
def profile_image(file)
|
|
61
|
-
|
|
60
|
+
x_update_profile_image(File.new(File.expand_path(file)))
|
|
62
61
|
say "@#{@rcfile.active_profile[0]}'s image has been updated."
|
|
63
62
|
end
|
|
64
63
|
map %w[avatar image] => :profile_image
|
|
65
64
|
|
|
66
65
|
desc "website URI", "Sets the website field on your profile."
|
|
67
66
|
def website(uri)
|
|
68
|
-
|
|
67
|
+
x_update_profile(url: uri)
|
|
69
68
|
say "@#{@rcfile.active_profile[0]}'s website has been updated."
|
|
70
69
|
end
|
|
71
70
|
end
|