novacloud_client 0.1.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 (36) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop_todo.yml +5 -0
  3. data/CHANGELOG.md +5 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +142 -0
  6. data/Rakefile +12 -0
  7. data/lib/novacloud_client/client.rb +110 -0
  8. data/lib/novacloud_client/configuration.rb +31 -0
  9. data/lib/novacloud_client/errors.rb +26 -0
  10. data/lib/novacloud_client/middleware/authentication.rb +49 -0
  11. data/lib/novacloud_client/middleware/error_handler.rb +62 -0
  12. data/lib/novacloud_client/objects/base.rb +49 -0
  13. data/lib/novacloud_client/objects/control_log_entry.rb +21 -0
  14. data/lib/novacloud_client/objects/control_result.rb +55 -0
  15. data/lib/novacloud_client/objects/player.rb +37 -0
  16. data/lib/novacloud_client/objects/player_status.rb +29 -0
  17. data/lib/novacloud_client/objects/queued_request.rb +17 -0
  18. data/lib/novacloud_client/objects/screen.rb +21 -0
  19. data/lib/novacloud_client/objects/screen_detail.rb +29 -0
  20. data/lib/novacloud_client/objects/screen_monitor.rb +12 -0
  21. data/lib/novacloud_client/objects/solutions/offline_export_result.rb +80 -0
  22. data/lib/novacloud_client/objects/solutions/over_spec_detection_result.rb +80 -0
  23. data/lib/novacloud_client/objects/solutions/publish_result.rb +32 -0
  24. data/lib/novacloud_client/resources/base.rb +29 -0
  25. data/lib/novacloud_client/resources/concerns/payload_serializer.rb +46 -0
  26. data/lib/novacloud_client/resources/control.rb +250 -0
  27. data/lib/novacloud_client/resources/logs.rb +39 -0
  28. data/lib/novacloud_client/resources/players.rb +120 -0
  29. data/lib/novacloud_client/resources/scheduled_control.rb +221 -0
  30. data/lib/novacloud_client/resources/screens.rb +66 -0
  31. data/lib/novacloud_client/resources/solutions.rb +154 -0
  32. data/lib/novacloud_client/support/key_transform.rb +32 -0
  33. data/lib/novacloud_client/version.rb +5 -0
  34. data/lib/novacloud_client.rb +9 -0
  35. data/sig/novacloud_client.rbs +4 -0
  36. metadata +97 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 59ea901e539b0594fc46cb945244af7b0b1435d6c636d8faad0ee2ca185d2958
