flow_chat 0.1.0 → 0.2.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 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: []