rublox 0.2.0 → 0.3.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.
@@ -1,98 +1,59 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "time"
3
+ require "rublox/derive/model"
4
+ require "rublox/bases/base_user"
4
5
 
5
6
  module Rublox
6
- # The state of the presence.
7
- #
8
- # Can be offline, online/on the website, playing a game, developing on studio.
9
- module PresenceType
10
- # The user is/was offline.
11
- OFFLINE = :Offline
12
- # The user is/was online (this also applies to the website).
13
- ONLINE = :Online
14
- # The user is/was playing a game.
15
- GAME = :"In game"
16
- # The user is/was developing on studio.
17
- STUDIO = :"In Studio"
18
-
19
- # @!visibility private
20
- PRESENCE_MAP = [
21
- OFFLINE,
22
- ONLINE,
23
- GAME,
24
- STUDIO
25
- ].freeze
26
-
27
- # Convert the Roblox PresenceType enum response to rublox's PresenceType
28
- # equivalent.
29
- # @!visibility private
30
- # @param enum [Integer]
31
- # @return [Symbol]
32
- def self.enum_to_presence_type(enum)
33
- PRESENCE_MAP[enum]
34
- end
35
- end
36
-
37
- # @note This class is handled internally by the public interface such as
38
- # {Client#user_presence_from_id}. You should not be creating it yourself.
39
- # The {Presence} class corresponds to a response you can get via
40
- # https://presence.roblox.com/v1/presence/users. You can use it to get information
41
- # about the presence states of users.
42
- class Presence
43
- # @return [PresenceType] the current presence type
44
- attr_reader :presence_type
45
-
46
- # @return [PresenceType] the last presence type of the user
47
- attr_reader :last_presence_type
48
-
49
- # @note Unlike it sounds, this is not a numerical ID like of a user. It's a
50
- # randomly generated string with hexadecimal numbers containing the server's
51
- # job ID (which looks like "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"), it can be
52
- # also accessed in-game via the game.JobId property.
53
- # @return [String, nil] the job ID of the game last played by the user.
54
- attr_reader :game_job_id
55
-
56
- # @return [Time] the date at which the user was last online
57
- attr_reader :last_online_date
58
-
59
- def initialize(data, client)
60
- @presence_type = PresenceType.enum_to_presence_type(data["userPresenceType"])
61
- @last_location = data["lastLocation"]
62
- @place_id = data["placeId"]
63
- @root_place_id = data["rootPlaceId"]
64
- @game_job_id = data["gameId"]
65
- @universe_id = data["universeId"]
66
- @user_id = data["userId"]
67
- @last_online_date = Time.iso8601(data["lastOnline"])
68
-
69
- @client = client
70
- end
71
-
72
- # @todo add Place class
73
- # @return [Place, nil] the place last visited by the user, can be nil if the
74
- # user has never played a game
75
- def place
76
- return unless @place_id
77
- end
78
-
79
- # @todo add Place class
80
- # @return [Place, nil] the root of the place last visited by the user, can
81
- # be nil if the user has never played a game
82
- def root_place
83
- return unless @root_place_id
84
- end
85
-
86
- # @todo add Universe class
87
- # @return [Universe, nil] the universe of the place last visited by the user,
88
- # can be nil if the user has never played a game
89
- def universe
90
- return unless @universe_id
91
- end
92
-
93
- # @return [FullUser] the user tied to the presence
94
- def user
95
- @client.user_from_id(@user_id)
96
- end
97
- end
7
+ module Models
8
+ module PresenceType
9
+ OFFLINE = :offline
10
+ ONLINE = :online
11
+ IN_GAME = :in_game
12
+ IN_STUDIO = :in_studio
13
+
14
+ PRESENCE_MAP = [
15
+ OFFLINE,
16
+ ONLINE,
17
+ IN_GAME,
18
+ IN_STUDIO
19
+ ].freeze
20
+ private_constant :PRESENCE_MAP
21
+
22
+ # @!visibility private
23
+ def self.get(enum)
24
+ PRESENCE_MAP[enum]
25
+ end
26
+ end
27
+
28
+ class Presence
29
+ include Derive::Model
30
+
31
+ # @return [PresenceType]
32
+ attr_reader :user_presence_type
33
+ # @return [String]
34
+ attr_reader :last_location
35
+ # attr_reader :place_id
36
+ # attr_reader :root_place_id
37
+ # attr_reader :game_id
38
+ # attr_reader :universe_id
39
+ # @return [Bases::BaseUser]
40
+ attr_reader :user
41
+ # @return [Time]
42
+ attr_reader :last_online
43
+
44
+ # @!visibility private
45
+ def initialize(data)
46
+ @user_presence_type = PresenceType.get(data["userPresenceType"])
47
+ @last_location = data["lastLocation"]
48
+ # @place_id = data["placeId"]
49
+ # @root_place_id = data["rootPlaceId"]
50
+ # @game_id = data["gameId"]
51
+ # @universe_id = data["universeId"]
52
+ @user = Bases::BaseUser.new(data["userId"])
53
+ @last_online = Time.iso8601(data["lastOnline"])
54
+
55
+ @id = @user_id
56
+ end
57
+ end
58
+ end
98
59
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rublox/derive/user"
4
+
5
+ module Rublox
6
+ module Models
7
+ class SkinnyUser
8
+ include Derive::User
9
+
10
+ # @return [Integer]
11
+ attr_reader :id
12
+ # @return [String]
13
+ attr_reader :name
14
+ # @return [String]
15
+ attr_reader :display_name
16
+ # @return [Boolean]
17
+ attr_reader :has_verified_badge
18
+
19
+ # @!visibility private
20
+ def initialize(data)
21
+ @id = data["id"] || data["userId"]
22
+ @name = data["name"] || data["username"]
23
+ @display_name = data["displayName"]
24
+ @has_verified_badge = data["hasVerifiedBadge"]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "http"
4
+ require "json"
5
+
6
+ require "rublox/util/errors"
7
+
8
+ module Rublox
9
+ module APIHelper
10
+ @client = HTTP::Client.new
11
+
12
+ # @!visibility private
13
+ def self.roblosecurity=(roblosecurity)
14
+ @client = @client.cookies(
15
+ {
16
+ ".ROBLOSECURITY" => roblosecurity
17
+ }
18
+ )
19
+ end
20
+
21
+ def self.get(url, *args)
22
+ request(:get, url, *args)
23
+ end
24
+
25
+ def self.post(url, *args)
26
+ request(:post, url, *args)
27
+ end
28
+
29
+ def self.patch(url, *args)
30
+ request(:patch, url, *args)
31
+ end
32
+
33
+ def self.delete(url, *args)
34
+ request(:delete, url, *args)
35
+ end
36
+
37
+ def self.request(method, url, *args)
38
+ response = @client.request(method, url, *args)
39
+ case response.status
40
+ when 200
41
+ JSON.parse(response.body)
42
+ when 403
43
+ if JSON.parse(response.body).any? { |error| error.code.zero? }
44
+ @client = @client.headers(
45
+ {
46
+ "x-csrf-token" => response.headers
47
+ }
48
+ )
49
+ end
50
+ when 401
51
+ raise Errors::InvalidROBLOSECURITYError
52
+ else
53
+ raise Errors::UnhandledStatusCodeError.new(response, get_errors_from_response(response))
54
+ end
55
+ end
56
+
57
+ def self.get_errors_from_response(response)
58
+ body = JSON.parse(response.body)
59
+ rescue JSON::ParserError
60
+ "\ncould not parse errors, raw body:\n#{response.body}\n"
61
+ else
62
+ body["errors"].reduce("") do |error_message, error|
63
+ error_message + "\tCode: #{error['code']}\n\tMessage: #{error['message']}\n\n"
64
+ end
65
+ end
66
+
67
+ private_class_method :request, :get_errors_from_response
68
+ end
69
+ end
@@ -1,109 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rublox
4
- # This module contains errors that can be raised by rublox methods.
5
- module Errors
6
- # Exception raised when a user cannot be found.
7
- class UserNotFoundError < StandardError
8
- # @return [Integer, "(no ID information provided)"] the given user's ID
9
- attr_reader :user_id
10
-
11
- # @return [String, "(no username information provided)"] the given user's
12
- # username
13
- attr_reader :user_username
14
-
15
- # @param user_id [Integer]
16
- # @param username [String, nil]
17
- def initialize(
18
- user_id = "(no ID information provided)",
19
- username = "(no username information provided)"
20
- )
21
- @user_id = user_id
22
- @user_username = username
23
- super("The user of ID #{user_id} and username #{username} could not be found.")
24
- end
25
- end
26
-
27
- # Exception raised when a group cannot be found.
28
- class GroupNotFoundError < StandardError
29
- # @return [Integer] the given group's ID
30
- attr_reader :group_id
31
-
32
- # @param group_id [Integer]
33
- def initialize(group_id)
34
- @group_id = group_id
35
- super("The group of ID #{group_id} could not be found.")
36
- end
37
- end
38
-
39
- # Exception raised when a user's presence cannot be found.
40
- class PresenceNotFoundError < StandardError
41
- # @return [Integer] the presence user's ID
42
- attr_reader :user_id
43
-
44
- def initialize(user_id)
45
- @user_id = user_id
46
- super("The presence of the user with ID #{user_id} could not be found.")
47
- end
48
- end
49
-
50
- # Exception raised when a user is not part of a group.
51
- class MemberNotFoundError < StandardError
52
- # @return [Integer] the given user's ID
53
- attr_reader :user_id
54
-
55
- # @return [Integer] the given group's ID
56
- attr_reader :group_id
57
-
58
- # @param id [Integer]
59
- # @param group_id [Integer]
60
- def initialize(id, group_id)
61
- @user_id = id
62
- @group_id = group_id
63
- super("The user of ID #{id} is not part of this group of ID #{group_id}")
64
- end
65
- end
66
-
67
- # Exception raised when a role doesn't exist.
68
- class RoleNotFoundError < StandardError
69
- # @return [Integer] the given role's ID
70
- attr_reader :role_id
71
-
72
- # @return [Integer] the given group's ID
73
- attr_reader :group_id
74
-
75
- # @param role_id [Integer]
76
- # @param group_id [Integer]
77
- def initialize(role_id, group_id)
78
- @role_id = role_id
79
- @group_id = group_id
80
- super("The role of ID #{role_id} does not exist in group of ID #{group_id}.")
81
- end
82
- end
83
-
84
- # Exception raised when an unhandled status code is returned.
85
- class UnhandledStatusCodeError < StandardError
86
- # @return [HTTP::Response::Status] the unhandled status code
87
- attr_reader :status_code
88
-
89
- # @return [String] a string containing all the errors returned by the API
90
- # neatly formatted
91
- attr_reader :errors
92
-
93
- # @param status_code [Integer]
94
- # @param errors [String, nil]
95
- def initialize(status_code, errors = "")
96
- super("Unhandled status code #{status_code}.\nRoblox errors:\n#{errors}")
97
- @status_code = status_code
98
- @errors = errors
99
- end
100
- end
101
-
102
- # Exception raised when an invalid .ROBLOSECURITY cookie is used.
103
- class InvalidROBLOSECURITYError < StandardError
104
- def initialize
105
- super("A valid .ROBLOSECURITY cookie needs to be passed to Rublox's constructor for this action.")
106
- end
107
- end
108
- end
4
+ module Errors
5
+ # This error is raised when a user couldn't be found
6
+ class UserNotFoundError < StandardError
7
+ end
8
+
9
+ # This error is raised when a group couldn't be found
10
+ class GroupNotFoundError < StandardError
11
+ end
12
+
13
+ # This error is raised when one of the provided user IDs are invalid, or if a user ID is repeated
14
+ class PresenceRequestError < StandardError
15
+ # @!visibility private
16
+ def initialize
17
+ super("An invalid or repeated user ID was provided")
18
+ end
19
+ end
20
+
21
+ # This error is raised when an error returned by the Roblox API should be dealt by the user
22
+ class UnhandledStatusCodeError < StandardError
23
+ # @return [HTTP::Response] the response object
24
+ attr_reader :response
25
+ # @return [String] a formatted string of errors returned by the Roblox API
26
+ attr_reader :errors
27
+
28
+ # @!visibility private
29
+ def initialize(response, errors)
30
+ super("Unhandled status code #{response.status}.\nRoblox errors:\n#{errors}")
31
+ @response = response
32
+ @errors = errors
33
+ end
34
+ end
35
+
36
+ # This error is raised when a method that requires authenticated is called without a valid .ROBLOSECURITY cookie set
37
+ class InvalidROBLOSECURITYError < StandardError
38
+ # @!visibility private
39
+ def initialize
40
+ super("A valid .ROBLOSECURITY cookie needs to be passed to Rublox for this action.")
41
+ end
42
+ end
43
+ end
109
44
  end
