chat.rb 14 KB

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