flow_chat 0.4.1 → 0.4.2

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.
@@ -2,7 +2,7 @@ module FlowChat
2
2
  module Whatsapp
3
3
  class Configuration
4
4
  attr_accessor :access_token, :phone_number_id, :verify_token, :app_id, :app_secret,
5
- :webhook_url, :webhook_verify_token, :business_account_id, :name
5
+ :webhook_url, :webhook_verify_token, :business_account_id, :name
6
6
 
7
7
  # Class-level storage for named configurations
8
8
  @@configurations = {}
@@ -24,7 +24,7 @@ module FlowChat
24
24
  # Load configuration from Rails credentials or environment variables
25
25
  def self.from_credentials
26
26
  config = new(nil)
27
-
27
+
28
28
  if defined?(Rails) && Rails.application.credentials.whatsapp
29
29
  credentials = Rails.application.credentials.whatsapp
30
30
  config.access_token = credentials[:access_token]
@@ -36,13 +36,13 @@ module FlowChat
36
36
  config.business_account_id = credentials[:business_account_id]
37
37
  else
38
38
  # Fallback to environment variables
39
- config.access_token = ENV['WHATSAPP_ACCESS_TOKEN']
40
- config.phone_number_id = ENV['WHATSAPP_PHONE_NUMBER_ID']
41
- config.verify_token = ENV['WHATSAPP_VERIFY_TOKEN']
42
- config.app_id = ENV['WHATSAPP_APP_ID']
43
- config.app_secret = ENV['WHATSAPP_APP_SECRET']
44
- config.webhook_url = ENV['WHATSAPP_WEBHOOK_URL']
45
- config.business_account_id = ENV['WHATSAPP_BUSINESS_ACCOUNT_ID']
39
+ config.access_token = ENV["WHATSAPP_ACCESS_TOKEN"]
40
+ config.phone_number_id = ENV["WHATSAPP_PHONE_NUMBER_ID"]
41
+ config.verify_token = ENV["WHATSAPP_VERIFY_TOKEN"]
42
+ config.app_id = ENV["WHATSAPP_APP_ID"]
43
+ config.app_secret = ENV["WHATSAPP_APP_SECRET"]
44
+ config.webhook_url = ENV["WHATSAPP_WEBHOOK_URL"]
45
+ config.business_account_id = ENV["WHATSAPP_BUSINESS_ACCOUNT_ID"]
46
46
  end
47
47
 
48
48
  config
@@ -110,4 +110,4 @@ module FlowChat
110
110
  end
111
111
  end
112
112
  end
113
- end
113
+ end
@@ -32,9 +32,7 @@ module FlowChat
32
32
  end
33
33
 
34
34
  # Expose client for out-of-band messaging
35
- def client
36
- @client
37
- end
35
+ attr_reader :client
38
36
 
39
37
  private
40
38
 
@@ -53,7 +51,7 @@ module FlowChat
53
51
  params = controller.request.params
54
52
 
55
53
  verify_token = @config.verify_token
56
-
54
+
57
55
  if params["hub.verify_token"] == verify_token
58
56
  controller.render plain: params["hub.challenge"]
59
57
  else
@@ -159,7 +157,7 @@ module FlowChat
159
157
  def handle_message_background(context, controller)
160
158
  # Process the flow synchronously (maintaining controller context)
161
159
  response = @app.call(context)
162
-
160
+
163
161
  if response
164
162
  # Queue only the response delivery asynchronously
