rublox 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.MD +4 -0
- data/Gemfile +6 -0
- data/LICENSE +21 -0
- data/README.MD +68 -0
- data/Rakefile +33 -0
- data/lib/rublox/derive/group.rb +50 -0
- data/lib/rublox/derive/user.rb +89 -0
- data/lib/rublox/models/full_group.rb +60 -0
- data/lib/rublox/models/full_user.rb +49 -0
- data/lib/rublox/models/group_member.rb +54 -0
- data/lib/rublox/models/group_role.rb +70 -0
- data/lib/rublox/models/group_shout.rb +62 -0
- data/lib/rublox/models/limited_user.rb +34 -0
- data/lib/rublox/models/presence.rb +98 -0
- data/lib/rublox/util/cache.rb +39 -0
- data/lib/rublox/util/errors.rb +109 -0
- data/lib/rublox/util/http_client.rb +95 -0
- data/lib/rublox/util/pages.rb +85 -0
- data/lib/rublox/util/url.rb +25 -0
- data/lib/rublox/version.rb +6 -0
- data/lib/rublox.rb +136 -0
- data/rublox.gemspec +32 -0
- metadata +150 -0
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "time"
|
4
|
+
|
5
|
+
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
|
98
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rublox
|
4
|
+
# @note Only use if you have an use case that the library doesn't cover (and
|
5
|
+
# create an issue and perhaps we'll implement it!).
|
6
|
+
module Cache
|
7
|
+
# The key for the user cache.
|
8
|
+
USER = :users
|
9
|
+
# The key for the group cache.
|
10
|
+
GROUP = :groups
|
11
|
+
# The key for the page cache.
|
12
|
+
PAGE = :pages
|
13
|
+
|
14
|
+
# @!visiblity private
|
15
|
+
@cache = {
|
16
|
+
users: {},
|
17
|
+
groups: {},
|
18
|
+
pages: {}
|
19
|
+
}
|
20
|
+
|
21
|
+
# Try to get an object from cache.
|
22
|
+
# @param type [Symbol] {USER}, {GROUP} or {PAGE}
|
23
|
+
# @param id [Integer] the ID of the object
|
24
|
+
# @return [FullUser, FullGroup, Pages, nil]
|
25
|
+
def self.get(type, id)
|
26
|
+
@cache[type][id]
|
27
|
+
end
|
28
|
+
|
29
|
+
# Set an object in the cache, under the type's key.
|
30
|
+
# @param type [Symbol] {USER}, {GROUP} or {PAGE}
|
31
|
+
# @param id [Integer] the ID of the object
|
32
|
+
# @param object [FullUser, FullGroup, Pages] the object to be added to the
|
33
|
+
# cache
|
34
|
+
# @return [nil]
|
35
|
+
def self.set(type, id, object)
|
36
|
+
@cache[type][id] = object
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
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
|
109
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "http"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
require "rublox/util/errors"
|
7
|
+
|
8
|
+
module Rublox
|
9
|
+
# @note Only use if you have an use case that the library doesn't cover (and
|
10
|
+
# create an issue and perhaps we'll implement it!).
|
11
|
+
# The {HTTPClient} is a wrapper around the http library, designed specifically
|
12
|
+
# for the Roblox API. It automatically handles the X-CSRF-TOKEN and the
|
13
|
+
# .ROBLOSECURITY cookie.
|
14
|
+
class HTTPClient
|
15
|
+
# @param roblosecurity [String]
|
16
|
+
def initialize(roblosecurity = "")
|
17
|
+
@client = HTTP.cookies(
|
18
|
+
{
|
19
|
+
".ROBLOSECURITY": roblosecurity
|
20
|
+
}
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Send a GET request to the specified URL.
|
25
|
+
# @param url [String]
|
26
|
+
# @return [Hash]
|
27
|
+
def get(url, *args)
|
28
|
+
request(:get, url, *args)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Send a POST request to the specified URL.
|
32
|
+
# @param url [String]
|
33
|
+
# @return [Hash]
|
34
|
+
def post(url, *args)
|
35
|
+
request(:post, url, *args)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Send a PATCH request to the specified URL.
|
39
|
+
# @param url [String]
|
40
|
+
# @return [Hash]
|
41
|
+
def patch(url, *args)
|
42
|
+
request(:patch, url, *args)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Send a DELETE request to the specified URL.
|
46
|
+
# @param url [String]
|
47
|
+
# @return [Hash]
|
48
|
+
def delete(url, *args)
|
49
|
+
request(:delete, url, *args)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# @param verb [Symbol]
|
55
|
+
# @param url [String]
|
56
|
+
# @return [Hash]
|
57
|
+
def request(verb, url, *args)
|
58
|
+
response = @client.request(verb, url, *args)
|
59
|
+
return JSON.parse(response.body) if response.status == 200
|
60
|
+
|
61
|
+
handle_status_code(response, verb, url, *args)
|
62
|
+
end
|
63
|
+
|
64
|
+
# @param response [HTTP::Response]
|
65
|
+
def handle_status_code(response, verb, url, *args)
|
66
|
+
case response.status
|
67
|
+
# token validation failed
|
68
|
+
when 403
|
69
|
+
@client = @client.headers(
|
70
|
+
{
|
71
|
+
"x-csrf-token": response.headers["x-csrf-token"]
|
72
|
+
}
|
73
|
+
)
|
74
|
+
# retry the request
|
75
|
+
request(verb, url, *args)
|
76
|
+
# invalid .ROBLOSECURITY cookie
|
77
|
+
when 401
|
78
|
+
raise Errors::InvalidROBLOSECURITYError
|
79
|
+
else
|
80
|
+
raise Errors::UnhandledStatusCodeError.new(response.status, get_errors_from_response(response))
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# @param response [HTTP::Response]
|
85
|
+
def get_errors_from_response(response)
|
86
|
+
body = JSON.parse(response.body)
|
87
|
+
rescue JSON::ParserError
|
88
|
+
"\ncould not parse errors, raw body:\n#{response.body}\n"
|
89
|
+
else
|
90
|
+
body["errors"].reduce("") do |error_message, error|
|
91
|
+
error_message + "\tCode: #{error['code']}\n\tMessage: #{error['message']}\n\n"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rublox/util/url"
|
4
|
+
require "rublox/util/cache"
|
5
|
+
|
6
|
+
module Rublox
|
7
|
+
# The order to sort pages in.
|
8
|
+
module SortOrder
|
9
|
+
# Sort the pages ina ascending order.
|
10
|
+
ASCENDING = "Asc"
|
11
|
+
# Sort the pages in descending order.
|
12
|
+
DESCENDING = "Desc"
|
13
|
+
end
|
14
|
+
|
15
|
+
# The {Pages} class acts as an iterator over pages returned by the API.
|
16
|
+
# @!visibility private
|
17
|
+
# @todo Make more customizable, polish and battle-test
|
18
|
+
# @note Only use if you have an use case that the library doesn't cover (and
|
19
|
+
# create an issue and perhaps we'll implement it!).
|
20
|
+
class Pages
|
21
|
+
# How many items are returned per page
|
22
|
+
PAGE_DATA_LIMIT = 100
|
23
|
+
|
24
|
+
# @!visibility private
|
25
|
+
DEFAULT_REQUEST_PARAMETERS = {
|
26
|
+
params: {
|
27
|
+
sortOrder: SortOrder::DESCENDING,
|
28
|
+
limit: PAGE_DATA_LIMIT
|
29
|
+
}
|
30
|
+
}.freeze
|
31
|
+
|
32
|
+
# @param client [Client]
|
33
|
+
# @param initial_data [Hash]
|
34
|
+
# @param url [String]
|
35
|
+
# @param request_params [Hash]
|
36
|
+
def initialize(client, initial_data, url, request_params = {}, &data_handler)
|
37
|
+
request_params[:params] = DEFAULT_REQUEST_PARAMETERS[:params].merge(
|
38
|
+
request_params
|
39
|
+
)
|
40
|
+
|
41
|
+
@client = client
|
42
|
+
@raw_data = initial_data
|
43
|
+
@url = url
|
44
|
+
@request_parameters = request_parameters
|
45
|
+
@data_handler = data_handler
|
46
|
+
|
47
|
+
@data = []
|
48
|
+
end
|
49
|
+
|
50
|
+
# Iterate over the pages
|
51
|
+
def each
|
52
|
+
raise unless block_given?
|
53
|
+
# i = 0
|
54
|
+
|
55
|
+
# until @raw_data
|
56
|
+
# if i == @data.length
|
57
|
+
# i = 0
|
58
|
+
# data = Cache.get(Cache::PAGE, @raw_data["nextPageCursor"])
|
59
|
+
# if data
|
60
|
+
# @data = data
|
61
|
+
# else
|
62
|
+
# @raw_data, @data = next_page
|
63
|
+
# break if @raw_data.empty?
|
64
|
+
|
65
|
+
# Cache.set(Cache::PAGE, @raw_data["nextPageCursor"], @data)
|
66
|
+
# end
|
67
|
+
# end
|
68
|
+
|
69
|
+
# yield @data[i]
|
70
|
+
# i += 1
|
71
|
+
# end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# @return [Array] tuple {raw data, processed data}
|
77
|
+
def next_page
|
78
|
+
@request_parameters[:params][:cursor] = @raw_data["nextPageCursor"]
|
79
|
+
|
80
|
+
data = @client.http_client.get(@url, @request_parameters)
|
81
|
+
|
82
|
+
[data["data"], @data_handler.call(data["data"])]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rublox
|
4
|
+
# Module for making URLs.
|
5
|
+
module URL
|
6
|
+
# The base URL to be used. If using a proxy, you can change the URL as long
|
7
|
+
# as it follows Roblox's API structure:
|
8
|
+
# https://users.roblox.com/v1/users/1
|
9
|
+
# Bad:
|
10
|
+
# https://myproxy.test/get-user/1
|
11
|
+
# https://myproxy.test/users/1
|
12
|
+
# https://users.myproxy.test/users/1
|
13
|
+
# Good:
|
14
|
+
# https://users.myproxy.test/v1/users/1
|
15
|
+
BASE_URL = "roblox.com"
|
16
|
+
|
17
|
+
# Creates an endpoint URL from the the given ApiSite and path.
|
18
|
+
# @param api_site [String] ApiSite
|
19
|
+
# @param path [String]
|
20
|
+
# @return [String] the endpoint
|
21
|
+
def self.endpoint(api_site, path)
|
22
|
+
"https://#{api_site}.#{BASE_URL}/#{path}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/rublox.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "http"
|
4
|
+
|
5
|
+
require "rublox/version"
|
6
|
+
require "rublox/util/http_client"
|
7
|
+
require "rublox/util/cache"
|
8
|
+
require "rublox/models/full_user"
|
9
|
+
require "rublox/models/full_group"
|
10
|
+
require "rublox/models/presence"
|
11
|
+
|
12
|
+
# rublox is a Roblox web API wrapper written in Ruby. It aims to provide an
|
13
|
+
# 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
|
+
# Docs: https://rubydoc.info/gems/rublox
|
18
|
+
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
|
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
|
48
|
+
|
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
|
58
|
+
|
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)
|
70
|
+
|
71
|
+
user
|
72
|
+
end
|
73
|
+
|
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?
|
90
|
+
|
91
|
+
user_from_id(
|
92
|
+
data[0]["id"]
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
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
|
105
|
+
|
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)
|
114
|
+
|
115
|
+
group
|
116
|
+
end
|
117
|
+
|
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
|
136
|
+
end
|
data/rublox.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/rublox/version"
|
4
|
+
|
5
|
+
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"]
|
24
|
+
|
25
|
+
spec.add_dependency "http", "~> 5.0.2"
|
26
|
+
|
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"
|
32
|
+
end
|