data/lib/rublox.rb CHANGED
@@ -1,136 +1,89 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "http"
4
-
5
- require "rublox/version"
6
- require "rublox/util/http_client"
7
- require "rublox/util/cache"
3
+ require "rublox/util/api_helper"
4
+ require "rublox/util/errors"
8
5
  require "rublox/models/full_user"
9
- require "rublox/models/full_group"
6
+ require "rublox/models/skinny_user"
10
7
  require "rublox/models/presence"
8
+ require "rublox/bases/base_user"
9
+ require "rublox/bases/base_group"
11
10
 
12
11
  # rublox is a Roblox web API wrapper written in Ruby. It aims to provide an
13
12
  # object oriented interface to get and modify data from Roblox's web API.
14
- #
15
- # Repository: https://github.com/roblox-api-wrappers/rublox
16
- #
17
13
  # Docs: https://rubydoc.info/gems/rublox
18
14
  module Rublox
19
- # The {Client} object is the gateway to the API. Tt supplies methods that
20
- # return classes modeled after the interactions you can do with the API.
21
- #
22
- # Initialize the client with a .ROBLOSECURITY cookie if you need functionality
23
- # that requires it.
24
- # @example
25
- # require "rublox"
26
- # # without a cookie
27
- # client = Rublox::Client.new
28
- # # with a cookie
29
- # client = Rublox::Client.new("_|WARNING:-DO-NOT-SHARE-THIS.--Sharing-this ...")
30
- class Client
31
- # @note The HTTP client should only be used when there are no methods
32
- # provided by the library to achieve what you want.
33
- # @return [HTTPClient]
34
- attr_reader :http_client
15
+ # Sets the .ROBLOSECURITY cookie to send authenticated requests
16
+ def self.roblosecurity=(roblosecurity)
17
+ APIHelper.roblosecurity = roblosecurity
18
+ end
19
+
20
+ # Returns the current authenticated user
21
+ def self.authenticated_user
22
+ Models::SkinnyUser.new(APIHelper.get("https://users.roblox.com/v1/users/authenticated"))
23
+ end
24
+
25
+ # Returns a BaseUser
26
+ def self.base_user(id)
27
+ Bases::BaseUser.new(id)
28
+ end
29
+
30
+ # Returns a user
31
+ def self.user_from_id(id)
32
+ Models::FullUser.new(APIHelper.get("https://users.roblox.com/v1/users/#{id}"))
33
+ rescue Errors::UnhandledStatusCodeError => e
34
+ raise Errors::UserNotFoundError, cause: nil if e.response.status == 404
35
35
 
