chat.rb 14 KB

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