reolink-http 0.1.0

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