chat.rb 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
  2. class Chat < ApplicationModel
  3. validates :name, presence: true
  4. store :preferences
  5. =begin
  6. get the customer state of a chat
  7. chat = Chat.find(123)
  8. chat.customer_state(session_id = nil)
  9. returns
  10. chat_disabled - chat is disabled
  11. {
  12. state: 'chat_disabled'
  13. }
  14. returns (without session_id)
  15. offline - no agent is online
  16. {
  17. state: 'offline'
  18. }
  19. no_seats_available - no agent is available (all slots are used, max_queue is full)
  20. {
  21. state: 'no_seats_available'
  22. }
  23. online - ready for chats
  24. {
  25. state: 'online'
  26. }
  27. returns (session_id)
  28. reconnect - position of waiting list for new chat
  29. {
  30. state: 'reconnect',
  31. position: chat_session.position,
  32. }
  33. reconnect - chat session already exists, serve agent and session chat messages (to redraw chat history)
  34. {
  35. state: 'reconnect',
  36. session: session,
  37. agent: user,
  38. }
  39. =end
  40. def customer_state(session_id = nil)
  41. return { state: 'chat_disabled' } if !Setting.get('chat')
  42. # reconnect
  43. if session_id
  44. chat_session = Chat::Session.find_by(session_id: session_id, state: %w[waiting running])
  45. if chat_session
  46. if chat_session.state == 'running'
  47. user = nil
  48. if chat_session.user_id
  49. chat_user = User.lookup(id: chat_session.user_id)
  50. url = nil
  51. if chat_user.image && chat_user.image != 'none'
  52. url = "#{Setting.get('http_type')}://#{Setting.get('fqdn')}/api/v1/users/image/#{chat_user.image}"
  53. end
  54. user = {
  55. name: chat_user.fullname,
  56. avatar: url,
  57. }
  58. # get queue position if needed
  59. session = Chat::Session.messages_by_session_id(session_id)
  60. if session
  61. return {
  62. state: 'reconnect',
  63. session: session,
  64. agent: user,
  65. }
  66. end
  67. end
  68. elsif chat_session.state == 'waiting'
  69. return {
  70. state: 'reconnect',
  71. position: chat_session.position,
  72. }
  73. end
  74. end
  75. end
  76. # check if agents are available
  77. if Chat.active_agent_count([id]).zero?
  78. return { state: 'offline' }
  79. end
  80. # if all seads are used
  81. waiting_count = Chat.waiting_chat_count(id)
  82. if waiting_count >= max_queue
  83. return {
  84. state: 'no_seats_available',
  85. queue: waiting_count,
  86. }
  87. end
  88. # seads are available
  89. { state: 'online' }
  90. end
  91. =begin
  92. get available chat_ids for agent
  93. chat_ids = Chat.agent_active_chat_ids(User.find(123))
  94. returns
  95. [1, 2, 3]
  96. =end
  97. def self.agent_active_chat_ids(user)
  98. return [] if user.preferences[:chat].blank?
  99. return [] if user.preferences[:chat][:active].blank?
  100. chat_ids = []
  101. user.preferences[:chat][:active].each do |chat_id, state|
  102. next if state != 'on'
  103. chat_ids.push chat_id.to_i
  104. end
  105. return [] if chat_ids.blank?
  106. chat_ids
  107. end
  108. =begin
  109. get current agent state
  110. Chat.agent_state(123)
  111. returns
  112. {
  113. state: 'chat_disabled'
  114. }
  115. {
  116. waiting_chat_count: 1,
  117. waiting_chat_session_list: [
  118. {
  119. id: 17,
  120. chat_id: 1,
  121. session_id: "81e58184385aabe92508eb9233200b5e",
  122. name: "",
  123. state: "waiting",
  124. user_id: nil,
  125. preferences: {
  126. "url: "http://localhost:3000/chat.html",
  127. "participants: ["70332537618180"],
  128. "remote_ip: nil,
  129. "geo_ip: nil,
  130. "dns_name: nil,
  131. },
  132. updated_by_id: nil,
  133. created_by_id: nil,
  134. created_at: Thu, 02 May 2019 10:10:45 UTC +00:00,
  135. updated_at: Thu, 02 May 2019 10:10:45 UTC +00:00},
  136. }
  137. ],
  138. running_chat_count: 0,
  139. running_chat_session_list: [],
  140. active_agent_count: 2,
  141. active_agent_ids: [1, 2],
  142. seads_available: 5,
  143. seads_total: 15,
  144. active: true, # agent is available for chats
  145. assets: { ...related assets... },
  146. }
  147. =end
  148. def self.agent_state(user_id)
  149. return { state: 'chat_disabled' } if !Setting.get('chat')
  150. current_user = User.lookup(id: user_id)
  151. return { error: "No such user with id: #{user_id}" } if !current_user
  152. chat_ids = agent_active_chat_ids(current_user)
  153. assets = {}
  154. Chat.where(active: true).each do |chat|
  155. assets = chat.assets(assets)
  156. end
  157. active_agent_ids = []
  158. active_agents(chat_ids).each do |user|
  159. active_agent_ids.push user.id
  160. assets = user.assets(assets)
  161. end
  162. running_chat_session_list_local = running_chat_session_list(chat_ids)
  163. running_chat_session_list_local.each do |session|
  164. next if !session['user_id']
  165. user = User.lookup(id: session['user_id'])
  166. next if !user
  167. assets = user.assets(assets)
  168. end
  169. {
  170. waiting_chat_count: waiting_chat_count(chat_ids),
  171. waiting_chat_session_list: waiting_chat_session_list(chat_ids),
  172. running_chat_count: running_chat_count(chat_ids),
  173. running_chat_session_list: running_chat_session_list_local,
  174. active_agent_count: active_agent_count(chat_ids),
  175. active_agent_ids: active_agent_ids,
  176. seads_available: seads_available(chat_ids),
  177. seads_total: seads_total(chat_ids),
  178. active: Chat::Agent.state(user_id),
  179. assets: assets,
  180. }
  181. end
  182. =begin
  183. check if agent is available for chat_ids
  184. chat_ids = Chat.agent_active_chat?(User.find(123), [1, 2])
  185. returns
  186. true|false
  187. =end
  188. def self.agent_active_chat?(user, chat_ids)
  189. return true if user.preferences[:chat].blank?
  190. return true if user.preferences[:chat][:active].blank?
  191. chat_ids.each do |chat_id|
  192. return true if user.preferences[:chat][:active][chat_id] == 'on' || user.preferences[:chat][:active][chat_id.to_s] == 'on'
  193. end
  194. false
  195. end
  196. =begin
  197. list all active sessins by user_id
  198. Chat.agent_state_with_sessions(123)
  199. returns
  200. the same as Chat.agent_state(123) but with the following addition
  201. active_sessions: [
  202. {
  203. id: 19,
  204. chat_id: 1,
  205. session_id: "f28b2704e381c668c9b59215e9481310",
  206. name: "",
  207. state: "running",
  208. user_id: 3,
  209. preferences: {
  210. url: "http://localhost/chat.html",
  211. participants: ["70332475730240", "70332481108980"],
  212. remote_ip: nil,
  213. geo_ip: nil,
  214. dns_name: nil
  215. },
  216. updated_by_id: nil,
  217. created_by_id: nil,
  218. created_at: Thu, 02 May 2019 11:48:25 UTC +00:00,
  219. updated_at: Thu, 02 May 2019 11:48:29 UTC +00:00,
  220. messages: []
  221. }
  222. ]
  223. =end
  224. def self.agent_state_with_sessions(user_id)
  225. return { state: 'chat_disabled' } if !Setting.get('chat')
  226. result = agent_state(user_id)
  227. result[:active_sessions] = Chat::Session.active_chats_by_user_id(user_id)
  228. result
  229. end
  230. =begin
  231. get count if waiting sessions in given chats
  232. Chat.waiting_chat_count(chat_ids)
  233. returns
  234. 123
  235. =end
  236. def self.waiting_chat_count(chat_ids)
  237. Chat::Session.where(state: ['waiting'], chat_id: chat_ids).count
  238. end
  239. def self.waiting_chat_session_list(chat_ids)
  240. sessions = []
  241. Chat::Session.where(state: ['waiting'], chat_id: chat_ids).each do |session|
  242. sessions.push session.attributes
  243. end
  244. sessions
  245. end
  246. =begin
  247. get count running sessions in given chats
  248. Chat.running_chat_count(chat_ids)
  249. returns
  250. 123
  251. =end
  252. def self.running_chat_count(chat_ids)
  253. Chat::Session.where(state: ['running'], chat_id: chat_ids).count
  254. end
  255. def self.running_chat_session_list(chat_ids)
  256. sessions = []
  257. Chat::Session.where(state: ['running'], chat_id: chat_ids).each do |session|
  258. sessions.push session.attributes
  259. end
  260. sessions
  261. end
  262. =begin
  263. get count of active sessions in given chats
  264. Chat.active_chat_count(chat_ids)
  265. returns
  266. 123
  267. =end
  268. def self.active_chat_count(chat_ids)
  269. Chat::Session.where(state: %w[waiting running], chat_id: chat_ids).count
  270. end
  271. =begin
  272. get user agents with concurrent
  273. Chat.available_agents_with_concurrent(chat_ids)
  274. returns
  275. {
  276. 123: 5,
  277. 124: 1,
  278. 125: 2,
  279. }
  280. =end
  281. def self.available_agents_with_concurrent(chat_ids, diff = 2.minutes)
  282. agents = {}
  283. Chat::Agent.where(active: true).where('updated_at > ?', Time.zone.now - diff).each do |record|
  284. user = User.lookup(id: record.updated_by_id)
  285. next if !user
  286. next if !agent_active_chat?(user, chat_ids)
  287. agents[record.updated_by_id] = record.concurrent
  288. end
  289. agents
  290. end
  291. =begin
  292. get count of active agents in given chats
  293. Chat.active_agent_count(chat_ids)
  294. returns
  295. 123
  296. =end
  297. def self.active_agent_count(chat_ids, diff = 2.minutes)
  298. count = 0
  299. Chat::Agent.where(active: true).where('updated_at > ?', Time.zone.now - diff).each do |record|
  300. user = User.lookup(id: record.updated_by_id)
  301. next if !user
  302. next if !agent_active_chat?(user, chat_ids)
  303. count += 1
  304. end
  305. count
  306. end
  307. =begin
  308. get active agents in given chats
  309. Chat.active_agent_count(chat_ids)
  310. returns
  311. [User.find(123), User.find(345)]
  312. =end
  313. def self.active_agents(chat_ids, diff = 2.minutes)
  314. users = []
  315. Chat::Agent.where(active: true).where('updated_at > ?', Time.zone.now - diff).each do |record|
  316. user = User.lookup(id: record.updated_by_id)
  317. next if !user
  318. next if !agent_active_chat?(user, chat_ids)
  319. users.push user
  320. end
  321. users
  322. end
  323. =begin
  324. get count all possible seads (possible chat sessions) in given chats
  325. Chat.seads_total(chat_ids)
  326. returns
  327. 123
  328. =end
  329. def self.seads_total(chat_ids, diff = 2.minutes)
  330. total = 0
  331. available_agents_with_concurrent(chat_ids, diff).each_value do |concurrent|
  332. total += concurrent
  333. end
  334. total
  335. end
  336. =begin
  337. get count all available seads (available chat sessions) in given chats
  338. Chat.seads_available(chat_ids)
  339. returns
  340. 123
  341. =end
  342. def self.seads_available(chat_ids, diff = 2.minutes)
  343. seads_total(chat_ids, diff) - active_chat_count(chat_ids)
  344. end
  345. =begin
  346. broadcast new agent status to all agents
  347. Chat.broadcast_agent_state_update(chat_ids)
  348. optional you can ignore it for dedicated user
  349. Chat.broadcast_agent_state_update(chat_ids, ignore_user_id)
  350. =end
  351. def self.broadcast_agent_state_update(chat_ids, ignore_user_id = nil)
  352. # send broadcast to agents
  353. Chat::Agent.where('active = ? OR updated_at > ?', true, Time.zone.now - 8.hours).each do |item|
  354. next if item.updated_by_id == ignore_user_id
  355. user = User.lookup(id: item.updated_by_id)
  356. next if !user
  357. next if !agent_active_chat?(user, chat_ids)
  358. data = {
  359. event: 'chat_status_agent',
  360. data: Chat.agent_state(item.updated_by_id),
  361. }
  362. Sessions.send_to(item.updated_by_id, data)
  363. end
  364. end
  365. =begin
  366. broadcast new customer queue position to all waiting customers
  367. Chat.broadcast_customer_state_update(chat_id)
  368. =end
  369. def self.broadcast_customer_state_update(chat_id)
  370. # send position update to other waiting sessions
  371. position = 0
  372. Chat::Session.where(state: 'waiting', chat_id: chat_id).order(created_at: :asc).each do |local_chat_session|
  373. position += 1
  374. data = {
  375. event: 'chat_session_queue',
  376. data: {
  377. state: 'queue',
  378. position: position,
  379. session_id: local_chat_session.session_id,
  380. },
  381. }
  382. local_chat_session.send_to_recipients(data)
  383. end
  384. end
  385. =begin
  386. cleanup old chat messages
  387. Chat.cleanup
  388. optional you can put the max oldest chat entries
  389. Chat.cleanup(12.months)
  390. =end
  391. def self.cleanup(diff = 12.months)
  392. Chat::Session.where(state: 'closed').where('updated_at < ?', Time.zone.now - diff).each do |chat_session|
  393. Chat::Message.where(chat_session_id: chat_session.id).delete_all
  394. chat_session.destroy
  395. end
  396. true
  397. end
  398. =begin
  399. close chat sessions where participants are offline
  400. Chat.cleanup_close
  401. optional you can put the max oldest chat sessions as argument
  402. Chat.cleanup_close(5.minutes)
  403. =end
  404. def self.cleanup_close(diff = 5.minutes)
  405. Chat::Session.where.not(state: 'closed').where('updated_at < ?', Time.zone.now - diff).each do |chat_session|
  406. next if chat_session.recipients_active?
  407. chat_session.state = 'closed'
  408. chat_session.save
  409. message = {
  410. event: 'chat_session_closed',
  411. data: {
  412. session_id: chat_session.session_id,
  413. realname: 'System',
  414. },
  415. }
  416. chat_session.send_to_recipients(message)
  417. end
  418. true
  419. end
  420. =begin
  421. check if ip address is blocked for chat
  422. chat = Chat.find(123)
  423. chat.blocked_ip?(ip)
  424. =end
  425. def blocked_ip?(ip)
  426. return false if ip.blank?
  427. return false if block_ip.blank?
  428. ips = block_ip.split(';')
  429. ips.each do |local_ip|
  430. return true if ip == local_ip.strip
  431. return true if ip.match?(/#{local_ip.strip.gsub(/\*/, '.+?')}/)
  432. end
  433. false
  434. end
  435. =begin
  436. check if country is blocked for chat
  437. chat = Chat.find(123)
  438. chat.blocked_country?(ip)
  439. =end
  440. def blocked_country?(ip)
  441. return false if ip.blank?
  442. return false if block_country.blank?
  443. geo_ip = Service::GeoIp.location(ip)
  444. return false if geo_ip.blank?
  445. return false if geo_ip['country_code'].blank?
  446. countries = block_country.split(';')
  447. countries.each do |local_country|
  448. return true if geo_ip['country_code'] == local_country
  449. end
  450. false
  451. end
  452. end