Class: UnifiProtect::API

Inherits:
Object
  • Object
show all
Defined in:
lib/unifi_protect/api.rb

Defined Under Namespace

Classes: DownloadedFile, RefreshBearerTokenError, RequestError

Instance Method Summary collapse

Constructor Details

#initialize(host: nil, port: 7443, username: nil, password: nil, download_path: nil) ⇒ API

Returns a new instance of API.



13
14
15
16
17
18
19
# File 'lib/unifi_protect/api.rb', line 13

def initialize(host: nil, port: 7443, username: nil, password: nil, download_path: nil)
  @host = host
  @port = port
  @username = username
  @password = password
  @download_path = download_path
end

Instance Method Details

#base_uriObject



29
30
31
# File 'lib/unifi_protect/api.rb', line 29

def base_uri
  URI::HTTPS.build(host: @host, port: @port, path: '/api/')
end

#bearer_tokenObject



95
96
97
# File 'lib/unifi_protect/api.rb', line 95

def bearer_token
  @bearer_token ||= refresh_bearer_token
end

#bootstrapObject



148
149
150
# File 'lib/unifi_protect/api.rb', line 148

def bootstrap
  request_with_json_response(uri(path: 'bootstrap'))
end

#bootstrap_jsonObject



144
145
146
# File 'lib/unifi_protect/api.rb', line 144

def bootstrap_json
  request_with_raw_response(uri(path: 'bootstrap'))
end

#camera_snapshot(camera:, local_file: nil, time: Time.now) ⇒ Object



152
153
154
155
156
157
158
# File 'lib/unifi_protect/api.rb', line 152

def camera_snapshot(camera:, local_file: nil, time: Time.now)
  ts = time.utc.to_i * 1000
  local_file ||= "#{camera}_#{ts}.jpg"

  query = URI.encode_www_form(force: true, ts: ts)
  download_file(uri(path: "cameras/#{camera}/snapshot", query: query), local_file: local_file)
end

#download_file(uri, method: :get, body: nil, local_file:) ⇒ Object



131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/unifi_protect/api.rb', line 131

def download_file(uri, method: :get, body: nil, local_file:)
  file = local_file
  file = File.join(@download_path, file) if @download_path

  File.open(file, 'wb') do |f|
    r = request_with_chunked_response(uri, method: method, body: body) do |chunk, _total, _length|
      f.write(chunk)
    end

    DownloadedFile.new(file: file, size: r.content_length)
  end
end

#http_clientObject



48
49
50
# File 'lib/unifi_protect/api.rb', line 48

def http_client
  @http_client ||= new_http_client
end

#http_get_with_bearer_token(uri) ⇒ Object



73
74
75
76
77
78
# File 'lib/unifi_protect/api.rb', line 73

def http_get_with_bearer_token(uri)
  headers = {
    'Authorization' => 'Bearer ' + bearer_token,
  }
  Net::HTTP::Get.new(uri.request_uri, headers)
end

#http_post_with_bearer_token(uri, body: nil) ⇒ Object



62
63
64
65
66
67
68
69
70
71
# File 'lib/unifi_protect/api.rb', line 62

def http_post_with_bearer_token(uri, body: nil)
  headers = {
    'Content-Type' => 'application/json',
    'Authorization' => 'Bearer ' + bearer_token,
  }
  request = Net::HTTP::Post.new(uri.request_uri, headers)
  request.body = body

  request
end

#http_post_with_username_password(uri) ⇒ Object



52
53
54
55
56
57
58
59
60
# File 'lib/unifi_protect/api.rb', line 52

def http_post_with_username_password(uri)
  headers = {
    'Content-Type' => 'application/json',
  }
  request = Net::HTTP::Post.new(uri.request_uri, headers)
  request.body = { username: @username, password: @password }.to_json

  request
end

#http_request_with_bearer_token(uri, method: :get, body: nil) ⇒ Object



