rublox 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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