chat.rb 14 KB

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