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,502 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+ require_relative "relationships"
5
+ require_relative "cursor"
6
+ require_relative "resource"
7
+ require_relative "user_collections"
8
+ require_relative "user_finders"
9
+
10
+ module X
11
+ module Resources
12
+ # A user account
13
+ # @api public
14
+ class ::X::User < Resource
15
+ include Relationships
16
+ include UserCollections
17
+ extend UserFinders
18
+
19
+ # The mixins of the class by their full names, which YARD needs to resolve them in a class opened under X
20
+ #
21
+ # @!parse
22
+ # include X::Resources::Relationships
23
+ # include X::Resources::UserCollections
24
+ # extend X::Resources::UserFinders
25
+
26
+ # The user fields the object layer requests: every one that does not depend on who is authenticated, since a
27
+ # field that does, such as connection_status, would make every request fail for a client that cannot read it;
28
+ # the identifiers of referenced posts, and the affiliation, come with their expansions
29
+ #
30
+ # A minor release may add to it the fields the API adds, so that a lookup asks for them too; see
31
+ # {Resource#hydrated?} for what that means for a resource looked up with a list of fields of its own.
32
+ FIELDS = %w[created_at description entities id is_identity_verified location name parody profile_banner_url
33
+ profile_image_url protected public_metrics subscriber_count subscription_type url username verified
34
+ verified_followers_count verified_type withheld].freeze
35
+ # Every expansion available on user endpoints
36
+ #
37
+ # The API gives a user its affiliation when a request asks for the affiliation expansion, which it takes only
38
+ # at the endpoints whose data is users, so the users a post, a list, a space, or a direct message includes hold
39
+ # none, and hydrate looks them up with it.
40
+ #
41
+ # A minor release may add to it the expansions the API adds, so that a lookup asks for them too; see
42
+ # {Resource#hydrated?} for what that means for a resource looked up with a list of expansions of its own.
43
+ EXPANSIONS = %w[affiliation most_recent_post_id pinned_post_id].freeze
44
+ # Maximum number of users per page of a user search
45
+ # @api private
46
+ MAX_SEARCH_RESULTS = 1000
47
+ private_constant :MAX_SEARCH_RESULTS
48
+
49
+ class << self
50
+ # The API endpoint used to look up users by identifier
51
+ #
52
+ # @api private
53
+ # @return [String] the endpoint
54
+ # @example Get the endpoint
55
+ # X::User.__send__(:endpoint) # => "users"
56
+ def endpoint = "users"
57
+
58
+ # The key under which users appear in the includes of a response
59
+ #
60
+ # @api private
61
+ # @return [String] the includes key
62
+ # @example Get the includes key
63
+ # X::User.__send__(:includes_key) # => "users"
64
+ def includes_key = "users"
65
+
66
+ # The query parameter that selects user fields
67
+ #
68
+ # @api private
69
+ # @return [String] the fields parameter
70
+ # @example Get the fields parameter
71
+ # X::User.__send__(:fields_key) # => "user.fields"
72
+ def fields_key = "user.fields"
73
+
74
+ private :endpoint, :includes_key, :fields_key
75
+
76
+ # The default query parameters requesting every user field and expansion
77
+ #
78
+ # @api public
79
+ # @return [Hash{String => Array<String>}] the default query parameters
80
+ # @example Get the default parameters
81
+ # X::User.default_params["user.fields"]
82
+ def default_params
83
+ {"user.fields" => FIELDS, "post.fields" => Post::FIELDS, "expansions" => EXPANSIONS}
84
+ end
85
+
86
+ # Search users
87
+ #
88
+ # @api public
89
+ # @param query [String] the search query
90
+ # @param client [Object] the client used to make the requests
91
+ # @param params [Hash] query parameters merged over the default parameters
92
+ # @return [Cursor] a cursor over the matching users
93
+ # @example Print the users matching a query
94
+ # X::User.search("ruby", client: client).each { |user| puts user.username }
95
+ def search(query, client:, **params)
96
+ Cursor.__send__(:build, self, "users/search", client:, params: {query:, max_results: MAX_SEARCH_RESULTS}.merge(params), token_param: "next_token")
97
+ end
98
+ end
99
+
100
+ # @!attribute [r] name
101
+ # The display name
102
+ # @api public
103
+ # @return [String, nil] the display name
104
+ # @example Get the name
105
+ # user.name # => "Erik Berlin"
106
+ attribute :name
107
+
108
+ # @!attribute [r] username
109
+ # The handle, without the leading at sign
110
+ # @api public
111
+ # @return [String, nil] the username
112
+ # @example Get the username
113
+ # user.username # => "sferik"
114
+ attribute :username
115
+
116
+ # @!attribute [r] description
117
+ # The profile description
118
+ # @api public
119
+ # @return [String, nil] the description
120
+ # @example Get the description
121
+ # user.description
122
+ attribute :description
123
+
124
+ # @!attribute [r] location
125
+ # The profile location
126
+ # @api public
127
+ # @return [String, nil] the location
128
+ # @example Get the location
129
+ # user.location
130
+ attribute :location
131
+
132
+ # @!attribute [r] url
133
+ # The URL of the website the profile links to
134
+ #
135
+ # The API gives it shortened, as a t.co link, and the URL it stands for is in the url entities of
136
+ # {#entities}. It is not the address of the profile on x.com, which {#permalink} and {#uri} read.
137
+ #
138
+ # @api public
139
+ # @return [String, nil] the URL, or nil for a profile that links to no website
140
+ # @example Get the URL
141
+ # user.url # => "https://t.co/abc"
142
+ attribute :url
143
+
144
+ # @!attribute [r] profile_image_url
145
+ # The profile image URL
146
+ # @api public
147
+ # @return [String, nil] the profile image URL
148
+ # @example Get the profile image URL
149
+ # user.profile_image_url
150
+ attribute :profile_image_url
151
+
152
+ # @!attribute [r] profile_banner_url
153
+ # The profile banner URL
154
+ # @api public
155
+ # @return [String, nil] the profile banner URL
156
+ # @example Get the profile banner URL
157
+ # user.profile_banner_url
158
+ attribute :profile_banner_url
159
+
160
+ # @!attribute [r] created_at
161
+ # The time when the account was created
162
+ # @api public
163
+ # @return [Time, nil] the creation time
164
+ # @example Get the creation time
165
+ # user.created_at
166
+ attribute :created_at, :time
167
+
168
+ # @!attribute [r] protected
169
+ # Whether the account's posts are protected
170
+ # @api public
171
+ # @return [Boolean, nil] true if the posts are protected
172
+ # @example Check whether a user is protected
173
+ # user.protected?
174
+ attribute :protected, :boolean
175
+
176
+ # @!method protected?
177
+ # Check whether the account's posts are protected
178
+ # @api public
179
+ # @return [Boolean] true if the posts are protected
180
+ # @example Check whether the account's posts are protected
181
+ # user.protected?
182
+
183
+ # @!attribute [r] verified
184
+ # Whether the account is verified
185
+ # @api public
186
+ # @return [Boolean, nil] true if the account is verified
187
+ # @example Check whether a user is verified
188
+ # user.verified?
189
+ attribute :verified, :boolean
190
+
191
+ # @!method verified?
192
+ # Check whether the account is verified
193
+ # @api public
194
+ # @return [Boolean] true if the account is verified
195
+ # @example Check whether the account is verified
196
+ # user.verified?
197
+
198
+ # @!attribute [r] verified_type
199
+ # The verification type: blue, business, government, or none
200
+ # @api public
201
+ # @return [String, nil] the verification type
202
+ # @example Get the verification type
203
+ # user.verified_type
204
+ attribute :verified_type
205
+
206
+ # @!attribute [r] parody
207
+ # Whether the account labels itself a parody
208
+ # @api public
209
+ # @return [Boolean, nil] true if the account is labelled a parody
210
+ # @example Check whether a user is a parody account
211
+ # user.parody?
212
+ attribute :parody, :boolean
213
+
214
+ # @!method parody?
215
+ # Check whether the account labels itself a parody
216
+ # @api public
217
+ # @return [Boolean] true if the account is labelled a parody
218
+ # @example Leave out the parody accounts
219
+ # users.reject(&:parody?)
220
+
221
+ # @!attribute [r] identity_verified
222
+ # Whether the account's identity is verified, the is_identity_verified field
223
+ # @api public
224
+ # @return [Boolean, nil] true if the identity is verified
225
+ # @example Check whether a user's identity is verified
226
+ # user.identity_verified?
227
+ attribute :identity_verified, :boolean, key: %w[is_identity_verified]
228
+
229
+ # @!method identity_verified?
230
+ # Check whether the account's identity is verified
231
+ # @api public
232
+ # @return [Boolean] true if the identity is verified
233
+ # @example Check whether a user's identity is verified
234
+ # user.identity_verified?
235
+
236
+ # @!attribute [r] subscription_type
237
+ # The subscription the account pays for: Basic, Premium, PremiumPlus, or None
238
+ # @api public
239
+ # @return [String, nil] the subscription type
240
+ # @example Get the subscription type
241
+ # user.subscription_type
242
+ attribute :subscription_type
243
+
244
+ # @!attribute [r] affiliation
245
+ # The organization the account is affiliated with, with its badge
246
+ #
247
+ # The API gives it for the affiliation expansion, which only the endpoints whose data is users take, so a user
248
+ # a post, a list, a space, or a direct message includes holds none until it is hydrated.
249
+ #
250
+ # @api public
251
+ # @return [Hash, nil] the affiliation, with its description, url, badge_url, and user_id
252
+ # @example Get the affiliated organization
253
+ # user.affiliation&.fetch("description")
254
+ attribute :affiliation, :object
255
+
256
+ # @!attribute [r] affiliated_with_ids
257
+ # The identifiers of the accounts this account is affiliated with
258
+ # @api public
259
+ # @return [Array<Integer>] the identifiers, empty if there are none
260
+ # @example Get the identifiers of the accounts it is affiliated with
261
+ # user.affiliated_with_ids
262
+ attribute :affiliated_with_ids, :integers, key: %w[affiliation user_id]
263
+
264
+ # @!attribute [r] verified_followers_count
265
+ # The number of verified followers
266
+ # @api public
267
+ # @return [Integer, nil] the verified follower count
268
+ # @example Get the verified follower count
269
+ # user.verified_followers_count
270
+ attribute :verified_followers_count, :integer
271
+
272
+ # @!attribute [r] subscriber_count
273
+ # The number of users who subscribe to the user
274
+ # @api public
275
+ # @return [Integer, nil] the subscriber count
276
+ # @example Get the subscriber count
277
+ # user.subscriber_count
278
+ attribute :subscriber_count, :integer
279
+
280
+ # @!attribute [r] connection_status
281
+ # How the authenticated user and this user are connected
282
+ #
283
+ # No lookup asks for it unless user.fields names it, so a user looked up otherwise holds none, and reads nil
284
+ # rather than an empty list, which would say the users are not connected.
285
+ #
286
+ # @api public
287
+ # @return [Array<String>, nil] following, followed_by, blocking, muting, follow_request_sent, or
288
+ # follow_request_received, or nil when the response holds none
289
+ # @example Check whether this user follows the authenticated user
290
+ # client.find_user("sferik", "user.fields": "connection_status").connection_status.include?("followed_by")
291
+ attribute :connection_status, :requested_list
292
+
293
+ # @!attribute [r] receives_your_dm
294
+ # Whether the authenticated user can send this user a direct message
295
+ #
296
+ # It depends on who is authenticated, so no lookup asks for it unless user.fields names it, and a user looked
297
+ # up otherwise holds none, and reads nil rather than false, which would say the user receives none.
298
+ #
299
+ # @api public
300
+ # @return [Boolean, nil] true if the authenticated user can send this user a direct message, or nil when the
301
+ # response holds none
302
+ # @example Check whether a user can be sent a direct message
303
+ # client.find_user("sferik", "user.fields": "receives_your_dm").receives_your_dm?
304
+ attribute :receives_your_dm, :boolean
305
+
306
+ # @!method receives_your_dm?
307
+ # Check whether the authenticated user can send this user a direct message
308
+ #
309
+ # A user whose response holds no receives_your_dm, such as one looked up without asking for it, is not known to
310
+ # receive one, and reads false.
311
+ #
312
+ # @api public
313
+ # @return [Boolean] true if the response says the authenticated user can send this user a direct message
314
+ # @example Message the users who can be sent one
315
+ # users.select(&:receives_your_dm?).each { |user| client.create_dm(user, "Hello!") }
316
+
317
+ # @!attribute [r] subscribes_to_you
318
+ # Whether this user subscribes to the authenticated user
319
+ #
320
+ # It depends on who is authenticated, so no lookup asks for it unless user.fields names it, and a user looked
321
+ # up otherwise holds none, and reads nil rather than false.
322
+ #
323
+ # @api public
324
+ # @return [Boolean, nil] true if this user subscribes to the authenticated user, or nil when the response holds
325
+ # none
326
+ # @example Check whether a user subscribes to the authenticated user
327
+ # client.find_user("sferik", "user.fields": "subscribes_to_you").subscribes_to_you?
328
+ attribute :subscribes_to_you, :boolean
329
+
330
+ # @!method subscribes_to_you?
331
+ # Check whether this user subscribes to the authenticated user
332
+ #
333
+ # A user whose response holds no subscribes_to_you, such as one looked up without asking for it, reads false.
334
+ #
335
+ # @api public
336
+ # @return [Boolean] true if the response says this user subscribes to the authenticated user
337
+ # @example Find the subscribers among some users
338
+ # users.select(&:subscribes_to_you?)
339
+
340
+ # @!attribute [r] subscription
341
+ # The subscription between this user and the authenticated user
342
+ #
343
+ # It depends on who is authenticated, so no lookup asks for it unless user.fields names it, and a user looked
344
+ # up otherwise holds none.
345
+ #
346
+ # @api public
347
+ # @return [Hash, nil] the subscription, with subscribes_to_you, or nil when the response holds none
348
+ # @example Check whether a user subscribes to the authenticated user
349
+ # client.find_user("sferik", "user.fields": "subscription").subscription&.fetch("subscribes_to_you")
350
+ attribute :subscription, :object
351
+
352
+ # @!attribute [r] pinned_post_id
353
+ # The identifier of the pinned post
354
+ # @api public
355
+ # @return [Integer, nil] the pinned post identifier
356
+ # @example Get the pinned post identifier
357
+ # user.pinned_post_id
358
+ attribute :pinned_post_id, :integer, tweet_key: %w[pinned_tweet_id]
359
+
360
+ # @!attribute [r] most_recent_post_id
361
+ # The identifier of the most recent post
362
+ # @api public
363
+ # @return [Integer, nil] the most recent post identifier
364
+ # @example Get the most recent post identifier
365
+ # user.most_recent_post_id
366
+ attribute :most_recent_post_id, :integer, tweet_key: %w[most_recent_tweet_id]
367
+
368
+ # @!attribute [r] entities
369
+ # The entities found in the description and URL
370
+ # @api public
371
+ # @return [Hash, nil] the entities
372
+ # @example Get the entities
373
+ # user.entities
374
+ attribute :entities, :object
375
+
376
+ # @!attribute [r] withheld
377
+ # The withholding details
378
+ # @api public
379
+ # @return [Hash, nil] the withholding details
380
+ # @example Get the withholding details
381
+ # user.withheld
382
+ attribute :withheld, :object
383
+
384
+ # @!attribute [r] public_metrics
385
+ # The public metrics
386
+ # @api public
387
+ # @return [Hash, nil] the public metrics
388
+ # @example Get the public metrics
389
+ # user.public_metrics
390
+ attribute :public_metrics, :object
391
+
392
+ # @!attribute [r] followers_count
393
+ # The number of followers
394
+ # @api public
395
+ # @return [Integer, nil] the follower count
396
+ # @example Get the follower count
397
+ # user.followers_count
398
+ attribute :followers_count, :integer, key: %w[public_metrics followers_count]
399
+
400
+ # @!attribute [r] following_count
401
+ # The number of followed users
402
+ # @api public
403
+ # @return [Integer, nil] the following count
404
+ # @example Get the following count
405
+ # user.following_count
406
+ attribute :following_count, :integer, key: %w[public_metrics following_count]
407
+
408
+ # @!attribute [r] post_count
409
+ # The number of posts
410
+ # @api public
411
+ # @return [Integer, nil] the post count
412
+ # @example Get the post count
413
+ # user.post_count
414
+ attribute :post_count, :integer, key: %w[public_metrics post_count], tweet_key: %w[public_metrics tweet_count]
415
+
416
+ # @!attribute [r] listed_count
417
+ # The number of lists the user is a member of
418
+ # @api public
419
+ # @return [Integer, nil] the listed count
420
+ # @example Get the listed count
421
+ # user.listed_count
422
+ attribute :listed_count, :integer, key: %w[public_metrics listed_count]
423
+
424
+ # @!attribute [r] like_count
425
+ # The number of posts the user has liked
426
+ # @api public
427
+ # @return [Integer, nil] the like count
428
+ # @example Get the like count
429
+ # user.like_count
430
+ attribute :like_count, :integer, key: %w[public_metrics like_count]
431
+
432
+ # @!attribute [r] media_count
433
+ # The number of photos and videos the user has posted
434
+ # @api public
435
+ # @return [Integer, nil] the media count
436
+ # @example Get the media count
437
+ # user.media_count
438
+ attribute :media_count, :integer, key: %w[public_metrics media_count]
439
+
440
+ # @!method pinned_post
441
+ # The pinned post, from the includes or as a stub holding only its identifier
442
+ # @api public
443
+ # @return [Post, nil] the pinned post
444
+ # @example Get the pinned post
445
+ # user.pinned_post
446
+ reference :pinned_post, :Post, key: %w[pinned_post_id], tweet_key: %w[pinned_tweet_id]
447
+
448
+ # @!method most_recent_post
449
+ # The most recent post, from the includes or as a stub holding only its identifier
450
+ # @api public
451
+ # @return [Post, nil] the most recent post
452
+ # @example Get the most recent post
453
+ # user.most_recent_post
454
+ reference :most_recent_post, :Post, key: %w[most_recent_post_id], tweet_key: %w[most_recent_tweet_id]
455
+
456
+ # @!method affiliated_with
457
+ # The accounts this account is affiliated with, such as its organization
458
+ #
459
+ # They come from the includes, or as stubs holding their identifiers. They point the other way from
460
+ # affiliates, the accounts affiliated with this one.
461
+ # @api public
462
+ # @return [Array<User>] the accounts it is affiliated with, empty if there are none
463
+ # @example Get the username of the affiliated organization
464
+ # user.affiliated_with.first&.hydrate&.username
465
+ references :affiliated_with, :User, key: %w[affiliation user_id]
466
+
467
+ # The other names of attributes, as the aliases YARD reads them as
468
+ #
469
+ # @!parse
470
+ # alias_method :tweet_count, :post_count
471
+ # alias_method :pinned_tweet_id, :pinned_post_id
472
+ # alias_method :most_recent_tweet_id, :most_recent_post_id
473
+ attribute_alias :tweet_count, :post_count
474
+ attribute_alias :pinned_tweet_id, :pinned_post_id
475
+ attribute_alias :most_recent_tweet_id, :most_recent_post_id
476
+ alias_method :pinned_tweet, :pinned_post
477
+ alias_method :most_recent_tweet, :most_recent_post
478
+
479
+ # The permalink of the profile, by username when known and by identifier otherwise
480
+ #
481
+ # It is the address of the profile on x.com, not the website the profile links to, which {#url} reads.
482
+ #
483
+ # @api public
484
+ # @return [String] the x.com address of the profile
485
+ # @raise [InvalidAttribute] if the response holds the username as something other than a String
486
+ # @example Get the permalink
487
+ # user.permalink # => "https://x.com/sferik"
488
+ def permalink = "https://x.com/#{Shape.read_string("#{self.class}#permalink", username) || "i/user/#{id}"}"
489
+
490
+ # The permalink of the user as a URI
491
+ #
492
+ # It is the address of the profile on x.com, not the website the profile links to, which {#url} reads.
493
+ #
494
+ # @api public
495
+ # @return [URI::Generic] the x.com address of the user
496
+ # @raise [InvalidAttribute] if the response holds the username as something other than a String
497
+ # @example Get the address as a URI
498
+ # user.uri # => #<URI::HTTPS https://x.com/sferik>
499
+ def uri = URI(permalink)
500
+ end
501
+ end
502
+ end