80
81
82
83
84
85
# File 'lib/unifi_protect/api.rb', line 80

def http_request_with_bearer_token(uri, method: :get, body: nil)
  return http_get_with_bearer_token(uri) if method == :get
  return http_post_with_bearer_token(uri, body: body) if method == :post

  nil
end

#inspectObject



25
26
27
# File 'lib/unifi_protect/api.rb', line 25

def inspect
  to_s
end

#new_http_clientObject



40
41
42
43
44
45
46
# File 'lib/unifi_protect/api.rb', line 40

def new_http_client
  http_client = Net::HTTP.new(base_uri.host, base_uri.port)
  http_client.use_ssl = true
  http_client.verify_mode = OpenSSL::SSL::VERIFY_NONE

  http_client
end

#refresh_bearer_tokenObject



87
88
89
90
91
92
93
# File 'lib/unifi_protect/api.rb', line 87

def refresh_bearer_token
  response = http_client.request(http_post_with_username_password(uri(path: 'auth')))

  raise RefreshBearerTokenError, "#{response.code} #{response.msg}: #{response.body}" unless response.code == '200'

  @bearer_token = response['Authorization']
end

#request_with_chunked_response(uri, method: :get, body: nil, exception_class: RequestError) ⇒ Object



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/unifi_protect/api.rb', line 115

def request_with_chunked_response(uri, method: :get, body: nil, exception_class: RequestError)
  raise 'no block provided' unless block_given?

  http_client.request(http_request_with_bearer_token(uri, method: method, body: body)) do |response|
    raise exception_class, "#{response.code} #{response.msg}: #{response.body}" unless response.code == '200'

    chunk_total = 0
    response.read_body do |chunk|
      chunk_total += chunk.size
      yield chunk, chunk_total, response.content_length
    end

    response
  end
end

#request_with_json_response(uri, method: :get, body: nil, exception_class: RequestError) ⇒ Object

Raises:

  • (exception_class)


107
108
109
110
111
112
113
# File 'lib/unifi_protect/api.rb', line 107

def request_with_json_response(uri, method: :get, body: nil, exception_class: RequestError)
  response = http_client.request(http_request_with_bearer_token(uri, method: method, body: body))

  raise exception_class, "#{response.code} #{response.msg}: #{response.body}" unless response.code == '200'

  JSON.parse(response.body, object_class: OpenStruct)
end

#request_with_raw_response(uri, method: :get, body: nil, exception_class: RequestError) ⇒ Object

Raises:

  • (exception_class)


99
100
101
102
103
104
105
# File 'lib/unifi_protect/api.rb', line 99

def request_with_raw_response(uri, method: :get, body: nil, exception_class: RequestError)
  response = http_client.request(http_request_with_bearer_token(uri, method: method, body: body))

  raise exception_class, "#{response.code} #{response.msg}: #{response.body}" unless response.code == '200'

  response.body
end

#to_sObject



21
22
23
# File 'lib/unifi_protect/api.rb', line 21

def to_s
  "#<#{self.class.name} base_uri=#{base_uri.to_s.inspect} username=#{@username.inspect}>"
end

#uri(path:, query: nil) ⇒ Object



33
34
35
36
37
38
# File 'lib/unifi_protect/api.rb', line 33

def uri(path:, query: nil)
  uri = URI.join(base_uri, path)
  uri.query = query if query

  uri
end

#video_export(camera:, start_time:, end_time:, local_file: nil) ⇒ Object



160
161
162
163
164
165
166
167
# File 'lib/unifi_protect/api.rb', line 160

def video_export(camera:, start_time:, end_time:, local_file: nil)
  start_ts = start_time.utc.to_i * 1000
  end_ts = end_time.utc.to_i * 1000
  local_file ||= "#{camera}_#{start_ts}_#{end_ts}.mp4"

  query = URI.encode_www_form(camera: camera, start: start_ts, end: end_ts)
  download_file(uri(path: 'video/export', query: query), local_file: local_file)
end