4
+ data.tar.gz: ede71795f8cda337f5c6ca715a156a46bdfd9300f0f6426a06510ddc8210d4ad
5
+ SHA512:
6
+ metadata.gz: f8215e3cdca0858e12d65d7f100bad2b4ca13e987631f95e973d511dcb4de902251124d309c7f5abff2b098cfe8b31d99bd4b0fe9f7e1e4aae5a1e5585a7835f
7
+ data.tar.gz: c605efa841060bba5a18fb413c549acc05e0413952751b5f57c4dd50fd17202c5f42bb15210e4bbf0dc89dce8b92828b472dc26e128ae8238cdd25393d4cabda
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,5 @@
1
+ Metrics/MethodLength:
2
+ Max: 20
3
+
4
+ Metrics/ClassLength:
5
+ Max: 200
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2025-10-26
4
+
5
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Chayut Orapinpatipat
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # NovaCloud Client (WIP)
2
+
3
+ Sprint 01 delivered the core HTTP client for the NovaCloud Open Platform. The gem now:
4
+
5
+ - Manages configuration once (`app_key`, `app_secret`, `service_domain`).
6
+ - Handles authentication headers automatically via Faraday middleware.
7
+ - Maps HTTP errors to a typed exception hierarchy.
8
+ - Normalizes GET/POST payloads and parses JSON responses.
9
+
10
+ Sprint 02 expands on this foundation with dedicated resource helpers (`client.players`, `client.control`, `client.scheduled_control`, `client.solutions`) and typed response objects (e.g., `NovacloudClient::Objects::Player`).
11
+
12
+ ## Resource Overview
13
+
14
+ - **Players**: `list`, `statuses`, `running_status`
15
+ - **Control**: `brightness`, `volume`, `video_source`, `screen_power`, `screen_status`, `screenshot`, `reboot`, `ntp_sync`, `synchronous_playback`, `request_result`
16
+ - **Scheduled Control**: `screen_status`, `reboot`, `volume`, `brightness`, `video_source`
17
+ - **Solutions**: `emergency_page`, `cancel_emergency`, `common_solution`, `offline_export`, `set_over_spec_detection`, `program_over_spec_detection`
18
+ - **Screens** (VNNOXCare): `list`, `monitor`, `detail`
19
+ - **Logs**: `control_history`
20
+
21
+ > **Heads-up:** NovaCloud's public API docs (as of October 2025) do not expose
22
+ > "material" endpoints for uploading, listing, or deleting media assets. This
23
+ > client therefore expects assets to be hosted already (either uploaded via the
24
+ > VNNOX UI or served from your own CDN) and referenced by URL in solution
25
+ > payloads.
26
+
27
+ ## Quick Start
28
+
29
+ ```ruby
30
+ require "novacloud_client"
31
+
32
+ client = NovacloudClient::Client.new(
33
+ app_key: "YOUR_APP_KEY",
34
+ app_secret: "YOUR_APP_SECRET",
35
+ service_domain: "open-us.vnnox.com"
36
+ )
37
+
38
+ players = client.players.list(count: 20)
39
+ first_player = players.first
40
+
41
+ statuses = client.players.statuses(player_ids: players.map(&:player_id))
42
+
43
+ queue = client.players.config_status(
44
+ player_ids: players.map(&:player_id),
45
+ notice_url: "https://example.com/status-webhook"
46
+ )
47
+
48
+ client.control.ntp_sync(
49
+ player_ids: players.map(&:player_id),
50
+ server: "ntp1.aliyun.com",
51
+ enable: true
52
+ )
53
+
54
+ client.scheduled_control.brightness(
55
+ player_ids: players.map(&:player_id),
56
+ schedules: {
57
+ start_date: Date.today.strftime("%Y-%m-%d"),
58
+ end_date: (Date.today + 30).strftime("%Y-%m-%d"),
59
+ exec_time: "07:00:00",
60
+ type: 0,
61
+ value: 55
62
+ }
63
+ )
64
+
65
+ request = client.control.brightness(
66
+ player_ids: players.map(&:player_id),
67
+ brightness: 80,
68
+ notice_url: "https://example.com/callback"
69
+ )
70
+
71
+ result = client.control.request_result(request_id: request.request_id)
72
+ puts result.all_successful?
73
+
74
+ screens = client.screens.list(status: 1)
75
+ puts screens.first.name
76
+
77
+ client.solutions.emergency_page(
78
+ player_ids: [first_player.player_id],
79
+ attribute: { duration: 20_000, normal_program_status: "PAUSE", spots_type: "IMMEDIATELY" },
80
+ page: {
81
+ name: "urgent-alert",
82
+ widgets: [
83
+ {
84
+ type: "PICTURE",
85
+ z_index: 1,
86
+ duration: 10_000,
87
+ url: "https://example.com/alert.png",
88
+ layout: { x: "0%", y: "0%", width: "100%", height: "100%" }
89
+ }
90
+ ]
91
+ }
92
+ )
93
+
94
+ offline_bundle = client.solutions.offline_export(
95
+ program_type: 1,
96
+ plan_version: "V2",
97
+ pages: [
98
+ {
99
+ name: "main",
100
+ widgets: [
101
+ { type: "PICTURE", md5: "abc", url: "https://cdn.example.com/img.jpg" }
102
+ ]
103
+ }
104
+ ]
105
+ )
106
+
107
+ puts offline_bundle.plan_json.url
108
+
109
+ over_spec_result = client.solutions.program_over_spec_detection(
110
+ player_ids: [first_player.player_id],
111
+ pages: [
112
+ {
113
+ page_id: 1,
114
+ widgets: [
115
+ { widget_id: 1, type: "VIDEO", url: "https://cdn.example.com/video.mp4", width: "3840", height: "2160" }
116
+ ]
117
+ }
118
+ ]
119
+ )
120
+
121
+ if over_spec_result.items.any?(&:over_spec?)
122
+ warn "Program exceeds specifications"
123
+ end
124
+ ```
125
+
126
+ ### Development
127
+
128
+ ```bash
129
+ bundle install
130
+ bundle exec rspec
131
+ bundle exec rubocop
132
+ ```
133
+
134
+ ### Documentation
135
+
136
+ Run YARD to generate HTML API documentation for the gem:
137
+
138
+ ```bash
139
+ bundle exec yard doc
140
+ ```
141
+
142
+ Then browse the docs via the generated `doc/index.html` or launch a local server with `bundle exec yard server --reload`.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "json"
5
+
6
+ require_relative "errors"
7
+ require_relative "configuration"
8
+ require_relative "middleware/authentication"
9
+ require_relative "middleware/error_handler"
10
+ require_relative "resources/players"
11
+ require_relative "resources/control"
12
+ require_relative "resources/scheduled_control"
13
+ require_relative "resources/solutions"
14
+ require_relative "resources/screens"
15
+ require_relative "resources/logs"
16
+
17
+ module NovacloudClient
18
+ # Central entry point for interacting with the NovaCloud API.
19
+ class Client
20
+ attr_reader :config
21
+
22
+ def initialize(app_key:, app_secret:, service_domain:, &faraday_block)
23
+ @config = Configuration.new
24
+ @config.app_key = app_key
25
+ @config.app_secret = app_secret
26
+ @config.service_domain = service_domain
27
+ @config.validate!
28
+
29
+ @faraday_block = faraday_block
30
+ end
31
+
32
+ def connection
33
+ @connection ||= build_connection
34
+ end
35
+
36
+ def request(http_method:, endpoint:, params: {})
37
+ symbolized_method = http_method.to_sym
38
+ response = connection.public_send(symbolized_method) do |req|
39
+ req.url endpoint
40
+ apply_request_payload(req, symbolized_method, params)
41
+ end
42
+
43
+ parse_body(response)
44
+ end
45
+
46
+ def players
47
+ @players ||= Resources::Players.new(self)
48
+ end
49
+
50
+ def control
51
+ @control ||= Resources::Control.new(self)
52
+ end
53
+
54
+ def scheduled_control
55
+ @scheduled_control ||= Resources::ScheduledControl.new(self)
56
+ end
57
+
58
+ def solutions
59
+ @solutions ||= Resources::Solutions.new(self)
60
+ end
61
+
62
+ def screens
63
+ @screens ||= Resources::Screens.new(self)
64
+ end
65
+
66
+ def logs
67
+ @logs ||= Resources::Logs.new(self)
68
+ end
69
+
70
+ private
71
+
72
+ def build_connection
73
+ Faraday.new(url: config.base_url) do |faraday|
74
+ faraday.headers["Accept"] = "application/json"
75
+
76
+ faraday.use Middleware::Authentication,
77
+ app_key: config.app_key,
78
+ app_secret: config.app_secret
79
+ faraday.use Middleware::ErrorHandler
80
+
81
+ @faraday_block&.call(faraday)
82
+
83
+ faraday.adapter config.adapter
84
+ end
85
+ end
86
+
87
+ def apply_request_payload(request, http_method, params)
88
+ return if params.empty?
89
+
90
+ case http_method
91
+ when :get, :delete
92
+ request.params.update(params)
93
+ else
94
+ request.headers["Content-Type"] = "application/json; charset=utf-8"
95
+ request.body = JSON.generate(params)
96
+ end
97
+ end
98
+
99
+ def parse_body(response)
100
+ body = response.body
101
+ return nil if body.nil?
102
+ return body unless body.is_a?(String)
103
+ return nil if body.strip.empty?
104
+
105
+ JSON.parse(body)
106
+ rescue JSON::ParserError
107
+ body
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NovacloudClient
4
+ # Holds configuration for a NovacloudClient::Client instance.
5
+ class Configuration
6
+ attr_accessor :app_key, :app_secret, :service_domain, :adapter
7
+
8
+ def initialize
9
+ @adapter = :net_http
10
+ end
11
+
12
+ def base_url
13
+ "https://#{service_domain}"
14
+ end
15
+
16
+ def validate!
17
+ raise ArgumentError, "app_key is required" if blank?(app_key)
18
+ raise ArgumentError, "app_secret is required" if blank?(app_secret)
19
+ raise ArgumentError, "service_domain is required" if blank?(service_domain)
20
+ end
21
+
22
+ private
23
+
24
+ def blank?(value)
25
+ return true if value.nil?
26
+ return value.strip.empty? if value.is_a?(String)
27
+
28
+ value.respond_to?(:empty?) ? value.empty? : false
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NovacloudClient
4
+ # Base error class allowing access to the Faraday environment.
5
+ class Error < StandardError
6
+ attr_reader :response
7
+
8
+ def initialize(message = nil, response: nil)
9
+ super(message)
10
+ @response = response
11
+ end
12
+ end
13
+
14
+ class ClientError < Error; end
15
+ class BadRequestError < ClientError; end
16
+ class AuthenticationError < ClientError; end
17
+ class PermissionError < ClientError; end
18
+ class NotAcceptableError < ClientError; end
19
+ class RateLimitError < ClientError; end
20
+
21
+ class ServerError < Error; end
22
+ class InternalServerError < ServerError; end
23
+ class BadGatewayError < ServerError; end
24
+ class ServiceUnavailableError < ServerError; end
25
+ class GatewayTimeoutError < ServerError; end
26
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "digest"
5
+ require "securerandom"
6
+
7
+ module NovacloudClient
8
+ module Middleware
9
+ # Injects the NovaCloud authentication headers into every request.
10
+ class Authentication < Faraday::Middleware
11
+ def initialize(app, app_key:, app_secret:)
12
+ super(app)
13
+ @app_key = app_key
14
+ @app_secret = app_secret
15
+ end
16
+
17
+ def call(env)
18
+ cur_time = current_utc_timestamp
19
+ nonce = generate_nonce
20
+ checksum = checksum_for(nonce, cur_time)
21
+
22
+ headers = env.request_headers
23
+ headers["AppKey"] = @app_key
24
+ headers["Nonce"] = nonce
25
+ headers["CurTime"] = cur_time
26
+ headers["CheckSum"] = checksum
27
+
28
+ @app.call(env)
29
+ end
30
+
31
+ private
32
+
33
+ def current_utc_timestamp
34
+ Time.now.utc.to_i.to_s
35
+ end
36
+
37
+ def generate_nonce
38
+ timestamp_component = (Time.now.utc.to_f * 1_000_000).to_i.to_s(36)
39
+ random_component = SecureRandom.alphanumeric(16)
40
+ (timestamp_component + random_component)[0, 16]
41
+ end
42
+
43
+ def checksum_for(nonce, cur_time)
44
+ signature = @app_secret + nonce + cur_time
45
+ Digest::SHA256.hexdigest(signature)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+
5
+ require_relative "../errors"
6
+
7
+ module NovacloudClient
8
+ module Middleware
9
+ # Maps HTTP error responses to NovacloudClient exception classes.
10
+ class ErrorHandler < Faraday::Middleware
11
+ ERROR_MAP = {
12
+ 400 => NovacloudClient::BadRequestError,
13
+ 401 => NovacloudClient::AuthenticationError,
14
+ 403 => NovacloudClient::PermissionError,
15
+ 406 => NovacloudClient::NotAcceptableError,
16
+ 429 => NovacloudClient::RateLimitError,
17
+ 500 => NovacloudClient::InternalServerError,
18
+ 502 => NovacloudClient::BadGatewayError,
19
+ 503 => NovacloudClient::ServiceUnavailableError,
20
+ 504 => NovacloudClient::GatewayTimeoutError
21
+ }.freeze
22
+
23
+ def call(env)
24
+ @app.call(env).on_complete do |response_env|
25
+ handle_response(response_env)
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def handle_response(env)
32
+ status = env.status.to_i
33
+ return if (200..299).cover?(status)
34
+
35
+ error_class = ERROR_MAP[status] || fallback_error(status)
36
+ message = "HTTP #{status}: #{summary_from(env)}"
37
+ raise error_class.new(message, response: env)
38
+ end
39
+
40
+ def fallback_error(status)
41
+ if (400..499).cover?(status)
42
+ NovacloudClient::ClientError
43
+ else
44
+ NovacloudClient::ServerError
45
+ end
46
+ end
47
+
48
+ def summary_from(env)
49
+ body = env.body
50
+ return "No response body" if body.nil?
51
+
52
+ if body.is_a?(String)
53
+ body.strip.empty? ? "Empty body" : body.strip[0, 200]
54
+ elsif body.respond_to?(:to_json)
55
+ body.to_json[0, 200]
56
+ else
57
+ body.to_s[0, 200]
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ module NovacloudClient
6
+ module Objects
7
+ # Base class providing attribute assignment and coercion helpers.
8
+ class Base
9
+ def initialize(attributes = {})
10
+ assign_attributes(attributes)
11
+ end
12
+
13
+ private
14
+
15
+ def assign_attributes(attributes)
16
+ attributes.each do |key, value|
17
+ writer = "#{key}="
18
+ if respond_to?(writer)
19
+ public_send(writer, value)
20
+ next
21
+ end
22
+
23
+ normalized_writer = normalize_writer(key)
24
+ next if normalized_writer == writer
25
+
26
+ public_send(normalized_writer, value) if respond_to?(normalized_writer)
27
+ end
28
+ end
29
+
30
+ def parse_timestamp(value)
31
+ return nil if value.nil?
32
+ return value if value.is_a?(Time)
33
+
34
+ Time.parse(value.to_s)
35
+ rescue ArgumentError
36
+ value
37
+ end
38
+
39
+ def normalize_writer(key)
40
+ normalized = key.to_s
41
+ normalized = normalized.gsub(/([A-Z\d]+)([A-Z][a-z])/, "\\1_\\2")
42
+ .gsub(/([a-z\d])([A-Z])/, "\\1_\\2")
43
+ .tr("-", "_")
44
+ .downcase
45
+ "#{normalized}="
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module NovacloudClient
6
+ module Objects
7
+ # Represents an execution record for a remote control command.
8
+ class ControlLogEntry < Base
9
+ attr_accessor :status, :type
10
+ attr_reader :execute_time
11
+
12
+ def execute_time=(value)
13
+ @execute_time = parse_timestamp(value)
14
+ end
15
+
16
+ def success?
17
+ status.to_i.zero? ? false : status.to_i == 1
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module NovacloudClient
6
+ module Objects
7
+ # Represents the result of a control or queued request returning success/fail lists.
8
+ class ControlResult < Base
9
+ attr_reader :successes, :failures
10
+
11
+ def initialize(attributes = {})
12
+ @successes = []
13
+ @failures = []
14
+ super
15
+ end
16
+
17
+ def success=(value)
18
+ @successes = Array(value)
19
+ end
20
+
21
+ def fail=(value)
22
+ @failures = Array(value)
23
+ end
24
+
25
+ def all_successful?
26
+ failures.empty?
27
+ end
28
+
29
+ def partial_success?
30
+ successes.any? && failures.any?
31
+ end
32
+
33
+ def all_failed?
34
+ successes.empty?
35
+ end
36
+
37
+ def success_count
38
+ successes.size
39
+ end
40
+
41
+ def failure_count
42
+ failures.size
43
+ end
44
+
45
+ # Maintain API compatibility with camelCase keys.
46
+ def success
47
+ successes
48
+ end
49
+
50
+ def fail
51
+ failures
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module NovacloudClient
6
+ module Objects
7
+ # Represents a player returned from NovaCloud player APIs.
8
+ class Player < Base
9
+ attr_accessor :player_id, :player_type, :name, :sn, :version, :ip
10
+ attr_reader :last_online_time, :online_status
11
+
12
+ def last_online_time=(value)
13
+ @last_online_time = parse_timestamp(value)
14
+ end
15
+
16
+ def online_status=(value)
17
+ @online_status = value.to_i
18
+ end
19
+
20
+ def online?
21
+ online_status == 1
22
+ end
23
+
24
+ def offline?
25
+ !online?
26
+ end
27
+
28
+ def synchronous?
29
+ player_type.to_i == 1
30
+ end
31
+
32
+ def asynchronous?
33
+ player_type.to_i == 2
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module NovacloudClient
6
+ module Objects
7
+ # Represents basic status information for a player.
8
+ class PlayerStatus < Base
9
+ attr_accessor :player_id, :sn
10
+ attr_reader :online_status, :last_online_time
11
+
12
+ def online_status=(value)
13
+ @online_status = value.to_i
14
+ end
15
+
16
+ def last_online_time=(value)
17
+ @last_online_time = parse_timestamp(value)
18
+ end
19
+
20
+ def online?
21
+ online_status == 1
22
+ end
23
+
24
+ def offline?
25
+ !online?
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "control_result"
4
+
5
+ module NovacloudClient
6
+ module Objects
7
+ # Represents the enqueue result for asynchronous player commands.
8
+ class QueuedRequest < ControlResult
9
+ attr_reader :request_id
10
+
11
+ # Queue responses include a request ID used to poll for results later.
12
+ def request_id=(value)
13
+ @request_id = value&.to_s
14
+ end
15
+ end
16
+ end
17
+ end