36
- # Initialize the client with a .ROBLOSECURITY cookie if you require functionality
37
- # that needs it.
38
- # @example
39
- # require "rublox"
40
- # # without a cookie
41
- # client = Rublox::Client.new
42
- # # with a cookie
43
- # client = Rublox::Client.new("_|WARNING:-DO-NOT-SHARE-THIS.--Sharing-this ...")
44
- # @param roblosecurity [String, nil] a valid .ROBLOSECURITY cookie
45
- def initialize(roblosecurity = "")
46
- @http_client = HTTPClient.new(roblosecurity)
47
- end
36
+ raise
37
+ end
48
38
 
49
- # @example
50
- # client = Rublox::Client.new
51
- # user = client.user_from_id(1)
52
- # puts user.username # -> Roblox
53
- # @param id [Integer] the user's ID
54
- # @return [FullUser] a model of the user specified by the ID
55
- def user_from_id(id)
56
- user = Cache.get(Cache::USER, id)
57
- return user if user
39
+ def self.users_from_ids(ids, exclude_banned_users: false)
40
+ APIHelper.post(
41
+ "https://users.roblox.com/v1/users",
42
+ json: {
43
+ userIds: ids,
44
+ excludeBannedUsers: exclude_banned_users
45
+ }
46
+ )["data"].map { |data| Models::SkinnyUser.new(data) }
47
+ end
58
48
 
