x-resources 1.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 +7 -0
- data/.yardopts +9 -0
- data/CHANGELOG.md +254 -0
- data/LICENSE.txt +21 -0
- data/README.md +148 -0
- data/lib/x/resources/abstract_class.rb +29 -0
- data/lib/x/resources/actions/direct_messages.rb +99 -0
- data/lib/x/resources/actions/engagement.rb +95 -0
- data/lib/x/resources/actions/lists.rb +126 -0
- data/lib/x/resources/actions/posts.rb +77 -0
- data/lib/x/resources/actions/relationships.rb +91 -0
- data/lib/x/resources/actions.rb +22 -0
- data/lib/x/resources/api.rb +40 -0
- data/lib/x/resources/attributes.rb +203 -0
- data/lib/x/resources/batch.rb +43 -0
- data/lib/x/resources/batch_finders.rb +185 -0
- data/lib/x/resources/bookmark_folder.rb +23 -0
- data/lib/x/resources/community.rb +137 -0
- data/lib/x/resources/cursor.rb +493 -0
- data/lib/x/resources/direct_message.rb +325 -0
- data/lib/x/resources/direct_message_conversations.rb +147 -0
- data/lib/x/resources/errors.rb +104 -0
- data/lib/x/resources/finders.rb +255 -0
- data/lib/x/resources/identity.rb +65 -0
- data/lib/x/resources/includes.rb +216 -0
- data/lib/x/resources/list.rb +336 -0
- data/lib/x/resources/lookups/communities.rb +56 -0
- data/lib/x/resources/lookups/direct_messages.rb +86 -0
- data/lib/x/resources/lookups/lists.rb +44 -0
- data/lib/x/resources/lookups/media.rb +72 -0
- data/lib/x/resources/lookups/posts.rb +198 -0
- data/lib/x/resources/lookups/spaces.rb +87 -0
- data/lib/x/resources/lookups/trends.rb +38 -0
- data/lib/x/resources/lookups/users.rb +221 -0
- data/lib/x/resources/lookups.rb +25 -0
- data/lib/x/resources/marshalling.rb +93 -0
- data/lib/x/resources/matching_rule.rb +107 -0
- data/lib/x/resources/media.rb +278 -0
- data/lib/x/resources/media_ids.rb +74 -0
- data/lib/x/resources/memo.rb +54 -0
- data/lib/x/resources/page.rb +394 -0
- data/lib/x/resources/page_limit.rb +80 -0
- data/lib/x/resources/pages.rb +270 -0
- data/lib/x/resources/parallel.rb +82 -0
- data/lib/x/resources/personalized_trend.rb +124 -0
- data/lib/x/resources/place.rb +107 -0
- data/lib/x/resources/poll.rb +75 -0
- data/lib/x/resources/post.rb +615 -0
- data/lib/x/resources/post_collections.rb +67 -0
- data/lib/x/resources/post_counts.rb +215 -0
- data/lib/x/resources/post_search.rb +86 -0
- data/lib/x/resources/post_usage.rb +203 -0
- data/lib/x/resources/post_writes.rb +140 -0
- data/lib/x/resources/published_count.rb +31 -0
- data/lib/x/resources/references.rb +121 -0
- data/lib/x/resources/relation_writes.rb +54 -0
- data/lib/x/resources/relationships.rb +77 -0
- data/lib/x/resources/resource.rb +535 -0
- data/lib/x/resources/serialization.rb +58 -0
- data/lib/x/resources/shape.rb +167 -0
- data/lib/x/resources/space.rb +332 -0
- data/lib/x/resources/topic.rb +59 -0
- data/lib/x/resources/trend.rb +130 -0
- data/lib/x/resources/user.rb +502 -0
- data/lib/x/resources/user_collections.rb +213 -0
- data/lib/x/resources/user_finders.rb +282 -0
- data/lib/x/resources/utils.rb +358 -0
- data/lib/x/resources/value_equality.rb +38 -0
- data/lib/x/resources/value_marshalling.rb +89 -0
- data/lib/x/resources/version.rb +25 -0
- data/lib/x/resources.rb +22 -0
- data/sig/manifest.yaml +7 -0
- data/sig/x-resources.rbs +813 -0
- metadata +140 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../post"
|
|
4
|
+
require_relative "../post_usage"
|
|
5
|
+
|
|
6
|
+
module X
|
|
7
|
+
module Resources
|
|
8
|
+
module Lookups
|
|
9
|
+
# Look up, search, and count posts, and report how many posts the app has read, mixed into a client through API
|
|
10
|
+
#
|
|
11
|
+
# Internal to x-resources: X::Resources::API includes it, and its methods are public API of the client that
|
|
12
|
+
# includes API, but the module is only how they are grouped, and some of them need the methods of another,
|
|
13
|
+
# so include API rather than this module alone.
|
|
14
|
+
#
|
|
15
|
+
# @api semipublic
|
|
16
|
+
module Posts
|
|
17
|
+
# Look up a post by identifier
|
|
18
|
+
#
|
|
19
|
+
# @api public
|
|
20
|
+
# @param id [String, Integer, Post] the identifier
|
|
21
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
22
|
+
# @return [Post, nil] the post or nil if the post was not found
|
|
23
|
+
# @yieldparam problem [Problem] each problem the API reported, such as a resource that was not found
|
|
24
|
+
# @example Look up a post
|
|
25
|
+
# client.find_post(1234567890).text
|
|
26
|
+
def find_post(id, **params, &)
|
|
27
|
+
Post.find(id, client: self, **params, &)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Look up a post by identifier, which must exist
|
|
31
|
+
#
|
|
32
|
+
# @api public
|
|
33
|
+
# @param id [String, Integer, Post] the identifier
|
|
34
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
35
|
+
# @return [Post] the post
|
|
36
|
+
# @raise [MissingResource] if the post was not found
|
|
37
|
+
# @example Look up a post
|
|
38
|
+
# client.find_post!(1234567890).text
|
|
39
|
+
def find_post!(id, **params)
|
|
40
|
+
Post.find!(id, client: self, **params)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Look up many posts by identifier, in parallel batches
|
|
44
|
+
#
|
|
45
|
+
# @api public
|
|
46
|
+
# @param ids [Array<String, Integer, Post>] the identifiers
|
|
47
|
+
# @param concurrency [Integer] the number of batches looked up at once, which must be at least one; each is
|
|
48
|
+
# a request of up to 100 posts, so a lower number spends a rate limit more slowly
|
|
49
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
50
|
+
# @return [Array<Post>] the posts that were found
|
|
51
|
+
# @raise [ArgumentError] if the concurrency is less than one
|
|
52
|
+
# @yieldparam problem [Problem] each problem the API reported, such as a resource that was not found
|
|
53
|
+
# @example Look up many posts
|
|
54
|
+
# client.find_all_posts([1234567890, 1234567891])
|
|
55
|
+
# @example Look up many posts one batch at a time
|
|
56
|
+
# client.find_all_posts(ids, concurrency: 1)
|
|
57
|
+
def find_all_posts(ids, concurrency: BatchFinders::DEFAULT_CONCURRENCY, **params, &)
|
|
58
|
+
Post.find_all(ids, client: self, concurrency:, **params, &)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Search recent posts
|
|
62
|
+
#
|
|
63
|
+
# @api public
|
|
64
|
+
# @param query [String] the search query
|
|
65
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
66
|
+
# @return [Cursor] a cursor over the matching posts
|
|
67
|
+
# @example Print posts about Ruby
|
|
68
|
+
# client.search_posts("ruby -is:retweet").each { |post| puts post.text }
|
|
69
|
+
def search_posts(query, **params)
|
|
70
|
+
Post.search(query, client: self, **params)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# The posts of the authenticated user that other users have reposted
|
|
74
|
+
#
|
|
75
|
+
# @api public
|
|
76
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
77
|
+
# @return [Cursor] a cursor over the reposted posts
|
|
78
|
+
# @example Print the reposted posts
|
|
79
|
+
# client.reposts_of_me.each { |post| puts post.text }
|
|
80
|
+
def reposts_of_me(**params)
|
|
81
|
+
Post.reposts_of_me(client: self, **params)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Search the full archive of posts
|
|
85
|
+
#
|
|
86
|
+
# @api public
|
|
87
|
+
# @param query [String] the search query
|
|
88
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
89
|
+
# @return [Cursor] a cursor over the matching posts
|
|
90
|
+
# @example Print every post about Ruby
|
|
91
|
+
# client.search_all_posts("ruby -is:retweet").each { |post| puts post.text }
|
|
92
|
+
def search_all_posts(query, **params)
|
|
93
|
+
Post.search_all(query, client: self, **params)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Count the recent posts that match a query, without reading them
|
|
97
|
+
#
|
|
98
|
+
# The API bills a count by the request, not by the post, and refuses OAuth 1.0a for it, so a client that signs
|
|
99
|
+
# with OAuth 1.0a counts with a copy that authenticates as the app.
|
|
100
|
+
#
|
|
101
|
+
# @api public
|
|
102
|
+
# @param query [String] the search query
|
|
103
|
+
# @param params [Hash] query parameters, such as start_time and end_time, and max_pages, the most pages of
|
|
104
|
+
# counts to request
|
|
105
|
+
# @return [Integer] the number of matching posts
|
|
106
|
+
# @example Count the recent posts about Ruby with an app-only client
|
|
107
|
+
# client.count_posts("ruby")
|
|
108
|
+
def count_posts(query, **params) = Post.count(query, client: self, **params) # steep:ignore DifferentMethodParameterKind
|
|
109
|
+
|
|
110
|
+
# Count the posts from the full archive that match a query, without reading them
|
|
111
|
+
#
|
|
112
|
+
# The API bills a count by the request, not by the post, and refuses OAuth 1.0a for it, so a client that signs
|
|
113
|
+
# with OAuth 1.0a counts with a copy that authenticates as the app. A client signed in with OAuth 2.0 as a user
|
|
114
|
+
# that holds no credentials of the app counts as the user, which the full archive refuses with X::Forbidden.
|
|
115
|
+
#
|
|
116
|
+
# @api public
|
|
117
|
+
# @param query [String] the search query
|
|
118
|
+
# @param params [Hash] query parameters, such as start_time and end_time, and the max_pages of
|
|
119
|
+
# X::Post.count_all, which limits the pages of counts requested
|
|
120
|
+
# @return [Integer] the number of matching posts
|
|
121
|
+
# @example Count every post about Ruby from 2024
|
|
122
|
+
# client.count_all_posts("ruby", start_time: "2024-01-01T00:00:00Z", end_time: "2025-01-01T00:00:00Z")
|
|
123
|
+
def count_all_posts(query, **params) = Post.count_all(query, client: self, **params) # steep:ignore DifferentMethodParameterKind
|
|
124
|
+
|
|
125
|
+
# Count the posts from the last seven days that match a query, by period
|
|
126
|
+
#
|
|
127
|
+
# The API bills a count by the request, not by the post, and refuses OAuth 1.0a for it, so a client that signs
|
|
128
|
+
# with OAuth 1.0a counts with a copy that authenticates as the app.
|
|
129
|
+
#
|
|
130
|
+
# @api public
|
|
131
|
+
# @param query [String] the search query
|
|
132
|
+
# @param params [Hash] query parameters, such as granularity, which is day by default, and max_pages, the most
|
|
133
|
+
# pages of counts to request
|
|
134
|
+
# @return [Hash{Range<Time> => Integer}] the number of matching posts, keyed by the time each period spans,
|
|
135
|
+
# from its start up to, but not including, its end, oldest first
|
|
136
|
+
# @example Count the recent posts about Ruby by hour
|
|
137
|
+
# client.count_posts_by_period("ruby", granularity: "hour")
|
|
138
|
+
def count_posts_by_period(query, **params) = Post.count_by_period(query, client: self, **params) # steep:ignore DifferentMethodParameterKind
|
|
139
|
+
|
|
140
|
+
# Count the posts from the full archive that match a query, by period
|
|
141
|
+
#
|
|
142
|
+
# The API bills a count by the request, not by the post, and refuses OAuth 1.0a for it, so a client that signs
|
|
143
|
+
# with OAuth 1.0a counts with a copy that authenticates as the app. A client signed in with OAuth 2.0 as a user
|
|
144
|
+
# that holds no credentials of the app counts as the user, which the full archive refuses with X::Forbidden.
|
|
145
|
+
#
|
|
146
|
+
# @api public
|
|
147
|
+
# @param query [String] the search query
|
|
148
|
+
# @param params [Hash] query parameters, such as granularity, which is day by default, and the max_pages of
|
|
149
|
+
# X::Post.count_all_by_period, which limits the pages of counts requested
|
|
150
|
+
# @return [Hash{Range<Time> => Integer}] the number of matching posts, keyed by the time each period spans,
|
|
151
|
+
# from its start up to, but not including, its end, oldest first
|
|
152
|
+
# @example Count the posts about Ruby by day in 2024
|
|
153
|
+
# client.count_all_posts_by_period("ruby", start_time: "2024-01-01T00:00:00Z", end_time: "2025-01-01T00:00:00Z")
|
|
154
|
+
def count_all_posts_by_period(query, **params) = Post.count_all_by_period(query, client: self, **params) # steep:ignore DifferentMethodParameterKind
|
|
155
|
+
|
|
156
|
+
# Look up how many posts the app's project has read
|
|
157
|
+
#
|
|
158
|
+
# The usage endpoint takes app-only authentication alone, so a client that signs with OAuth 1.0a looks it up
|
|
159
|
+
# with a copy that authenticates as the app, and one signed in with OAuth 2.0 as a user that holds no
|
|
160
|
+
# credentials of the app is refused with X::Forbidden.
|
|
161
|
+
#
|
|
162
|
+
# A response that holds no usage returns nil, as current_user does for a users/me that holds no user, and
|
|
163
|
+
# passes the problems it reported to the block, if there is one.
|
|
164
|
+
#
|
|
165
|
+
# @api public
|
|
166
|
+
# @param params [Hash] query parameters, such as days, the number of days to report, which is 7 by default
|
|
167
|
+
# @return [PostUsage, nil] the usage, or nil if the response holds none
|
|
168
|
+
# @yieldparam problem [Problem] each problem the API reported
|
|
169
|
+
# @example Check how much of the monthly cap remains
|
|
170
|
+
# usage = client.post_usage
|
|
171
|
+
# usage.project_cap - usage.project_usage if usage
|
|
172
|
+
def post_usage(**params, &) = PostUsage.current(client: self, **params, &)
|
|
173
|
+
|
|
174
|
+
# Look up how many posts the app's project has read, which must be returned
|
|
175
|
+
#
|
|
176
|
+
# @api public
|
|
177
|
+
# @param params [Hash] query parameters, such as days, the number of days to report, which is 7 by default
|
|
178
|
+
# @return [PostUsage] the usage
|
|
179
|
+
# @raise [MissingResource] if the API returns no usage
|
|
180
|
+
# @example Check how much of the monthly cap remains
|
|
181
|
+
# usage = client.post_usage!
|
|
182
|
+
# usage.project_cap - usage.project_usage
|
|
183
|
+
def post_usage!(**params) = PostUsage.current!(client: self, **params)
|
|
184
|
+
|
|
185
|
+
alias_method :find_tweet, :find_post
|
|
186
|
+
alias_method :find_tweet!, :find_post!
|
|
187
|
+
alias_method :find_all_tweets, :find_all_posts
|
|
188
|
+
alias_method :search_tweets, :search_posts
|
|
189
|
+
alias_method :search_all_tweets, :search_all_posts
|
|
190
|
+
alias_method :retweets_of_me, :reposts_of_me
|
|
191
|
+
alias_method :count_tweets, :count_posts
|
|
192
|
+
alias_method :count_all_tweets, :count_all_posts
|
|
193
|
+
alias_method :count_tweets_by_period, :count_posts_by_period
|
|
194
|
+
alias_method :count_all_tweets_by_period, :count_all_posts_by_period
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../space"
|
|
4
|
+
|
|
5
|
+
module X
|
|
6
|
+
module Resources
|
|
7
|
+
module Lookups
|
|
8
|
+
# Look up and search spaces, mixed into a client through API
|
|
9
|
+
#
|
|
10
|
+
# Internal to x-resources: X::Resources::API includes it, and its methods are public API of the client that
|
|
11
|
+
# includes API, but the module is only how they are grouped, and some of them need the methods of another,
|
|
12
|
+
# so include API rather than this module alone.
|
|
13
|
+
#
|
|
14
|
+
# @api semipublic
|
|
15
|
+
module Spaces
|
|
16
|
+
# Look up a space by identifier
|
|
17
|
+
#
|
|
18
|
+
# @api public
|
|
19
|
+
# @param id [String, Integer, Space] the identifier
|
|
20
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
21
|
+
# @return [Space, nil] the space or nil if the space was not found
|
|
22
|
+
# @yieldparam problem [Problem] each problem the API reported, such as a resource that was not found
|
|
23
|
+
# @example Look up a space
|
|
24
|
+
# client.find_space("1DXxyRYNejbKM").title
|
|
25
|
+
def find_space(id, **params, &)
|
|
26
|
+
Space.find(id, client: self, **params, &)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Look up a space by identifier, which must exist
|
|
30
|
+
#
|
|
31
|
+
# @api public
|
|
32
|
+
# @param id [String, Integer, Space] the identifier
|
|
33
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
34
|
+
# @return [Space] the space
|
|
35
|
+
# @raise [MissingResource] if the space was not found
|
|
36
|
+
# @example Look up a space
|
|
37
|
+
# client.find_space!("1DXxyRYNejbKM").title
|
|
38
|
+
def find_space!(id, **params)
|
|
39
|
+
Space.find!(id, client: self, **params)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Look up many spaces by identifier, in parallel batches
|
|
43
|
+
#
|
|
44
|
+
# @api public
|
|
45
|
+
# @param ids [Array<String, Integer, Space>] the identifiers
|
|
46
|
+
# @param concurrency [Integer] the number of batches looked up at once, which must be at least one
|
|
47
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
48
|
+
# @return [Array<Space>] the spaces that were found
|
|
49
|
+
# @raise [ArgumentError] if the concurrency is less than one
|
|
50
|
+
# @yieldparam problem [Problem] each problem the API reported, such as a resource that was not found
|
|
51
|
+
# @example Look up many spaces
|
|
52
|
+
# client.find_all_spaces(["1DXxyRYNejbKM", "1OwGWzarWnNKQ"]).map(&:title)
|
|
53
|
+
def find_all_spaces(ids, concurrency: BatchFinders::DEFAULT_CONCURRENCY, **params, &)
|
|
54
|
+
Space.find_all(ids, client: self, concurrency:, **params, &)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Look up the live and scheduled spaces many users created, in parallel batches
|
|
58
|
+
#
|
|
59
|
+
# @api public
|
|
60
|
+
# @param users [Array<User, String, Integer>] the users who created the spaces, or their identifiers
|
|
61
|
+
# @param concurrency [Integer] the number of batches looked up at once, which must be at least one
|
|
62
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
63
|
+
# @return [Array<Space>] the spaces, frozen, empty if the users created none
|
|
64
|
+
# @raise [ArgumentError] if a user is not a user or the identifier of one, or the concurrency is less than
|
|
65
|
+
# one, before a request
|
|
66
|
+
# @yieldparam problem [Problem] each problem the API reported
|
|
67
|
+
# @example Print the live spaces a user created
|
|
68
|
+
# client.find_all_spaces_by_creator([7505382]).select { |space| space.state.eql?("live") }.map(&:title)
|
|
69
|
+
def find_all_spaces_by_creator(users, concurrency: BatchFinders::DEFAULT_CONCURRENCY, **params, &)
|
|
70
|
+
Space.find_all_by_creator(users, client: self, concurrency:, **params, &)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Search spaces by their titles
|
|
74
|
+
#
|
|
75
|
+
# @api public
|
|
76
|
+
# @param query [String] the search query
|
|
77
|
+
# @param params [Hash] query parameters merged over the default parameters, such as state: live or scheduled
|
|
78
|
+
# @return [Cursor] a cursor over the matching spaces
|
|
79
|
+
# @example Print the live spaces about Ruby
|
|
80
|
+
# client.search_spaces("ruby", state: "live").each { |space| puts space.title }
|
|
81
|
+
def search_spaces(query, **params)
|
|
82
|
+
Space.search(query, client: self, **params)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../personalized_trend"
|
|
4
|
+
require_relative "../trend"
|
|
5
|
+
|
|
6
|
+
module X
|
|
7
|
+
module Resources
|
|
8
|
+
module Lookups
|
|
9
|
+
# Read the topics trending in a place, and for the authenticated user, mixed into a client through API
|
|
10
|
+
#
|
|
11
|
+
# Internal to x-resources: X::Resources::API includes it, and its methods are public API of the client that
|
|
12
|
+
# includes API, but the module is only how they are grouped, so include API rather than this module alone.
|
|
13
|
+
#
|
|
14
|
+
# @api semipublic
|
|
15
|
+
module Trends
|
|
16
|
+
# The topics trending in a place
|
|
17
|
+
#
|
|
18
|
+
# @api public
|
|
19
|
+
# @param woeid [Integer, String] the Yahoo! Where On Earth identifier of the place, such as 1 for the world
|
|
20
|
+
# @param params [Hash] query parameters, such as max_trends, which is 50, the most the API returns, unless given
|
|
21
|
+
# @return [Array<Trend>] the trends, frozen
|
|
22
|
+
# @raise [ArgumentError] if the WOEID is not a number, before a request
|
|
23
|
+
# @example Print the ten topics trending most in the world
|
|
24
|
+
# client.trends(1, max_trends: 10).each { |trend| puts trend.name }
|
|
25
|
+
def trends(woeid, **params) = Trend.at(woeid, client: self, **params)
|
|
26
|
+
|
|
27
|
+
# The topics trending for the authenticated user
|
|
28
|
+
#
|
|
29
|
+
# @api public
|
|
30
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
31
|
+
# @return [Array<PersonalizedTrend>] the trends, frozen
|
|
32
|
+
# @example Print the topics trending for the authenticated user
|
|
33
|
+
# client.personalized_trends.each { |trend| puts trend.name }
|
|
34
|
+
def personalized_trends(**params) = PersonalizedTrend.all(client: self, **params)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../user"
|
|
4
|
+
|
|
5
|
+
module X
|
|
6
|
+
module Resources
|
|
7
|
+
module Lookups
|
|
8
|
+
# Look up and search users, and the authenticated user, mixed into a client through API
|
|
9
|
+
#
|
|
10
|
+
# Internal to x-resources: X::Resources::API includes it, and its methods are public API of the client that
|
|
11
|
+
# includes API, but the module is only how they are grouped, and some of them need the methods of another,
|
|
12
|
+
# so include API rather than this module alone.
|
|
13
|
+
#
|
|
14
|
+
# @api semipublic
|
|
15
|
+
module Users
|
|
16
|
+
# Look up a user by identifier or username
|
|
17
|
+
#
|
|
18
|
+
# @api public
|
|
19
|
+
# @param id_or_username [Integer, User, String] an identifier or a user, or a username
|
|
20
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
21
|
+
# @return [User, nil] the user or nil if the user was not found
|
|
22
|
+
# @example Look up a user by username
|
|
23
|
+
# client.find_user("sferik")
|
|
24
|
+
# @yieldparam problem [Problem] each problem the API reported, such as a resource that was not found
|
|
25
|
+
# @example Look up a user by identifier
|
|
26
|
+
# client.find_user(7505382)
|
|
27
|
+
def find_user(id_or_username, **params, &)
|
|
28
|
+
User.find(id_or_username, client: self, **params, &)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Look up a user by identifier or username, which must exist
|
|
32
|
+
#
|
|
33
|
+
# @api public
|
|
34
|
+
# @param id_or_username [Integer, User, String] an identifier or a user, or a username
|
|
35
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
36
|
+
# @return [User] the user
|
|
37
|
+
# @raise [MissingResource] if the user was not found
|
|
38
|
+
# @example Look up a user by username
|
|
39
|
+
# client.find_user!("sferik")
|
|
40
|
+
def find_user!(id_or_username, **params)
|
|
41
|
+
User.find!(id_or_username, client: self, **params)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Look up a user by username
|
|
45
|
+
#
|
|
46
|
+
# It looks every value up as a username, a String of digits as the account whose handle is that number, as
|
|
47
|
+
# find_user looks up any String, so code that reads a value from elsewhere says which it means, as
|
|
48
|
+
# find_user_by_id does.
|
|
49
|
+
#
|
|
50
|
+
# @api public
|
|
51
|
+
# @param username [String] the username, with or without a leading at sign
|
|
52
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
53
|
+
# @return [User, nil] the user or nil if the user was not found
|
|
54
|
+
# @raise [ArgumentError] if the value is not a username
|
|
55
|
+
# @yieldparam problem [Problem] each problem the API reported, such as a resource that was not found
|
|
56
|
+
# @example Look up a user whose username is a number
|
|
57
|
+
# client.find_user_by_username("1234567890")
|
|
58
|
+
def find_user_by_username(username, **params, &)
|
|
59
|
+
User.find_by_username(username, client: self, **params, &)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Look up a user by username, which must exist
|
|
63
|
+
#
|
|
64
|
+
# @api public
|
|
65
|
+
# @param username [String] the username, with or without a leading at sign
|
|
66
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
67
|
+
# @return [User] the user
|
|
68
|
+
# @raise [ArgumentError] if the value is not a username
|
|
69
|
+
# @raise [MissingResource] if the user was not found
|
|
70
|
+
# @example Look up a user by username
|
|
71
|
+
# client.find_user_by_username!("sferik")
|
|
72
|
+
def find_user_by_username!(username, **params)
|
|
73
|
+
User.find_by_username!(username, client: self, **params)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Look up a user by identifier
|
|
77
|
+
#
|
|
78
|
+
# A String of digits is an identifier, as it is read from a response or an environment variable, so this
|
|
79
|
+
# looks the account that number identifies up, where find_user would take it for a username.
|
|
80
|
+
#
|
|
81
|
+
# @api public
|
|
82
|
+
# @param id [String, Integer, User] the identifier, or a user
|
|
83
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
84
|
+
# @return [User, nil] the user or nil if the user was not found
|
|
85
|
+
# @raise [ArgumentError] if the value is not an identifier
|
|
86
|
+
# @yieldparam problem [Problem] each problem the API reported, such as a resource that was not found
|
|
87
|
+
# @example Look up a user by an identifier read as a String
|
|
88
|
+
# client.find_user_by_id(ENV.fetch("USER_ID"))
|
|
89
|
+
def find_user_by_id(id, **params, &)
|
|
90
|
+
User.find_by_id(id, client: self, **params, &)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Look up a user by identifier, which must exist
|
|
94
|
+
#
|
|
95
|
+
# @api public
|
|
96
|
+
# @param id [String, Integer, User] the identifier, or a user
|
|
97
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
98
|
+
# @return [User] the user
|
|
99
|
+
# @raise [ArgumentError] if the value is not an identifier
|
|
100
|
+
# @raise [MissingResource] if the user was not found
|
|
101
|
+
# @example Look up a user by an identifier read as a String
|
|
102
|
+
# client.find_user_by_id!("7505382")
|
|
103
|
+
def find_user_by_id!(id, **params)
|
|
104
|
+
User.find_by_id!(id, client: self, **params)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Look up many users by identifier or username, in parallel batches
|
|
108
|
+
#
|
|
109
|
+
# @api public
|
|
110
|
+
# @param ids_or_usernames [Array<Integer, User, String>] identifiers or users, or usernames
|
|
111
|
+
# @param concurrency [Integer] the number of batches looked up at once, which must be at least one; each is
|
|
112
|
+
# a request of up to 100 users, so a lower number spends a rate limit more slowly
|
|
113
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
114
|
+
# @return [Array<User>] the users that were found
|
|
115
|
+
# @raise [ArgumentError] if the concurrency is less than one
|
|
116
|
+
# @yieldparam problem [Problem] each problem the API reported, such as a resource that was not found
|
|
117
|
+
# @example Look up many users by username
|
|
118
|
+
# client.find_all_users(["sferik", "gem"])
|
|
119
|
+
# @example Look up many users one batch at a time
|
|
120
|
+
# client.find_all_users(ids, concurrency: 1)
|
|
121
|
+
def find_all_users(ids_or_usernames, concurrency: BatchFinders::DEFAULT_CONCURRENCY, **params, &)
|
|
122
|
+
User.find_all(ids_or_usernames, client: self, concurrency:, **params, &)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Look up many users by username, in parallel batches
|
|
126
|
+
#
|
|
127
|
+
# It looks every value up as a username, Strings of digits as the accounts whose handles are those numbers,
|
|
128
|
+
# as find_all_users looks up any String, so code that reads values from elsewhere says which it means, as
|
|
129
|
+
# find_all_users_by_id does.
|
|
130
|
+
#
|
|
131
|
+
# @api public
|
|
132
|
+
# @param usernames [Array<String>] the usernames, with or without leading at signs
|
|
133
|
+
# @param concurrency [Integer] the number of batches looked up at once, which must be at least one
|
|
134
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
135
|
+
# @return [Array<User>] the users that were found
|
|
136
|
+
# @raise [ArgumentError] if a value is not a username, or if the concurrency is less than one
|
|
137
|
+
# @yieldparam problem [Problem] each problem the API reported, such as a username that was not found
|
|
138
|
+
# @example Look up many users by username
|
|
139
|
+
# client.find_all_users_by_username(["sferik", "1234567890"])
|
|
140
|
+
def find_all_users_by_username(usernames, concurrency: BatchFinders::DEFAULT_CONCURRENCY, **params, &)
|
|
141
|
+
User.find_all_by_username(usernames, client: self, concurrency:, **params, &)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Look up many users by identifier, in parallel batches
|
|
145
|
+
#
|
|
146
|
+
# A String of digits is an identifier, as it is read from a response or an environment variable, so this
|
|
147
|
+
# looks the accounts those numbers identify up, where find_all_users would take them for usernames.
|
|
148
|
+
#
|
|
149
|
+
# @api public
|
|
150
|
+
# @param ids [Array<String, Integer, User>] the identifiers, or users
|
|
151
|
+
# @param concurrency [Integer] the number of batches looked up at once, which must be at least one
|
|
152
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
153
|
+
# @return [Array<User>] the users that were found
|
|
154
|
+
# @raise [ArgumentError] if a value is not an identifier, or if the concurrency is less than one
|
|
155
|
+
# @yieldparam problem [Problem] each problem the API reported, such as an identifier that was not found
|
|
156
|
+
# @example Look up many users by identifier, read as Strings
|
|
157
|
+
# client.find_all_users_by_id(ENV.fetch("USER_IDS").split(","))
|
|
158
|
+
def find_all_users_by_id(ids, concurrency: BatchFinders::DEFAULT_CONCURRENCY, **params, &)
|
|
159
|
+
User.find_all_by_id(ids, client: self, concurrency:, **params, &)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Look up the authenticated user
|
|
163
|
+
#
|
|
164
|
+
# Each call looks the user up, as X::User.current does, so its counts and profile are as they are now. Keep
|
|
165
|
+
# the user it returns to read them again without a request.
|
|
166
|
+
#
|
|
167
|
+
# @api public
|
|
168
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
169
|
+
# @return [User, nil] the authenticated user, or nil if the API returns none
|
|
170
|
+
# @yieldparam problem [Problem] each problem the API reported
|
|
171
|
+
# @example Print the name of the authenticated user
|
|
172
|
+
# puts client.current_user&.name
|
|
173
|
+
def current_user(**params, &)
|
|
174
|
+
User.current(client: self, **params, &)&.tap { |user| Utils.remember_user_id(self, user.id) }
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Look up the authenticated user, who must be found
|
|
178
|
+
#
|
|
179
|
+
# Each call looks the user up, as X::User.current! does, so its counts and profile are as they are now. Keep
|
|
180
|
+
# the user it returns to read them again without a request.
|
|
181
|
+
#
|
|
182
|
+
# @api public
|
|
183
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
184
|
+
# @return [User] the authenticated user
|
|
185
|
+
# @raise [MissingResource] if the API returns no user
|
|
186
|
+
# @example Print the home timeline of the authenticated user
|
|
187
|
+
# client.current_user!.home_timeline.each { |post| puts post.text }
|
|
188
|
+
def current_user!(**params)
|
|
189
|
+
User.current!(client: self, **params).tap { |user| Utils.remember_user_id(self, user.id) }
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# The identifier of the authenticated user, from an OAuth 1.0a token if possible
|
|
193
|
+
#
|
|
194
|
+
# An OAuth 1.0a access token begins with the identifier of its user, so a client that holds one needs no
|
|
195
|
+
# lookup. Any other client looks the user up the first time, unless current_user or current_user! already
|
|
196
|
+
# has, and keeps the identifier, which never changes, for as long as it holds the same authenticator, since
|
|
197
|
+
# a client whose credentials change authenticates as someone else. An X::Client keeps it even when frozen; a
|
|
198
|
+
# frozen client without memoize keeps nothing, and looks the user up each time.
|
|
199
|
+
#
|
|
200
|
+
# @api public
|
|
201
|
+
# @return [Integer] the identifier
|
|
202
|
+
# @raise [MissingResource] if the user is looked up and the API returns none
|
|
203
|
+
# @example Get the identifier of the authenticated user
|
|
204
|
+
# client.current_user_id # => 7505382
|
|
205
|
+
def current_user_id = Utils.authenticated_user_id(self) || Utils.remembered_user_id(self) || current_user!.id
|
|
206
|
+
|
|
207
|
+
# Search users
|
|
208
|
+
#
|
|
209
|
+
# @api public
|
|
210
|
+
# @param query [String] the search query
|
|
211
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
212
|
+
# @return [Cursor] a cursor over the matching users
|
|
213
|
+
# @example Print the users matching a query
|
|
214
|
+
# client.search_users("ruby").each { |user| puts user.username }
|
|
215
|
+
def search_users(query, **params)
|
|
216
|
+
User.search(query, client: self, **params)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lookups/communities"
|
|
4
|
+
require_relative "lookups/direct_messages"
|
|
5
|
+
require_relative "lookups/lists"
|
|
6
|
+
require_relative "lookups/media"
|
|
7
|
+
require_relative "lookups/posts"
|
|
8
|
+
require_relative "lookups/spaces"
|
|
9
|
+
require_relative "lookups/trends"
|
|
10
|
+
require_relative "lookups/users"
|
|
11
|
+
|
|
12
|
+
module X
|
|
13
|
+
module Resources
|
|
14
|
+
# Lookups, searches, and collections, the modules of which X::Resources::API includes
|
|
15
|
+
#
|
|
16
|
+
# Internal to x-resources: a namespace of the modules API includes into a client, and not itself included, so that
|
|
17
|
+
# the modules it holds are not constants of the client, where the name of one, such as Media, would shadow a
|
|
18
|
+
# constant of the same name in a class that inherits from the client. Include API rather than any of them.
|
|
19
|
+
#
|
|
20
|
+
# @api private
|
|
21
|
+
module Lookups
|
|
22
|
+
end
|
|
23
|
+
private_constant :Lookups
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "errors"
|
|
4
|
+
require_relative "includes"
|
|
5
|
+
|
|
6
|
+
module X
|
|
7
|
+
module Resources
|
|
8
|
+
# The state of a resource Marshal and YAML write and read
|
|
9
|
+
#
|
|
10
|
+
# Internal to x-resources: the methods it gives a resource, marshal_dump, marshal_load, encode_with, and init_with,
|
|
11
|
+
# are public API, but the module is only how they are given, and which classes include it can change within 1.x.
|
|
12
|
+
#
|
|
13
|
+
# @api semipublic
|
|
14
|
+
module Marshalling
|
|
15
|
+
# The number of the format of the state Marshal writes, which every release of 1.x writes
|
|
16
|
+
#
|
|
17
|
+
# A later release of 1.x adds to the state only what an earlier one ignores, parts after those it reads and keys of a
|
|
18
|
+
# Hash it does not read, so that the state one release of 1.x writes is read by every other, earlier or later.
|
|
19
|
+
#
|
|
20
|
+
# @api private
|
|
21
|
+
MARSHAL_FORMAT = 1
|
|
22
|
+
# The name YAML writes each part of the state under, in the order Marshal writes them
|
|
23
|
+
# @api private
|
|
24
|
+
YAML_KEYS = %w[format attrs hydrated includes problems query].freeze
|
|
25
|
+
private_constant :MARSHAL_FORMAT, :YAML_KEYS
|
|
26
|
+
|
|
27
|
+
# The state Marshal writes, which leaves out the client
|
|
28
|
+
#
|
|
29
|
+
# The client is left out, since it holds credentials and a connection. What is written is plain data, led by
|
|
30
|
+
# the number of its format, so that a resource written by one release of 1.x is read by a later one: its
|
|
31
|
+
# attributes, whether it is hydrated, and, of the response it came from, the included objects it refers to, and
|
|
32
|
+
# those they refer to in turn, the problems about any of them, and the query, so that the references it
|
|
33
|
+
# resolves, and the problems it and they report, are what they were, while the rest of the response is left
|
|
34
|
+
# out, however many other resources it held.
|
|
35
|
+
#
|
|
36
|
+
# @api public
|
|
37
|
+
# @return [Array] the number of the format, then the state of the resource
|
|
38
|
+
# @example Cache a user
|
|
39
|
+
# Rails.cache.write("user", user)
|
|
40
|
+
def marshal_dump
|
|
41
|
+
data, problems, query = includes.state_of([self]) # steep:ignore NoMethod
|
|
42
|
+
[MARSHAL_FORMAT, attrs, hydrated?, data, problems, query]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Restore a resource Marshal read, which has no client and so makes no request
|
|
46
|
+
#
|
|
47
|
+
# What Marshal reads is a client-less resource that answers its readers, resolves the references its response
|
|
48
|
+
# included, and reports its problems, as the resource that was written did, and raises from a hydrate, refresh,
|
|
49
|
+
# or collection that would make a request. It is hydrated if it was, and the query of its request asks for every
|
|
50
|
+
# field this release requests, so that one written before a minor release added to the fields is not.
|
|
51
|
+
#
|
|
52
|
+
# @api public
|
|
53
|
+
# @param state [Array] the state Marshal wrote
|
|
54
|
+
# @return [void]
|
|
55
|
+
# @raise [UnsupportedFormat] if the state is of a format this release does not read
|
|
56
|
+
# @example Read a cached user
|
|
57
|
+
# Marshal.load(Marshal.dump(user)).username # => "sferik"
|
|
58
|
+
def marshal_load(state)
|
|
59
|
+
format, attrs, hydrated, data, problems, query = state
|
|
60
|
+
raise UnsupportedFormat, "#{self.class} reads format #{MARSHAL_FORMAT} of Marshal, not #{format.inspect}" unless MARSHAL_FORMAT.eql?(format)
|
|
61
|
+
|
|
62
|
+
includes = Includes.new(data, problems:, query:)
|
|
63
|
+
setup(attrs, client: nil, hydrated: includes.hydrated_as_read?(self.class, hydrated), includes:) # steep:ignore NoMethod
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Write the state Marshal writes as YAML, which leaves out the client
|
|
67
|
+
#
|
|
68
|
+
# YAML reads no marshal_dump, and would write every instance variable, the client and its credentials among
|
|
69
|
+
# them, so a resource says how it is written: each part of the state Marshal writes, under its name.
|
|
70
|
+
#
|
|
71
|
+
# @api public
|
|
72
|
+
# @param coder [Psych::Coder] the coder YAML writes the resource with
|
|
73
|
+
# @return [void]
|
|
74
|
+
# @example Write a user as YAML, as a queue writes the arguments of a job
|
|
75
|
+
# YAML.dump(user)
|
|
76
|
+
def encode_with(coder) = YAML_KEYS.zip(marshal_dump) { |key, value| coder[key] = value }
|
|
77
|
+
|
|
78
|
+
# Restore a resource YAML read, as Marshal restores one
|
|
79
|
+
#
|
|
80
|
+
# It has no client, and so makes no request.
|
|
81
|
+
#
|
|
82
|
+
#
|
|
83
|
+
# @api public
|
|
84
|
+
# @param coder [Psych::Coder] the coder YAML read the resource with
|
|
85
|
+
# @return [void]
|
|
86
|
+
# @raise [UnsupportedFormat] if the state is of a format this release does not read
|
|
87
|
+
# @example Read a user written as YAML
|
|
88
|
+
# YAML.unsafe_load(YAML.dump(user)).username # => "sferik"
|
|
89
|
+
def init_with(coder) = marshal_load(coder.map.values_at(*YAML_KEYS))
|
|
90
|
+
end
|
|
91
|
+
private_constant :Marshalling
|
|
92
|
+
end
|
|
93
|
+
end
|