flow_chat 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77864972a34ad8761c6ce77a4f3574b9f45b4f8ef3210c34ca376812d0b54a41
4
- data.tar.gz: 47b860cf8170d1cda13fa280ba92ba731c3060ca7d955b727b22ce8367ee8e97
3
+ metadata.gz: 6eba909e6ae38dfeb26be6f854b185ca2c8097397aa4eea8a094d47e1d3fcd56
4
+ data.tar.gz: c47e6bf434684b87c7f0c8b2ebb9b6536c5bc17efe46fa3bec3876b976401f96
5
5
  SHA512:
6
- metadata.gz: fd997d03b94ce1051ab2ad6483b5864fb2b223b510d96d45833c7908a6928b419fdc4c5512730c80bf86e5f1b9741f2fc3d7c377a1f4ba8ea2b7baa0ed2244bf
7
- data.tar.gz: c39477b7915c3abe9f2b1ff5c31b99eb3a618ecd2ab53e5405fb34e8564822510ad16dd94a2ceb664e33ada5549910c096b38e1a925e8609a3f73c855b392d3d
6
+ metadata.gz: f52ba0115aa8000f9de120d436c9800318c8635efa86979b17f2dc01104d6ae65f66dcc193ada68a0a36ff238e9b6a87ef9ee66e00581ca4b5faa72a31fb25a0
7
+ data.tar.gz: 508138465e83f318e8e889f4e11ea19a709fc06c310b76c44388f56b84c94521fa44b45cc734fcfc07acf3b1cf38bbc764614e1d7252ee7b40ab4e0a20c55877
data/flow_chat.gemspec CHANGED
@@ -6,9 +6,9 @@ Gem::Specification.new do |spec|
6
6
  spec.authors = ["Stefan Froelich"]
7
7
  spec.email = ["sfroelich01@gmail.com"]
8
8
 
9
- spec.summary = "Enable USSD processing support in Rails."
10
- spec.description = "Enable USSD processing support in Rails."
11
- spec.homepage = "https://github.com/ussd-engine/api-base"
9
+ spec.summary = "Framework for processing Menu based conversations e.g. USSD in Rails."
10
+ spec.description = "Framework for processing Menu based conversations e.g. USSD in Rails."
11
+ spec.homepage = "https://github.com/radioactive-labs/flow_chat"
12
12
  spec.license = "MIT"
13
13
  spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
14
14
 
@@ -12,6 +12,8 @@ module FlowChat
12
12
  @data[key] = value
13
13
  end
14
14
 
15
+ def input = @data["request.input"]
16
+
15
17
  def session = @data["session"]
16
18
 
17
19
  def controller = @data["controller"]
@@ -7,8 +7,8 @@ module FlowChat
7
7
  @session_data = (session_store[session_id] || {}).with_indifferent_access
8
8
  end
9
9
 
10
- def get(key, default = nil)
11
- session_data[key] || default
10
+ def get(key)
11
+ session_data[key]
12
12
  end
13
13
 
14
14
  def set(key, value)
@@ -5,8 +5,8 @@ module FlowChat
5
5
 
6
6
  def initialize(context)
7
7
  @context = context
8
- @session = context["session"]
9
- @input = context["request.input"]
8
+ @session = context.session
9
+ @input = context.input
10
10
  @navigation_stack = []
11
11
  end
12
12
 
@@ -18,26 +18,20 @@ module FlowChat
18
18
  # context["request.type"] = params["MSGTYPE"] ? :initial : :response
19
19
  context["request.input"] = params["USERDATA"].presence
20
20
 
21
- type, msg, choices = @app.call(context)
21
+ type, prompt, choices = @app.call(context)
22
22
 
23
23
  context.controller.render json: {
24
24
  USERID: params["USERID"],
25
25
  MSISDN: params["MSISDN"],
26
- MSG: build_message(msg, choices),
26
+ MSG: render_prompt(prompt, choices),
27
27
  MSGTYPE: type == :prompt
28
28
  }
29
29
  end
30
30
 
31
31
  private
32
32
 
