flow_chat 0.3.0 → 0.4.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/Gemfile +1 -0
- data/README.md +642 -86
- data/examples/initializer.rb +31 -0
- data/examples/media_prompts_examples.rb +28 -0
- data/examples/multi_tenant_whatsapp_controller.rb +244 -0
- data/examples/ussd_controller.rb +264 -0
- data/examples/whatsapp_controller.rb +140 -0
- data/examples/whatsapp_media_examples.rb +406 -0
- data/examples/whatsapp_message_job.rb +111 -0
- data/lib/flow_chat/base_processor.rb +67 -0
- data/lib/flow_chat/config.rb +36 -0
- data/lib/flow_chat/session/cache_session_store.rb +84 -0
- data/lib/flow_chat/session/middleware.rb +14 -6
- data/lib/flow_chat/simulator/controller.rb +78 -0
- data/lib/flow_chat/simulator/views/simulator.html.erb +1707 -0
- data/lib/flow_chat/ussd/app.rb +25 -0
- data/lib/flow_chat/ussd/gateway/nalo.rb +2 -0
- data/lib/flow_chat/ussd/gateway/nsano.rb +6 -0
- data/lib/flow_chat/ussd/middleware/resumable_session.rb +1 -1
- data/lib/flow_chat/ussd/processor.rb +14 -42
- data/lib/flow_chat/ussd/prompt.rb +39 -5
- data/lib/flow_chat/version.rb +1 -1
- data/lib/flow_chat/whatsapp/app.rb +64 -0
- data/lib/flow_chat/whatsapp/client.rb +439 -0
- data/lib/flow_chat/whatsapp/configuration.rb +113 -0
- data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +213 -0
- data/lib/flow_chat/whatsapp/middleware/executor.rb +30 -0
- data/lib/flow_chat/whatsapp/processor.rb +26 -0
- data/lib/flow_chat/whatsapp/prompt.rb +251 -0
- data/lib/flow_chat/whatsapp/send_job_support.rb +79 -0
- data/lib/flow_chat/whatsapp/template_manager.rb +162 -0
- data/lib/flow_chat.rb +1 -0
- metadata +21 -3
- data/lib/flow_chat/ussd/simulator/controller.rb +0 -51
- data/lib/flow_chat/ussd/simulator/views/simulator.html.erb +0 -239
data/lib/flow_chat/ussd/app.rb
CHANGED
@@ -28,6 +28,31 @@ module FlowChat
|
|
28
28
|
def say(msg)
|
29
29
|
raise FlowChat::Interrupt::Terminate.new(msg)
|
30
30
|
end
|
31
|
+
|
32
|
+
# WhatsApp-specific data accessors (not supported in USSD)
|
33
|
+
def contact_name
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def message_id
|
38
|
+
context["request.message_id"]
|
39
|
+
end
|
40
|
+
|
41
|
+
def timestamp
|
42
|
+
context["request.timestamp"]
|
43
|
+
end
|
44
|
+
|
45
|
+
def location
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def media
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def phone_number
|
54
|
+
context["request.msisdn"]
|
55
|
+
end
|
31
56
|
end
|
32
57
|
end
|
33
58
|
end
|
@@ -12,6 +12,8 @@ module FlowChat
|
|
12
12
|
params = context.controller.request.params
|
13
13
|
|
14
14
|
context["request.id"] = params["USERID"]
|
15
|
+
context["request.message_id"] = SecureRandom.uuid
|
16
|
+
context["request.timestamp"] = Time.current.iso8601
|
15
17
|
context["request.gateway"] = :nalo
|
16
18
|
context["request.network"] = nil
|
17
19
|
context["request.msisdn"] = Phonelib.parse(params["MSISDN"]).e164
|
@@ -12,6 +12,12 @@ module FlowChat
|
|
12
12
|
controller = context["controller"]
|
13
13
|
controller.request
|
14
14
|
|
15
|
+
# Add timestamp for all requests
|
16
|
+
context["request.timestamp"] = Time.current.iso8601
|
17
|
+
|
18
|
+
# Set a basic message_id (can be enhanced based on actual Nsano implementation)
|
19
|
+
context["request.message_id"] = SecureRandom.uuid
|
20
|
+
|
15
21
|
# input = context["rack.input"].read
|
16
22
|
# context["rack.input"].rewind
|
17
23
|
# if input.present?
|
@@ -29,7 +29,7 @@ module FlowChat
|
|
29
29
|
return true unless FlowChat::Config.ussd.resumable_sessions_timeout_seconds
|
30
30
|
|
31
31
|
last_active_at = Time.parse session.dig("context", "last_active_at")
|
32
|
-
(Time.
|
32
|
+
(Time.current - FlowChat::Config.ussd.resumable_sessions_timeout_seconds) < last_active_at
|
33
33
|
rescue
|
34
34
|
false
|
35
35
|
end
|
@@ -1,55 +1,27 @@
|
|
1
|
-
require "middleware"
|
2
|
-
|
3
1
|
module FlowChat
|
4
2
|
module Ussd
|
5
|
-
class Processor
|
6
|
-
attr_reader :middleware, :gateway
|
7
|
-
|
8
|
-
def initialize(controller)
|
9
|
-
@context = FlowChat::Context.new
|
10
|
-
@context["controller"] = controller
|
11
|
-
@middleware = ::Middleware::Builder.new(name: "ussd.middleware")
|
12
|
-
|
13
|
-
yield self if block_given?
|
14
|
-
end
|
15
|
-
|
16
|
-
def use_gateway(gateway)
|
17
|
-
@gateway = gateway
|
18
|
-
self
|
19
|
-
end
|
20
|
-
|
21
|
-
def use_session_store(session_store)
|
22
|
-
@context["session.store"] = session_store
|
23
|
-
self
|
24
|
-
end
|
25
|
-
|
26
|
-
def use_middleware(middleware)
|
27
|
-
@middleware.use middleware
|
28
|
-
self
|
29
|
-
end
|
30
|
-
|
3
|
+
class Processor < FlowChat::BaseProcessor
|
31
4
|
def use_resumable_sessions
|
32
5
|
middleware.insert_before 0, FlowChat::Ussd::Middleware::ResumableSession
|
33
6
|
self
|
34
7
|
end
|
35
8
|
|
36
|
-
|
37
|
-
@context["flow.name"] = flow_class.name.underscore
|
38
|
-
@context["flow.class"] = flow_class
|
39
|
-
@context["flow.action"] = action
|
9
|
+
protected
|
40
10
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
b.use FlowChat::Ussd::Middleware::Pagination
|
45
|
-
b.use middleware
|
46
|
-
b.use FlowChat::Ussd::Middleware::Executor
|
47
|
-
end.inject_logger(Rails.logger)
|
11
|
+
def middleware_name
|
12
|
+
"ussd.middleware"
|
13
|
+
end
|
48
14
|
|
49
|
-
|
15
|
+
def build_middleware_stack
|
16
|
+
create_middleware_stack("ussd")
|
17
|
+
end
|
50
18
|
|
51
|
-
|
19
|
+
def configure_middleware_stack(builder)
|
20
|
+
builder.use FlowChat::Session::Middleware
|
21
|
+
builder.use FlowChat::Ussd::Middleware::Pagination
|
22
|
+
builder.use middleware
|
23
|
+
builder.use FlowChat::Ussd::Middleware::Executor
|
52
24
|
end
|
53
25
|
end
|
54
26
|
end
|
55
|
-
end
|
27
|
+
end
|
@@ -7,23 +7,31 @@ module FlowChat
|
|
7
7
|
@user_input = input
|
8
8
|
end
|
9
9
|
|
10
|
-
def ask(msg, choices: nil, convert: nil, validate: nil, transform: nil)
|
10
|
+
def ask(msg, choices: nil, convert: nil, validate: nil, transform: nil, media: nil)
|
11
11
|
if user_input.present?
|
12
12
|
input = user_input
|
13
13
|
input = convert.call(input) if convert.present?
|
14
14
|
validation_error = validate.call(input) if validate.present?
|
15
15
|
|
16
|
-
|
16
|
+
if validation_error.present?
|
17
|
+
# Include media URL in validation error message
|
18
|
+
original_message_with_media = build_message_with_media(msg, media)
|
19
|
+
prompt!([validation_error, original_message_with_media].join("\n\n"), choices:)
|
20
|
+
end
|
17
21
|
|
18
22
|
input = transform.call(input) if transform.present?
|
19
23
|
return input
|
20
24
|
end
|
21
25
|
|
22
|
-
|
26
|
+
# Include media URL in the message for USSD
|
27
|
+
final_message = build_message_with_media(msg, media)
|
28
|
+
prompt! final_message, choices:
|
23
29
|
end
|
24
30
|
|
25
|
-
def say(message)
|
26
|
-
|
31
|
+
def say(message, media: nil)
|
32
|
+
# Include media URL in the message for USSD
|
33
|
+
final_message = build_message_with_media(message, media)
|
34
|
+
terminate! final_message
|
27
35
|
end
|
28
36
|
|
29
37
|
def select(msg, choices)
|
@@ -43,6 +51,32 @@ module FlowChat
|
|
43
51
|
|
44
52
|
private
|
45
53
|
|
54
|
+
def build_message_with_media(message, media)
|
55
|
+
return message unless media
|
56
|
+
|
57
|
+
media_url = media[:url] || media[:path]
|
58
|
+
media_type = media[:type] || :image
|
59
|
+
|
60
|
+
# For USSD, we append the media URL to the message
|
61
|
+
media_text = case media_type.to_sym
|
62
|
+
when :image
|
63
|
+
"📷 Image: #{media_url}"
|
64
|
+
when :document
|
65
|
+
"📄 Document: #{media_url}"
|
66
|
+
when :audio
|
67
|
+
"🎵 Audio: #{media_url}"
|
68
|
+
when :video
|
69
|
+
"🎥 Video: #{media_url}"
|
70
|
+
when :sticker
|
71
|
+
"😊 Sticker: #{media_url}"
|
72
|
+
else
|
73
|
+
"📎 Media: #{media_url}"
|
74
|
+
end
|
75
|
+
|
76
|
+
# Combine message with media information
|
77
|
+
"#{message}\n\n#{media_text}"
|
78
|
+
end
|
79
|
+
|
46
80
|
def build_select_choices(choices)
|
47
81
|
case choices
|
48
82
|
when Array
|
data/lib/flow_chat/version.rb
CHANGED
@@ -0,0 +1,64 @@
|
|
1
|
+
module FlowChat
|
2
|
+
module Whatsapp
|
3
|
+
class App
|
4
|
+
attr_reader :session, :input, :context, :navigation_stack
|
5
|
+
|
6
|
+
def initialize(context)
|
7
|
+
@context = context
|
8
|
+
@session = context.session
|
9
|
+
@input = context.input
|
10
|
+
@navigation_stack = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def screen(key)
|
14
|
+
raise ArgumentError, "a block is expected" unless block_given?
|
15
|
+
raise ArgumentError, "screen has been presented" if navigation_stack.include?(key)
|
16
|
+
|
17
|
+
navigation_stack << key
|
18
|
+
return session.get(key) if session.get(key).present?
|
19
|
+
|
20
|
+
user_input = input
|
21
|
+
if session.get("$started_at$").nil?
|
22
|
+
session.set("$started_at$", Time.current.iso8601)
|
23
|
+
user_input = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
prompt = FlowChat::Whatsapp::Prompt.new user_input
|
27
|
+
@input = nil # input is being submitted to prompt so we clear it
|
28
|
+
|
29
|
+
value = yield prompt
|
30
|
+
session.set(key, value)
|
31
|
+
value
|
32
|
+
end
|
33
|
+
|
34
|
+
def say(msg)
|
35
|
+
raise FlowChat::Interrupt::Terminate.new([:text, msg, {}])
|
36
|
+
end
|
37
|
+
|
38
|
+
# WhatsApp-specific data accessors (read-only)
|
39
|
+
def contact_name
|
40
|
+
context["request.contact_name"]
|
41
|
+
end
|
42
|
+
|
43
|
+
def message_id
|
44
|
+
context["request.message_id"]
|
45
|
+
end
|
46
|
+
|
47
|
+
def timestamp
|
48
|
+
context["request.timestamp"]
|
49
|
+
end
|
50
|
+
|
51
|
+
def location
|
52
|
+
context["request.location"]
|
53
|
+
end
|
54
|
+
|
55
|
+
def media
|
56
|
+
context["request.media"]
|
57
|
+
end
|
58
|
+
|
59
|
+
def phone_number
|
60
|
+
context["request.msisdn"]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|