reolink-http 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0df4961d3dddac35c27d48dc2a9d859d9e863c7bef207458642083fce41334d0
4
+ data.tar.gz: 56002d3e9acb54e5d6c305f3cf49552f01c173b894dcdd252b5ec73139974e0c
5
+ SHA512:
6
+ metadata.gz: 8a993b24bf83f29ba5f6e92cf75528276c793a2f07406a95a18b8c500dec593140f4eb76c03e73c082c75e9736a018fa16cb0612d0083fe99000ea1ab81fbf8d
7
+ data.tar.gz: 1700ad1ce09feb1a8105bc8c3678a547fad5f10a38bb5e11dde13fa7d91ba2ac227600690125368a653dae257b94d4c1170392a98ef916510334943f4953ecdf
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Reolink
4
+ class HTTP
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "net/http"
5
+
6
+ require_relative "http/version"
7
+
8
+ module Reolink
9
+ # Provides an abstraction of the Reolink device API.
10
+ # See the Reolink API documentation for commands, parameters, and response fields.
11
+ class HTTP
12
+ attr_reader :host, :token, :token_expires_at
13
+
14
+ def initialize(host, username: nil, password: nil)
15
+ @host = host
16
+ @username = username
17
+ @password = password
18
+ @token = nil
19
+ @token_expires_at = Time.new(0)
20
+ end
21
+
22
+ def method_missing(method_name, params = {}, **kwargs)
23
+ # Assume any missing method is a valid Reolink command. We have no good way to validate this: the API changes and
24
+ # will be a maintenance burden, and GetAbility does not return all commands. An invalid command will still cause
25
+ # a proper error response, so this functionality is correct, if not ideal.
26
+ command(command: method_name.to_s.split("_").map(&:capitalize).join,
27
+ params:,
28
+ **kwargs)
29
+ end
30
+
31
+ def respond_to_missing?(*_args)
32
+ true
33
+ end
34
+
35
+ def command(method: :post, **kwargs)
36
+ Net::HTTP.start(host, use_ssl: true, verify_mode: OpenSSL::SSL::VERIFY_NONE) do |http|
37
+ refresh_token(http:) if token_expires_at <= Time.now
38
+ case method
39
+ when :post then post(http:, **kwargs)
40
+ when :get then get(http:, **kwargs)
41
+ else
42
+ raise "Unknown method #{method}"
43
+ end
44
+ end
45
+ end
46
+
47
+ def refresh_token(http:)
48
+ login_response = post(http:, command: "Login", params: { "User" => { "Version" => "0",
49
+ "userName" => @username,
50
+ "password" => @password } })
51
+ .first.value["Token"]
52
+ @token = login_response["name"]
53
+ @token_expires_at = Time.now + login_response["leaseTime"]
54
+ end
55
+
56
+ private
57
+
58
+ def get(http:, command:, params: {})
59
+ http.get("/api.cgi?#{URI.encode_www_form({ cmd: command, token:, **params })}")
60
+ end
61
+
62
+ # `verbose` is passed as the `action` parameter. Ignored for non-Get* endpoints.
63
+ # 0 -- get current value only
64
+ # 1 -- get initial, range, and current value
65
+ def post(http:, command:, params: {}, verbose: true)
66
+ params = { command.delete_prefix("Set") => params } if command.start_with?("Set")
67
+
68
+ http.post("/api.cgi?cmd=#{command}" + ("&token=#{token}" if token).to_s,
69
+ JSON.generate([{ cmd: command, action: verbose ? 1 : 0, param: params }]))
70
+ .then { |http_response| Response.from_json(http_response.body) }
71
+ end
72
+
73
+ # Represents a response from the device.
74
+ # cmd -- name of command
75
+ # code -- status code. 0 means no errors.
76
+ # initial -- initial value of attribute, for Get* requests when `verbose` is true
77
+ # range -- range of possible values for attribute, for Get* requests when `verbose` is true
78
+ # current -- current value of attribute, for Get* requests
79
+ # error -- error code and details. nil if no error.
80
+ class Response
81
+ # Parse a JSON string from the device and return *an array* of Response objects.
82
+ def self.from_json(str)
83
+ JSON.parse(str)
84
+ .map do |response|
85
+ new(**response.transform_keys(&:to_sym))
86
+ end
87
+ end
88
+
89
+ attr_reader :cmd, :code, :initial, :range, :value, :error
90
+
91
+ def initialize(cmd:, code:, initial: nil, range: nil, value: nil, error: nil)
92
+ @cmd = cmd
93
+ @code = code
94
+ @initial = initial&.[](cmd.delete_prefix("Get")) || initial
95
+ @range = range&.[](cmd.delete_prefix("Get")) || range
96
+ @value = value&.[](cmd.delete_prefix("Get")) || value
97
+ @error = error
98
+ end
99
+ end
100
+ end
101
+ end
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: reolink-http
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Eddie Lebow
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-07-10 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ - elebow@users.noreply.github.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/reolink/http.rb
21
+ - lib/reolink/http/version.rb
22
+ homepage: https://codeberg.org/elebow/reolink-http-ruby
23
+ licenses:
24
+ - AGPL-3.0-or-later
25
+ metadata:
26
+ homepage_uri: https://codeberg.org/elebow/reolink-http-ruby
27
+ source_code_uri: https://codeberg.org/elebow/reolink-http-ruby
28
+ changelog_uri: https://codeberg.org/elebow/reolink-http-ruby/src/CHANGELOG.md
29
+ rubygems_mfa_required: 'true'
30
+ post_install_message:
31
+ rdoc_options: []
32
+ require_paths:
33
+ - lib
34
+ required_ruby_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: 3.1.0
39
+ required_rubygems_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ requirements: []
45
+ rubygems_version: 3.4.1
46
+ signing_key:
47
+ specification_version: 4
48
+ summary: Interface for Reolink's HTTP device API.
49
+ test_files: []