33
- def build_message(msg, choices)
34
- [msg, build_choices(choices)].compact.join "\n\n"
35
- end
36
-
37
- def build_choices(choices)
38
- return unless choices.present?
39
-
40
- choices.map { |i, c| "#{i}. #{c}" }.join "\n"
33
+ def render_prompt(prompt, choices)
34
+ FlowChat::Ussd::Renderer.new(prompt, choices).render
41
35
  end
42
36
  end
43
37
  end
@@ -11,17 +11,16 @@ module FlowChat
11
11
  @session = context.session
12
12
 
13
13
  if intercept?
14
- @context["ussd.response"] = handle_intercepted_request
15
- [200, {}, [""]]
14
+ type, prompt = handle_intercepted_request
15
+ [type, prompt, []]
16
16
  else
17
17
  @session.delete "ussd.pagination"
18
- res = @app.call(context)
18
+ type, prompt, choices = @app.call(context)
19
19
 
20
- if @context["ussd.response"].present?
21
- @context["ussd.response"] = maybe_paginate @context["ussd.response"]
22
- end
20
+ prompt = FlowChat::Ussd::Renderer.new(prompt, choices).render
21
+ type, prompt = maybe_paginate(type, prompt) if prompt.present?
23
22
 
24
- res
23
+ [type, prompt, []]
25
24
  end
26
25
  end
27
26
 
@@ -29,57 +28,59 @@ module FlowChat
29
28
 
30
29
  def intercept?
31
30
  pagination_state.present? &&
32
- (pagination_state[:type].to_sym == :terminal ||
33
- ([Config.pagination_next_option, Config.pagination_back_option].include? @context["ussd.request"][:input]))
31
+ (pagination_state["type"].to_sym == :terminal ||
32
+ ([Config.pagination_next_option, Config.pagination_back_option].include? @context.input))
34
33
  end
35
34
 
36
35
  def handle_intercepted_request
37
36
  Config.logger&.info "FlowChat::Middleware::Pagination :: Intercepted to handle pagination"
38
37
  start, finish, has_more = calculate_offsets
39
- type = (pagination_state[:type].to_sym == :terminal && !has_more) ? :terminal : :prompt
40
- body = pagination_state[:body][start..finish].strip + build_pagination_options(type, has_more)
38
+ type = (pagination_state["type"].to_sym == :terminal && !has_more) ? :terminal : :prompt
39
+ prompt = pagination_state["prompt"][start..finish].strip + build_pagination_options(type, has_more)
41
40
  set_pagination_state(current_page, start, finish)
42
41
 
43
- {body: body, type: type}
42
+ [type, prompt]
44
43
  end
45
44
 
46
- def maybe_paginate(response)
47
- if response[:body].length > Config.pagination_page_size
48
- Config.logger&.info "FlowChat::Middleware::Pagination :: Response length (#{response[:body].length}) exceeds page size (#{Config.pagination_page_size}). Paginating."
49
- body = response[:body][0..single_option_slice_size]
45
+ def maybe_paginate(type, prompt)
46
+ if prompt.length > Config.pagination_page_size
47
+ original_prompt = prompt
48
+ Config.logger&.info "FlowChat::Middleware::Pagination :: Response length (#{prompt.length}) exceeds page size (#{Config.pagination_page_size}). Paginating."
49
+ prompt = prompt[0..single_option_slice_size]
50
50
  # Ensure we do not cut words and options off in the middle.
51
- current_pagebreak = response[:body][single_option_slice_size + 1].blank? ? single_option_slice_size : body.rindex("\n") || body.rindex(" ") || single_option_slice_size
52
- set_pagination_state(1, 0, current_pagebreak, response[:body], response[:type])
53
- response[:body] = body[0..current_pagebreak].strip + "\n\n" + next_option
54
- response[:type] = :prompt
51
+ current_pagebreak = prompt[single_option_slice_size + 1].blank? ? single_option_slice_size : prompt.rindex("\n") || prompt.rindex(" ") || single_option_slice_size
52
+ set_pagination_state(1, 0, current_pagebreak, original_prompt, type)
53
+ prompt = prompt[0..current_pagebreak].strip + "\n\n" + next_option
54
+ type = :prompt
55
55
  end