165
163
  send_data = {
@@ -170,7 +168,7 @@ module FlowChat
170
168
 
171
169
  # Get job class from configuration
172
170
  job_class_name = FlowChat::Config.whatsapp.background_job_class
173
-
171
+
174
172
  # Enqueue background job for sending only
175
173
  begin
176
174
  job_class = job_class_name.constantize
@@ -186,12 +184,12 @@ module FlowChat
186
184
 
187
185
  def handle_message_simulator(context, controller)
188
186
  response = @app.call(context)
189
-
187
+
190
188
  if response
191
189
  # For simulator mode, return the response data in the HTTP response
192
190
  # instead of actually sending via WhatsApp API
193
191
  message_payload = @client.build_message_payload(response, context["request.msisdn"])
194
-
192
+
195
193
  simulator_response = {
196
194
  mode: "simulator",
197
195
  webhook_processed: true,
@@ -204,10 +202,10 @@ module FlowChat
204
202
  }
205
203
 
206
204
  controller.render json: simulator_response
207
- return
205
+ nil
208
206
  end
209
207
  end
210
208
  end
211
209
  end
212
210
  end
213
- end
211
+ end
@@ -27,4 +27,4 @@ module FlowChat
27
27
  end
28
28
  end
29
29
  end
30
- end
30
+ end
@@ -23,4 +23,4 @@ module FlowChat
23
23
  end
24
24
  end
25
25
  end
26
- end
26
+ end
@@ -57,10 +57,10 @@ module FlowChat
57
57
  end
58
58
 
59
59
  buttons = [
60
- { id: "yes", title: "Yes" },
61
- { id: "no", title: "No" }
60
+ {id: "yes", title: "Yes"},
61
+ {id: "no", title: "No"}
62
62
  ]
63
- raise FlowChat::Interrupt::Prompt.new([:interactive_buttons, message, { buttons: buttons }])
63
+ raise FlowChat::Interrupt::Prompt.new([:interactive_buttons, message, {buttons: buttons}])
64
64
  end
65
65
 
66
66
  private
@@ -72,15 +72,15 @@ module FlowChat
72
72
 
73
73
  case media_type.to_sym
74
74
  when :image
75
- [:media_image, "", { url: url, caption: message }]
75
+ [:media_image, "", {url: url, caption: message}]
76
76
  when :document
77
- [:media_document, "", { url: url, caption: message, filename: filename }]
77
+ [:media_document, "", {url: url, caption: message, filename: filename}]
78
78
  when :audio
79
- [:media_audio, "", { url: url, caption: message }]
79
+ [:media_audio, "", {url: url, caption: message}]
80
80
  when :video
81
- [:media_video, "", { url: url, caption: message }]
81
+ [:media_video, "", {url: url, caption: message}]
82
82
  when :sticker
83
- [:media_sticker, "", { url: url }] # Stickers don't support captions
83
+ [:media_sticker, "", {url: url}] # Stickers don't support captions
84
84
  else
85
85
  raise ArgumentError, "Unsupported media type: #{media_type}"
86
86
  end
@@ -113,21 +113,19 @@ module FlowChat
113
113
  }
114
114
  end
115
115
 
116
- [:interactive_buttons, message, { buttons: buttons }]
116
+ [:interactive_buttons, message, {buttons: buttons}]
117
117
  end
118
118
 
119
119
  def build_list_prompt(message, choices)
120
120
  items = choices.map do |key, value|
121
121
  original_text = value.to_s
122
122
  truncated_title = truncate_text(original_text, 24)
123
-
123
+
124
124
  # If title was truncated, put full text in description (up to 72 chars)
125
125
  description = if original_text.length > 24
