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,213 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "bookmark_folder"
|
|
4
|
+
require_relative "utils"
|
|
5
|
+
|
|
6
|
+
module X
|
|
7
|
+
module Resources
|
|
8
|
+
# The collections of a user: the users they follow and are followed by, their posts, timelines, bookmarks, and
|
|
9
|
+
# lists, included into User
|
|
10
|
+
#
|
|
11
|
+
# Internal to x-resources: the methods it gives a user, such as followers, are public API, but the module is only how
|
|
12
|
+
# they are shared, and which classes extend or include it can change within 1.x.
|
|
13
|
+
#
|
|
14
|
+
# @api semipublic
|
|
15
|
+
module UserCollections
|
|
16
|
+
# Maximum number of users per page of the followers, followed users, affiliates, blocked users, or muted users
|
|
17
|
+
# @api private
|
|
18
|
+
MAX_FOLLOW_RESULTS = 1000
|
|
19
|
+
# Maximum number of posts, lists, or bookmark folders per page of the other collections of a user
|
|
20
|
+
# @api private
|
|
21
|
+
MAX_RESULTS = 100
|
|
22
|
+
private_constant :MAX_FOLLOW_RESULTS, :MAX_RESULTS
|
|
23
|
+
|
|
24
|
+
# The users following this user
|
|
25
|
+
#
|
|
26
|
+
# @api public
|
|
27
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
28
|
+
# @return [Cursor] a cursor over the followers
|
|
29
|
+
# @example Print every follower
|
|
30
|
+
# user.followers.each { |follower| puts follower.username }
|
|
31
|
+
def followers(**params)
|
|
32
|
+
cursor(User, "users/#{id}/followers", max_results: MAX_FOLLOW_RESULTS, total: :followers_count, **params)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# The users this user follows
|
|
36
|
+
#
|
|
37
|
+
# @api public
|
|
38
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
39
|
+
# @return [Cursor] a cursor over the followed users
|
|
40
|
+
# @example Count the followed users
|
|
41
|
+
# user.following.count
|
|
42
|
+
def following(**params)
|
|
43
|
+
cursor(User, "users/#{id}/following", max_results: MAX_FOLLOW_RESULTS, total: :following_count, **params)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# The users affiliated with this user, such as the people of an organization
|
|
47
|
+
#
|
|
48
|
+
# They are the accounts whose affiliation names this user, which affiliated_with reads of each of them, so they
|
|
49
|
+
# point the other way from the affiliated_with of this user.
|
|
50
|
+
#
|
|
51
|
+
# @api public
|
|
52
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
53
|
+
# @return [Cursor] a cursor over the users affiliated with this one
|
|
54
|
+
# @example Print the users affiliated with an organization
|
|
55
|
+
# client.find_user!("X").affiliates.each { |user| puts user.username }
|
|
56
|
+
def affiliates(**params) = cursor(User, "users/#{id}/affiliates", max_results: MAX_FOLLOW_RESULTS, **params)
|
|
57
|
+
|
|
58
|
+
# The users this user blocks, which must be the authenticated user
|
|
59
|
+
#
|
|
60
|
+
# @api public
|
|
61
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
62
|
+
# @return [Cursor] a cursor over the blocked users
|
|
63
|
+
# @example Print every blocked user
|
|
64
|
+
# client.current_user!.blocking.each { |user| puts user.username }
|
|
65
|
+
def blocking(**params)
|
|
66
|
+
cursor(User, "users/#{id}/blocking", max_results: MAX_FOLLOW_RESULTS, **params)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# The users this user mutes, which must be the authenticated user
|
|
70
|
+
#
|
|
71
|
+
# @api public
|
|
72
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
73
|
+
# @return [Cursor] a cursor over the muted users
|
|
74
|
+
# @example Print every muted user
|
|
75
|
+
# client.current_user!.muting.each { |user| puts user.username }
|
|
76
|
+
def muting(**params)
|
|
77
|
+
cursor(User, "users/#{id}/muting", max_results: MAX_FOLLOW_RESULTS, **params)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# The posts by this user
|
|
81
|
+
#
|
|
82
|
+
# @api public
|
|
83
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
84
|
+
# @return [Cursor] a cursor over the posts
|
|
85
|
+
# @example Print the most recent posts
|
|
86
|
+
# user.posts.first(10).each { |post| puts post.text }
|
|
87
|
+
def posts(**params)
|
|
88
|
+
cursor(Post, "users/#{id}/tweets", max_results: MAX_RESULTS, min_results: 5, **params)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# The home timeline of this user, which must be the authenticated user
|
|
92
|
+
#
|
|
93
|
+
# @api public
|
|
94
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
95
|
+
# @return [Cursor] a cursor over the posts by the users this user follows, newest first
|
|
96
|
+
# @example Print the home timeline
|
|
97
|
+
# client.current_user!.home_timeline.first(10).each { |post| puts post.text }
|
|
98
|
+
def home_timeline(**params)
|
|
99
|
+
cursor(Post, "users/#{id}/timelines/reverse_chronological", max_results: MAX_RESULTS, **params)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# The posts mentioning this user
|
|
103
|
+
#
|
|
104
|
+
# @api public
|
|
105
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
106
|
+
# @return [Cursor] a cursor over the mentions
|
|
107
|
+
# @example Print the most recent mentions
|
|
108
|
+
# user.mentions.first(10).each { |post| puts post.text }
|
|
109
|
+
def mentions(**params)
|
|
110
|
+
cursor(Post, "users/#{id}/mentions", max_results: MAX_RESULTS, min_results: 5, **params)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# The posts liked by this user
|
|
114
|
+
#
|
|
115
|
+
# @api public
|
|
116
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
117
|
+
# @return [Cursor] a cursor over the liked posts
|
|
118
|
+
# @example Print the most recently liked posts
|
|
119
|
+
# user.liked_posts.first(10).each { |post| puts post.text }
|
|
120
|
+
def liked_posts(**params)
|
|
121
|
+
cursor(Post, "users/#{id}/liked_tweets", max_results: MAX_RESULTS, min_results: 5, **params)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# The posts bookmarked by the authenticated user, or those of one of their folders
|
|
125
|
+
#
|
|
126
|
+
# The API gives the posts of a folder by their identifiers alone, and takes no fields for them, so they are
|
|
127
|
+
# stubs, which hydrate together, a lookup's worth at a time, as the stubs of any cursor do.
|
|
128
|
+
#
|
|
129
|
+
# The bookmark endpoints take only the authentication of a user, OAuth 2.0 user context or OAuth 1.0a, so an
|
|
130
|
+
# app-only client is refused them; only bookmarking and unbookmarking a post refuse OAuth 1.0a too.
|
|
131
|
+
#
|
|
132
|
+
# @api public
|
|
133
|
+
# @param folder [BookmarkFolder, String, Integer, nil] the folder or its identifier, or nil for every bookmark
|
|
134
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
135
|
+
# @return [Cursor] a cursor over the bookmarked posts
|
|
136
|
+
# @raise [ArgumentError] if the folder is not a folder or the identifier of one, before a request
|
|
137
|
+
# @example Print the bookmarked posts
|
|
138
|
+
# client.current_user!.bookmarks.each { |post| puts post.text }
|
|
139
|
+
# @example Print the posts of the first bookmark folder
|
|
140
|
+
# user = client.current_user!
|
|
141
|
+
# if (folder = user.bookmark_folders.first)
|
|
142
|
+
# posts = user.bookmarks(folder:).to_a
|
|
143
|
+
# X::Post.hydrate_all(posts, client: client).each { |post| puts post.text }
|
|
144
|
+
# end
|
|
145
|
+
def bookmarks(folder: nil, **params)
|
|
146
|
+
return cursor(Post, "users/#{id}/bookmarks", max_results: MAX_RESULTS, **params) if folder.nil?
|
|
147
|
+
|
|
148
|
+
cursor(Post, "users/#{id}/bookmarks/folders/#{Utils.id_of(folder, BookmarkFolder)}", max_results: MAX_RESULTS, ids_only: true, **params)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# The bookmark folders of the authenticated user
|
|
152
|
+
#
|
|
153
|
+
# @api public
|
|
154
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
155
|
+
# @return [Cursor] a cursor over the folders
|
|
156
|
+
# @example Print the names of the bookmark folders
|
|
157
|
+
# client.current_user!.bookmark_folders.each { |folder| puts folder.name }
|
|
158
|
+
def bookmark_folders(**params)
|
|
159
|
+
cursor(BookmarkFolder, "users/#{id}/bookmarks/folders", max_results: MAX_RESULTS, **params)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# The lists owned by this user
|
|
163
|
+
#
|
|
164
|
+
# @api public
|
|
165
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
166
|
+
# @return [Cursor] a cursor over the owned lists
|
|
167
|
+
# @example Print the owned lists
|
|
168
|
+
# user.owned_lists.each { |list| puts list.name }
|
|
169
|
+
def owned_lists(**params)
|
|
170
|
+
cursor(List, "users/#{id}/owned_lists", max_results: MAX_RESULTS, **params)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# The lists this user is a member of
|
|
174
|
+
#
|
|
175
|
+
# @api public
|
|
176
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
177
|
+
# @return [Cursor] a cursor over the list memberships
|
|
178
|
+
# @example Print the list memberships
|
|
179
|
+
# user.list_memberships.each { |list| puts list.name }
|
|
180
|
+
def list_memberships(**params)
|
|
181
|
+
cursor(List, "users/#{id}/list_memberships", max_results: MAX_RESULTS, total: :listed_count, **params)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# The lists this user follows
|
|
185
|
+
#
|
|
186
|
+
# @api public
|
|
187
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
188
|
+
# @return [Cursor] a cursor over the followed lists
|
|
189
|
+
# @example Print the followed lists
|
|
190
|
+
# user.followed_lists.each { |list| puts list.name }
|
|
191
|
+
def followed_lists(**params)
|
|
192
|
+
cursor(List, "users/#{id}/followed_lists", max_results: MAX_RESULTS, **params)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# The lists this user has pinned
|
|
196
|
+
#
|
|
197
|
+
# The API returns them in one response, without pages.
|
|
198
|
+
#
|
|
199
|
+
# @api public
|
|
200
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
201
|
+
# @return [Cursor] a cursor over the pinned lists
|
|
202
|
+
# @example Print the pinned lists
|
|
203
|
+
# client.current_user!.pinned_lists.each { |list| puts list.name }
|
|
204
|
+
def pinned_lists(**params)
|
|
205
|
+
cursor(List, "users/#{id}/pinned_lists", max_results: nil, **params)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
alias_method :tweets, :posts
|
|
209
|
+
alias_method :liked_tweets, :liked_posts
|
|
210
|
+
end
|
|
211
|
+
private_constant :UserCollections
|
|
212
|
+
end
|
|
213
|
+
end
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "errors"
|
|
4
|
+
require_relative "batch_finders"
|
|
5
|
+
require_relative "utils"
|
|
6
|
+
|
|
7
|
+
module X
|
|
8
|
+
module Resources
|
|
9
|
+
# Class methods that look users up by identifier or username, and the authenticated user, extended into User
|
|
10
|
+
#
|
|
11
|
+
# Internal to x-resources: the methods it gives User, such as X::User.find_by_username, are public API, but the module
|
|
12
|
+
# is only how they are shared, and which classes extend or include it can change within 1.x.
|
|
13
|
+
#
|
|
14
|
+
# @api semipublic
|
|
15
|
+
module UserFinders
|
|
16
|
+
include BatchFinders
|
|
17
|
+
|
|
18
|
+
# Look up a user by identifier or username
|
|
19
|
+
#
|
|
20
|
+
# An Integer or a user is looked up by identifier, and a String by username, with or without a leading at sign.
|
|
21
|
+
#
|
|
22
|
+
# @api public
|
|
23
|
+
# @param id_or_username [Integer, User, String] an identifier or a user, or a username
|
|
24
|
+
# @param client [Object] the client used to make the request
|
|
25
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
26
|
+
# @return [User, nil] the user or nil if the user was not found, whether the API answers 200 with no data or a 404
|
|
27
|
+
# that reports the user as not found
|
|
28
|
+
# @raise [X::NotFound] if the API answers any other 404, such as one from a client pointed at the wrong host or
|
|
29
|
+
# API version
|
|
30
|
+
# @raise [ArgumentError] if the value is neither an identifier nor a username
|
|
31
|
+
# @yieldparam problem [Problem] each problem the API reported, such as a user that was not found
|
|
32
|
+
# @example Look up a user by username
|
|
33
|
+
# X::User.find("sferik", client: client)
|
|
34
|
+
def find(id_or_username, client:, **params, &)
|
|
35
|
+
return super if Utils.id?(id_or_username)
|
|
36
|
+
|
|
37
|
+
find_by_username(_ = id_or_username, client:, **params, &)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Look up a user by identifier or username, which must exist
|
|
41
|
+
#
|
|
42
|
+
# An Integer or a user is looked up by identifier, and a String by username, as find looks them up, so the error
|
|
43
|
+
# it raises names a username as find_by_username! names one, with the at sign of a handle.
|
|
44
|
+
#
|
|
45
|
+
# @api public
|
|
46
|
+
# @param id_or_username [Integer, User, String] an identifier or a user, or a username
|
|
47
|
+
# @param client [Object] the client used to make the request
|
|
48
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
49
|
+
# @return [User] the user
|
|
50
|
+
# @raise [ArgumentError] if the value is neither an identifier nor a username
|
|
51
|
+
# @raise [MissingResource] if the user was not found, whether the API answers 200 with no data or a 404 that
|
|
52
|
+
# reports the user as not found
|
|
53
|
+
# @raise [X::NotFound] if the API answers any other 404, such as one from a client pointed at the wrong host or
|
|
54
|
+
# API version
|
|
55
|
+
# @example Look up a user by username
|
|
56
|
+
# X::User.find!("sferik", client: client)
|
|
57
|
+
def find!(id_or_username, client:, **params)
|
|
58
|
+
return super if Utils.id?(id_or_username)
|
|
59
|
+
|
|
60
|
+
find_by_username!(_ = id_or_username, client:, **params)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Look up many users by identifier or username, in parallel batches
|
|
64
|
+
#
|
|
65
|
+
# Integers and users are looked up by identifier, and Strings by username. The users come back in the
|
|
66
|
+
# order they were asked for, one for each value that was found, so a user asked for twice, by identifier or by
|
|
67
|
+
# username, comes back twice, without the ones that were not found. Every username is checked before any
|
|
68
|
+
# request, so one that is not a username raises before the identifiers are looked up, and billed.
|
|
69
|
+
#
|
|
70
|
+
# @api public
|
|
71
|
+
# @param ids_or_usernames [Array<Integer, User, String>] identifiers or users, or usernames
|
|
72
|
+
# @param client [Object] the client used to make the requests
|
|
73
|
+
# @param concurrency [Integer] the number of batches looked up at once, which must be at least one; the
|
|
74
|
+
# identifiers and the usernames are looked up one kind after the other, each kind that many batches at a time
|
|
75
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
76
|
+
# @return [Array<User>] the users that were found, one for each value that names one, frozen
|
|
77
|
+
# @raise [ArgumentError] if a String is not a username, or if the concurrency is less than one, before any request
|
|
78
|
+
# @yieldparam problem [Problem] each problem the API reported, such as a user that was not found
|
|
79
|
+
# @example Look up many users by username
|
|
80
|
+
# X::User.find_all(["sferik", "gem"], client: client)
|
|
81
|
+
def find_all(ids_or_usernames, client:, concurrency: DEFAULT_CONCURRENCY, **params, &)
|
|
82
|
+
ids, usernames = ids_or_usernames.partition { |value| Utils.id?(value) }
|
|
83
|
+
usernames.each { |username| Utils.username!(username) }
|
|
84
|
+
found = super(ids, client:, concurrency:, **params) #: Array[User]
|
|
85
|
+
in_order(found + find_all_by_username(_ = usernames, client:, concurrency:, **params, &), ids_or_usernames)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Look up many users by identifier, in parallel batches
|
|
89
|
+
#
|
|
90
|
+
# A String of digits is an identifier, as it is read from a response or an environment variable, so this looks
|
|
91
|
+
# the accounts those numbers identify up, where find_all would take them for usernames. The users come back in
|
|
92
|
+
# the order they were asked for, one for each identifier that was found, as find_all returns them.
|
|
93
|
+
#
|
|
94
|
+
# @api public
|
|
95
|
+
# @param ids [Array<String, Integer, User>] the identifiers, or users
|
|
96
|
+
# @param client [Object] the client used to make the requests
|
|
97
|
+
# @param concurrency [Integer] the number of batches looked up at once, which must be at least one
|
|
98
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
99
|
+
# @return [Array<User>] the users that were found, one for each identifier that names one, frozen
|
|
100
|
+
# @raise [ArgumentError] if a value is not an identifier, or if the concurrency is less than one
|
|
101
|
+
# @yieldparam problem [Problem] each problem the API reported, such as an identifier that was not found
|
|
102
|
+
# @example Look up many users by identifier, read as Strings
|
|
103
|
+
# X::User.find_all_by_id(ENV.fetch("USER_IDS").split(","), client: client)
|
|
104
|
+
def find_all_by_id(ids, client:, concurrency: DEFAULT_CONCURRENCY, **params, &)
|
|
105
|
+
ids = ids.map { |id| Utils.id_of(id, self) }
|
|
106
|
+
in_order_of(lookup_in_batches(endpoint!, batch_key, ids, client:, concurrency:, **params, &), ids) #: Array[User]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Look up a user by identifier
|
|
110
|
+
#
|
|
111
|
+
# A String of digits is an identifier, as it is read from a response or an environment variable, so this looks
|
|
112
|
+
# the account that number identifies up, where find would take it for a username.
|
|
113
|
+
#
|
|
114
|
+
# @api public
|
|
115
|
+
# @param id [String, Integer, User] the identifier, or a user
|
|
116
|
+
# @param client [Object] the client used to make the request
|
|
117
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
118
|
+
# @return [User, nil] the user or nil if the user was not found, whether the API answers 200 with no data or a 404
|
|
119
|
+
# that reports the user as not found
|
|
120
|
+
# @raise [X::NotFound] if the API answers any other 404, such as one from a client pointed at the wrong host or
|
|
121
|
+
# API version
|
|
122
|
+
# @raise [ArgumentError] if the value is not an identifier
|
|
123
|
+
# @yieldparam problem [Problem] each problem the API reported, such as a user that was not found
|
|
124
|
+
# @example Look up a user by an identifier read as a String
|
|
125
|
+
# X::User.find_by_id(ENV.fetch("USER_ID"), client: client)
|
|
126
|
+
def find_by_id(id, client:, **params, &)
|
|
127
|
+
locate("#{endpoint!}/#{Utils.id_of(id, self)}", client:, **params, &) #: User?
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Look up a user by identifier, which must exist
|
|
131
|
+
#
|
|
132
|
+
# @api public
|
|
133
|
+
# @param id [String, Integer, User] the identifier, or a user
|
|
134
|
+
# @param client [Object] the client used to make the request
|
|
135
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
136
|
+
# @return [User] the user
|
|
137
|
+
# @raise [ArgumentError] if the value is not an identifier
|
|
138
|
+
# @raise [MissingResource] if the user was not found, whether the API answers 200 with no data or a 404 that
|
|
139
|
+
# reports the user as not found
|
|
140
|
+
# @raise [X::NotFound] if the API answers any other 404, such as one from a client pointed at the wrong host or
|
|
141
|
+
# API version
|
|
142
|
+
# @example Look up a user by an identifier read as a String
|
|
143
|
+
# X::User.find_by_id!("7505382", client: client)
|
|
144
|
+
def find_by_id!(id, client:, **params)
|
|
145
|
+
name = Utils.id_of(id, self)
|
|
146
|
+
locate!("#{endpoint!}/#{name}", name, client:, **params) #: User
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Look up many users by username, in parallel batches
|
|
150
|
+
#
|
|
151
|
+
# The users come back in the order they were asked for, one for each username that was found, whatever its
|
|
152
|
+
# case, as find_all returns them, and each username is asked for once, however often, and in whatever case, it
|
|
153
|
+
# is given.
|
|
154
|
+
#
|
|
155
|
+
# @api public
|
|
156
|
+
# @param usernames [Array<String>] the usernames, with or without leading at signs
|
|
157
|
+
# @param client [Object] the client used to make the requests
|
|
158
|
+
# @param concurrency [Integer] the number of batches looked up at once, which must be at least one
|
|
159
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
160
|
+
# @return [Array<User>] the users that were found, one for each username that names one, frozen
|
|
161
|
+
# @raise [ArgumentError] if a value is not a username, or if the concurrency is less than one
|
|
162
|
+
# @yieldparam problem [Problem] each problem the API reported, such as a username that was not found
|
|
163
|
+
# @example Look up many users by username
|
|
164
|
+
# X::User.find_all_by_username(["sferik", "gem"], client: client)
|
|
165
|
+
def find_all_by_username(usernames, client:, concurrency: DEFAULT_CONCURRENCY, **params, &)
|
|
166
|
+
usernames = usernames.map { |username| normalize(Utils.username!(username)) }
|
|
167
|
+
found = lookup_in_batches("users/by", :usernames, usernames, client:, concurrency:, **params, &) #: Array[User]
|
|
168
|
+
in_order(found, usernames)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Look up a user by username
|
|
172
|
+
#
|
|
173
|
+
# It looks every value up as a username, a String of digits as the account whose handle is that number, as find
|
|
174
|
+
# looks up any String, so code that reads a value from elsewhere says which it means, as find_by_id does.
|
|
175
|
+
#
|
|
176
|
+
# @api public
|
|
177
|
+
# @param username [String] the username, with or without a leading at sign
|
|
178
|
+
# @param client [Object] the client used to make the request
|
|
179
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
180
|
+
# @return [User, nil] the user or nil if the user was not found, whether the API answers 200 with no data or a 404
|
|
181
|
+
# that reports the user as not found
|
|
182
|
+
# @raise [X::NotFound] if the API answers any other 404, such as one from a client pointed at the wrong host or
|
|
183
|
+
# API version
|
|
184
|
+
# @raise [ArgumentError] if the value is not a username
|
|
185
|
+
# @yieldparam problem [Problem] each problem the API reported, such as a user that was not found
|
|
186
|
+
# @example Look up a user by username
|
|
187
|
+
# X::User.find_by_username("sferik", client: client)
|
|
188
|
+
def find_by_username(username, client:, **params, &)
|
|
189
|
+
locate("users/by/username/#{Utils.username!(username)}", client:, **params, &) #: User?
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Look up a user by username, which must exist
|
|
193
|
+
#
|
|
194
|
+
# @api public
|
|
195
|
+
# @param username [String] the username, with or without a leading at sign
|
|
196
|
+
# @param client [Object] the client used to make the request
|
|
197
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
198
|
+
# @return [User] the user
|
|
199
|
+
# @raise [ArgumentError] if the value is not a username
|
|
200
|
+
# @raise [MissingResource] if the user was not found, whether the API answers 200 with no data or a 404 that
|
|
201
|
+
# reports the user as not found
|
|
202
|
+
# @raise [X::NotFound] if the API answers any other 404, such as one from a client pointed at the wrong host or
|
|
203
|
+
# API version
|
|
204
|
+
# @example Look up a user by username
|
|
205
|
+
# X::User.find_by_username!("sferik", client: client)
|
|
206
|
+
def find_by_username!(username, client:, **params)
|
|
207
|
+
locate!("users/by/username/#{Utils.username!(username)}", "@#{Utils.username(username)}", client:, **params) #: User
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Look up the authenticated user
|
|
211
|
+
#
|
|
212
|
+
# Its path names no user, so a 404 to it is no user that is missing, and raises X::NotFound as any request does.
|
|
213
|
+
#
|
|
214
|
+
# @api public
|
|
215
|
+
# @param client [Object] the client used to make the request
|
|
216
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
217
|
+
# @return [User, nil] the authenticated user
|
|
218
|
+
# @yieldparam problem [Problem] each problem the API reported
|
|
219
|
+
# @example Look up the authenticated user
|
|
220
|
+
# X::User.current(client: client)
|
|
221
|
+
def current(client:, **params, &)
|
|
222
|
+
lookup("users/me", client:, **params, &) #: User?
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Look up the authenticated user, who must be found
|
|
226
|
+
#
|
|
227
|
+
# @api public
|
|
228
|
+
# @param client [Object] the client used to make the request
|
|
229
|
+
# @param params [Hash] query parameters merged over the default parameters
|
|
230
|
+
# @return [User] the authenticated user
|
|
231
|
+
# @raise [MissingResource] if the API returns no user
|
|
232
|
+
# @example Look up the authenticated user
|
|
233
|
+
# X::User.current!(client: client)
|
|
234
|
+
def current!(client:, **params)
|
|
235
|
+
problems = [] #: Array[Problem]
|
|
236
|
+
current(client:, **params) { |problem| problems << problem } || raise(MissingResource.new("users/me returned no user", problems:))
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
private
|
|
240
|
+
|
|
241
|
+
# Order users as they were asked for, one for each value that names one
|
|
242
|
+
# @api private
|
|
243
|
+
# @param users [Array<User>] the users found
|
|
244
|
+
# @param ids_or_usernames [Array<Integer, User, String>] the identifiers, users, and usernames asked for
|
|
245
|
+
# @return [Array<User>] the user each value names, in the order of the values, frozen
|
|
246
|
+
# @raise [InvalidAttribute] if the response holds a username that is not a String
|
|
247
|
+
def in_order(users, ids_or_usernames)
|
|
248
|
+
by_key = users.to_h { |user| [key_of(user), user] }.merge(users.filter_map { |user| username_key(user) }.to_h)
|
|
249
|
+
ids_or_usernames.filter_map { |value| by_key[key_of(value)] }.freeze
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# The key that matches a user found to a username it was asked for by
|
|
253
|
+
#
|
|
254
|
+
# It is nil for a user without one. A username is a String, so it is keyed as one, after an at sign, and never
|
|
255
|
+
# matches an identifier.
|
|
256
|
+
#
|
|
257
|
+
# @api private
|
|
258
|
+
# @param user [User] the user found
|
|
259
|
+
# @return [Array(String, User), nil] the key of the username and the user, or nil if the user holds no username
|
|
260
|
+
# @raise [InvalidAttribute] if the response holds a username that is not a String
|
|
261
|
+
def username_key(user)
|
|
262
|
+
username = user.username
|
|
263
|
+
["@#{Utils.read("#{self}#username", username) { normalize(String.try_convert(username) || raise(ArgumentError)) }}", user] unless username.nil?
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# The key that matches a user to the identifier or username it was asked for by
|
|
267
|
+
# @api private
|
|
268
|
+
# @param value [Integer, User, String] an identifier or user, or a username
|
|
269
|
+
# @return [String] the identifier, or the username in lowercase after an at sign
|
|
270
|
+
def key_of(value)
|
|
271
|
+
Utils.id?(value) ? Utils.id_of(value, self) : "@#{normalize(value)}"
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# A username without an at sign, in lowercase, since case does not matter
|
|
275
|
+
# @api private
|
|
276
|
+
# @param username [String] the username, with or without a leading at sign
|
|
277
|
+
# @return [String] the normalized username
|
|
278
|
+
def normalize(username) = Utils.username(username).downcase
|
|
279
|
+
end
|
|
280
|
+
private_constant :UserFinders
|
|
281
|
+
end
|
|
282
|
+
end
|