56
- response
56
+ [type, prompt]
57
57
  end
58
58
 
59
59
  def calculate_offsets
60
60
  page = current_page
61
- offset = pagination_state[:offsets][page]
61
+ offset = pagination_state["offsets"][page.to_s]
62
62
  if offset.present?
63
63
  Config.logger&.debug "FlowChat::Middleware::Pagination :: Reusing cached offset for page: #{page}"
64
- start = offset[:start]
65
- finish = offset[:finish]
66
- has_more = pagination_state[:body].length > finish
64
+ start = offset["start"]
65
+ finish = offset["finish"]
66
+ has_more = pagination_state["prompt"].length > finish
67
67
  else
68
68
  Config.logger&.debug "FlowChat::Middleware::Pagination :: Calculating offset for page: #{page}"
69
69
  # We are guaranteed a previous offset because it was set in maybe_paginate
70
- previous_offset = pagination_state[:offsets][page - 1]
71
- start = previous_offset[:finish] + 1
72
- has_more, len = (pagination_state[:body].length > start + single_option_slice_size) ? [true, dual_options_slice_size] : [false, single_option_slice_size]
70
+ previous_page = page - 1
71
+ previous_offset = pagination_state["offsets"][previous_page.to_s]
72
+ start = previous_offset["finish"] + 1
73
+ has_more, len = (pagination_state["prompt"].length > start + single_option_slice_size) ? [true, dual_options_slice_size] : [false, single_option_slice_size]
73
74
  finish = start + len
74
- if start > pagination_state[:body].length
75
+ if start > pagination_state["prompt"].length
75
76
  Config.logger&.debug "FlowChat::Middleware::Pagination :: No content exists for page: #{page}. Reverting to page: #{page - 1}"
76
77
  page -= 1
77
78
  has_more = false
78
- start = previous_offset[:start]
79
- finish = previous_offset[:finish]
79
+ start = previous_offset["start"]
80
+ finish = previous_offset["finish"]
80
81
  else
81
- body = pagination_state[:body][start..finish]
82
- current_pagebreak = pagination_state[:body][finish + 1].blank? ? len : body.rindex("\n") || body.rindex(" ") || len
82
+ prompt = pagination_state["prompt"][start..finish]
83
+ current_pagebreak = pagination_state["prompt"][finish + 1].blank? ? len : prompt.rindex("\n") || prompt.rindex(" ") || len
83
84
  finish = start + current_pagebreak
84
85
  end
85
86
  end
@@ -126,28 +127,31 @@ module FlowChat
126
127
  end
127
128
 
128
129
  def current_page
129
- current_page = pagination_state[:page]
130
- if @context["ussd.request"][:input] == Config.pagination_back_option
131
- current_page -= 1
132
- elsif @context["ussd.request"][:input] == Config.pagination_next_option
133
- current_page += 1
130
+ page = pagination_state["page"]
131
+ if @context.input == Config.pagination_back_option
132
+ page -= 1
133
+ elsif @context.input == Config.pagination_next_option
134
+ page += 1
134
135
  end
135
- [current_page, 1].max
136
+ [page, 1].max
136
137
  end
137
138
 
138
139
  def pagination_state
139
- @context.session.get("pagination", {})
140
+ @pagination_state ||= @context.session.get("ussd.pagination") || {}
140
141
  end
141
142
 
142
- def set_pagination_state(page, offset_start, offset_finish, body = nil, type = nil)
143
- offsets = pagination_state[:offsets] || {}
143
+ def set_pagination_state(page, offset_start, offset_finish, prompt = nil, type = nil)
144
+ offsets = pagination_state["offsets"] || {}
144
145
  offsets[page] = {start: offset_start, finish: offset_finish}