126
- truncate_text(original_text, 72)
127
- else
128
- nil
129
- end
130
-
126
+ truncate_text(original_text, 72)
127
+ end
128
+
131
129
  {
132
130
  id: key.to_s,
133
131
  title: truncated_title,
@@ -136,8 +134,8 @@ module FlowChat
136
134
  end
137
135
 
138
136
  # If 10 or fewer items, use single section
139
- if items.length <= 10
140
- sections = [
137
+ sections = if items.length <= 10
138
+ [
141
139
  {
142
140
  title: "Options",
143
141
  rows: items
@@ -145,10 +143,10 @@ module FlowChat
145
143
  ]
146
144
  else
147
145
  # Paginate into multiple sections (max 10 items per section)
148
- sections = items.each_slice(10).with_index.map do |section_items, index|
146
+ items.each_slice(10).with_index.map do |section_items, index|
149
147
  start_num = (index * 10) + 1
150
148
  end_num = start_num + section_items.length - 1
151
-
149
+
152
150
  {
153
151
  title: "#{start_num}-#{end_num}",
154
152
  rows: section_items
@@ -156,7 +154,7 @@ module FlowChat
156
154
  end
157
155
  end
158
156
 
159
- [:interactive_list, message, { sections: sections }]
157
+ [:interactive_list, message, {sections: sections}]
160
158
  end
161
159
 
162
160
  def process_input(input, transform, validate, convert)
@@ -178,8 +176,8 @@ module FlowChat
178
176
  end
179
177
 
180
178
  def process_selection(input, choices, transform, validate, convert)
181
- choice_hash = choices.is_a?(Array) ?
182
- choices.each_with_index.to_h { |choice, index| [index.to_s, choice] } :
179
+ choice_hash = choices.is_a?(Array) ?
180
+ choices.each_with_index.to_h { |choice, index| [index.to_s, choice] } :
183
181
  choices
184
182
 
185
183
  # Check if input matches a valid choice
@@ -199,13 +197,11 @@ module FlowChat
199
197
 
200
198
  def process_boolean(input, transform, validate, convert)
201
199
  boolean_value = case input.to_s.downcase
202
- when "yes", "y", "1", "true"
203
- true
204
- when "no", "n", "0", "false"
205
- false
206
- else
207
- nil
208
- end
200
+ when "yes", "y", "1", "true"
201
+ true
202
+ when "no", "n", "0", "false"
203
+ false
204
+ end
209
205
 
210
206
  if boolean_value.nil?
211
207
  raise FlowChat::Interrupt::Prompt.new([:text, "Please answer with Yes or No.", {}])
@@ -220,7 +216,7 @@ module FlowChat
220
216
  raise ArgumentError, "choices cannot be empty"
221
217
  end
222
218
 
223
- choice_count = choices.is_a?(Array) ? choices.length : choices.length
219
+ choice_count = choices.length
224
220
 
225
221
  # WhatsApp supports max 100 total items across all sections
226
222
  if choice_count > 100
@@ -248,4 +244,4 @@ module FlowChat
248
244
  end
249
245
  end
250
246
  end
251
- end
247
+ end
@@ -15,11 +15,11 @@ module FlowChat
15
15
  def perform_whatsapp_send(send_data)
16
16
  config = resolve_whatsapp_config(send_data)
17
17
  client = FlowChat::Whatsapp::Client.new(config)
18
-
18
+
19
19
  result = client.send_message(send_data[:msisdn], send_data[:response])
20
-
20
+
21
21
  if result
22
- Rails.logger.info "WhatsApp message sent successfully: #{result['messages']&.first&.dig('id')}"
22
+ Rails.logger.info "WhatsApp message sent successfully: #{result["messages"]&.first&.dig("id")}"
23
23
  on_whatsapp_send_success(send_data, result)
24
24
  else
25
25
  Rails.logger.error "Failed to send WhatsApp message to #{send_data[:msisdn]}"
@@ -47,20 +47,20 @@ module FlowChat
47
47
  def handle_whatsapp_send_error(error, send_data, config = nil)
48
48
  Rails.logger.error "WhatsApp send job error: #{error.message}"
49
49
  Rails.logger.error error.backtrace&.join("\n") if error.backtrace
50
-
50
+
51
51
  # Try to send error message to user if we have config
52
52
  if config
53
53
  begin
54
54
  client = FlowChat::Whatsapp::Client.new(config)
55
55
  client.send_text(
56
- send_data[:msisdn],
56
+ send_data[:msisdn],
57
57
  "⚠️ We're experiencing technical difficulties. Please try again in a few minutes."
58
58
  )
59
59
  rescue => send_error
60
60
  Rails.logger.error "Failed to send error message: #{send_error.message}"
61
61
  end
62
62
  end
63
-
63
+
64
64
  # Re-raise for job retry logic
65
65
  raise error
66
66
  end
@@ -76,4 +76,4 @@ module FlowChat
76
76
  end
77
77
  end
78
78
  end
79
- end
79
+ end
@@ -16,7 +16,7 @@ module FlowChat
16
16
  type: "template",
17
17
  template: {
18
18
  name: template_name,
19
- language: { code: language },
19
+ language: {code: language},
20
20
  components: components
21
21
  }
22
22
  }
@@ -27,7 +27,7 @@ module FlowChat
27
27
  # Common template structures
28
28
  def send_welcome_template(to:, name: nil)
29
29
  components = []
30
-
30
+
31
31
  if name
32
32
  components << {
33
33
  type: "header",
@@ -87,7 +87,7 @@ module FlowChat
87
87
  def create_template(name:, category:, language: "en_US", components: [])
88
88
  business_account_id = @config.business_account_id
89
89
  uri = URI("https://graph.facebook.com/v18.0/#{business_account_id}/message_templates")
90
-
90
+
91
91
  template_data = {
92
92
  name: name,
93
93
  category: category, # AUTHENTICATION, MARKETING, UTILITY
@@ -111,7 +111,7 @@ module FlowChat
111
111
  def list_templates
112
112
  business_account_id = @config.business_account_id
113
113
  uri = URI("https://graph.facebook.com/v18.0/#{business_account_id}/message_templates")
114
-
114
+
115
115
  http = Net::HTTP.new(uri.host, uri.port)
116
116
  http.use_ssl = true
117
117
 
@@ -125,7 +125,7 @@ module FlowChat
125
125
  # Get template status
126
126
  def template_status(template_id)
127
127
  uri = URI("https://graph.facebook.com/v18.0/#{template_id}")
128
-
128
+
129
129
  http = Net::HTTP.new(uri.host, uri.port)
130
130
  http.use_ssl = true
131
131
 
@@ -149,7 +149,7 @@ module FlowChat
149
149
  request.body = message_data.to_json
150
150
 
151
151
  response = http.request(request)
152
-
152
+
153
153
  unless response.is_a?(Net::HTTPSuccess)
154
154
  Rails.logger.error "WhatsApp Template API error: #{response.body}"
155
155
  return nil
@@ -159,4 +159,4 @@ module FlowChat
159
159
  end
160
160
  end
161
161
  end
162
- end
162
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flow_chat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Froelich