Skip to content

Commit af5750f

Browse files
committed
add ussd build module
1 parent ec434a1 commit af5750f

File tree

3 files changed

+202
-0
lines changed

3 files changed

+202
-0
lines changed

ussd/builder.go

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package ussd
2+
3+
import (
4+
"strings"
5+
6+
"github.com/ochom/gutils/arrays"
7+
"github.com/ochom/gutils/logs"
8+
)
9+
10+
// GetMenuFunc returns the menu function
11+
type GetMenuFunc func(params map[string]string) string
12+
13+
type Children []*Step
14+
15+
// Step a ussd step
16+
type Step struct {
17+
Key string
18+
Menu GetMenuFunc
19+
End bool
20+
Params map[string]string
21+
Children Children
22+
}
23+
24+
// NewStep creates a new step
25+
func NewStep(menuFunc GetMenuFunc) Step {
26+
return Step{
27+
Menu: menuFunc,
28+
Params: make(map[string]string),
29+
}
30+
}
31+
32+
// AddStep adds a new step
33+
func (s *Step) AddStep(step Step) {
34+
s.Children = append(s.Children, &step)
35+
}
36+
37+
// GetResponse returns the response
38+
func (s *Step) GetResponse() string {
39+
response := s.Menu(s.Params)
40+
if strings.HasPrefix(response, "CON ") || strings.HasPrefix(response, "END ") {
41+
return response
42+
}
43+
44+
if s.End {
45+
return "END " + response
46+
}
47+
48+
return "CON " + response
49+
}
50+
51+
// getMatchingChild returns the matching child
52+
func (s *Step) getMatchingChild(ussdPart string) *Step {
53+
// first check exact matching children
54+
child := arrays.Find(s.Children, func(c *Step) bool {
55+
return strings.EqualFold(ussdPart, c.Key)
56+
})
57+
58+
if child != nil {
59+
return child
60+
}
61+
62+
// then check if the key is a wildcard
63+
child = arrays.Find(s.Children, func(c *Step) bool {
64+
return c.Key == ""
65+
})
66+
67+
return child
68+
}
69+
70+
// walk goes through the menu and finds matching children
71+
func (s *Step) walk(ussdParts []string) *Step {
72+
remainingPieces := len(ussdParts)
73+
74+
// break if no piece is left
75+
if remainingPieces == 0 {
76+
return s
77+
}
78+
79+
// break if only once piece is left by finding the child that matches the remaining piece
80+
if remainingPieces == 1 {
81+
return s.getMatchingChild(ussdParts[0])
82+
}
83+
84+
// first check kids that exactly match the first piece
85+
for _, child := range s.Children {
86+
if strings.EqualFold(ussdParts[0], child.Key) {
87+
return child.walk(ussdParts[1:])
88+
}
89+
}
90+
91+
// then check if the first piece is a wildcard
92+
for _, child := range s.Children {
93+
if child.Key == "" {
94+
return child.walk(ussdParts[1:])
95+
}
96+
}
97+
98+
return nil
99+
}
100+
101+
// Parse takes a ussd string and returns the right child
102+
func (s *Step) Parse(sessionData map[string]string, ussdParts []string) *Step {
103+
child := s.walk(ussdParts)
104+
if child == nil {
105+
logs.Error("Could not get the correct child for the ussd string")
106+
return nil
107+
}
108+
109+
remainingPieces := len(ussdParts)
110+
if remainingPieces > 1 {
111+
sessionData["input"] = ussdParts[remainingPieces-1]
112+
}
113+
114+
child.Params = sessionData
115+
return child
116+
}

ussd/session.go

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package ussd
2+
3+
var session map[string]map[string]string
4+
5+
func init() {
6+
session = make(map[string]map[string]string)
7+
}
8+
9+
func HasSession(sessionId string) bool {
10+
_, ok := session[sessionId]
11+
return ok
12+
}
13+
14+
func GetSession(sessionId string) map[string]string {
15+
if !HasSession(sessionId) {
16+
SetSession(sessionId, make(map[string]string))
17+
}
18+
19+
return session[sessionId]
20+
}
21+
22+
func SetSession(sessionId string, params map[string]string) {
23+
session[sessionId] = params
24+
}
25+
26+
func RemoveSession(sessionId string) {
27+
delete(session, sessionId)
28+
}
29+
30+
func ClearSession() {
31+
session = make(map[string]map[string]string)
32+
}

ussd/ussd.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package ussd
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/ochom/gutils/logs"
8+
)
9+
10+
var mainMenu *Step
11+
12+
func InitMenu(step *Step) {
13+
if mainMenu == nil {
14+
mainMenu = step
15+
}
16+
}
17+
18+
type Params struct {
19+
Ussd string
20+
SessionId string
21+
PhoneNumber string
22+
}
23+
24+
func Parse(data Params) (*Step, error) {
25+
if mainMenu == nil {
26+
return nil, fmt.Errorf("mainMenu has not been created")
27+
}
28+
29+
data.Ussd = strings.TrimSuffix(data.Ussd, "#")
30+
data.Ussd = strings.TrimPrefix(data.Ussd, "*")
31+
32+
parts := []string{}
33+
if data.Ussd != "" {
34+
parts = strings.Split(data.Ussd, "*")
35+
}
36+
37+
// check if session exists
38+
if !HasSession(data.SessionId) {
39+
SetSession(data.SessionId, make(map[string]string))
40+
}
41+
42+
mainMenu.Params = map[string]string{
43+
"phone_number": data.PhoneNumber,
44+
"session_id": data.SessionId,
45+
"ussd": data.Ussd,
46+
}
47+
step := mainMenu.Parse(mainMenu.Params, parts)
48+
if step == nil {
49+
logs.Error("Could not get the correct child for the ussd string")
50+
return nil, fmt.Errorf("could not get the correct child for the ussd string")
51+
}
52+
53+
return step, nil
54+
}

0 commit comments

Comments
 (0)