• XSS.stack #1 – первый литературный журнал от юзеров форума

Простой Live Chat для поддержки с администрированием в TG

Кодер

WebDev
Забанен
Регистрация
26.10.2022
Сообщения
360
Реакции
272
Гарант сделки
14
Депозит
1.5004 Ł и др.
Пожалуйста, обратите внимание, что пользователь заблокирован
Ы, снова уставший все делал, поэтому просто вот исходы без лишнего текста
Легко интегрировать на любой сайт))
node.js для бекенда, фронт простой html+js

На бекенде вам необходимо указать токен от бота и ваш чат айди
На фронте найти следующую строку и указать в ней свой сервер или домен, а также заменить ws на wss если вы будете использовать SSL

const socket = new WebSocket("ws://localhost:3000");

Небольшая демонстрация
1743311252149.png

1743311285881.png


index.html:
HTML:
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>xss.pro</title>
</head>
<body>
  <div id="SupportChat"></div>
  <script src="./main.js"></script>
</body>
</html>

main.js:

JavaScript:
function initSupportChat(containerId) {
    const container = document.getElementById(containerId);
    if (!container) {
      console.error("Контейнер с id=" + containerId + " не найден!");
      return;
    }

    const styleEl = document.createElement("style");
    styleEl.textContent = `
      #chatToggleButton {
        position: fixed;
        bottom: 20px;
        right: 20px;
        width: 50px;
        height: 50px;
        border-radius: 50%;
        box-shadow: 0 2px 6px rgba(0,0,0,0.3);
        cursor: pointer;
        font-size: 24px;
        display: flex;
        align-items: center;
        justify-content: center;
        background-color: #007bff;
        color: #fff;
        border: none;
        outline: none;
        z-index: 9999;
      }
      #chatToggleButton:hover {
        background-color: #0056b3;
      }
      #chatContainer {
        position: fixed;
        bottom: 80px;
        right: 20px;
        width: 320px;
        max-height: 450px;
        min-height: 450px;
        display: none;
        flex-direction: column;
        z-index: 9999;

        background-color: #f1f1f1;
        border-radius: 10px;
        box-shadow: 0 2px 10px rgba(0,0,0,0.3);
      }
      #chatHeader {
        background-color: #007bff;
        color: #fff;
        padding: 10px;
        border-top-left-radius: 10px;
        border-top-right-radius: 10px;
        font-weight: bold;
        display: flex;
        align-items: center;
        justify-content: space-between;
      }
      .close-btn {
        cursor: pointer;
        background: none;
        border: none;
        font-size: 18px;
        color: #fff;
      }
      .close-btn:hover {
        color: #ddd;
      }
      #chatMessages {
        flex: 1;
        padding: 10px;
        overflow-y: auto;
        background-color: #fff;
        border-bottom: 1px solid #ccc;
      }
      #chatInputArea {
        display: flex;
        flex-direction: column;
        background-color: #f1f1f1;
        border-bottom-left-radius: 10px;
        border-bottom-right-radius: 10px;
        padding: 10px;
      }
      #userMessage {
        resize: none;
        font-family: sans-serif;
        padding: 8px;
        margin-bottom: 8px;
        border-radius: 6px;
        border: 1px solid #ccc;
      }
      #sendButton {
        align-self: flex-end;
        border: none;
        background-color: #007bff;
        color: #fff;
        padding: 8px 16px;
        border-radius: 6px;
        cursor: pointer;
      }
      #sendButton:hover {
        background-color: #0056b3;
      }
      .message-line {
        display: flex;
        flex-direction: column;
        margin-bottom: 10px;
        max-width: 100%;
      }
      .message-author {
        font-size: 0.8em;
        margin-bottom: 2px;
        color: #666;
      }
      .user-message-bubble {
        align-self: flex-start;
        background-color: #e5e5ea;
        color: #333;
        border-radius: 10px;
        padding: 8px 12px;
        box-shadow: 0 1px 3px rgba(0,0,0,0.2);
        word-wrap: break-word;
      }
      .support-message-bubble {
        align-self: flex-end;
        background-color: #007bff;
        color: #fff;
        border-radius: 10px;
        padding: 8px 12px;
        box-shadow: 0 1px 3px rgba(0,0,0,0.2);
        word-wrap: break-word;
      }
    `;
    document.head.appendChild(styleEl);

    container.innerHTML = `
      <button id="chatToggleButton">💬</button>
      <div id="chatContainer">
        <div id="chatHeader">
          <span>Support</span>
          <button class="close-btn" id="chatCloseBtn">&times;</button>
        </div>
        <div id="chatMessages"></div>
        <div id="chatInputArea">
          <textarea id="userMessage" rows="2" placeholder="Your message..."></textarea>
          <button id="sendButton">Send message</button>
        </div>
      </div>
    `;

    let storedUserId = localStorage.getItem("chatUserId");
    if (!storedUserId) {
      storedUserId = "user_" + Math.random().toString(36).substr(2, 9);
      localStorage.setItem("chatUserId", storedUserId);
    }
    let userIP = null;
    fetch('https://api.ipify.org?format=json')
      .then(res => res.json())
      .then(data => {
        userIP = data.ip;
      })
      .catch(err => {
        console.warn(err);
      });

    const socket = new WebSocket("ws://localhost:3000");
    socket.onopen = () => {
      console.log("Ws done");
      socket.send(JSON.stringify({
        type: "init",
        userId: storedUserId
      }));
    };
    socket.onmessage = (event) => {
      const data = JSON.parse(event.data);
      if (data.type === 'chatHistory') {
        const messages = data.data;
        document.getElementById("chatMessages").innerHTML = "";
        messages.forEach(msg => {
          addMessageToChat(msg.from, msg.text);
        });
      }
      if (data.type === 'supportReply') {
        addMessageToChat('support', data.data);
      }
    };
    socket.onclose = () => console.log("Ws close");

    function sendMessage() {
      const input = document.getElementById("userMessage");
      const message = input.value.trim();
      if (!message) return;

      socket.send(JSON.stringify({
        type: 'message',
        data: message,
        ip: userIP || "???"
      }));

      addMessageToChat(storedUserId, message);
      input.value = "";
      input.focus();
    }
    const userMessageInput = document.getElementById("userMessage");
    userMessageInput.addEventListener('keydown', function(e) {
      if (e.key === 'Enter' && !e.shiftKey) {
        e.preventDefault();
        sendMessage();
      }
    });

    document.getElementById("sendButton").onclick = sendMessage;

    function addMessageToChat(author, text) {
      const chatBox = document.getElementById("chatMessages");

      const messageLine = document.createElement("div");
      messageLine.classList.add("message-line");

      const authorLabel = document.createElement("div");
      authorLabel.classList.add("message-author");

      const bubble = document.createElement("div");

      if (author === storedUserId) {
        authorLabel.textContent = "You";
        bubble.classList.add("user-message-bubble");
      } else {
        authorLabel.textContent = "Support";
        bubble.classList.add("support-message-bubble");
        authorLabel.style.textAlign = "right";
      }

      bubble.textContent = text;
      messageLine.appendChild(authorLabel);
      messageLine.appendChild(bubble);
      chatBox.appendChild(messageLine);
      chatBox.scrollTop = chatBox.scrollHeight;
    }

    function toggleChat() {
      const chat = document.getElementById("chatContainer");
      if (chat.style.display === "none" || chat.style.display === "") {
        chat.style.display = "flex";
      } else {
        chat.style.display = "none";
      }
    }

    document.getElementById("chatToggleButton").onclick = toggleChat;
    document.getElementById("chatCloseBtn").onclick = toggleChat;
  }

  initSupportChat("SupportChat");