59
- data = @http_client.get(
60
- URL.endpoint("users", "v1/users/#{id}")
61
- )
62
- rescue Errors::UnhandledStatusCodeError
63
- raise Errors::UserNotFoundError, id
64
- else
65
- user = FullUser.new(
66
- data,
67
- self
68
- )
69
- Cache.set(Cache::USER, id, user)
49
+ def self.users_from_usernames(usernames, exclude_banned_users: false)
50
+ APIHelper.post(
51
+ "https://users.roblox.com/v1/usernames/users",
52
+ json: {
53
+ usernames: usernames,
54
+ excludeBannedUsers: exclude_banned_users
55
+ }
56
+ )["data"].map { |data| Models::SkinnyUser.new(data) }
57
+ end
70
58
 
71
- user
72
- end
59
+ def self.user_from_username(username, exclude_banned_users: false)
60
+ users_from_usernames([username], exclude_banned_users: exclude_banned_users)[0] or raise Errors::UserNotFoundError
61
+ end
73
62
 
74
- # @note This method sends 2 requests, use {#user_from_id} if possible.
75
- # @example
76
- # client = Rublox::Client.new
77
- # user = client.user_from_username("Roblox")
78
- # puts user.id # -> 1
79
- # @param username [String] the user's username
80
- # @return [FullUser] a model of the user specified by the ID
81
- def user_from_username(username)
82
- data = @http_client.post(
83
- URL.endpoint("users", "/v1/usernames/users"),
84
- json: {
85
- usernames: [username],
86
- excludeBannedUsers: false
87
- }
88
- )["data"]
89
- raise Errors::UserNotFoundError.new(nil, username) if data.empty?
63
+ def self.base_group(id)
64
+ Bases::BaseGroup.new(id)
65
+ end
90
66
 
