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,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module X
|
|
4
|
+
module Resources
|
|
5
|
+
# The number the API publishes for a collection of a resource, such as the followers_count of a user, which a
|
|
6
|
+
# cursor over the collection reads, included into Resource
|
|
7
|
+
# @api private
|
|
8
|
+
module PublishedCount
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
# A block reading the attribute holding the number the API publishes
|
|
12
|
+
#
|
|
13
|
+
# A resource without the attribute, such as a stub, is hydrated to read it, which costs one lookup rather than
|
|
14
|
+
# paging through the collection. Given fresh: true, as by a cursor that was refreshed, the block looks the
|
|
15
|
+
# resource up again, as refresh does, to read the number the API publishes now.
|
|
16
|
+
#
|
|
17
|
+
# @api private
|
|
18
|
+
# @param total [Symbol, nil] the attribute name, or nil if the API publishes no number
|
|
19
|
+
# @return [Proc, nil] the block, or nil if the API publishes no number
|
|
20
|
+
def counter(total)
|
|
21
|
+
return if total.nil?
|
|
22
|
+
|
|
23
|
+
lambda do |fresh: false|
|
|
24
|
+
# @type var fresh: bool
|
|
25
|
+
fresh ? refresh&.public_send(total) : public_send(total) || hydrate&.public_send(total)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
private_constant :PublishedCount
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module X
|
|
4
|
+
module Resources
|
|
5
|
+
# Resolves the posts a post refers to through its referenced_posts attribute
|
|
6
|
+
#
|
|
7
|
+
# Internal to x-resources: the methods it gives a post, such as replied_to, are public API, but the module is only how
|
|
8
|
+
# they are shared, and which classes extend or include it can change within 1.x.
|
|
9
|
+
#
|
|
10
|
+
# @api semipublic
|
|
11
|
+
module References
|
|
12
|
+
# Referenced post type for replies
|
|
13
|
+
# @api private
|
|
14
|
+
REPLIED_TO = "replied_to"
|
|
15
|
+
# Referenced post type for quotes
|
|
16
|
+
# @api private
|
|
17
|
+
QUOTED = "quoted"
|
|
18
|
+
# Referenced post type for reposts, as the API labels them
|
|
19
|
+
# @api private
|
|
20
|
+
REPOSTED = "reposted"
|
|
21
|
+
# Referenced post type for reposts, as the API documentation labels them
|
|
22
|
+
# @api private
|
|
23
|
+
RETWEETED = "retweeted"
|
|
24
|
+
private_constant :REPLIED_TO, :QUOTED, :REPOSTED, :RETWEETED
|
|
25
|
+
|
|
26
|
+
# The referenced posts, resolved from the includes or built as stubs
|
|
27
|
+
#
|
|
28
|
+
# @api public
|
|
29
|
+
# @return [Array<Post>] the referenced posts
|
|
30
|
+
# @raise [InvalidAttribute] if the response holds a referenced post that is not an object
|
|
31
|
+
# @example Get the referenced posts
|
|
32
|
+
# post.references
|
|
33
|
+
def references
|
|
34
|
+
referenced_posts.filter_map do |reference|
|
|
35
|
+
resolve(Post, reference["id"]) #: Post?
|
|
36
|
+
end.freeze
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# The post this post replies to
|
|
40
|
+
#
|
|
41
|
+
# @api public
|
|
42
|
+
# @return [Post, nil] the replied-to post
|
|
43
|
+
# @raise [InvalidAttribute] if the response holds a referenced post that is not an object
|
|
44
|
+
# @example Get the replied-to post
|
|
45
|
+
# post.replied_to
|
|
46
|
+
def replied_to
|
|
47
|
+
reference(REPLIED_TO)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# The post this post quotes
|
|
51
|
+
#
|
|
52
|
+
# @api public
|
|
53
|
+
# @return [Post, nil] the quoted post
|
|
54
|
+
# @raise [InvalidAttribute] if the response holds a referenced post that is not an object
|
|
55
|
+
# @example Get the quoted post
|
|
56
|
+
# post.quoted
|
|
57
|
+
def quoted
|
|
58
|
+
reference(QUOTED)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The post this post reposts
|
|
62
|
+
#
|
|
63
|
+
# @api public
|
|
64
|
+
# @return [Post, nil] the reposted post
|
|
65
|
+
# @raise [InvalidAttribute] if the response holds a referenced post that is not an object
|
|
66
|
+
# @example Get the reposted post
|
|
67
|
+
# post.reposted
|
|
68
|
+
def reposted
|
|
69
|
+
reference(REPOSTED, RETWEETED)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Check whether this post is a reply
|
|
73
|
+
#
|
|
74
|
+
# @api public
|
|
75
|
+
# @return [Boolean] true if the post replies to another post
|
|
76
|
+
# @example Check whether a post is a reply
|
|
77
|
+
# post.reply?
|
|
78
|
+
def reply?
|
|
79
|
+
!replied_to.nil?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Check whether this post is a quote
|
|
83
|
+
#
|
|
84
|
+
# @api public
|
|
85
|
+
# @return [Boolean] true if the post quotes another post
|
|
86
|
+
# @example Check whether a post is a quote
|
|
87
|
+
# post.quote?
|
|
88
|
+
def quote?
|
|
89
|
+
!quoted.nil?
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Check whether this post is a repost
|
|
93
|
+
#
|
|
94
|
+
# @api public
|
|
95
|
+
# @return [Boolean] true if the post reposts another post
|
|
96
|
+
# @example Check whether a post is a repost
|
|
97
|
+
# post.repost?
|
|
98
|
+
def repost?
|
|
99
|
+
!reposted.nil?
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
alias_method :retweeted, :reposted
|
|
103
|
+
alias_method :retweet?, :repost?
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
# Resolve the first referenced post of any of some types
|
|
108
|
+
# @api private
|
|
109
|
+
# @param types [Array<String>] the referenced post types
|
|
110
|
+
# @return [Post, nil] the referenced post or nil if there is none of those types
|
|
111
|
+
# @raise [InvalidAttribute] if the response holds a referenced post that is not an object
|
|
112
|
+
def reference(*types)
|
|
113
|
+
found = referenced_posts.find { |element| types.include?(element["type"]) }
|
|
114
|
+
return if found.nil?
|
|
115
|
+
|
|
116
|
+
resolve(Post, found["id"]) #: Post?
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
private_constant :References
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "user"
|
|
4
|
+
require_relative "utils"
|
|
5
|
+
|
|
6
|
+
module X
|
|
7
|
+
module Resources
|
|
8
|
+
# Relating users, posts, and lists to the authenticated user, and removing the relations
|
|
9
|
+
#
|
|
10
|
+
# A relation, such as following a user or liking a post, is written as the authenticated user alone, so the methods
|
|
11
|
+
# of a client that write one, such as follow and like, write it here, for the user the client authenticates as,
|
|
12
|
+
# and no user does.
|
|
13
|
+
#
|
|
14
|
+
# @api private
|
|
15
|
+
module RelationWrites
|
|
16
|
+
extend self
|
|
17
|
+
|
|
18
|
+
# Relate a resource to the authenticated user and report the resulting state
|
|
19
|
+
#
|
|
20
|
+
# @api private
|
|
21
|
+
# @param client [Object] the client used to make the request
|
|
22
|
+
# @param user [Integer, String] the identifier of the authenticated user
|
|
23
|
+
# @param relation [String] the relation endpoint, such as following, likes, or pinned_lists
|
|
24
|
+
# @param field [Hash{String => String}] the request body, the identifier of the related resource under its field
|
|
25
|
+
# @param states [Array<String>] the response fields reporting the state, any of which is true once it exists
|
|
26
|
+
# @return [Boolean] true if the relation now exists
|
|
27
|
+
# @raise [ArgumentError] if the identifier of the authenticated user is not a number, before a request
|
|
28
|
+
# @example Like a post as the authenticated user
|
|
29
|
+
# X::Resources::RelationWrites.relate(client, client.current_user_id, "likes", {"tweet_id" => "1234567890"}, "liked")
|
|
30
|
+
def relate(client, user, relation, field, *states)
|
|
31
|
+
body = client.post("users/#{Utils.id_of(user, User)}/#{relation}", field, **Utils::JSON_CLASSES)
|
|
32
|
+
states.any? { |state| Utils.written(body, state).eql?(true) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Remove a relation from the authenticated user and report the resulting state
|
|
36
|
+
#
|
|
37
|
+
# @api private
|
|
38
|
+
# @param client [Object] the client used to make the request
|
|
39
|
+
# @param user [Integer, String] the identifier of the authenticated user
|
|
40
|
+
# @param relation [String] the relation endpoint, such as following, likes, or pinned_lists
|
|
41
|
+
# @param target [String] the identifier of the related resource
|
|
42
|
+
# @param state [String] the response field reporting the state
|
|
43
|
+
# @return [Boolean] true if the relation no longer exists
|
|
44
|
+
# @raise [ArgumentError] if the identifier of the authenticated user is not a number, before a request
|
|
45
|
+
# @example Unlike a post as the authenticated user
|
|
46
|
+
# X::Resources::RelationWrites.unrelate(client, client.current_user_id, "likes", "1234567890", "liked")
|
|
47
|
+
def unrelate(client, user, relation, target, state)
|
|
48
|
+
body = client.delete("users/#{Utils.id_of(user, User)}/#{relation}/#{target}", **Utils::JSON_CLASSES)
|
|
49
|
+
Utils.written(body, state).eql?(false)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
private_constant :RelationWrites
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "x/core"
|
|
4
|
+
require_relative "page_limit"
|
|
5
|
+
require_relative "utils"
|
|
6
|
+
|
|
7
|
+
module X
|
|
8
|
+
module Resources
|
|
9
|
+
# The relationships of a user with other users, read as the authenticated user sees them
|
|
10
|
+
#
|
|
11
|
+
# It reads them and changes none: a relationship changes as the authenticated user alone, so the client changes it,
|
|
12
|
+
# as in client.follow(user), rather than a user, which may be anyone.
|
|
13
|
+
#
|
|
14
|
+
# Internal to x-resources: the methods it gives a user, such as follows?, are public API, but the module is only how
|
|
15
|
+
# they are shared, and which classes extend or include it can change within 1.x.
|
|
16
|
+
#
|
|
17
|
+
# @api semipublic
|
|
18
|
+
module Relationships
|
|
19
|
+
# Check whether this user follows a user
|
|
20
|
+
#
|
|
21
|
+
# When either user is the authenticated user, one lookup of the other's connection_status answers.
|
|
22
|
+
# Otherwise the users this user follows are scanned until one matches, up to 1,000 a page, and the
|
|
23
|
+
# API bills every user returned, so checking an account that follows thousands can cost dollars, and max_pages
|
|
24
|
+
# limits the pages the scan reads, raising PageLimitReached rather than read past them. A client that
|
|
25
|
+
# authenticates as the app alone has no authenticated user, which the API refuses to look up, so it scans.
|
|
26
|
+
# Any other failure to look the authenticated user up raises, rather than scan every user this one follows.
|
|
27
|
+
#
|
|
28
|
+
# @api public
|
|
29
|
+
# @param user [User, String, Integer] the user or their identifier
|
|
30
|
+
# @param max_pages [Integer, nil] the most pages of followed users to scan, or nil for no limit
|
|
31
|
+
# @return [Boolean] true if this user follows the user
|
|
32
|
+
# @raise [ArgumentError] if max_pages is neither an Integer of at least 1 nor nil, before a request
|
|
33
|
+
# @raise [PageLimitReached] if the scan reads max_pages pages without the user, and the API names another
|
|
34
|
+
# @example Check whether the authenticated user follows someone, in one lookup
|
|
35
|
+
# client.current_user!.follows?(other)
|
|
36
|
+
# @example Scan no more than five pages of the users another user follows
|
|
37
|
+
# X::User.find("jack", client: client).follows?(other, max_pages: 5)
|
|
38
|
+
def follows?(user, max_pages: nil)
|
|
39
|
+
target, max_pages = User.from_id(user), PageLimit.check!(max_pages)
|
|
40
|
+
case authenticated_user_id
|
|
41
|
+
when id then connection_status_of(target).include?("following")
|
|
42
|
+
when target.id then connection_status_of(self).include?("followed_by")
|
|
43
|
+
else PageLimit.scan(following.stubs, target, what: "User#follows?", max_pages:)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
# The identifier of the authenticated user, when the client knows it
|
|
50
|
+
#
|
|
51
|
+
# A client that authenticates as the app alone has no authenticated user, and asks the API for one in vain,
|
|
52
|
+
# so the refusal of the credentials of the client leaves the identifier unknown rather than end the check. Any
|
|
53
|
+
# other error, such as a rate limit, a failure of the API, or of the network, ends it, since a scan in its place
|
|
54
|
+
# would page through every user this one follows, which the API bills.
|
|
55
|
+
#
|
|
56
|
+
# @api private
|
|
57
|
+
# @return [Integer, nil] the identifier, or nil if the client has no current_user_id or cannot read one
|
|
58
|
+
# @raise [X::Error] if the API fails to answer for another reason than the credentials of the client
|
|
59
|
+
def authenticated_user_id
|
|
60
|
+
current = client! #: untyped
|
|
61
|
+
current.current_user_id if current.respond_to?(:current_user_id)
|
|
62
|
+
rescue X::Forbidden, X::Unauthorized
|
|
63
|
+
nil
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# How the authenticated user is connected to a user, in one lookup
|
|
67
|
+
# @api private
|
|
68
|
+
# @param user [User, String, Integer] the user or their identifier
|
|
69
|
+
# @return [Array<String>] the connection statuses, empty if the user was not found
|
|
70
|
+
def connection_status_of(user)
|
|
71
|
+
found = User.find(user, client: client!, "user.fields": "connection_status", "post.fields": nil, expansions: nil)
|
|
72
|
+
Array(found&.connection_status)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
private_constant :Relationships
|
|
76
|
+
end
|
|
77
|
+
end
|