event_trigger 0.1.0 → 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 +4 -4
- data/.ruby-version +1 -0
- data/CHANGELOG.md +8 -0
- data/README.md +42 -2
- data/examples/initializer.rb +32 -0
- data/examples/plain_ruby.rb +46 -0
- data/lib/event_trigger/configuration.rb +15 -2
- data/lib/event_trigger/defaults.rb +11 -0
- data/lib/event_trigger/providers/discord_provider.rb +76 -0
- data/lib/event_trigger/providers/http_poster.rb +56 -0
- data/lib/event_trigger/providers/whatsapp_provider.rb +102 -0
- data/lib/event_trigger/registry.rb +5 -1
- data/lib/event_trigger/version.rb +1 -1
- data/lib/event_trigger.rb +2 -0
- data/lib/generators/event_trigger/templates/event_trigger.rb +13 -0
- metadata +63 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 39c0803bd55c50a4d09940dc90870adcbc78170e5637492808914cb809d9617b
|
|
4
|
+
data.tar.gz: 51cba5d01cd0528446a26fb23784c25227919b7a44b21ef05abb9d40f98d2f58
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '09db06666e34aa573d1651cb4d9dfdeaadbc967699f5bdc2fd0d23b344f8efe829afe97d3c175f3ba333894f7aec08a833cf4a6644c56bb06e37735e510d3d79'
|
|
7
|
+
data.tar.gz: 86c01b75ebad0a5d4ee400f2feb6f03cc5c1e5ccf2bc2eeae1131cb5e8ef0c595d0f761ce78f396431bc103048ee47030bb38c7adf451c548e8a645bc0959b27
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.2.11
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
## [0.1.0] - 2026-09-18
|
|
4
|
+
|
|
5
|
+
- Initial release: configurable event-based notifications (Slack, Email,
|
|
6
|
+
Webhook + custom providers), global/per-provider enable flags,
|
|
7
|
+
per-provider event allow-lists, handlers, failure isolation, logging,
|
|
8
|
+
Rails Railtie, RSpec suite.
|
data/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Anything can be sent in `data:` -- passed through to providers/handlers.
|
|
|
14
14
|
|
|
15
15
|
## Features
|
|
16
16
|
|
|
17
|
-
- Providers: **Slack**, **Email**, **Webhook**, plus custom providers
|
|
17
|
+
- Providers: **Slack**, **Email**, **Webhook**, **Discord**, **WhatsApp (Twilio)**, plus custom providers
|
|
18
18
|
- Central `EventTrigger.configure` block (plain Ruby and Rails)
|
|
19
19
|
- Per-provider `enabled` flag + global `config.enabled` kill-switch
|
|
20
20
|
- Per-provider `events` allow-list (`nil`/empty = all events)
|
|
@@ -115,7 +115,47 @@ EventTrigger.register_provider(:sms, SmsProvider)
|
|
|
115
115
|
EventTrigger.configure { |c| c.for(:sms).enabled = true }
|
|
116
116
|
```
|
|
117
117
|
|
|
118
|
-
Architecture: EventTrigger -> Event -> Enabled Providers
|
|
118
|
+
Architecture: EventTrigger -> Event -> Enabled Providers
|
|
119
|
+
(Slack/Email/Webhook/Discord/WhatsApp).
|
|
120
|
+
|
|
121
|
+
## Discord provider (`config.discord`)
|
|
122
|
+
|
|
123
|
+
Ported from `RailsErrorNotifier`: posts the rich embed payload
|
|
124
|
+
(`username: RailsErrorNotifier`, Backtrace/Context fields, ~1014-char
|
|
125
|
+
truncation) to a Discord incoming-webhook URL.
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
config.discord.enabled = true
|
|
129
|
+
config.discord.webhook_url = ENV["DISCORD_WEBHOOK_URL"]
|
|
130
|
+
config.discord.events = ["app.error"]
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## WhatsApp provider (`config.whatsapp`, via Twilio)
|
|
134
|
+
|
|
135
|
+
Ported from `RailsErrorNotifier`:
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
config.whatsapp.enabled = true
|
|
139
|
+
config.whatsapp.twilio_sid = ENV["TWILIO_ACCOUNT_SID"]
|
|
140
|
+
config.whatsapp.twilio_token = ENV["TWILIO_AUTH_TOKEN"]
|
|
141
|
+
config.whatsapp.twilio_from = ENV["TWILIO_WHATSAPP_FROM"] # whatsapp:+14155238886
|
|
142
|
+
config.whatsapp.twilio_to = ENV["TWILIO_WHATSAPP_TO"]
|
|
143
|
+
config.whatsapp.events = ["app.error"]
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Per-trigger override: `data: { whatsapp_to: "whatsapp:+919..." }`.
|
|
147
|
+
Numbers without the `whatsapp:` scheme are normalized automatically.
|
|
148
|
+
|
|
149
|
+
## Crash safety
|
|
150
|
+
|
|
151
|
+
Every new provider follows the same contract as the old ones:
|
|
152
|
+
- Missing config / missing `twilio-ruby` gem / network errors raise
|
|
153
|
+
`EventTrigger::Error` inside `#deliver` only.
|
|
154
|
+
- `EventTrigger::Dispatcher` rescues per provider, logs
|
|
155
|
+
`provider ':x' failed ...`, and keeps running the rest — **the host
|
|
156
|
+
app never crashes because a notification failed**.
|
|
157
|
+
- HTTP providers (Slack/Webhook/Discord) try stdlib `Net::HTTP`
|
|
158
|
+
first and fall back to `Faraday` when installed.
|
|
119
159
|
|
|
120
160
|
## Logging
|
|
121
161
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Sample Rails initializer for EventTrigger.
|
|
4
|
+
# Copy to your app as config/initializers/event_trigger.rb
|
|
5
|
+
# and adjust values for your environment.
|
|
6
|
+
|
|
7
|
+
EventTrigger.configure do |config|
|
|
8
|
+
config.enabled = true
|
|
9
|
+
|
|
10
|
+
config.slack.enabled = true
|
|
11
|
+
config.slack.webhook_url = ENV["SLACK_WEBHOOK_URL"]
|
|
12
|
+
config.slack.events = %w[
|
|
13
|
+
loan.activated
|
|
14
|
+
loan.overdue
|
|
15
|
+
payment.received
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
config.email.enabled = false
|
|
19
|
+
config.email.from = ENV["EVENT_TRIGGER_EMAIL_FROM"]
|
|
20
|
+
config.email.to = ENV["EVENT_TRIGGER_EMAIL_TO"]
|
|
21
|
+
config.email.events = %w[
|
|
22
|
+
loan.activated
|
|
23
|
+
loan.foreclosed
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
config.webhook.enabled = false
|
|
27
|
+
# config.webhook.url = ENV["EVENT_TRIGGER_WEBHOOK_URL"]
|
|
28
|
+
# config.webhook.events = %w[loan.activated]
|
|
29
|
+
|
|
30
|
+
# Optional logging (defaults to no logging):
|
|
31
|
+
# config.logger = Rails.logger
|
|
32
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Plain-Ruby usage example (no Rails required).
|
|
4
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
5
|
+
require "event_trigger"
|
|
6
|
+
require "logger"
|
|
7
|
+
|
|
8
|
+
EventTrigger.configure do |config|
|
|
9
|
+
config.enabled = true
|
|
10
|
+
config.logger = Logger.new($stdout)
|
|
11
|
+
|
|
12
|
+
config.slack.enabled = true
|
|
13
|
+
config.slack.webhook_url = ENV["SLACK_WEBHOOK_URL"]
|
|
14
|
+
config.slack.events = ["loan.activated", "loan.overdue", "payment.received"]
|
|
15
|
+
|
|
16
|
+
config.email.enabled = false
|
|
17
|
+
config.email.from = ENV["EVENT_TRIGGER_EMAIL_FROM"]
|
|
18
|
+
config.email.to = "ops@example.com"
|
|
19
|
+
config.email.events = ["loan.activated", "loan.foreclosed"]
|
|
20
|
+
|
|
21
|
+
config.webhook.enabled = false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Custom per-event logic alongside providers:
|
|
25
|
+
EventTrigger.on("loan.activated") do |event|
|
|
26
|
+
puts "Custom handler saw loan #{event.payload[:loan_id]} for #{event.payload[:customer_name]}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Anything can be sent in `data:` -- it is passed through to providers/handlers.
|
|
30
|
+
EventTrigger.trigger(
|
|
31
|
+
"loan.activated",
|
|
32
|
+
data: {
|
|
33
|
+
loan_id: 123,
|
|
34
|
+
customer_name: "Kamlesh"
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Error-style payload (Slack text vs Discord embed chosen automatically):
|
|
39
|
+
EventTrigger.trigger(
|
|
40
|
+
"app.error",
|
|
41
|
+
data: {
|
|
42
|
+
error: "Something went wrong",
|
|
43
|
+
backtrace: caller.first(5),
|
|
44
|
+
context: { user_id: 42 }
|
|
45
|
+
}
|
|
46
|
+
)
|
|
@@ -65,7 +65,7 @@ module EventTrigger
|
|
|
65
65
|
# Global configuration object yielded by EventTrigger.configure.
|
|
66
66
|
class Configuration
|
|
67
67
|
attr_accessor :enabled, :logger
|
|
68
|
-
attr_reader :slack, :email, :webhook
|
|
68
|
+
attr_reader :slack, :email, :webhook, :discord, :whatsapp
|
|
69
69
|
|
|
70
70
|
# Backwards-compatible top-level shortcuts required by the spec:
|
|
71
71
|
# config.slack_webhook_url = ENV["SLACK_WEBHOOK_URL"]
|
|
@@ -77,6 +77,8 @@ module EventTrigger
|
|
|
77
77
|
@slack = ProviderConfig.new
|
|
78
78
|
@email = ProviderConfig.new
|
|
79
79
|
@webhook = ProviderConfig.new
|
|
80
|
+
@discord = ProviderConfig.new
|
|
81
|
+
@whatsapp = ProviderConfig.new
|
|
80
82
|
@custom = {}
|
|
81
83
|
end
|
|
82
84
|
|
|
@@ -96,6 +98,14 @@ module EventTrigger
|
|
|
96
98
|
email.from = value
|
|
97
99
|
end
|
|
98
100
|
|
|
101
|
+
def discord_webhook_url
|
|
102
|
+
discord.webhook_url
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def discord_webhook_url=(value)
|
|
106
|
+
discord.webhook_url = value
|
|
107
|
+
end
|
|
108
|
+
|
|
99
109
|
# Access (or lazily create) the config for any provider name,
|
|
100
110
|
# including future/custom providers:
|
|
101
111
|
# config.for(:sms).enabled = true
|
|
@@ -105,6 +115,8 @@ module EventTrigger
|
|
|
105
115
|
when :slack then slack
|
|
106
116
|
when :email then email
|
|
107
117
|
when :webhook then webhook
|
|
118
|
+
when :discord then discord
|
|
119
|
+
when :whatsapp then whatsapp
|
|
108
120
|
else
|
|
109
121
|
@custom[key] ||= ProviderConfig.new
|
|
110
122
|
end
|
|
@@ -113,7 +125,8 @@ module EventTrigger
|
|
|
113
125
|
|
|
114
126
|
# All provider configs keyed by name, including custom ones.
|
|
115
127
|
def providers
|
|
116
|
-
{ slack: slack, email: email, webhook: webhook
|
|
128
|
+
{ slack: slack, email: email, webhook: webhook,
|
|
129
|
+
discord: discord, whatsapp: whatsapp }.merge(@custom)
|
|
117
130
|
end
|
|
118
131
|
|
|
119
132
|
def enabled?
|
|
@@ -35,6 +35,17 @@ module EventTrigger
|
|
|
35
35
|
config.webhook.url ||= ENV["EVENT_TRIGGER_WEBHOOK_URL"]
|
|
36
36
|
# Leave webhook subscribed to nothing until the app opts in.
|
|
37
37
|
config.webhook.events ||= []
|
|
38
|
+
|
|
39
|
+
config.discord.enabled = false
|
|
40
|
+
config.discord.webhook_url ||= ENV["DISCORD_WEBHOOK_URL"]
|
|
41
|
+
config.discord.events ||= []
|
|
42
|
+
|
|
43
|
+
config.whatsapp.enabled = false
|
|
44
|
+
config.whatsapp.twilio_sid ||= ENV["TWILIO_ACCOUNT_SID"]
|
|
45
|
+
config.whatsapp.twilio_token ||= ENV["TWILIO_AUTH_TOKEN"]
|
|
46
|
+
config.whatsapp.twilio_from ||= ENV["TWILIO_WHATSAPP_FROM"]
|
|
47
|
+
config.whatsapp.twilio_to ||= ENV["TWILIO_WHATSAPP_TO"]
|
|
48
|
+
config.whatsapp.events ||= []
|
|
38
49
|
end
|
|
39
50
|
end
|
|
40
51
|
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
require_relative "../provider"
|
|
5
|
+
require_relative "http_poster"
|
|
6
|
+
|
|
7
|
+
module EventTrigger
|
|
8
|
+
module Providers
|
|
9
|
+
# Sends event notifications to Discord via an incoming-webhook URL,
|
|
10
|
+
# using a rich embed payload (ported from RailsErrorNotifier).
|
|
11
|
+
#
|
|
12
|
+
# Config:
|
|
13
|
+
# config.discord.enabled = true
|
|
14
|
+
# config.discord.webhook_url = ENV["DISCORD_WEBHOOK_URL"]
|
|
15
|
+
# config.discord.username = "RailsErrorNotifier" # optional
|
|
16
|
+
# config.discord.events = ["app.error"]
|
|
17
|
+
#
|
|
18
|
+
# Crash-safe: failures raise EventTrigger::Error, which the
|
|
19
|
+
# Dispatcher logs — the host app never crashes because of Discord.
|
|
20
|
+
class DiscordProvider < Provider
|
|
21
|
+
include HttpPoster
|
|
22
|
+
|
|
23
|
+
MAX_FIELD_VALUE = 1024
|
|
24
|
+
|
|
25
|
+
def self.provider_name
|
|
26
|
+
:discord
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def deliver(event)
|
|
30
|
+
url = config.webhook_url || config.url
|
|
31
|
+
raise Error, "Discord webhook_url is not configured" if url.nil? || url.to_s.strip.empty?
|
|
32
|
+
|
|
33
|
+
post_json(url.to_s, build_payload(event), service: "Discord")
|
|
34
|
+
log("delivered event '#{event.name}'")
|
|
35
|
+
true
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def build_payload(event)
|
|
41
|
+
payload = event.payload
|
|
42
|
+
backtrace_text = truncate_for_discord((payload[:backtrace] || payload["backtrace"] || ["No backtrace"]).first(10).join("\n"))
|
|
43
|
+
context_text = truncate_for_discord((payload[:context] || payload["context"] || payload).inspect)
|
|
44
|
+
{
|
|
45
|
+
username: config.username || "RailsErrorNotifier",
|
|
46
|
+
embeds: [
|
|
47
|
+
{
|
|
48
|
+
title: "🚨 Error Occurred",
|
|
49
|
+
description: embed_description(event, payload),
|
|
50
|
+
color: 0xFF0000,
|
|
51
|
+
fields: [
|
|
52
|
+
{ name: "Backtrace", value: "```\n#{backtrace_text}\n```", inline: false },
|
|
53
|
+
{ name: "Context", value: "```\n#{context_text}\n```", inline: false }
|
|
54
|
+
],
|
|
55
|
+
timestamp: Time.now.utc.iso8601
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def embed_description(event, payload)
|
|
62
|
+
err = payload[:error] || payload["error"] || event.name
|
|
63
|
+
truncate(err.to_s, 1500)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def truncate(str, limit)
|
|
67
|
+
s = str.to_s
|
|
68
|
+
s.length > limit ? "#{s[0, limit]}…" : s
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def truncate_for_discord(text)
|
|
72
|
+
truncate(text.to_s, MAX_FIELD_VALUE - 10) # leave room for ``` block
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "json"
|
|
6
|
+
|
|
7
|
+
module EventTrigger
|
|
8
|
+
module Providers
|
|
9
|
+
# Shared JSON POST helper with a Net::HTTP -> Faraday fallback chain.
|
|
10
|
+
#
|
|
11
|
+
# Primary transport is stdlib Net::HTTP (zero extra deps). If the
|
|
12
|
+
# request itself raises (DNS, timeout, connection error), it falls
|
|
13
|
+
# back to Faraday when available. HTTP error statuses raise
|
|
14
|
+
# EventTrigger::Error directly (no retry — same result expected).
|
|
15
|
+
module HttpPoster
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def post_json(url, body, headers = { "Content-Type" => "application/json" }, service: "HTTP")
|
|
19
|
+
payload = JSON.generate(body)
|
|
20
|
+
begin
|
|
21
|
+
net_http_post(url, payload, headers, service)
|
|
22
|
+
rescue EventTrigger::Error
|
|
23
|
+
raise
|
|
24
|
+
rescue StandardError => e
|
|
25
|
+
faraday_post(url, payload, headers, service, e)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def net_http_post(url, payload, headers, service)
|
|
30
|
+
uri = URI.parse(url.to_s)
|
|
31
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
32
|
+
http.use_ssl = uri.scheme == "https"
|
|
33
|
+
http.open_timeout = 5
|
|
34
|
+
http.read_timeout = 10
|
|
35
|
+
request = Net::HTTP::Post.new(uri.request_uri, headers)
|
|
36
|
+
request.body = payload
|
|
37
|
+
response = http.request(request)
|
|
38
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
39
|
+
raise Error, "#{service} delivery failed (HTTP #{response.code}): #{response.body}"
|
|
40
|
+
end
|
|
41
|
+
response
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def faraday_post(url, payload, headers, service, original_error)
|
|
45
|
+
require "faraday"
|
|
46
|
+
response = Faraday.post(url.to_s, payload, headers)
|
|
47
|
+
unless response.success?
|
|
48
|
+
raise Error, "#{service} delivery failed (HTTP #{response.status}): #{response.body}"
|
|
49
|
+
end
|
|
50
|
+
response
|
|
51
|
+
rescue LoadError
|
|
52
|
+
raise original_error
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require "twilio-ruby"
|
|
5
|
+
rescue LoadError
|
|
6
|
+
# Optional at load time: #deliver raises a clear EventTrigger::Error
|
|
7
|
+
# when the gem is missing, which the Dispatcher logs without
|
|
8
|
+
# crashing the host app.
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
require_relative "../provider"
|
|
12
|
+
|
|
13
|
+
module EventTrigger
|
|
14
|
+
module Providers
|
|
15
|
+
# Sends event notifications via WhatsApp using Twilio
|
|
16
|
+
# (ported from RailsErrorNotifier).
|
|
17
|
+
#
|
|
18
|
+
# Config:
|
|
19
|
+
# config.whatsapp.enabled = true
|
|
20
|
+
# config.whatsapp.twilio_sid = ENV["TWILIO_ACCOUNT_SID"]
|
|
21
|
+
# config.whatsapp.twilio_token = ENV["TWILIO_AUTH_TOKEN"]
|
|
22
|
+
# config.whatsapp.twilio_from = "whatsapp:+14155238886"
|
|
23
|
+
# config.whatsapp.twilio_to = "whatsapp:+919876543210"
|
|
24
|
+
# config.whatsapp.events = ["app.error"]
|
|
25
|
+
#
|
|
26
|
+
# Aliases: account_sid/sid, auth_token/token, from/to.
|
|
27
|
+
# Per-trigger override: data: { whatsapp_to: "whatsapp:+..." }
|
|
28
|
+
#
|
|
29
|
+
# Crash-safe: missing gem/credentials/network errors raise
|
|
30
|
+
# EventTrigger::Error, which the Dispatcher logs — the host app
|
|
31
|
+
# never crashes because of WhatsApp.
|
|
32
|
+
class WhatsappProvider < Provider
|
|
33
|
+
MAX_BODY = 1000
|
|
34
|
+
|
|
35
|
+
def self.provider_name
|
|
36
|
+
:whatsapp
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def deliver(event)
|
|
40
|
+
unless defined?(Twilio::REST::Client)
|
|
41
|
+
raise Error, 'twilio-ruby gem is not installed (add `gem "twilio-ruby"` to your Gemfile)'
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
sid = config.twilio_sid || config.account_sid || config.sid
|
|
45
|
+
token = config.twilio_token || config.auth_token || config.token
|
|
46
|
+
from = config.twilio_from || config.from
|
|
47
|
+
to = event.payload[:whatsapp_to] || event.payload["whatsapp_to"] ||
|
|
48
|
+
config.twilio_to || config.to
|
|
49
|
+
|
|
50
|
+
raise Error, "WhatsApp twilio_sid is not configured" if blank?(sid)
|
|
51
|
+
raise Error, "WhatsApp twilio_token is not configured" if blank?(token)
|
|
52
|
+
raise Error, "WhatsApp sender (twilio_from) is not configured" if blank?(from)
|
|
53
|
+
raise Error, "WhatsApp recipient (twilio_to) is not configured" if blank?(to)
|
|
54
|
+
|
|
55
|
+
client = Twilio::REST::Client.new(sid.to_s, token.to_s)
|
|
56
|
+
client.messages.create(
|
|
57
|
+
from: normalize_number(from),
|
|
58
|
+
to: normalize_number(to),
|
|
59
|
+
body: build_body(event)
|
|
60
|
+
)
|
|
61
|
+
log("delivered event '#{event.name}' to #{to}")
|
|
62
|
+
true
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def build_body(event)
|
|
68
|
+
payload = event.payload
|
|
69
|
+
err = payload[:error] || payload["error"]
|
|
70
|
+
text =
|
|
71
|
+
if err
|
|
72
|
+
bt = payload[:backtrace] || payload["backtrace"]
|
|
73
|
+
first = Array(bt).first || "No backtrace"
|
|
74
|
+
ctx = payload[:context] || payload["context"]
|
|
75
|
+
msg = "🚨 Error: #{err}\nBacktrace: #{first}"
|
|
76
|
+
msg += "\nContext: #{ctx.inspect}" if ctx
|
|
77
|
+
msg
|
|
78
|
+
else
|
|
79
|
+
summary = payload.map { |k, v| "#{k}: #{v}" }.join(", ")
|
|
80
|
+
summary = payload.inspect if summary.empty?
|
|
81
|
+
"[#{event.name}] #{summary}"
|
|
82
|
+
end
|
|
83
|
+
truncate(text)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Twilio requires the whatsapp: scheme; add it when missing.
|
|
87
|
+
def normalize_number(value)
|
|
88
|
+
s = value.to_s.strip
|
|
89
|
+
s.start_with?("whatsapp:") ? s : "whatsapp:#{s}"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def blank?(value)
|
|
93
|
+
value.nil? || value.to_s.strip.empty?
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def truncate(str, limit = MAX_BODY)
|
|
97
|
+
s = str.to_s
|
|
98
|
+
s.length > limit ? "#{s[0, limit]}…" : s
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -24,15 +24,19 @@ module EventTrigger
|
|
|
24
24
|
@providers.key?(name.to_sym)
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
# Restore the
|
|
27
|
+
# Restore the five built-in providers (used by reset!).
|
|
28
28
|
def reset!
|
|
29
29
|
@providers = {}
|
|
30
30
|
require_relative "providers/slack_provider"
|
|
31
31
|
require_relative "providers/email_provider"
|
|
32
32
|
require_relative "providers/webhook_provider"
|
|
33
|
+
require_relative "providers/discord_provider"
|
|
34
|
+
require_relative "providers/whatsapp_provider"
|
|
33
35
|
register(:slack, Providers::SlackProvider)
|
|
34
36
|
register(:email, Providers::EmailProvider)
|
|
35
37
|
register(:webhook, Providers::WebhookProvider)
|
|
38
|
+
register(:discord, Providers::DiscordProvider)
|
|
39
|
+
register(:whatsapp, Providers::WhatsappProvider)
|
|
36
40
|
end
|
|
37
41
|
end
|
|
38
42
|
end
|
data/lib/event_trigger.rb
CHANGED
|
@@ -8,6 +8,8 @@ require_relative "event_trigger/provider"
|
|
|
8
8
|
require_relative "event_trigger/providers/slack_provider"
|
|
9
9
|
require_relative "event_trigger/providers/email_provider"
|
|
10
10
|
require_relative "event_trigger/providers/webhook_provider"
|
|
11
|
+
require_relative "event_trigger/providers/discord_provider"
|
|
12
|
+
require_relative "event_trigger/providers/whatsapp_provider"
|
|
11
13
|
require_relative "event_trigger/dispatcher"
|
|
12
14
|
require_relative "event_trigger/defaults"
|
|
13
15
|
require_relative "event_trigger/railtie" if defined?(Rails::Railtie)
|
|
@@ -26,4 +26,17 @@ EventTrigger.configure do |config|
|
|
|
26
26
|
config.webhook.url = ENV["EVENT_TRIGGER_WEBHOOK_URL"]
|
|
27
27
|
# nil (or []) = ALL events.
|
|
28
28
|
config.webhook.events = ["loan.activated"]
|
|
29
|
+
|
|
30
|
+
config.discord.enabled = false
|
|
31
|
+
config.discord.webhook_url = ENV["DISCORD_WEBHOOK_URL"]
|
|
32
|
+
# nil (or []) = ALL events.
|
|
33
|
+
config.discord.events = []
|
|
34
|
+
|
|
35
|
+
config.whatsapp.enabled = false
|
|
36
|
+
config.whatsapp.twilio_sid = ENV["TWILIO_ACCOUNT_SID"]
|
|
37
|
+
config.whatsapp.twilio_token = ENV["TWILIO_AUTH_TOKEN"]
|
|
38
|
+
config.whatsapp.twilio_from = ENV["TWILIO_WHATSAPP_FROM"]
|
|
39
|
+
config.whatsapp.twilio_to = ENV["TWILIO_WHATSAPP_TO"]
|
|
40
|
+
# nil (or []) = ALL events.
|
|
41
|
+
config.whatsapp.events = []
|
|
29
42
|
end
|
metadata
CHANGED
|
@@ -1,15 +1,55 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: event_trigger
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kamlesh Parmar
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: faraday
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.0'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '3.0'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '1.0'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.0'
|
|
33
|
+
- !ruby/object:Gem::Dependency
|
|
34
|
+
name: twilio-ruby
|
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '6.6'
|
|
40
|
+
- - "<"
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '8.0'
|
|
43
|
+
type: :runtime
|
|
44
|
+
prerelease: false
|
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
46
|
+
requirements:
|
|
47
|
+
- - ">="
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: '6.6'
|
|
50
|
+
- - "<"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '8.0'
|
|
13
53
|
- !ruby/object:Gem::Dependency
|
|
14
54
|
name: rake
|
|
15
55
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -52,6 +92,20 @@ dependencies:
|
|
|
52
92
|
- - ">="
|
|
53
93
|
- !ruby/object:Gem::Version
|
|
54
94
|
version: '0.3'
|
|
95
|
+
- !ruby/object:Gem::Dependency
|
|
96
|
+
name: actionmailer
|
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
|
98
|
+
requirements:
|
|
99
|
+
- - ">="
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '6.0'
|
|
102
|
+
type: :development
|
|
103
|
+
prerelease: false
|
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
105
|
+
requirements:
|
|
106
|
+
- - ">="
|
|
107
|
+
- !ruby/object:Gem::Version
|
|
108
|
+
version: '6.0'
|
|
55
109
|
description: 'EventTrigger provides a simple event system with pluggable notification
|
|
56
110
|
providers (Slack, Email, Webhook, custom). Configure providers once, then call EventTrigger.trigger("event.name",
|
|
57
111
|
data: {...}) and the gem dispatches to the enabled providers configured for that
|
|
@@ -62,18 +116,25 @@ executables: []
|
|
|
62
116
|
extensions: []
|
|
63
117
|
extra_rdoc_files: []
|
|
64
118
|
files:
|
|
119
|
+
- ".ruby-version"
|
|
120
|
+
- CHANGELOG.md
|
|
65
121
|
- LICENSE.txt
|
|
66
122
|
- README.md
|
|
67
123
|
- Rakefile
|
|
124
|
+
- examples/initializer.rb
|
|
125
|
+
- examples/plain_ruby.rb
|
|
68
126
|
- lib/event_trigger.rb
|
|
69
127
|
- lib/event_trigger/configuration.rb
|
|
70
128
|
- lib/event_trigger/defaults.rb
|
|
71
129
|
- lib/event_trigger/dispatcher.rb
|
|
72
130
|
- lib/event_trigger/event.rb
|
|
73
131
|
- lib/event_trigger/provider.rb
|
|
132
|
+
- lib/event_trigger/providers/discord_provider.rb
|
|
74
133
|
- lib/event_trigger/providers/email_provider.rb
|
|
134
|
+
- lib/event_trigger/providers/http_poster.rb
|
|
75
135
|
- lib/event_trigger/providers/slack_provider.rb
|
|
76
136
|
- lib/event_trigger/providers/webhook_provider.rb
|
|
137
|
+
- lib/event_trigger/providers/whatsapp_provider.rb
|
|
77
138
|
- lib/event_trigger/railtie.rb
|
|
78
139
|
- lib/event_trigger/registry.rb
|
|
79
140
|
- lib/event_trigger/version.rb
|