qismo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qismo
4
+ #
5
+ # Base object of Qismo object
6
+ #
7
+ class Base
8
+ #
9
+ # Transform object to hash
10
+ #
11
+ # @return [Hash]
12
+ #
13
+ def as_json
14
+ hash = {}
15
+ instance_variables.each { |var| hash[var.to_s.delete("@")] = instance_variable_get(var) }
16
+
17
+ hash
18
+ end
19
+
20
+ # @return [Array<Object>]
21
+ def self.descendants
22
+ ObjectSpace.each_object(Class).select { |klass| klass < self }
23
+ end
24
+
25
+ private
26
+
27
+ #
28
+ # Initialize object
29
+ #
30
+ # @param attrs [Hash] Attributes of object
31
+ #
32
+ def initialize(attrs = {})
33
+ attrs.each do |key, value|
34
+ initiate_object(key, value)
35
+ end
36
+ end
37
+
38
+ #
39
+ # Dynamically tranform hash to class attributes
40
+ #
41
+ # @param key [String,Symbol]
42
+ # @param value [String,Integer,TrueClass,FalseClass,Hash,Array]
43
+ #
44
+ #
45
+ def initiate_object(key, value)
46
+ if value.is_a?(TrueClass) || value.is_a?(FalseClass)
47
+ key_s = key.to_s
48
+ method_name = key_s.start_with?("is_") ? key_s.gsub("is_", "") : key_s
49
+ method_name += "?"
50
+
51
+ self.class.define_method(method_name) do
52
+ value
53
+ end
54
+ end
55
+
56
+ instance_variable_set("@#{key}", value)
57
+ self.class.class_eval { attr_reader(key.to_sym) }
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qismo
4
+ #
5
+ # Qismo bot object
6
+ #
7
+ class Bot
8
+ # @!parse
9
+ # extend Resources::BotResource
10
+ extend Resources::BotResource
11
+ end
12
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qismo
4
+ #
5
+ # Class to handle nil value
6
+ #
7
+ class NullObject
8
+ #
9
+ # @return [TrueClass,FalseClass]
10
+ #
11
+ def !
12
+ true
13
+ end
14
+
15
+ #
16
+ # @return [TrueClass,FalseClass]
17
+ #
18
+ def respond_to?(*)
19
+ true
20
+ end
21
+
22
+ #
23
+ # @return [TrueClass,FalseClass]
24
+ #
25
+ def instance_of?(klass)
26
+ raise(TypeError, "class or module required") unless klass.is_a?(Class)
27
+
28
+ self.class == klass
29
+ end
30
+
31
+ #
32
+ # @return [TrueClass,FalseClass]
33
+ #
34
+ def kind_of?(mod)
35
+ raise(TypeError, "class or module required") unless mod.is_a?(Module)
36
+
37
+ self.class.ancestors.include?(mod)
38
+ end
39
+
40
+ alias_method :is_a?, :kind_of?
41
+
42
+ #
43
+ # @return [Integer]
44
+ #
45
+ def <=>(other)
46
+ if other.is_a?(self.class)
47
+ 0
48
+ else
49
+ -1
50
+ end
51
+ end
52
+
53
+ #
54
+ # @return [TrueClass,FalseClass]
55
+ #
56
+ def nil?
57
+ true
58
+ end
59
+
60
+ #
61
+ # @return [String]
62
+ #
63
+ def as_json(*)
64
+ "null"
65
+ end
66
+
67
+ #
68
+ # @return [String]
69
+ #
70
+ def to_json(*args)
71
+ nil.to_json(*args)
72
+ end
73
+
74
+ #
75
+ # @return [nil]
76
+ #
77
+ def presence
78
+ nil
79
+ end
80
+
81
+ #
82
+ # @return [TrueClass,FalseClass]
83
+ #
84
+ def blank?
85
+ true
86
+ end
87
+
88
+ #
89
+ # @return [TrueClass,FalseClass]
90
+ #
91
+ def present?
92
+ false
93
+ end
94
+
95
+ #
96
+ # @return [NullObject]
97
+ #
98
+ def method_missing(m, *args, &block)
99
+ self
100
+ end
101
+
102
+ #
103
+ # @return [TrueClass,FalseClass]
104
+ #
105
+ def respond_to_missing?(method_name, *args)
106
+ false
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qismo
4
+ #
5
+ # Office hour model
6
+ #
7
+ # @!attribute day [Integer]
8
+ # @!attribute starttime [String]
9
+ # @!attribute endtime [String]
10
+ #
11
+ class OfficeHour < Base
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qismo
4
+ #
5
+ # Office setting model
6
+ #
7
+ # @!attribute online_message [String]
8
+ # @!attribute offline_message [String]
9
+ # @!attribute timezone [String]
10
+ # @!attribute send_online_if_resolved [TrueClass, FalseClass]
11
+ # @!attribute send_offline_each_message [TrueClass, FalseClass]
12
+ # @!attribute is_in_office_hour [TrueClass, FalseClass]
13
+ # @!attribute office_hours [Array<OfficeHour>]
14
+ class OfficeSetting < Base
15
+ # @!parse
16
+ # extend Resources::OfficeSettingResource
17
+ extend Resources::OfficeSettingResource
18
+ end
19
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qismo
4
+ #
5
+ # Room model
6
+ #
7
+ # @!attribute channel_id [Integer]
8
+ # @!attribute contact_id [Integer]
9
+ # @!attribute is_handled_by_bot [Truelass, FalseClass]
10
+ # @!attribute is_resolved [Truelass, FalseClass]
11
+ # @!attribute is_waiting [Truelass, FalseClass]
12
+ # @!attribute last_comment_sender [String]
13
+ # @!attribute last_comment_sender_type [String]
14
+ # @!attribute last_comment_text [String]
15
+ # @!attribute last_comment_timestamp [String]
16
+ # @!attribute last_customer_comment_text [String, nil]
17
+ # @!attribute last_customer_timestamp [String]
18
+ # @!attribute name [String]
19
+ # @!attribute room_badge [String]
20
+ # @!attribute room_id [String]
21
+ # @!attribute room_type [String]
22
+ # @!attribute source [String]
23
+ # @!attribute user_avatar_url [String]
24
+ # @!attribute user_id [String]
25
+ #
26
+ class Room < Base
27
+ # @!parse
28
+ # extend Resources::RoomResource
29
+ extend Resources::RoomResource
30
+ end
31
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qismo
4
+ #
5
+ # User model
6
+ #
7
+ # @!attribute id [Integer]
8
+ # @!attribute name [String]
9
+ # @!attribute authentication_token [String]
10
+ # @!attribute is_available [TrueClass, FalseClass]
11
+ # @!attribute direct_login_token [String]
12
+ # @!attribute long_lived_token [String]
13
+ #
14
+ class User < Base
15
+ # @!parse
16
+ # extend Resources::UserResource
17
+ extend Resources::UserResource
18
+ end
19
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qismo
4
+ module Operations
5
+ end
6
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qismo
4
+ module Resources
5
+ module AgentResource
6
+ #
7
+ # List agents
8
+ #
9
+ # @param options [Hash]
10
+ # @option options [Integer] :page Page number
11
+ # @option options [Integer] :limit Data offset per page
12
+ # @option options [String] :search Filter agent by query
13
+ # @option options [String] :scope Scoping search attribute. Can be `division`, `name`, or `email`
14
+ #
15
+ # @return [Array<Agent>]
16
+ #
17
+ def list(options = {})
18
+ resp = Qismo.client.get("/api/v2/admin/agents", options)
19
+ body = resp.http_body
20
+ agents = body.dig("data", "agents")
21
+ agents.dig("data", "agents").map { |agent| Agent.new(agent) }
22
+ end
23
+
24
+ #
25
+ # Get agent by id
26
+ #
27
+ # @param id [Integer] Agent's id
28
+ #
29
+ # @return [Agent]
30
+ #
31
+ def get(id)
32
+ resp = Qismo.client.call(:get, "/api/v2/admin/agent/#{id}")
33
+ body = resp.http_body
34
+ agent = body.dig("data", "agent")
35
+ Agent.new(agent)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qismo
4
+ module Resources
5
+ module BotResource
6
+ #
7
+ # Send message to room as bot
8
+ #
9
+ # @param options [Hash]
10
+ # @option options [String] :room_id
11
+ # @option options [String] :message
12
+ # @option options [String] :type required if type is not `text`
13
+ # @!attribute payload [Hash] required if type is not `text`
14
+ #
15
+ # @return [Response]
16
+ #
17
+ def send_message(options = {})
18
+ options[:sender_email] = Qismo.admin_email
19
+
20
+ validate_existence_of!(:sender_email, :message, :room_id, on: options)
21
+ options[:room_id] = options[:room_id].to_s
22
+
23
+ Qismo.client.post("/#{Qismo.client.app_id}/bot", options)
24
+ end
25
+
26
+ #
27
+ # Notify admin and/or agent that bot need human help
28
+ #
29
+ # @param room_id [Integer]
30
+ # @param roles [Array<Integer>]
31
+ # @param options [Hash]
32
+ # @option options [TrueClass] :find_online_agent
33
+ #
34
+ # @return [Response]
35
+ #
36
+ def handover(room_id, *roles, **options)
37
+ if roles.empty?
38
+ Qismo.client.request(:post, "/#{Qismo.client.app_id}/bot/#{room_id}/hand_over", {
39
+ headers: { authorization: Qismo.client.secret_key },
40
+ })
41
+ else
42
+ int_roles = []
43
+ roles.each { |role| int_roles.append(role) }
44
+
45
+ if roles.size == 1
46
+ options[:role] = int_roles.first
47
+ else
48
+ options[:roles] = int_roles
49
+ end
50
+
51
+ options = options.compact
52
+
53
+ Qismo.client.request(:post, "/#{Qismo.client.app_id}/bot/#{room_id}/hand_over_to_role", {
54
+ json: options,
55
+ headers: { authorization: Qismo.client.secret_key },
56
+ })
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qismo
4
+ module Resources
5
+ module OfficeSettingResource
6
+ #
7
+ # Get office hours
8
+ #
9
+ # @return [OfficeSetting]
10
+ #
11
+ def office_hours
12
+ resp = Qismo.client.get("/api/v1/admin/office_hours")
13
+ body = resp.http_body
14
+
15
+ data = body["data"]
16
+
17
+ morfed_data = {}
18
+ morfed_data["online_message"] = data["online_message"]
19
+ morfed_data["offline_message"] = data["offline_message"]
20
+ morfed_data["timezone"] = data["timezone"]
21
+ morfed_data["send_online_if_resolved"] = data["send_online_if_resolved"]
22
+ morfed_data["send_offline_each_message"] = data["send_offline_each_message"]
23
+
24
+ office_hours = []
25
+ data["office_hours"].each do |oh|
26
+ office_hours.append(OfficeHour.new(oh))
27
+ end
28
+
29
+ morfed_data["office_hours"] = office_hours
30
+ morfed_data["is_in_office_hour"] = check_is_in_office_hour?(
31
+ morfed_data["timezone"],
32
+ morfed_data["office_hours"]
33
+ )
34
+
35
+ OfficeSetting.new(morfed_data)
36
+ end
37
+
38
+ private
39
+
40
+ #
41
+ # Check if current timestamp is in office hour
42
+ #
43
+ # @param timezone [String,Integer]
44
+ # @param office_hours [Array<OfficeHour>]
45
+ #
46
+ # @return [TrueClass, FalseClass]
47
+ #
48
+ def check_is_in_office_hour?(timezone, office_hours)
49
+ current = Time.now.getlocal(timezone)
50
+ current_weekday = current.strftime("%u").to_i
51
+
52
+ today_oh = office_hours.find { |oh| oh.day == current_weekday }
53
+ return false if today_oh.nil?
54
+
55
+ start_time = Time.parse("#{current.year}-#{current.month}-#{current.day} #{today_oh.starttime} #{timezone}")
56
+ end_time = Time.parse("#{current.year}-#{current.month}-#{current.day} #{today_oh.endtime} #{timezone}")
57
+
58
+ int_start_time = start_time.to_i
59
+ int_end_time = end_time.to_i
60
+
61
+ int_current = current.to_i
62
+
63
+ (int_start_time..int_end_time).include?(int_current)
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qismo
4
+ module Resources
5
+ module RoomResource
6
+ #
7
+ # List rooms
8
+ #
9
+ # @param params [Hash]
10
+ #
11
+ # @return [Array<Room>]
12
+ #
13
+ def list(params = {})
14
+ resp = Qismo.client.get("/api/v2/customer_rooms", params)
15
+
16
+ body = resp.http_body
17
+ customer_rooms = body.dig("data", "customer_rooms")
18
+
19
+ rooms = []
20
+ customer_rooms.each do |cr|
21
+ rooms.append(Room.new(cr))
22
+ end
23
+
24
+ rooms
25
+ end
26
+
27
+ #
28
+ # Get room by id
29
+ #
30
+ # @param id [Integer]
31
+ #
32
+ # @return [Room]
33
+ #
34
+ def get(id)
35
+ resp = Qismo.client.get("/api/v2/customer_rooms/#{id}")
36
+
37
+ body = resp.http_body
38
+ room = body.dig("data", "customer_room")
39
+ if room == {}
40
+ raise NotFound.new(
41
+ "Room with id #{id} is not found",
42
+ http_code: 404,
43
+ http_headers: resp.http_headers,
44
+ http_body: body,
45
+ http_raw_body: resp.http_raw_body
46
+ )
47
+ end
48
+
49
+ Room.new(room)
50
+ end
51
+
52
+ #
53
+ # Assign an agent to a room
54
+ #
55
+ # @param room_id [String]
56
+ # @param agent_id [Integer]
57
+ # @param options [Hash]
58
+ #
59
+ # @return [Agent]
60
+ #
61
+ def assign_agent(room_id, agent_id, options = {})
62
+ options[:room_id] = room_id.to_s
63
+ options[:agent_id] = agent_id
64
+
65
+ resp = Qismo.client.post("/api/v1/admin/service/assign_agent", options)
66
+
67
+ body = resp.http_body
68
+ added_agent = body.dig("data", "added_agent")
69
+
70
+ Agent.new(added_agent)
71
+ end
72
+
73
+ #
74
+ # List agents who can be assigned to a room
75
+ #
76
+ # @param options [Hash]
77
+ # @option options [Integer, String] :room_id Required
78
+ # @option options [Integer, String] :limit Optional. Default: 10. Max: 25
79
+ # @option options [String] :cursor_before
80
+ # @option options [String] :cursor_after
81
+ # @option options [String] :search Filter agents by name or email
82
+ #
83
+ # @return [Array<Agent>]
84
+ #
85
+ def other_agents(options = {})
86
+ validate_existence_of!(:room_id)
87
+
88
+ resp = Qismo.client.call(:get, "/api/v2/admin/service/other_agents", params: options)
89
+
90
+ body = resp.http_body
91
+ qismo_agents = body.dig("data", "agents")
92
+
93
+ agents = []
94
+ qismo_agents.each do |qismo_agent|
95
+ agents.append(Agent.new(qismo_agent))
96
+ end
97
+
98
+ agents
99
+ end
100
+
101
+ alias_method :eligible_agents, :other_agents
102
+
103
+ #
104
+ # Resolve room
105
+ #
106
+ # @param options [Hash]
107
+ # @option options room_id [String]
108
+ # @option options notes [String]
109
+ # @option options last_comment_id [Integer]
110
+ # @option options is_send_email [TruClass, FalseClass]
111
+ # @option options extras optional [Hash]
112
+ #
113
+ # @return [Response]
114
+ #
115
+ def resolve(options = {})
116
+ validate_existence_of!(:room_id, on: options)
117
+
118
+ fetch_last_comment_id = lambda do
119
+ resp = Qismo.client.raw_request(:get, "https://api.qiscus.com/api/v2.1/rest/load_comments", {
120
+ headers: { qiscus_sdk_app_id: Qismo.client.app_id, qiscus_sdk_secret: Qismo.client.secret_key },
121
+ params: { room_id: options[:room_id], limit: 1 },
122
+ })
123
+
124
+ body = resp.http_body
125
+ if body.empty?
126
+ raise BadRequest.new(
127
+ "There is no message in room #{options[:room_id]}",
128
+ http_code: 200,
129
+ http_headers: resp.http_headers,
130
+ http_body: resp.http_body,
131
+ http_raw_body: resp.http_raw_body
132
+ )
133
+ end
134
+
135
+ id = body.dig("results", "comments", 0, "id")
136
+ if id.nil?
137
+ raise BadRequest.new(
138
+ "There is no message in room #{options[:room_id]}",
139
+ http_code: 200,
140
+ http_headers: resp.http_headers,
141
+ http_body: resp.http_body,
142
+ http_raw_body: resp.http_raw_body
143
+ )
144
+ end
145
+
146
+ id
147
+ end
148
+
149
+ if falsy?(options[:last_comment_id])
150
+ options[:last_comment_id] = fetch_last_comment_id.call
151
+ end
152
+
153
+ validate_existence_of!(:last_comment_id, :notes, :is_send_email, :extras, on: options)
154
+
155
+ options[:room_id] = options[:room_id].to_i
156
+ Qismo.client.post(:post, "/api/v1/admin/service/mark_as_resolved", options)
157
+ end
158
+
159
+ #
160
+ # Activate bot in a room
161
+ #
162
+ # @param id [Integer] Room id
163
+ #
164
+ # @return [Response]
165
+ #
166
+ def activate_bot(id)
167
+ Qismo.client.post("/bot/#{id}/activate", is_active: true)
168
+ end
169
+
170
+ #
171
+ # Deactivate bot in a room
172
+ #
173
+ # @param id [Integer] Room id
174
+ #
175
+ # @return [Response]
176
+ #
177
+ def deactivate_bot(id)
178
+ Qismo.client.post("/bot/#{id}/activate", is_active: true)
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Qismo
4
+ module Resources
5
+ module UserResource
6
+ #
7
+ # Login to Qismo
8
+ #
9
+ # @param email [String]
10
+ # @param password [String]
11
+ # @param role [String]
12
+ #
13
+ # @return [User]
14
+ #
15
+ def login(email, password, role: "admin")
16
+ path = if role == "admin"
17
+ "/api/v1/auth"
18
+ else
19
+ "/api/v1/#{Qismo.client.app_id}/agent_auth"
20
+ end
21
+
22
+ resp = Qismo.client.post(path, email: email, password: password)
23
+
24
+ body = resp.http_body
25
+
26
+ data = body.fetch("data", {})
27
+ user = data.fetch("user", {})
28
+ user[:long_lived_token] = data["long_lived_token"]
29
+
30
+ User.new(user)
31
+ end
32
+
33
+ #
34
+ # Logout from Qismo
35
+ #
36
+ # @param access_token [String]
37
+ #
38
+ # @return [Response]
39
+ #
40
+ def logout(access_token)
41
+ Qismo.client.request(:post, "/api/v2/auth/logout", headers: { authorization: access_token })
42
+ end
43
+ end
44
+ end
45
+ end