server.js:

JavaScript:
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const TelegramBot = require('node-telegram-bot-api');
const TELEGRAM_BOT_TOKEN = '7695799359:AAEMY39Fvh3213123123nPOwvW3uG6k';
const TELEGRAM_SUPPORT_CHAT_ID = -1111111;
const bot = new TelegramBot(TELEGRAM_BOT_TOKEN, { polling: true });
const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
let connections = {};
let messagesByUser = {};
let pendingReplies = {};
wss.on('connection', (ws) => {
  ws.on('close', () => {
    if (ws.userId && connections[ws.userId] === ws) {
      delete connections[ws.userId];
    }
  });
  ws.on('message', (message) => {
    try {
      const msgData = JSON.parse(message);
      if (msgData.type === 'init') {
        if (connections[msgData.userId]) {
          connections[msgData.userId].close();
        }
        ws.userId = msgData.userId;
        connections[ws.userId] = ws;
        const existingMessages = messagesByUser[ws.userId] || [];
        ws.send(JSON.stringify({
          type: 'chatHistory',
          data: existingMessages
        }));
      } else if (msgData.type === 'message') {
        const userId = ws.userId || 'unknown_user';
        const userMessage = msgData.data;
        const userIp = msgData.ip || '???';
        if (!messagesByUser[userId]) {
          messagesByUser[userId] = [];
        }
        messagesByUser[userId].push({
          from: userId,
          text: userMessage,
          timestamp: Date.now()
        });
        bot.sendMessage(
          TELEGRAM_SUPPORT_CHAT_ID,
          `Пользователь ${userId} \nIP: ${userIp}\n\n${userMessage}`,
          {
            reply_markup: {
              inline_keyboard: [
                [
                  {
                    text: 'Ответить',
                    callback_data: `reply_${userId}`
                  }
                ]
              ]
            }
          }
        );
      }
    } catch (err) {
      console.error(err);
    }
  });
});
bot.on('callback_query', async (query) => {
  const data = query.data;
  if (data && data.startsWith('reply_')) {
    const parts = data.split('_');
    const userId = parts.slice(1).join('_');
    const askMsg = await bot.sendMessage(query.message.chat.id, `Введите ответ для ${userId}:`, {
      reply_markup: {
        force_reply: true
      }
    });
    pendingReplies[askMsg.message_id] = { userId };
  }
});
bot.on('message', async (msg) => {
  if (
    msg.reply_to_message &&
    pendingReplies[msg.reply_to_message.message_id]
  ) {
    const { userId } = pendingReplies[msg.reply_to_message.message_id];
    const supportReply = msg.text;
    delete pendingReplies[msg.reply_to_message.message_id];
    const ws = connections[userId];
    if (ws && ws.readyState === WebSocket.OPEN) {
      if (!messagesByUser[userId]) {
        messagesByUser[userId] = [];
      }
      messagesByUser[userId].push({
        from: 'support',
        text: supportReply,
        timestamp: Date.now()
      });
      ws.send(JSON.stringify({
        type: 'supportReply',
        data: supportReply
      }));
    } else {
      bot.sendMessage(msg.chat.id, `Клиент ${userId} уже офлайн или не найден`);
    }
  }
});
app.get('/', (req, res) => {
  res.send('All good');
});
const PORT = 3000;
server.listen(PORT, () => {
  console.log(`Started server`);
});
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Пожалуйста, обратите внимание, что пользователь заблокирован
Сокет со временем отваливаться может по этому тебе нужно пинговать клиент и сервер какое то время, не всегда event close срабатывает
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Сокет со временем отваливаться может по этому тебе нужно пинговать клиент и сервер какое то время, не всегда event close срабатывает
Та это чисто вариант под пролив, там долго сессию держать и не нужно(как в моей голове)
Если развивать эту тему, то можно и полноценную панель сделать со сбором статистик, кликов и прочего)
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Та это чисто вариант под пролив, там долго сессию держать и не нужно(как в моей голове)
Если развивать эту тему, то можно и полноценную панель сделать со сбором статистик, кликов и прочего)
Просто сам делаю фигню с сокетами, плюс кластерная система а там пришлось редис прикрутить, потому что юзер и бот могут оказаться в разных кластерах
1743317723157.png

