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.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +9 -0
  3. data/CHANGELOG.md +254 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +148 -0
  6. data/lib/x/resources/abstract_class.rb +29 -0
  7. data/lib/x/resources/actions/direct_messages.rb +99 -0
  8. data/lib/x/resources/actions/engagement.rb +95 -0
  9. data/lib/x/resources/actions/lists.rb +126 -0
  10. data/lib/x/resources/actions/posts.rb +77 -0
  11. data/lib/x/resources/actions/relationships.rb +91 -0
  12. data/lib/x/resources/actions.rb +22 -0
  13. data/lib/x/resources/api.rb +40 -0
  14. data/lib/x/resources/attributes.rb +203 -0
  15. data/lib/x/resources/batch.rb +43 -0
  16. data/lib/x/resources/batch_finders.rb +185 -0
  17. data/lib/x/resources/bookmark_folder.rb +23 -0
  18. data/lib/x/resources/community.rb +137 -0
  19. data/lib/x/resources/cursor.rb +493 -0
  20. data/lib/x/resources/direct_message.rb +325 -0
  21. data/lib/x/resources/direct_message_conversations.rb +147 -0
  22. data/lib/x/resources/errors.rb +104 -0
  23. data/lib/x/resources/finders.rb +255 -0
  24. data/lib/x/resources/identity.rb +65 -0
  25. data/lib/x/resources/includes.rb +216 -0
  26. data/lib/x/resources/list.rb +336 -0
  27. data/lib/x/resources/lookups/communities.rb +56 -0
  28. data/lib/x/resources/lookups/direct_messages.rb +86 -0
  29. data/lib/x/resources/lookups/lists.rb +44 -0
  30. data/lib/x/resources/lookups/media.rb +72 -0
  31. data/lib/x/resources/lookups/posts.rb +198 -0
  32. data/lib/x/resources/lookups/spaces.rb +87 -0
  33. data/lib/x/resources/lookups/trends.rb +38 -0
  34. data/lib/x/resources/lookups/users.rb +221 -0
  35. data/lib/x/resources/lookups.rb +25 -0
  36. data/lib/x/resources/marshalling.rb +93 -0
  37. data/lib/x/resources/matching_rule.rb +107 -0
  38. data/lib/x/resources/media.rb +278 -0
  39. data/lib/x/resources/media_ids.rb +74 -0
  40. data/lib/x/resources/memo.rb +54 -0
  41. data/lib/x/resources/page.rb +394 -0
  42. data/lib/x/resources/page_limit.rb +80 -0
  43. data/lib/x/resources/pages.rb +270 -0
  44. data/lib/x/resources/parallel.rb +82 -0
  45. data/lib/x/resources/personalized_trend.rb +124 -0
  46. data/lib/x/resources/place.rb +107 -0
  47. data/lib/x/resources/poll.rb +75 -0
  48. data/lib/x/resources/post.rb +615 -0
  49. data/lib/x/resources/post_collections.rb +67 -0
  50. data/lib/x/resources/post_counts.rb +215 -0
  51. data/lib/x/resources/post_search.rb +86 -0
  52. data/lib/x/resources/post_usage.rb +203 -0
  53. data/lib/x/resources/post_writes.rb +140 -0
  54. data/lib/x/resources/published_count.rb +31 -0
  55. data/lib/x/resources/references.rb +121 -0
  56. data/lib/x/resources/relation_writes.rb +54 -0
  57. data/lib/x/resources/relationships.rb +77 -0
  58. data/lib/x/resources/resource.rb +535 -0
  59. data/lib/x/resources/serialization.rb +58 -0
  60. data/lib/x/resources/shape.rb +167 -0
  61. data/lib/x/resources/space.rb +332 -0
  62. data/lib/x/resources/topic.rb +59 -0
  63. data/lib/x/resources/trend.rb +130 -0
  64. data/lib/x/resources/user.rb +502 -0
  65. data/lib/x/resources/user_collections.rb +213 -0
  66. data/lib/x/resources/user_finders.rb +282 -0
  67. data/lib/x/resources/utils.rb +358 -0
  68. data/lib/x/resources/value_equality.rb +38 -0
  69. data/lib/x/resources/value_marshalling.rb +89 -0
  70. data/lib/x/resources/version.rb +25 -0
  71. data/lib/x/resources.rb +22 -0
  72. data/sig/manifest.yaml +7 -0
  73. data/sig/x-resources.rbs +813 -0
  74. 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