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.
- checksums.yaml +7 -0
- data/.rubocop_todo.yml +5 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +142 -0
- data/Rakefile +12 -0
- data/lib/novacloud_client/client.rb +110 -0
- data/lib/novacloud_client/configuration.rb +31 -0
- data/lib/novacloud_client/errors.rb +26 -0
- data/lib/novacloud_client/middleware/authentication.rb +49 -0
- data/lib/novacloud_client/middleware/error_handler.rb +62 -0
- data/lib/novacloud_client/objects/base.rb +49 -0
- data/lib/novacloud_client/objects/control_log_entry.rb +21 -0
- data/lib/novacloud_client/objects/control_result.rb +55 -0
- data/lib/novacloud_client/objects/player.rb +37 -0
- data/lib/novacloud_client/objects/player_status.rb +29 -0
- data/lib/novacloud_client/objects/queued_request.rb +17 -0
- data/lib/novacloud_client/objects/screen.rb +21 -0
- data/lib/novacloud_client/objects/screen_detail.rb +29 -0
- data/lib/novacloud_client/objects/screen_monitor.rb +12 -0
- data/lib/novacloud_client/objects/solutions/offline_export_result.rb +80 -0
- data/lib/novacloud_client/objects/solutions/over_spec_detection_result.rb +80 -0
- data/lib/novacloud_client/objects/solutions/publish_result.rb +32 -0
- data/lib/novacloud_client/resources/base.rb +29 -0
- data/lib/novacloud_client/resources/concerns/payload_serializer.rb +46 -0
- data/lib/novacloud_client/resources/control.rb +250 -0
- data/lib/novacloud_client/resources/logs.rb +39 -0
- data/lib/novacloud_client/resources/players.rb +120 -0
- data/lib/novacloud_client/resources/scheduled_control.rb +221 -0
- data/lib/novacloud_client/resources/screens.rb +66 -0
- data/lib/novacloud_client/resources/solutions.rb +154 -0
- data/lib/novacloud_client/support/key_transform.rb +32 -0
- data/lib/novacloud_client/version.rb +5 -0
- data/lib/novacloud_client.rb +9 -0
- data/sig/novacloud_client.rbs +4 -0
- 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
data/CHANGELOG.md
ADDED
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,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
|