1743317861126.png
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Пожалуйста, обратите внимание, что пользователь заблокирован
Решил пошалить снова ?)) Или заказ?
Ну мне бабло нужно, пока не знаю либо в аренду сдавать либо под %, на край самому пытаться траф лить
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Ну мне бабло нужно, пока не знаю либо в аренду сдавать либо под %, на край самому пытаться траф лить
Ну под процент тяжкое дело , очень много желающих найдется
Если только как-то полностью автоматизировать)
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Ну под процент тяжкое дело , очень много желающих найдется
Если только как-то полностью автоматизировать)
У меня другая проблема так как это вебсокеты сервер куда жрать больше будет, а ракошеливаться на сервер не желаю, а автоматизировать изи у меня там система регистраций и сама база уже сделана как SaaS мне по сути билдер допилить и пару параметров, ну и фичей к боту ибо я с сокетом пока занималься чтоб не отваливалься и лишних ботов не было которые не в сети, плюс это все основано на ноде уже подписанной которая разворачивается у юзера и скорее всего не будет детектов и боты долго держаться будут
 
Пожалуйста, обратите внимание, что пользователь заблокирован
У меня другая проблема так как это вебсокеты сервер куда жрать больше будет, а ракошеливаться на сервер не желаю, а автоматизировать изи у меня там система регистраций и сама база уже сделана как SaaS мне по сути билдер допилить и пару параметров, ну и фичей к боту ибо я с сокетом пока занималься чтоб не отваливалься и лишних ботов не было которые не в сети, плюс это все основано на ноде уже подписанной которая разворачивается у юзера и скорее всего не будет детектов и боты долго держаться будут
Ну тогда в аренду для начала очень хороший вариант, чтобы пополнить запасы и потом на эти запасы расширяться )
 
Пожалуйста, обратите внимание, что пользователь заблокирован
У меня другая проблема так как это вебсокеты сервер куда жрать больше будет, а ракошеливаться на сервер не желаю, а автоматизировать изи у меня там система регистраций и сама база уже сделана как SaaS мне по сути билдер допилить и пару параметров, ну и фичей к боту ибо я с сокетом пока занималься чтоб не отваливалься и лишних ботов не было которые не в сети, плюс это все основано на ноде уже подписанной которая разворачивается у юзера и скорее всего не будет детектов и боты долго держаться будут
обложку какую то в паинте даже нарисовал
1743318410928.png
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Пожалуйста, обратите внимание, что пользователь заблокирован
обложку какую то в паинте даже нарисовал
Нацени)) Через чатгпт прогнал
1743725769936.png
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Пожалуйста, обратите внимание, что пользователь заблокирован
ну такое блин ручками интересней рисовать
Ну просто вот хотел тебе показать!!! Прикольно же
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх