webinarjam 0.0.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: 007f29eaa5a4eeefb046bcfa9b02f3e25e39406acad46b0a395ae969533b91a3
4
+ data.tar.gz: df1a31482c8dd326010dc2e13ad6d60657a9a57b3502e3a363ff54b511adbd1b
5
+ SHA512:
6
+ metadata.gz: f223c1847c4e590be08cb8882e08806e04cac0d59f4b341507a06dd4a527a97074a117e658d6c1ffbac94062bfd77d8c228536c0cb4a20c63f0f46d819a26f30
7
+ data.tar.gz: dcd6b918f3131a4aab6f49f5215787343f8250a8fdc92bc509f4fa6712efa06ad6d8afb5a8d2e1629c895ace00cb2a9d5362ad24226cc1c77170011afe46adf8
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## [0.0.0] - 2026-08-13
4
+
5
+ - Initial release claiming the gem name.
6
+ - `WebinarJam::Client` covering all documented WebinarJam API endpoints:
7
+ `#webinars`, `#webinar`, `#register`, `#registrants`, `#unsubscribe`.
8
+ - EverWebinar support via `product: :everwebinar`.
9
+ - Typed errors (`AuthenticationError`, `RateLimitError`, `APIError`) and
10
+ opt-in automatic retries on HTTP 429.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Yi-Ru Lin
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,115 @@
1
+ # WebinarJam
2
+
3
+ A dependency-free Ruby client for the [WebinarJam / EverWebinar API](https://support.webinarjam.com/en/articles/15370144-connect-to-webinarjam-or-everwebinar-api).
4
+
5
+ Covers every documented endpoint:
6
+
7
+ | Method | Endpoint | Purpose |
8
+ |---|---|---|
9
+ | `#webinars` | `POST /webinars` | List all published webinars |
10
+ | `#webinar(id)` | `POST /webinar` | Details for one webinar (schedules, URLs, presenters) |
11
+ | `#register(...)` | `POST /register` | Register a user to a webinar |
12
+ | `#registrants(...)` | `POST /registrants` | List registrants and attendees |
13
+ | `#unsubscribe(...)` | `POST /unsubscribe` | Unsubscribe a lead from notifications |
14
+
15
+ ## Installation
16
+
17
+ ```ruby
18
+ # Gemfile
19
+ gem "webinarjam"
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ ```ruby
25
+ require "webinarjam"
26
+
27
+ client = WebinarJam::Client.new(api_key: ENV["WEBINARJAM_API_KEY"])
28
+ # api_key: defaults to ENV["WEBINARJAM_API_KEY"], so this is equivalent:
29
+ client = WebinarJam::Client.new
30
+
31
+ # Or configure once, module-wide:
32
+ WebinarJam.configure { |config| config.api_key = "your-key" }
33
+ client = WebinarJam.client
34
+
35
+ # List all webinars
36
+ client.webinars
37
+ # => { status: "success", webinars: [{ webinar_id: 1, webinar_hash: "abcd1234", ... }] }
38
+
39
+ # Details for one webinar (schedule ids live here)
40
+ details = client.webinar(1)
41
+ schedule = details[:webinar][:schedules].first[:schedule]
42
+
43
+ # Register a user
44
+ client.register(
45
+ webinar_id: 1,
46
+ schedule: schedule,
47
+ first_name: "Jane",
48
+ email: "jane@example.com",
49
+ # optional: last_name, country, state, timezone_id, ip_address,
50
+ # phone_country_code, phone, twilio_consent
51
+ )
52
+ # => { status: "success", user: { user_id: ..., live_room_url: ..., ... } }
53
+
54
+ # Registrants / attendees for one session (paginated)
55
+ listing = client.registrants(webinar_id: 1, schedule_id: schedule, search: "jane@example.com")
56
+ # => { status: "success", registrants: { current_page: 1, data: [{ lead_id: ..., email: ..., ... }] } }
57
+ # optional filters: attended_live, attended_replay, purchased, page,
58
+ # attended_live_timestamp, attended_replay_timestamp, date_range
59
+
60
+ # Unsubscribe a lead (lead id comes from #registrants)
61
+ client.unsubscribe(webinar_id: 1, lead_id: 818)
62
+ # => true (the API responds 204 No Content)
63
+ ```
64
+
65
+ All responses are parsed JSON with symbolized keys.
66
+
67
+ ### EverWebinar
68
+
69
+ The EverWebinar API shares the same shape under a different path prefix:
70
+
71
+ ```ruby
72
+ client = WebinarJam::Client.new(product: :everwebinar)
73
+ ```
74
+
75
+ ### Errors and rate limiting
76
+
77
+ Every API failure raises a subclass of `WebinarJam::Error`:
78
+
79
+ - `WebinarJam::ConfigurationError` — no API key provided.
80
+ - `WebinarJam::AuthenticationError` — HTTP 401/403 (invalid API key).
81
+ - `WebinarJam::RateLimitError` — HTTP 429. The API allows at most
82
+ **20 calls per second per user**.
83
+ - `WebinarJam::APIError` — any other error, including 2xx responses whose
84
+ body carries `"status": "error"`. Exposes `#http_status` and `#body`.
85
+
86
+ Opt into automatic retries on 429 (exponential backoff: 0.5s, 1s, 2s, ...):
87
+
88
+ ```ruby
89
+ client = WebinarJam::Client.new(max_retries: 3)
90
+ ```
91
+
92
+ ## Development
93
+
94
+ ```sh
95
+ bundle install
96
+ bundle exec rspec # unit specs + integration specs (VCR replay)
97
+ bundle exec rspec spec/webinarjam # unit specs only
98
+ bin/console # IRB with the gem and .env loaded
99
+ ```
100
+
101
+ Integration specs replay VCR cassettes committed under
102
+ `spec/fixtures/vcr_cassettes/` — no credentials needed. To re-record against
103
+ the live API, put a real key in `.env` (`WEBINARJAM_API_KEY=...`) and run:
104
+
105
+ ```sh
106
+ VCR_RECORD=all bundle exec rspec spec/integration
107
+ ```
108
+
109
+ The API key is filtered out of cassettes (`<API_KEY>`), but recorded bodies
110
+ still contain your account's webinar names and room URLs — review cassettes
111
+ before publishing them anywhere.
112
+
113
+ ## License
114
+
115
+ MIT
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "net/http"
5
+ require "uri"
6
+
7
+ module WebinarJam
8
+ # HTTP client for the WebinarJam / EverWebinar API.
9
+ #
10
+ # Every endpoint is a POST with a form-encoded body and returns JSON,
11
+ # except +unsubscribe+ which returns 204 No Content.
12
+ class Client
13
+ BASE_URL = "https://api.webinarjam.com"
14
+ PRODUCTS = %i[webinarjam everwebinar].freeze
15
+
16
+ attr_reader :product, :max_retries
17
+
18
+ # @param api_key [String] account-wide API key (defaults to ENV["WEBINARJAM_API_KEY"])
19
+ # @param product [Symbol] :webinarjam or :everwebinar
20
+ # @param max_retries [Integer] automatic retries (with backoff) on HTTP 429
21
+ def initialize(api_key: ENV["WEBINARJAM_API_KEY"], product: :webinarjam,
22
+ base_url: BASE_URL, open_timeout: 10, read_timeout: 30,
23
+ max_retries: 0)
24
+ if api_key.nil? || api_key.to_s.empty?
25
+ raise ConfigurationError,
26
+ "api_key is required (pass api_key: or set ENV[\"WEBINARJAM_API_KEY\"])"
27
+ end
28
+ product = product.to_sym
29
+ raise ArgumentError, "product must be one of #{PRODUCTS.inspect}" unless PRODUCTS.include?(product)
30
+
31
+ @api_key = api_key
32
+ @product = product
33
+ @base_url = base_url
34
+ @open_timeout = open_timeout
35
+ @read_timeout = read_timeout
36
+ @max_retries = max_retries
37
+ end
38
+
39
+ # Retrieve the full list of webinars published in the account.
40
+ # @return [Hash] { status: "success", webinars: [...] }
41
+ def webinars
42
+ post("webinars")
43
+ end
44
+
45
+ # Get details about one particular webinar.
46
+ # @return [Hash] { status: "success", webinar: {...} }
47
+ def webinar(webinar_id)
48
+ post("webinar", webinar_id: webinar_id)
49
+ end
50
+
51
+ # Register a user to a webinar.
52
+ #
53
+ # Optional params include: last_name, country, state, timezone_id,
54
+ # ip_address, phone_country_code, phone, twilio_consent.
55
+ # @return [Hash] { status: "success", user: {...} }
56
+ def register(webinar_id:, first_name:, email:, schedule:, **params)
57
+ post("register", params.merge(
58
+ webinar_id: webinar_id, first_name: first_name, email: email, schedule: schedule
59
+ ))
60
+ end
61
+
62
+ # Get a list of registrants and attendees for one webinar session.
63
+ #
64
+ # Optional params include: attended_live, attended_replay, purchased,
65
+ # page, attended_live_timestamp, attended_replay_timestamp, date_range,
66
+ # search.
67
+ # @return [Hash]
68
+ def registrants(webinar_id:, schedule_id:, **params)
69
+ post("registrants", params.merge(webinar_id: webinar_id, schedule_id: schedule_id))
70
+ end
71
+
72
+ # Unsubscribe a lead from a webinar's notification queue.
73
+ # @return [true] the API responds with 204 No Content on success
74
+ def unsubscribe(webinar_id:, lead_id:)
75
+ post("unsubscribe", webinar_id: webinar_id, lead_id: lead_id)
76
+ true
77
+ end
78
+
79
+ private
80
+
81
+ def post(path, params = {})
82
+ attempts = 0
83
+ begin
84
+ handle_response(perform_post(path, params))
85
+ rescue RateLimitError
86
+ raise if attempts >= @max_retries
87
+
88
+ attempts += 1
89
+ sleep(retry_backoff(attempts))
90
+ retry
91
+ end
92
+ end
93
+
94
+ def perform_post(path, params)
95
+ uri = URI("#{@base_url}/#{@product}/#{path}")
96
+ request = Net::HTTP::Post.new(uri)
97
+ request.set_form_data(params.compact.merge(api_key: @api_key))
98
+
99
+ Net::HTTP.start(uri.host, uri.port,
100
+ use_ssl: uri.scheme == "https",
101
+ open_timeout: @open_timeout,
102
+ read_timeout: @read_timeout) do |http|
103
+ http.request(request)
104
+ end
105
+ end
106
+
107
+ def handle_response(response)
108
+ status = response.code.to_i
109
+ return nil if status == 204
110
+
111
+ body = parse_json(response.body)
112
+
113
+ if (200..299).cover?(status)
114
+ return body unless body.is_a?(Hash) && body[:status] == "error"
115
+
116
+ raise APIError.new(error_message(body, status), http_status: status, body: body)
117
+ end
118
+
119
+ error_class = case status
120
+ when 401, 403 then AuthenticationError
121
+ when 429 then RateLimitError
122
+ else APIError
123
+ end
124
+ raise error_class.new(error_message(body, status), http_status: status, body: body)
125
+ end
126
+
127
+ def parse_json(raw)
128
+ JSON.parse(raw, symbolize_names: true)
129
+ rescue JSON::ParserError, TypeError
130
+ nil
131
+ end
132
+
133
+ def error_message(body, status)
134
+ return "HTTP #{status}" unless body.is_a?(Hash)
135
+
136
+ errors = body[:errors]
137
+ detail = case errors
138
+ when Hash then errors.map { |field, msg| "#{field}: #{msg}" }.join("; ")
139
+ when Array then errors.join("; ")
140
+ else body[:message] || body[:error]
141
+ end
142
+ detail && !detail.empty? ? detail : "HTTP #{status}"
143
+ end
144
+
145
+ def retry_backoff(attempt)
146
+ 0.5 * (2**(attempt - 1))
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebinarJam
4
+ class Error < StandardError; end
5
+
6
+ # Raised when the client is instantiated without an API key.
7
+ class ConfigurationError < Error; end
8
+
9
+ # Raised when the API returns an error, either as a non-2xx HTTP status or
10
+ # as a 2xx response whose JSON body carries `"status": "error"`.
11
+ class APIError < Error
12
+ attr_reader :http_status, :body
13
+
14
+ def initialize(message, http_status: nil, body: nil)
15
+ @http_status = http_status
16
+ @body = body
17
+ super(message)
18
+ end
19
+ end
20
+
21
+ # HTTP 401/403 — invalid or missing API key.
22
+ class AuthenticationError < APIError; end
23
+
24
+ # HTTP 429 — the API allows at most 20 calls per second per user.
25
+ class RateLimitError < APIError; end
26
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebinarJam
4
+ VERSION = "0.0.0"
5
+ end
data/lib/webinarjam.rb ADDED
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "webinarjam/version"
4
+ require_relative "webinarjam/errors"
5
+ require_relative "webinarjam/client"
6
+
7
+ module WebinarJam
8
+ class << self
9
+ attr_writer :api_key
10
+
11
+ def api_key
12
+ @api_key || ENV["WEBINARJAM_API_KEY"]
13
+ end
14
+
15
+ # WebinarJam.configure { |config| config.api_key = "..." }
16
+ def configure
17
+ yield self
18
+ self
19
+ end
20
+
21
+ # Convenience constructor honoring the module-level configuration.
22
+ def client(**options)
23
+ Client.new(api_key: api_key, **options)
24
+ end
25
+ end
26
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: webinarjam
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Yi-Ru Lin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-08-14 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: 'A dependency-free Ruby client for the WebinarJam and EverWebinar HTTP
14
+ API: list webinars, fetch webinar details, register users, list registrants and
15
+ attendees, and unsubscribe leads.'
16
+ email:
17
+ - lawrence@kaik.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - CHANGELOG.md
23
+ - LICENSE.txt
24
+ - README.md
25
+ - lib/webinarjam.rb
26
+ - lib/webinarjam/client.rb
27
+ - lib/webinarjam/errors.rb
28
+ - lib/webinarjam/version.rb
29
+ homepage: https://github.com/linyiru/webinarjam
30
+ licenses:
31
+ - MIT
32
+ metadata:
33
+ homepage_uri: https://github.com/linyiru/webinarjam
34
+ source_code_uri: https://github.com/linyiru/webinarjam
35
+ changelog_uri: https://github.com/linyiru/webinarjam/blob/main/CHANGELOG.md
36
+ rubygems_mfa_required: 'true'
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '3.0'
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubygems_version: 3.5.22
53
+ signing_key:
54
+ specification_version: 4
55
+ summary: Ruby client for the WebinarJam / EverWebinar API
56
+ test_files: []