alpr-sdk 0.1.1
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/README.md +119 -0
- data/lib/alpr/client.rb +99 -0
- data/lib/alpr/error.rb +46 -0
- data/lib/alpr/model.rb +45 -0
- data/lib/alpr/models.rb +622 -0
- data/lib/alpr/multipart.rb +90 -0
- data/lib/alpr/page.rb +40 -0
- data/lib/alpr/params.rb +107 -0
- data/lib/alpr/services.rb +90 -0
- data/lib/alpr/version.rb +14 -0
- data/lib/alpr/webhooks.rb +169 -0
- data/lib/alpr.rb +26 -0
- metadata +53 -0
data/lib/alpr/page.rb
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Alpr
|
|
4
|
+
# One page of a list: its items, and the cursor of the next page.
|
|
5
|
+
class Page
|
|
6
|
+
# @return [Array] the items of the page
|
|
7
|
+
attr_reader :items
|
|
8
|
+
# @return [String, nil] the cursor to ask the next page with; nil on the last page
|
|
9
|
+
attr_reader :next_cursor
|
|
10
|
+
|
|
11
|
+
def initialize(items:, next_cursor:)
|
|
12
|
+
@items = items.freeze
|
|
13
|
+
@next_cursor = next_cursor.nil? || next_cursor.to_s.empty? ? nil : next_cursor.to_s
|
|
14
|
+
freeze
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Builds a page from the API's hash ({"data": [...], "next": cursor}), each
|
|
18
|
+
# item through the block.
|
|
19
|
+
def self.from_hash(data, next_key = "next", &item)
|
|
20
|
+
new(items: (data["data"] || []).map(&item), next_cursor: data[next_key])
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Walks pages from the cursor +first+ (the newest page when nil) until the
|
|
24
|
+
# last one, yielding every item and ending at the first error; +fetch+
|
|
25
|
+
# answers the Page of the cursor it is given. A cursor that does not move
|
|
26
|
+
# would ask for the same page forever, so it ends the walk too.
|
|
27
|
+
def self.iterate(first, &fetch)
|
|
28
|
+
Enumerator.new do |yielder|
|
|
29
|
+
cursor = first.nil? || first.to_s.empty? ? nil : first.to_s
|
|
30
|
+
loop do
|
|
31
|
+
result = fetch.call(cursor)
|
|
32
|
+
result.items.each { |item| yielder << item }
|
|
33
|
+
break if result.next_cursor.nil? || result.next_cursor == cursor
|
|
34
|
+
|
|
35
|
+
cursor = result.next_cursor
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/alpr/params.rb
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Code generated by sdk/generator. DO NOT EDIT.
|
|
4
|
+
|
|
5
|
+
module Alpr
|
|
6
|
+
# The body of photos:detect.
|
|
7
|
+
class PhotosDetectParams < Params
|
|
8
|
+
MEMBERS = %i[image image_mode lpr_confidence min_ocr_confidence].freeze
|
|
9
|
+
|
|
10
|
+
# A foto, JPEG ou PNG.
|
|
11
|
+
# @return [String (the bytes)]
|
|
12
|
+
attr_reader :image
|
|
13
|
+
|
|
14
|
+
# Só NONE é aceito.
|
|
15
|
+
# @return [String ("NONE"), nil]
|
|
16
|
+
attr_reader :image_mode
|
|
17
|
+
|
|
18
|
+
# Piso de confiança do detector de placas.
|
|
19
|
+
# @return [Float, nil]
|
|
20
|
+
attr_reader :lpr_confidence
|
|
21
|
+
|
|
22
|
+
# Piso de confiança da leitura dos caracteres.
|
|
23
|
+
# @return [Float, nil]
|
|
24
|
+
attr_reader :min_ocr_confidence
|
|
25
|
+
|
|
26
|
+
def initialize(image:, image_mode: nil, lpr_confidence: nil, min_ocr_confidence: nil)
|
|
27
|
+
@image = image
|
|
28
|
+
@image_mode = image_mode
|
|
29
|
+
@lpr_confidence = lpr_confidence
|
|
30
|
+
@min_ocr_confidence = min_ocr_confidence
|
|
31
|
+
freeze
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# The multipart/form-data body as the API reads it: the photo as a file,
|
|
35
|
+
# the rest as text fields, an optional one left unset left out.
|
|
36
|
+
def to_form
|
|
37
|
+
form = Form.new
|
|
38
|
+
form.file("image", @image)
|
|
39
|
+
form.text("image_mode", @image_mode) unless @image_mode.nil?
|
|
40
|
+
form.text("lpr_confidence", @lpr_confidence) unless @lpr_confidence.nil?
|
|
41
|
+
form.text("min_ocr_confidence", @min_ocr_confidence) unless @min_ocr_confidence.nil?
|
|
42
|
+
form
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# The body of photos:ocr.
|
|
47
|
+
class PhotosOCRParams < Params
|
|
48
|
+
MEMBERS = %i[image].freeze
|
|
49
|
+
|
|
50
|
+
# A imagem da placa já recortada, JPEG ou PNG.
|
|
51
|
+
# @return [String (the bytes)]
|
|
52
|
+
attr_reader :image
|
|
53
|
+
|
|
54
|
+
def initialize(image:)
|
|
55
|
+
@image = image
|
|
56
|
+
freeze
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# The multipart/form-data body as the API reads it: the photo as a file,
|
|
60
|
+
# the rest as text fields, an optional one left unset left out.
|
|
61
|
+
def to_form
|
|
62
|
+
form = Form.new
|
|
63
|
+
form.file("image", @image)
|
|
64
|
+
form
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# The body of photos:recognize.
|
|
69
|
+
class PhotosRecognizeParams < Params
|
|
70
|
+
MEMBERS = %i[image image_mode lpr_confidence min_ocr_confidence].freeze
|
|
71
|
+
|
|
72
|
+
# A foto, JPEG ou PNG.
|
|
73
|
+
# @return [String (the bytes)]
|
|
74
|
+
attr_reader :image
|
|
75
|
+
|
|
76
|
+
# Só NONE é aceito.
|
|
77
|
+
# @return [String ("NONE"), nil]
|
|
78
|
+
attr_reader :image_mode
|
|
79
|
+
|
|
80
|
+
# Piso de confiança do detector de placas.
|
|
81
|
+
# @return [Float, nil]
|
|
82
|
+
attr_reader :lpr_confidence
|
|
83
|
+
|
|
84
|
+
# Piso de confiança da leitura dos caracteres.
|
|
85
|
+
# @return [Float, nil]
|
|
86
|
+
attr_reader :min_ocr_confidence
|
|
87
|
+
|
|
88
|
+
def initialize(image:, image_mode: nil, lpr_confidence: nil, min_ocr_confidence: nil)
|
|
89
|
+
@image = image
|
|
90
|
+
@image_mode = image_mode
|
|
91
|
+
@lpr_confidence = lpr_confidence
|
|
92
|
+
@min_ocr_confidence = min_ocr_confidence
|
|
93
|
+
freeze
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# The multipart/form-data body as the API reads it: the photo as a file,
|
|
97
|
+
# the rest as text fields, an optional one left unset left out.
|
|
98
|
+
def to_form
|
|
99
|
+
form = Form.new
|
|
100
|
+
form.file("image", @image)
|
|
101
|
+
form.text("image_mode", @image_mode) unless @image_mode.nil?
|
|
102
|
+
form.text("lpr_confidence", @lpr_confidence) unless @lpr_confidence.nil?
|
|
103
|
+
form.text("min_ocr_confidence", @min_ocr_confidence) unless @min_ocr_confidence.nil?
|
|
104
|
+
form
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Code generated by sdk/generator. DO NOT EDIT.
|
|
4
|
+
|
|
5
|
+
module Alpr
|
|
6
|
+
# The API's cameras.
|
|
7
|
+
class CamerasService
|
|
8
|
+
def initialize(client)
|
|
9
|
+
@client = client
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Listar as câmeras.
|
|
13
|
+
#
|
|
14
|
+
# @return [Array<Camera>]
|
|
15
|
+
def list(api_key: nil)
|
|
16
|
+
data = @client.request("GET", "/api/v1/cameras", api_key: api_key)
|
|
17
|
+
(data["data"] || []).map { |item| Camera.from_hash(item) }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# The API's photos.
|
|
22
|
+
class PhotosService
|
|
23
|
+
def initialize(client)
|
|
24
|
+
@client = client
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Localizar as placas de uma foto.
|
|
28
|
+
#
|
|
29
|
+
# @param params [PhotosDetectParams, Hash]
|
|
30
|
+
# @return [PhotoDetection]
|
|
31
|
+
def detect(params, api_key: nil)
|
|
32
|
+
data = @client.request("POST", "/api/v1/detect", form: PhotosDetectParams.coerce(params).to_form, api_key: api_key)
|
|
33
|
+
PhotoDetection.from_hash(data)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Ler uma placa já recortada.
|
|
37
|
+
#
|
|
38
|
+
# @param params [PhotosOCRParams, Hash]
|
|
39
|
+
# @return [PlateReading]
|
|
40
|
+
def ocr(params, api_key: nil)
|
|
41
|
+
data = @client.request("POST", "/api/v1/ocr", form: PhotosOCRParams.coerce(params).to_form, api_key: api_key)
|
|
42
|
+
PlateReading.from_hash(data)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Ler as placas de uma foto.
|
|
46
|
+
#
|
|
47
|
+
# @param params [PhotosRecognizeParams, Hash]
|
|
48
|
+
# @return [PhotoRecognition]
|
|
49
|
+
def recognize(params, api_key: nil)
|
|
50
|
+
data = @client.request("POST", "/api/v1/recognize", form: PhotosRecognizeParams.coerce(params).to_form, api_key: api_key)
|
|
51
|
+
PhotoRecognition.from_hash(data)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# The API's recognitions.
|
|
56
|
+
class RecognitionsService
|
|
57
|
+
def initialize(client)
|
|
58
|
+
@client = client
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Imagem de uma leitura.
|
|
62
|
+
#
|
|
63
|
+
# @return [DetectionImageLink]
|
|
64
|
+
def image(id, api_key: nil)
|
|
65
|
+
data = @client.request("GET", "/api/v1/detections/#{Alpr.segment(id)}/image", api_key: api_key)
|
|
66
|
+
DetectionImageLink.from_hash(data)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Listar as leituras. One page; +list_iter+ walks every page. +before+ is the
|
|
70
|
+
# cursor the previous page answered as +next_cursor+; nil asks for the newest page.
|
|
71
|
+
#
|
|
72
|
+
# @return [Page<Recognition>]
|
|
73
|
+
def list(before: nil, plate: nil, camera: nil, source: nil, api_key: nil)
|
|
74
|
+
query = { "before" => before, "plate" => plate, "camera" => camera, "source" => source }
|
|
75
|
+
data = @client.request("GET", "/api/v1/recognitions", query: query, api_key: api_key)
|
|
76
|
+
Page.from_hash(data, "next") { |item| Recognition.from_hash(item) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Walks every page of recognitions:list from the cursor +before+ (the newest when
|
|
80
|
+
# nil), item by item, until the last page; it ends at the first error.
|
|
81
|
+
#
|
|
82
|
+
# @return [Enumerator<Recognition>]
|
|
83
|
+
def list_iter(before: nil, plate: nil, camera: nil, source: nil, api_key: nil, &block)
|
|
84
|
+
items = Page.iterate(before) do |at|
|
|
85
|
+
list(before: at, plate: plate, camera: camera, source: source, api_key: api_key)
|
|
86
|
+
end
|
|
87
|
+
block ? items.each(&block) : items
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
data/lib/alpr/version.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Code generated by sdk/generator. DO NOT EDIT.
|
|
4
|
+
|
|
5
|
+
module Alpr
|
|
6
|
+
# This SDK's version.
|
|
7
|
+
VERSION = "0.1.1"
|
|
8
|
+
|
|
9
|
+
# The contract this SDK was generated from.
|
|
10
|
+
API_VERSION = "1.0.0"
|
|
11
|
+
|
|
12
|
+
# Alpr.sh's API. The workspace is the API key's: alpr_live_….
|
|
13
|
+
DEFAULT_BASE_URL = "https://alpr.sh"
|
|
14
|
+
end
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Code generated by sdk/generator. DO NOT EDIT.
|
|
4
|
+
|
|
5
|
+
require "json"
|
|
6
|
+
require "openssl"
|
|
7
|
+
|
|
8
|
+
# Verifying the webhooks Alpr.sh sends.
|
|
9
|
+
module Alpr
|
|
10
|
+
# How far from now a webhook's timestamp may be, in seconds.
|
|
11
|
+
WEBHOOK_TOLERANCE = 300
|
|
12
|
+
|
|
13
|
+
# How many signatures a request may list: one for the secret in use and one
|
|
14
|
+
# for each secret still signing during a rotation.
|
|
15
|
+
MAX_WEBHOOK_SIGNATURES = 6
|
|
16
|
+
|
|
17
|
+
# A request Alpr.sh did not sign, or signed too long ago.
|
|
18
|
+
class WebhookVerificationError < StandardError
|
|
19
|
+
def initialize(message = "alpr: webhook signature does not verify")
|
|
20
|
+
super
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# A verified body that is not an event, or not the event its name says.
|
|
25
|
+
class WebhookParseError < StandardError; end
|
|
26
|
+
|
|
27
|
+
# What every webhook event answers. +event_name+ is the event's name:
|
|
28
|
+
# recognition.created.
|
|
29
|
+
module WebhookEvent
|
|
30
|
+
# @return [String]
|
|
31
|
+
def event_name
|
|
32
|
+
@event
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The payload of
|
|
37
|
+
# * alert.triggered
|
|
38
|
+
class AlertTriggeredWebhookEvent < WebhookAlertTriggered
|
|
39
|
+
include WebhookEvent
|
|
40
|
+
|
|
41
|
+
TYPES = %w[alert.triggered].freeze
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The payload of
|
|
45
|
+
# * recognition.created
|
|
46
|
+
class RecognitionCreatedWebhookEvent < WebhookRecognitionCreated
|
|
47
|
+
include WebhookEvent
|
|
48
|
+
|
|
49
|
+
TYPES = %w[recognition.created].freeze
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# The payload of
|
|
53
|
+
# * webhook.test
|
|
54
|
+
class TestWebhookEvent < WebhookTest
|
|
55
|
+
include WebhookEvent
|
|
56
|
+
|
|
57
|
+
TYPES = %w[webhook.test].freeze
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# An event of a name this SDK does not know yet; its body is kept as it was
|
|
61
|
+
# sent.
|
|
62
|
+
class UnknownWebhookEvent < Model
|
|
63
|
+
include WebhookEvent
|
|
64
|
+
|
|
65
|
+
MEMBERS = %i[event id body].freeze
|
|
66
|
+
|
|
67
|
+
# @return [String] the event's name
|
|
68
|
+
attr_reader :event
|
|
69
|
+
# @return [String] the id the body carries, or an empty string
|
|
70
|
+
attr_reader :id
|
|
71
|
+
# @return [Hash] the whole body, decoded
|
|
72
|
+
attr_reader :body
|
|
73
|
+
|
|
74
|
+
def initialize(event:, id:, body:)
|
|
75
|
+
@event = event
|
|
76
|
+
@id = id
|
|
77
|
+
@body = body
|
|
78
|
+
freeze
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Checks a webhook Alpr.sh sent and answers its event.
|
|
83
|
+
#
|
|
84
|
+
# Alpr.sh signs the headers X-Alprsh-Timestamp and X-Alprsh-Delivery-Id and
|
|
85
|
+
# the body: X-Alprsh-Signature carries "sha256=" and the hex HMAC-SHA256 of
|
|
86
|
+
# "{timestamp}.{delivery-id}.{body}" under the endpoint's secret (the whole
|
|
87
|
+
# whsec_… text, as it is), and more than one signature while a secret is
|
|
88
|
+
# rotated -- one matching is enough. Pass the body exactly as it arrived,
|
|
89
|
+
# before parsing it, and deduplicate by the X-Alprsh-Delivery-Id header.
|
|
90
|
+
# +headers+ is a hash of the request's headers, in any case (a value may be a
|
|
91
|
+
# list, one a header line), or a Rack env. +now+ is for tests.
|
|
92
|
+
#
|
|
93
|
+
# @return [AlertTriggeredWebhookEvent, RecognitionCreatedWebhookEvent, TestWebhookEvent, UnknownWebhookEvent]
|
|
94
|
+
# @raise [WebhookVerificationError] the signature does not verify
|
|
95
|
+
# @raise [WebhookParseError] the body is not an event
|
|
96
|
+
# @raise [ArgumentError] the secret is empty
|
|
97
|
+
def self.verify_webhook(secret, headers, body, now: Time.now)
|
|
98
|
+
verify_webhook_signature(secret, headers, body, now: now)
|
|
99
|
+
webhook_event(body.to_s.b.force_encoding(Encoding::UTF_8))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Checks a webhook's signature as verify_webhook does, without reading its
|
|
103
|
+
# body.
|
|
104
|
+
#
|
|
105
|
+
# @return [nil]
|
|
106
|
+
# @raise [WebhookVerificationError] the signature does not verify
|
|
107
|
+
# @raise [ArgumentError] the secret is empty
|
|
108
|
+
def self.verify_webhook_signature(secret, headers, body, now: Time.now)
|
|
109
|
+
raise ArgumentError, "alpr: the webhook secret is empty" if secret.to_s.empty?
|
|
110
|
+
|
|
111
|
+
delivery_id = webhook_header(headers, "x-alprsh-delivery-id").first.to_s
|
|
112
|
+
stamp = webhook_header(headers, "x-alprsh-timestamp").first.to_s
|
|
113
|
+
# Only ASCII digits: the signer never sends a sign, a space or other digits.
|
|
114
|
+
raise WebhookVerificationError if delivery_id.empty? || !stamp.match?(/\A[0-9]+\z/)
|
|
115
|
+
raise WebhookVerificationError if (now.to_r - stamp.to_i).abs > WEBHOOK_TOLERANCE
|
|
116
|
+
|
|
117
|
+
listed = webhook_header(headers, "x-alprsh-signature").join(",").split(",", -1)
|
|
118
|
+
raise WebhookVerificationError if listed.empty? || listed.size > MAX_WEBHOOK_SIGNATURES
|
|
119
|
+
|
|
120
|
+
expected = OpenSSL::HMAC.digest("SHA256", secret.to_s.b, "#{stamp}.#{delivery_id}.".b + body.to_s.b)
|
|
121
|
+
matched = false
|
|
122
|
+
listed.each do |signature|
|
|
123
|
+
# One malformed signature refuses the request. Every listed signature is
|
|
124
|
+
# compared, in constant time.
|
|
125
|
+
encoded = signature.strip[/\Asha256=([0-9a-fA-F]{64})\z/, 1]
|
|
126
|
+
raise WebhookVerificationError if encoded.nil?
|
|
127
|
+
|
|
128
|
+
matched = true if OpenSSL.fixed_length_secure_compare([encoded].pack("H*"), expected)
|
|
129
|
+
end
|
|
130
|
+
raise WebhookVerificationError unless matched
|
|
131
|
+
|
|
132
|
+
nil
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# A verified body as the event its name says.
|
|
136
|
+
def self.webhook_event(raw)
|
|
137
|
+
body = begin
|
|
138
|
+
JSON.parse(raw)
|
|
139
|
+
rescue JSON::ParserError => e
|
|
140
|
+
raise WebhookParseError, "alpr: the webhook body is not an event: #{e.message}"
|
|
141
|
+
end
|
|
142
|
+
raise WebhookParseError, "alpr: the webhook body is not an event" unless body.is_a?(Hash) && body["event"].is_a?(String) && !body["event"].empty?
|
|
143
|
+
|
|
144
|
+
name = body["event"]
|
|
145
|
+
begin
|
|
146
|
+
case name
|
|
147
|
+
when *AlertTriggeredWebhookEvent::TYPES then AlertTriggeredWebhookEvent.from_hash(body)
|
|
148
|
+
when *RecognitionCreatedWebhookEvent::TYPES then RecognitionCreatedWebhookEvent.from_hash(body)
|
|
149
|
+
when *TestWebhookEvent::TYPES then TestWebhookEvent.from_hash(body)
|
|
150
|
+
else UnknownWebhookEvent.new(event: name, id: body["id"].is_a?(String) ? body["id"] : "", body: body)
|
|
151
|
+
end
|
|
152
|
+
rescue KeyError, TypeError, ArgumentError => e
|
|
153
|
+
raise WebhookParseError, "alpr: the webhook body is not a #{name} event: #{e.message}"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
private_class_method :webhook_event
|
|
157
|
+
|
|
158
|
+
# The values of a header, looked up in any case; a Rack env's
|
|
159
|
+
# HTTP_X_ALPRSH_DELIVERY_ID names x-alprsh-delivery-id. A header sent as
|
|
160
|
+
# several lines has several values.
|
|
161
|
+
def self.webhook_header(headers, name)
|
|
162
|
+
values = []
|
|
163
|
+
headers.each do |key, value|
|
|
164
|
+
values.concat(Array(value).map(&:to_s)) if key.to_s.downcase.tr("_", "-").delete_prefix("http-") == name
|
|
165
|
+
end
|
|
166
|
+
values
|
|
167
|
+
end
|
|
168
|
+
private_class_method :webhook_header
|
|
169
|
+
end
|
data/lib/alpr.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Code generated by sdk/generator. DO NOT EDIT.
|
|
4
|
+
|
|
5
|
+
# Alpr.sh's SDK for Ruby, generated from Alpr.sh's API contract.
|
|
6
|
+
#
|
|
7
|
+
# alpr = Alpr::Client.new(ENV.fetch("ALPR_API_KEY"))
|
|
8
|
+
# result = alpr.photos.recognize({ image: File.binread("photo.jpg") })
|
|
9
|
+
# alpr.recognitions.list_iter(plate: "BRA2E19").each { |reading| puts reading.captured_at }
|
|
10
|
+
module Alpr
|
|
11
|
+
# A path parameter, escaped.
|
|
12
|
+
def self.segment(value)
|
|
13
|
+
value.to_s.b.gsub(/[^A-Za-z0-9\-._~]/) { |char| format("%%%02X", char.ord) }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
require_relative "alpr/version"
|
|
18
|
+
require_relative "alpr/error"
|
|
19
|
+
require_relative "alpr/model"
|
|
20
|
+
require_relative "alpr/multipart"
|
|
21
|
+
require_relative "alpr/models"
|
|
22
|
+
require_relative "alpr/params"
|
|
23
|
+
require_relative "alpr/page"
|
|
24
|
+
require_relative "alpr/services"
|
|
25
|
+
require_relative "alpr/client"
|
|
26
|
+
require_relative "alpr/webhooks"
|
metadata
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: alpr-sdk
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Alpr.sh
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
executables: []
|
|
13
|
+
extensions: []
|
|
14
|
+
extra_rdoc_files: []
|
|
15
|
+
files:
|
|
16
|
+
- README.md
|
|
17
|
+
- lib/alpr.rb
|
|
18
|
+
- lib/alpr/client.rb
|
|
19
|
+
- lib/alpr/error.rb
|
|
20
|
+
- lib/alpr/model.rb
|
|
21
|
+
- lib/alpr/models.rb
|
|
22
|
+
- lib/alpr/multipart.rb
|
|
23
|
+
- lib/alpr/page.rb
|
|
24
|
+
- lib/alpr/params.rb
|
|
25
|
+
- lib/alpr/services.rb
|
|
26
|
+
- lib/alpr/version.rb
|
|
27
|
+
- lib/alpr/webhooks.rb
|
|
28
|
+
homepage: https://alpr.sh
|
|
29
|
+
licenses:
|
|
30
|
+
- MIT
|
|
31
|
+
metadata:
|
|
32
|
+
homepage_uri: https://alpr.sh
|
|
33
|
+
source_code_uri: https://github.com/alpr-sh/sdk
|
|
34
|
+
rubygems_mfa_required: 'true'
|
|
35
|
+
rdoc_options: []
|
|
36
|
+
require_paths:
|
|
37
|
+
- lib
|
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
39
|
+
requirements:
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '3.1'
|
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
requirements: []
|
|
49
|
+
rubygems_version: 4.0.16
|
|
50
|
+
specification_version: 4
|
|
51
|
+
summary: 'Alpr.sh''s SDK for Ruby: license-plate recognition from photos, readings,
|
|
52
|
+
cameras and webhooks.'
|
|
53
|
+
test_files: []
|