91
- user_from_id(
92
- data[0]["id"]
93
- )
94
- end
67
+ def self.group_from_id(id)
68
+ APIHelper.get("https://groups.roblox.com/v1/groups/#{id}")
69
+ rescue Errors::UnhandledStatusCodeError => e
70
+ raise Errors::GroupNotFoundError, cause: nil if e.response.status == 400
95
71
 
96
- # @example
97
- # client = Rublox::Client.new
98
- # group = client.group_from_id(1)
99
- # puts group.name # -> RobloHunks
100
- # @param id [Integer] the groups's ID
101
- # @return [FullGroup] a model of the group specified by the ID
102
- def group_from_id(id)
103
- group = Cache.get(Cache::GROUP, id)
104
- return group if group
72
+ raise
73
+ end
105
74
 
106
- data = @http_client.get(
107
- URL.endpoint("groups", "v1/groups/#{id}")
108
- )
109
- rescue Errors::UnhandledStatusCodeError
110
- raise Errors::GroupNotFoundError, id
111
- else
112
- group = FullGroup.new(data, self)
113
- Cache.set(Cache::GROUP, id, group)
75
+ def self.user_presences_from_ids(ids)
76
+ APIHelper.post(
77
+ "https://presence.roblox.com/v1/presence/users",
78
+ json: { userIds: ids }
79
+ )["userPresences"].map { |data| Models::Presence.new(data) }
80
+ rescue Errors::UnhandledStatusCodeError => e
81
+ raise Errors::PresenceRequestError, cause: nil if e.response.status == 400
114
82
 