145
- @session["ussd.pagination"] = {
146
- page: page,
147
- offsets: offsets,
148
- body: body || pagination_state[:body],
149
- type: type || pagination_state[:type]
146
+ prompt ||= pagination_state["prompt"]
147
+ type ||= pagination_state["type"]
148
+ @pagination_state = {
149
+ "page" => page,
150
+ "offsets" => offsets,
151
+ "prompt" => prompt,
152
+ "type" => type
150
153
  }
154
+ @session.set "ussd.pagination", @pagination_state
151
155
  end
152
156
  end
153
157
  end
@@ -33,21 +33,22 @@ module FlowChat
33
33
  self
34
34
  end
35
35
 
36
- def use_pagination
37
- middleware.use FlowChat::Ussd::Middleware::Pagination
38
- end
39
-
40
- def run(flow, action)
41
- @context["flow.class"] = flow
36
+ def run(flow_class, action)
37
+ @context["flow.name"] = flow_class.name.underscore
38
+ @context["flow.class"] = flow_class
42
39
  @context["flow.action"] = action
43
40
 
44
- ::Middleware::Builder.new name: "ussd" do |b|
41
+ stack = ::Middleware::Builder.new name: "ussd" do |b|
45
42
  b.use gateway
46
43
  b.use FlowChat::Session::Middleware
47
- # b.use FlowChat::Middleware::Pagination
44
+ b.use FlowChat::Ussd::Middleware::Pagination
48
45
  b.use middleware
49
46
  b.use FlowChat::Ussd::Middleware::Executor
50
- end.inject_logger(Rails.logger).call(@context)
47
+ end.inject_logger(Rails.logger)
48
+
49
+ yield stack if block_given?
50
+
51
+ stack.call(@context)
51
52
  end
52
53
  end
53
54
  end
@@ -0,0 +1,26 @@
1
+ module FlowChat
2
+ module Ussd
3
+ class Renderer
4
+ attr_reader :prompt, :choices
5
+
6
+ def initialize(prompt, choices)
7
+ @prompt = prompt
8
+ @choices = choices
9
+ end
10
+
11
+ def render = build_prompt
12
+
13
+ private
14
+
15
+ def build_prompt
16
+ [prompt, build_choices].compact.join "\n\n"
17
+ end
18
+
19
+ def build_choices
20
+ return unless choices.present?
21
+
22
+ choices.map { |i, c| "#{i}. #{c}" }.join "\n"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,3 +1,3 @@
1
1
  module FlowChat
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flow_chat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Froelich
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-04-04 00:00:00.000000000 Z
11
+ date: 2024-04-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zeitwerk
@@ -80,7 +80,7 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: 0.4.2
83
- description: Enable USSD processing support in Rails.
83
+ description: Framework for processing Menu based conversations e.g. USSD in Rails.
84
84
  email:
85
85
  - sfroelich01@gmail.com
86
86
  executables: []
@@ -111,17 +111,18 @@ files:
111
111
  - lib/flow_chat/ussd/middleware/resumable_session.rb
112
112
  - lib/flow_chat/ussd/processor.rb
113
113
  - lib/flow_chat/ussd/prompt.rb
114
+ - lib/flow_chat/ussd/renderer.rb
114
115
  - lib/flow_chat/ussd/simulator/controller.rb
115
116
  - lib/flow_chat/ussd/simulator/views/simulator.html.erb
116
117
  - lib/flow_chat/version.rb
117
- homepage: https://github.com/ussd-engine/api-base
118
+ homepage: https://github.com/radioactive-labs/flow_chat
118
119
  licenses:
119
120
  - MIT
120
121
  metadata:
121
122
  allowed_push_host: https://rubygems.org
122
- homepage_uri: https://github.com/ussd-engine/api-base
123
- source_code_uri: https://github.com/ussd-engine/api-base
124
- changelog_uri: https://github.com/ussd-engine/api-base
123
+ homepage_uri: https://github.com/radioactive-labs/flow_chat
124
+ source_code_uri: https://github.com/radioactive-labs/flow_chat
125
+ changelog_uri: https://github.com/radioactive-labs/flow_chat
125
126
  post_install_message:
126
127
  rdoc_options: []
127
128
  require_paths:
@@ -140,5 +141,5 @@ requirements: []
140
141
  rubygems_version: 3.5.6
141
142
  signing_key:
142
143
  specification_version: 4
143
- summary: Enable USSD processing support in Rails.
144
+ summary: Framework for processing Menu based conversations e.g. USSD in Rails.
144
145
  test_files: []