(() => { let Tag = new Proxy({}, { get(target, prop) { return (prop => (options = {}) => { if (!(options instanceof Object)) { throw 'Bad options'; } let el = document.createElement(prop); let processNestedObjects = (el, data, name, func = (el, name, data, key) => el[name][key] = data[key]) => { for (let key of Object.keys(data)) { func(el, name, data, key); } }; for (let name of Object.keys(options)) { if (name == 'attributes' && options[name] instanceof Object) { processNestedObjects(el, options[name], name, (el, name, data, key) => el.setAttribute(key, data[key])) continue; } if (name == 'dataset' && options[name] instanceof Object) { processNestedObjects(el, options[name], name); continue; } if (name == 'parent' && options[name] instanceof Element) { options[name].append(el); continue; } if (name == 'children' && options[name] instanceof Array) { processNestedObjects(el, options[name], name, (el, name, data, key) => (data[key] instanceof Element) ? el.append(data[key]) : null); continue; } if (name == 'style' && options[name] instanceof Object) { processNestedObjects(el, options[name], name); continue; } el[name] = options[name]; } return el; })(prop); } }); function htmlEncode(value) { let a = document.createElement('div'); a.innerText = value; return a.innerText; } // Параметры которые берутся из настроек let styleUrl = 'https://api.doctorznaet.online/web/css/chat-bots/main.css'; let name = 'Доктор Знает'; let avatarUrl = 'https://doctorznaet.online/static/logo_small.png'; let apiUrl = 'https://api.doctorznaet.online/chat-bots/widget?id=11'; let botId = '11'; let vkUrl = '#@vkUrl'; let tgUrl = '#@tgUrl'; let recaptchaPublic = 'undefined'; let mainColor = '#1894bf'; // ------------------------------------- if (vkUrl == '#@vkUrl') { vkUrl = null; } if (tgUrl == '#@tgUrl') { tgUrl = null; } if (mainColor == '#@color') { mainColor = null; } if (avatarUrl == '#@avatar') { avatarUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC0AAAAtCAYAAAA6GuKaAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFyGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNi4wLWMwMDIgNzkuMTY0NDYwLCAyMDIwLzA1LzEyLTE2OjA0OjE3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMiAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDIyLTAzLTIzVDEyOjE5OjIxKzAzOjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDIyLTAzLTIzVDEyOjE5OjIxKzAzOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMi0wMy0yM1QxMjoxOToyMSswMzowMCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpmYTZhYmEwNi02MThmLWM2NDItYmJlMC0yNzE2MTViMjAxNjEiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDplYWU2MTA1ZS1jYTEwLTlkNGItOGNjZS05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'; } // Достаем сессию из localStorage // Если ее нет, то создаем let chatId = window.localStorage.getItem('__chat__widget'); if (!chatId) { // Генерируем id чата chatId = ((base = 'QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890') => [...Array(20)] .map(_ => base[Math.random() * base.length | 0]).join('') )(); window.localStorage.setItem('__chat__widget', chatId); } // Достаем историю переписки из localStorage let history = window.localStorage.getItem('__chat__widget_history'); if (!history) { history = []; } else { history = JSON.parse(history); } let styles = Tag.link({ href: styleUrl, rel: 'stylesheet' }); document.head.append(styles); // После загрузки стилей добавляем контейнер чата styles.onload = () => { let fontStyles = Tag.link({ href: 'https://fonts.googleapis.com/css2?family=Mulish:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap', rel: 'stylesheet' }); document.head.append(fontStyles); // Добавляем шрифты fontStyles.onload = () => { console.log('styles loaded'); let firstTime = true; // Клик по кнопке отправить let sendClick = (messageInput, errorBox, messageContainer, buttonsContainer, self, token = null, isEmpty = false, buttonText = null) => function() { messageInput.value = messageInput.value.trim(); let text = buttonText ?? messageInput.value; if (text.length == 0 && !isEmpty) { // Ошибка если сообщение пустое if (errorBox.innerText.indexOf('Нельзя отправить пустое сообщение.') === -1) { errorBox.insertAdjacentHTML('beforeend', ' Нельзя отправить пустое сообщение.'); } return; } buttonsContainer.innerHTML = null; errorBox.innerHTML = null; sendMessage(text).then(response => response.json()).then(response => { // Когда пришел ответ на наше сообщение // Если текст не пришел if (!response.text) { return; } if (!isEmpty) { history.push({ // Добавляем наше сообщение в историю isMy: true, text: text }); } let wrap = messageContainer.parentElement; let pos = wrap.scrollHeight - wrap.clientHeight; let flag = false; // Устанавливаем флажок для корректировки позиции скролла if (pos <= wrap.scrollTop + 20) { flag = true; } history.push({ // Добавляем сообщение бота в историю isMy: false, text: response.text }); window.localStorage.setItem('__chat__widget_history', JSON.stringify(history)); // Сохраняем историю messageContainer.append(Tag.div({ // Добавляем сообщение className: 'chat__message', innerHTML: `
${response.text}
` })); // Добавляем кнопки buttonsContainer.innerHTML = null; for (let button of response.buttons ?? []) { let el = Tag.button({ innerText: button.text, onclick: (buttonsContainer => () => { verifyCaptchaAndSend(false, button.text); buttonsContainer.innerHTML = null; })(buttonsContainer) }); buttonsContainer.append(el); } // Корректируем скролл если нужно if (flag) { wrap.scroll(0, wrap.scrollHeight); } }).then(response => { // Если too many requests if (response && response.status == 429) { if (errorBox.innerText.indexOf('Слишком много запросов, попробуйте позже.') === -1) { errorBox.insertAdjacentHTML('beforeend', ' Слишком много запросов, попробуйте позже.'); } } }); if (!isEmpty) { messageContainer.append(Tag.div({ className: 'chat__message chat__message_my', innerHTML: `
${text}
` })); messageContainer.parentElement.scroll(0, messageContainer.parentElement.scrollHeight); messageInput.value = null; } }; // Отправка сообщения function sendMessage(text) { if (!chatId) { chatId = window.localStorage.getItem('__chat__widget'); if (!chatId) { // Генерируем id чата chatId = ((base = 'QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890') => [...Array(20)] .map(_ => base[Math.random() * base.length | 0]).join('') )(); window.localStorage.setItem('__chat__widget', chatId); } } let data = new FormData(); let payload = { message: { text: text, chat: { id: chatId } } }; data.append('json', JSON.stringify(payload)); return fetch(`${apiUrl}`, { method: 'POST', body: data }); } let verifyCaptchaAndSend = (isEmpty = false, buttonText = null) => { sendClick(messageInput, errorBox, messageContainer, buttonsContainer, sendBtn, null, isEmpty, buttonText)(); } // Создание элемента let chatContainer = Tag.div({ className: 'chat__wrap __display-none', innerHTML: `
${htmlEncode(name)}
` }); // Объявляем элементы let messageInput = chatContainer.querySelector('.chat__input'); let sendBtn = chatContainer.querySelector('.chat__send'); let errorBox = chatContainer.querySelector('.chat__error'); let messageContainer = chatContainer.querySelector('.chat__messages'); let buttonsContainer = chatContainer.querySelector('.chat__buttons'); // Клик по кнопке свернуть чат chatContainer.querySelector('.chat__hide').onclick = (chatContainer => function() { chatContainer.classList.toggle('__display-none'); chatContainer.parentElement.querySelector('.chat__show').classList.toggle('__display-none'); })(chatContainer); // Клик по кнопке закрыть чат /*chatContainer.querySelector('.chat__close').onclick = (chatContainer => function() { chatContainer.parentElement.remove(); })(chatContainer);*/ // Рисуем сообщения из истории (Сообщения которые уже были отправлены) for (let message of history) { messageContainer.append(Tag.div({ className: `chat__message ${message.isMy ? 'chat__message_my' : ''}`, innerHTML: `
${message.text}
` })); } // Клик на enter для отправки messageInput.onkeydown = (sendBtn => function(event) { if (event.key === 'Enter') { sendBtn.click(); event.preventDefault(); } })(sendBtn); // Эвент при вводе messageInput.oninput = (errorBox => function() { if (this.value.length > 300) { // Если длинное сообщение выводим ошибку if (errorBox.innerText.indexOf('Длина сообщения не должна превышать 300 символов.') !== -1) { return; } errorBox.insertAdjacentHTML('beforeend', ' Длина сообщения не должна превышать 300 символов.'); return; } else { // Иначе очищаем поле для ошибок errorBox.innerText = null; } })(errorBox); // Клик по кнопке отправить sendBtn.onclick = () => { verifyCaptchaAndSend(); } let chatWrap = Tag.div({ className: 'chat__popup', innerHTML: ` ` }); if (mainColor) { chatWrap.style.setProperty('--color-main', mainColor); } // Клик по кнопке показать чат let chatBtn = chatWrap.querySelector('.chat__show'); chatBtn.onclick = (chatMessages => () => { // Получаем приветственное сообщение let shouldScroll = false; if (firstTime) { firstTime = false; shouldScroll = true; if (!history || (history instanceof Array && !history.length)) { // Отправляем приветственное сообщение verifyCaptchaAndSend(true); } } chatBtn.classList.toggle('__display-none'); chatContainer.classList.toggle('__display-none'); if (shouldScroll) { chatMessages.scroll(0, chatMessages.scrollHeight); } })(chatContainer.querySelector('.chat__messages-wrap')); // Контейнер для капчи Tag.div({ id: `__ks-widget__${recaptchaPublic}`, parent: document.body, }); chatWrap.append(chatContainer); document.body.append(chatWrap); /*setTimeout((chatBtn => () => { if (window.document.documentElement.clientWidth > 960) { chatBtn.click(); } })(chatBtn), 250);*/ } } })(); console.log(JSON.parse('{"name":"Доктор Знает","avatar":"https://doctorznaet.online/static/logo_small.png","apiUrl":"https://api.doctorznaet.online/chat-bots/widget?id=11","botId":11,"vkUrl":"#@vkUrl","tgUrl":"#@tgUrl","recaptcha":"undefined","color":"#1894bf"}'));