115
- group
116
- end
83
+ raise
84
+ end
117
85
 
118
- # @param id [Integer] the user's ID
119
- # @return [Presence] a model of the presence specified by the user's ID
120
- def user_presence_from_id(id)
121
- data = http_client.post(
122
- URL.endpoint("presence", "v1/presence/users"),
123
- json: {
124
- userIds: [id]
125
- }
126
- )
127
- rescue Errors::UnhandledStatusCodeError
128
- raise Errors::PresenceNotFoundError, id
129
- else
130
- Presence.new(
131
- data["userPresences"][0],
132
- self
133
- )
134
- end
135
- end
86
+ def self.user_presence_from_id(id)
87
+ user_presences_from_ids([id])[0]
88
+ end
136
89
  end
data/rublox.gemspec CHANGED
@@ -1,32 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "lib/rublox/version"
4
-
5
3
  Gem::Specification.new do |spec|
6
- spec.name = "rublox"
7
- spec.version = Rublox::VERSION
8
- spec.authors = %w[Zamdie Keef]
9
- spec.email = "rorg.devv@gmail.com"
10
- spec.summary = "A Roblox web API wrapper written in Ruby"
11
- spec.description = "This gem allows easy interaction with the Roblox web API via class models."
12
- spec.homepage = "https://github.com/roblox-api-wrappers/rublox"
13
- spec.license = "MIT"
14
- spec.required_ruby_version = ">= 3.0"
15
- spec.files = Dir[
16
- "LICENSE",
17
- "CHANGELOG.MD",
18
- "lib/**/*.rb",
19
- "rublox.gemspec",
20
- "Gemfile",
21
- "Rakefile"
22
- ]
23
- spec.extra_rdoc_files = ["README.md"]
4
+ spec.name = "rublox"
5
+ spec.version = "0.3.0"
6
+ spec.authors = %w[zmλdie keef]
7
+ spec.email = "rorg.devv@gmail.com"
8
+ spec.summary = "A Roblox web API wrapper written in Ruby"
9
+ spec.description = "This gem allows easy interaction with the Roblox web API via modules and classes."
10
+ spec.homepage = "https://github.com/roblox-api-wrappers/rublox"
11
+ spec.metadata = {
12
+ "Documentation" => "https://rubydoc.info/gems/rublox",
13
+ "rubygems_mfa_required" => "true"
14
+ }
15
+ spec.license = "MIT"
16
+ spec.required_ruby_version = ">= 2.7.0"
17
+ spec.files = Dir[
18
+ "README.md",
19
+ "LICENSE",
20
+ "lib/**/*.rb",
21
+ "rublox.gemspec"
22
+ ]
24
23
 
25
- spec.add_dependency "http", "~> 5.0.2"
24
+ spec.add_dependency "http", "~> 5.1.1"
26
25
 
27
- spec.add_development_dependency "dotenv", "~> 2.7"
28
- spec.add_development_dependency "rake", "~> 13.0.6"
29
- spec.add_development_dependency "rubocop", "~> 1.21.0"
30
- spec.add_development_dependency "rubocop-performance", "~> 1.11.5"
31
- spec.add_development_dependency "yard", "~> 0.9.26"
26
+ spec.add_development_dependency "rubocop", "~> 1.44.1"
27
+ spec.add_development_dependency "rubocop-performance", "~> 1.15.2"
28
+ spec.add_development_dependency "rubocop-rake", "~> 0.6.0"
29
+ spec.add_development_dependency "yard", "~> 0.9.28"
32
30
  end