User: const { gpt, gptweb } = require("./API/gpt");
const { dalle } = require("./API/dalle");
const { gpt4o } = require("./API/gpt4o");
const { gpt3 } = require("./API/gpt3.5");
const { prompts } = require("./API/gpt-prompts");
const { blackbox } = require("./API/blackbox");
const { qwen } = require("./API/qwen");
const { gemini } = require("./API/gemini");

const WebSocket = require('ws');

const Bottleneck = require('bottleneck');

// Объект для хранения действий и состояния каждого клиента
const clientActions = {};
// Объект для хранения ограничителей по каждому клиенту
const limiters = {}; 
// Замените на ваш домен
const allowedOrigin = 'http://gpt.loc'; 

const wss = new WebSocket.Server({ port: 3000 });

wss.on('connection', (ws, req) => {
	
	const urlParams = new URLSearchParams(req.url.split('?')[1]);
    const userId = urlParams.get('token'); // Получаем UUID из URL
    const userIP = req.socket.remoteAddress;
	const userHeaders = req.headers;

	// Проверяем Origin
    if (!checkOrigin(userHeaders.origin, ws)) {
        return; // Если отклонено, выходим из обработчика
    }
	
	console.log('Client connected with user ID:', userId, 'Домен - ', userHeaders.origin);
	
	// Инициализируем состояние клиента, если его еще нет
    if (!clientActions[userId]) {
      clientActions[userId] = {
        actions: [], // Массив для хранения действий
        connected: true, // Статус подключения
      };
	  
	  // Создаем экземпляр Bottleneck для каждого клиента
      limiters[userId] = new Bottleneck({
          maxConcurrent: 1, // Максимальное количество одновременных задач
          minTime: 1000, // Минимальное время между запросами в миллисекундах
		  highWater: 0 // Не разрешать в очереди больше 0 запросов (игнорировать)
      });
    }

    ws.on('message', (message) => {
		// Проверяем, является ли сообщение экземпляром Buffer
		if (Buffer.isBuffer(message))
			message = message.toString(); 
		
		try {
			// Преобразуем строку JSON обратно в объект
			message = JSON.parse(message); 
			
			//Получаем историю чата из запроса
			messages = message.messages;
			
			// Используем Bottleneck для управления количеством запросов
            limiters[userId].schedule(() => {
                // Отправка запроса на сервер чата
                return call(messages, ws);
            }).catch(error => {
                //console.error('Rate limit exceeded, message ignored:', error);
                const response = { code: 2, message: "Превышен лимит запросов. Пожалуйста, попробуйте позже." };
                
                if (ws.readyState === WebSocket.OPEN) {
                    ws.send(JSON.stringify(response));
                }
            });
			
		} catch (error) {
				console.error('Error parsing JSON:', error);
				const response = { code: 2, message: "Произошла ошибка. Попробуйте еще раз."};
				console.log(response);
				
				if (ws.readyState === WebSocket.OPEN) {
					ws.send(JSON.stringify(response));
				}
		}
		
    });

    ws.on('close', () => {
		clientActions[userId].connected = false; // Обновление статуса подключения
		delete clientActions[userId]; // Удаление состояния клиента
		delete limiters[userId]; // Удаление ограничителя для данного клиента
        console.log('Client ',userId, ' disconnected');
    });
});


async function call(userMessage, ws) {
    try {
        let fullResponse = ""; // Переменная для результата
		
		console.log('Запрос клиента:', userMessage); 
		//throw new Error("Намеренная ошибка для тестирования catch");
        gpt4o({
            messages: userMessage,
            markdown: false
        }, (result) => {
			try {
				if (!result.finish) {
					// Запоминаем только последний ответ чата (он будет полный, промежуточные ответы - это накопления)
					fullResponse = result.message || "";
					//console.log(result);
				} else {
					// Если завершено, выводим полный ответ
					let response = { code: 1, message: fullResponse};
					console.log('Ответ чата: ', response);
					// Отправка ответа клиенту
					if (ws.readyState === WebSocket.OPEN) {
						ws.send(JSON.stringify(response));
					} else {
						console.warn(`Trying to send message to a closed WebSocket connection.`);
					}
				}
			}
			catch (callbackError) {
                console.error('Error processing callback result:', callbackError);
                let response = { code: 2, message: "Произогла ошибка. Попробуйте еще раз." };
                if (ws.readyState === WebSocket.OPEN) {
                    ws.send(JSON.stringify(response));
                }
            }
        });
    } catch (error) {
        console.error('Error:', error);
		let response = { code: 2, message: "Произогла ошибка. Попробуйте еще раз."};
		if (ws.readyState === WebSocket.OPEN) {
			ws.send(JSON.stringify(response));
		} else {
			console.warn(`Trying to send message to a closed WebSocket connection.`);
		}
    }
}

console.log('WebSocket сервер запущен через порт 3000');


function checkOrigin(origin, socket) {
    if (origin !== allowedOrigin) {
        console.log('Недопустимый домен. Соединение разорвано: ', origin);
        socket.close(); // Закрываем соединение, если домен не разрешен
        return false; // Возвращаем false для индикатора отклонения
    }
    return true; // Возвращаем true, если домен разрешен
}

Добавь механизм контроля по IP. Нужно следить чтобы с одного IP не приходило больше чем x запросов, в противном случае нужно ограничить пользователя.
Assistant:  добавления механизма контроля по IP, мы можем создать отдельный объект для отслеживания количества запросов от каждого IP-адреса. Затем мы будем проверять это количество перед обработкой каждого запроса. Если запросов от IP-адреса превышает установленный лимит, мы можем отклонить запросы от этого IP на определённое время.

Вот изменённый код с добавленным механизмом контроля по IP:

```javascript
const { gpt, gptweb } = require("./API/gpt");
const { dalle } = require("./API/dalle");
const { gpt4o } = require("./API/gpt4o");
const { gpt3 } = require("./API/gpt3.5");
const { prompts } = require("./API/gpt-prompts");
const { blackbox } = require("./API/blackbox");
const { qwen } = require("./API/qwen");
const { gemini } = require("./API/gemini");

const WebSocket = require('ws');
const Bottleneck = require('bottleneck');

const clientActions = {};
const limiters = {}; 
const IPRequestCounts = {}; // Объект для хранения счетчиков запросов по IP
const allowedOrigin = 'http://gpt.loc'; 
const MAX_REQUESTS_PER_IP = 10; // Максимальное количество запросов от одного IP
const BLOCK_TIME_MS = 60000; // Время блокировки IP в миллисекундах (60 секунд)

const wss = new WebSocket.Server({ port: 3000 });

wss.on('connection', (ws, req) => {
    const urlParams = new URLSearchParams(req.url.split('?')[1]);
    const userId = urlParams.get('token');
    const userIP = req.socket.remoteAddress;
    const userHeaders = req.headers;
        
    if (!checkOrigin(userHeaders.origin, ws)) {
        return;
    }
        
    console.log('Client connected with user ID:', userId, ' IP:', userIP, ' Domain:', userHeaders.origin);
        
    // Инициализируем состояние клиента, если его еще нет
    if (!clientActions[userId]) {
        clientActions[userId] = {
            actions: [],
            connected: true,
        };
        limiters[userId] = new Bottleneck({
            maxConcurrent: 1,
            minTime: 1000,
            highWater: 0
        });
    }
    
    // Инициализация счетчика для IP
    if (!IPRequestCounts[userIP]) {
        IPRequestCounts[userIP] = { count: 0, blockedUntil: 0 };
    }

    ws.on('message', (message) => {
        if (Buffer.isBuffer(message))
            message = message.toString(); 
                
        try {
            message = JSON.parse(message);
            let messages = message.messages;

            // Проверяем, заблокирован ли IP
            const currentTime = Date.now();
            if (IPRequestCounts[userIP].blockedUntil > currentTime) {
                const response = { code: 2, message: "Вы временно заблокированы за превышение лимита запросов. Пожалуйста, попробуйте позже." };
                if (ws.readyState === WebSocket.OPEN) {
                    ws.send(JSON.stringify(response));
                }
                return; // Прекращаем обработку, если IP заблокирован
            }

            // Увеличиваем счетчик запросов для IP
            IPRequestCounts[userIP].count++;

            // Проверка на превышение лимита запросов
            if (IPRequestCounts[userIP].count > MAX_REQUESTS_PER_IP) {
                IPRequestCounts[userIP].blockedUntil = currentTime + BLOCK_TIME_MS; // Блокируем IP
                const response = { code: 2, message: "Вы временно заблокированы за превышение лимита запросов." };
                if (ws.readyState === WebSocket.OPEN) {
                    ws.send(JSON.stringify(response));
                }
                return; // Прекращаем обработку, если лимит превышен
            }

            // Используем Bottleneck для управления количеством запросов
            limiters[userId].schedule(() => {
                // Отправка запроса на сервер чата
                return call(messages, ws);
            }).catch(error => {
                const response = { code: 2, message: "Превышен лимит запросов. Пожалуйста, попробуйте позже." };
                if (ws.readyState === WebSocket.OPEN) {
                    ws.send(JSON.stringify(response));
                }
            });

        } catch (error) {
            console.error('Error parsing JSON:', error);
            const response = { code: 2, message: "Произошла ошибка. Попробуйте еще раз." };
            if (ws.readyState === WebSocket.OPEN) {
                ws.send(JSON.stringify(response));
            }
        }
    });

    ws.on('close', () => {
        clientActions[userId].connected = false;
        delete clientActions[userId];
        delete limiters[userId];
        console.log('Client ', userId, ' disconnected');
    });
});


async function call(userMessage, ws) {
    try {
        let fullResponse = "";
        console.log('Запрос клиента:', userMessage); 
        gpt4o({
            messages: userMessage,
            markdown: false
        }, (result) => {
            try {
                if (!result.finish) {
                    fullResponse = result.message || "";
                } else {
                    let response = { code: 1, message: fullResponse };
                    console.log('Ответ чата: ', response);
                    if (ws.readyState === WebSocket.OPEN) {
                        ws.send(JSON.stringify(response));
                    }
                }
            } catch (callbackError) {
                console.error('Error processing callback result:', callbackError);
                let response = { code: 2, message: "Произошла ошибка. Попробуйте еще раз." };
                if (ws.readyState === WebSocket.OPEN) {
                    ws.send(JSON.stringify(response));
                }
            }
        });
    } catch (error) {
        console.error('Error:', error);
        let response = { code: 2, message: "Произошла ошибка. Попробуйте еще раз." };
        if (ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify(response));
        }
    }
}

console.log('WebSocket сервер запущен через порт 3000');

function checkOrigin(origin, socket) {
    if (origin !== allowedOrigin) {
        console.log('Недопустимый домен. Соединение разорвано: ', origin);
        socket.close(); 
        return false; 
    }
    return true; 
}
```

### Объяснение изменений:
1. **Добавление объекта `IPRequestCounts`**: Этот объект используется для хранения данных о количестве запросов от каждого IP-адреса и времени, когда этот IP будет разблокирован, если он превышает лимит запросов.

2. **Проверка заблокированных IP**: 
   - Перед обработкой каждого сообщения мы проверяем, заблокирован ли IP-адрес.
   - Если он заблокирован, отправляется ответ с сообщением о временной блокировке.

3. **Увеличение счетчика запросов**: Увеличиваем счетчик запросов для IP и проверяем, не превышает ли он максимальное количество разрешенных запросов.

4. **Блокировка IP**: Если лимит превышен, устанавливается время блокировки, и клиенту отправляется уведомление об этом.

С такой реализацией вы можете контролировать количество запросов с одного IP-адреса и предотвращать злоупотребления.
User: Нужно чтобы лимит устанавливался на промежуток времени. Например, временно блокировать запросы, если клиент отправляет больше 10 запросов в секунду.
Assistant:  установить лимит на количество запросов, отправляемых с одного IP-адреса в течение определенного временного интервала (например, 10 запросов в секунду), можно немного изменить код. Вместо фиксированного счетчика мы будем использовать временной интервал, чтобы предотвратить превышение лимита.

Вот обновленный код с учетом вашего требования:

```javascript
const { gpt, gptweb } = require("./API/gpt");
const { dalle } = require("./API/dalle");
const { gpt4o } = require("./API/gpt4o");
const { gpt3 } = require("./API/gpt3.5");
const { prompts } = require("./API/gpt-prompts");
const { blackbox } = require("./API/blackbox");
const { qwen } = require("./API/qwen");
const { gemini } = require("./API/gemini");

const WebSocket = require('ws');
const Bottleneck = require('bottleneck');

const clientActions = {};
const limiters = {}; 
const IPRequestCounts = {}; // Объект для хранения счетчиков запросов по IP
const allowedOrigin = 'http://gpt.loc'; 
const MAX_REQUESTS_PER_SECOND = 10; // Максимальное количество запросов от одного IP в секунду
const BLOCK_TIME_MS = 60000; // Время блокировки IP в миллисекундах (60 секунд)

const wss = new WebSocket.Server({ port: 3000 });

wss.on('connection', (ws, req) => {
    const urlParams = new URLSearchParams(req.url.split('?')[1]);
    const userId = urlParams.get('token');
    const userIP = req.socket.remoteAddress;
    const userHeaders = req.headers;

    if (!checkOrigin(userHeaders.origin, ws)) {
        return;
    }

    console.log('Client connected with user ID:', userId, ' IP:', userIP, ' Domain:', userHeaders.origin);

    // Инициализируем состояние клиента, если его еще нет
    if (!clientActions[userId]) {
        clientActions[userId] = {
            actions: [],
            connected: true,
        };
        limiters[userId] = new Bottleneck({
            maxConcurrent: 1,
            minTime: 1000,
            highWater: 0
        });
    }

    // Инициализация счетчика для IP
    if (!IPRequestCounts[userIP]) {
        IPRequestCounts[userIP] = { count: 0, lastRequestTime: Date.now(), blockedUntil: 0 };
    }

    ws.on('message', (message) => {
        if (Buffer.isBuffer(message))
            message = message.toString(); 

        try {
            message = JSON.parse(message);
            let messages = message.messages;

            // Проверяем, заблокирован ли IP
            const currentTime = Date.now();
            if (IPRequestCounts[userIP].blockedUntil > currentTime) {
                const response = { code: 2, message: "Вы временно заблокированы за превышение лимита запросов. Пожалуйста, попробуйте позже." };
                if (ws.readyState === WebSocket.OPEN) {
                    ws.send(JSON.stringify(response));
                }
                return; // Прекращаем обработку, если IP заблокирован
            }

            // Обновляем счетчик запросов по времени
            if (currentTime - IPRequestCounts[userIP].lastRequestTime < 1000) {
                IPRequestCounts[userIP].count++;
            } else {
                IPRequestCounts[userIP].count = 1; // Сброс счетчика
            }

            IPRequestCounts[userIP].lastRequestTime = currentTime;

            // Проверка на превышение лимита запросов
            if (IPRequestCounts[userIP].count > MAX_REQUESTS_PER_SECOND) {
                IPRequestCounts[userIP].blockedUntil = currentTime + BLOCK_TIME_MS; // Блокируем IP
                const response = { code: 2, message: "Вы временно заблокированы за превышение лимита запросов." };
                if (ws.readyState === WebSocket.OPEN) {
                    ws.send(JSON.stringify(response));
                }
                return; // Прекращаем обработку, если лимит превышен
            }

            // Используем Bottleneck для управления количеством запросов
            limiters[userId].schedule(() => {
                // Отправка запроса на сервер чата
                return call(messages, ws);
            }).catch(error => {
                const response = { code: 2, message: "Превышен лимит запросов. Пожалуйста, попробуйте позже." };
                if (ws.readyState === WebSocket.OPEN) {
                    ws.send(JSON.stringify(response));
                }
            });

        } catch (error) {
            console.error('Error parsing JSON:', error);
            const response = { code: 2, message: "Произошла ошибка. Попробуйте еще раз." };
            if (ws.readyState === WebSocket.OPEN) {
                ws.send(JSON.stringify(response));
            }
        }
    });

    ws.on('close', () => {
        clientActions[userId].connected = false;
        delete clientActions[userId];
        delete limiters[userId];
        console.log('Client ', userId, ' disconnected');
    });
});

async function call(userMessage, ws) {
    try {
        let fullResponse = "";
        console.log('Запрос клиента:', userMessage);
        gpt4o({
            messages: userMessage,
            markdown: false
        }, (result) => {
            try {
                if (!result.finish) {
                    fullResponse = result.message || "";
                } else {
                    let response = { code: 1, message: fullResponse };
                    console.log('Ответ чата: ', response);
                    if (ws.readyState === WebSocket.OPEN) {
                        ws.send(JSON.stringify(response));
                    }
                }
            } catch (callbackError) {
                console.error('Error processing callback result:', callbackError);
                let response = { code: 2, message: "Произошла ошибка. Попробуйте еще раз." };
                if (ws.readyState === WebSocket.OPEN) {
                    ws.send(JSON.stringify(response));
                }
            }
        });
    } catch (error) {
        console.error('Error:', error);
        let response = { code: 2, message: "Произошла ошибка. Попробуйте еще раз." };
        if (ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify(response));
        }
    }
}

console.log('WebSocket сервер запущен через порт 3000');

function checkOrigin(origin, socket) {
    if (origin !== allowedOrigin) {
        console.log('Недопустимый домен. Соединение разорвано: ', origin);
        socket.close(); 
        return false; 
    }
    return true; 
}
```

### Изменения
1. **Операции с IPRequestCounts**: Мы теперь храним `lastRequestTime`, чтобы отслеживать, когда был сделан последний запрос. Это позволяет нам сбрасывать счетчик, если прошла секунда.

2. **Лимит запросов**: Проверяем, сколько запросов пришло в течение последней секунды. Если их количество превышает `MAX_REQUESTS_PER_SECOND`, блокируем IP на `BLOCK_TIME_MS` миллисекунд.

3. **Сброс счетчика**: Если с момента последнего запроса прошла больше одной секунды, сбрасываем счетчик запросов.

### Пользовательский интерфейс
Теперь ваш сервер будет ограничивать IP-адреса, если они пытаются отправить более 10 запросов в секунду, и заблокирует такие IP-адреса на 60 секунд, что поможет предотвратить злоупотребления.
User: Существуют ли библиотеки для реализации такого механизма?
Assistant: , существует несколько библиотек, которые можно использовать для реализации механизма ограничения по IP и контроля частоты запросов. Вот некоторые из них:

1. **express-rate-limit**:
   - Эта библиотека предназначена для использования с Express.js, но может быть модифицирована для работы с другими фреймворками. Она позволяет устанавливать лимиты на количество запросов, которые может делать клиент за определенный промежуток времени (например, 100 запросов в минуту). Также поддерживает блокировку IP-адресов.
   - GitHub: [express-rate-limit](https://github.com/nfriedly/express-rate-limit)

   Пример использования:
   ```javascript
   const rateLimit = require('express-rate-limit');

   const limiter = rateLimit({
       windowMs: 1000, // 1 секунда
       max: 10, // лимит каждого IP-адреса на 10 запросов в окно
       message: "Слишком много запросов, попробуйте снова позже."
   });

   app.use(limiter);
   ```

2. **rate-limiter-flexible**:
   - Это более универсальная библиотека для ограничения частоты запросов, которая может использоваться как с Express, так и с другими фреймворками или даже без них. Она поддерживает различные хранилища, такие как Redis, MongoDB и другие.
   - GitHub: [rate-limiter-flexible](https://github.com/animus-coop/rate-limiter-flexible)

   Пример использования:
   ```javascript
   const { RateLimiterMemory } = require('rate-limiter-flexible');

   const rateLimiter = new RateLimiterMemory({
       points: 10, // количество разрешенных запросов
       duration: 1, // за 1 секунду
   });

   wss.on('connection', (ws, req) => {
       const userIP = req.socket.remoteAddress;
       
       rateLimiter.consume(userIP)
           .then(() => {
               // обработка запросов
           })
           .catch(() => {
               const response = { code: 2, message: "Слишком много запросов, попробуйте снова позже." };
               if (ws.readyState === WebSocket.OPEN) {
                   ws.send(JSON.stringify(response));
               }
           });
   });
   ```

3. **express-slow-down**:
   - Дополнительная библиотека, которая работает в паре с `express-rate-limit`, чтобы замедлить ответ по IP-адресу вместо полного блокирования после достижения лимита. Это может быть полезно для снижения числа запросов, исходящих от злоумышленников.
   - GitHub: [express-slow-down](https://github.com/nfriedly/express-slow-down)

   Пример использования:
   ```javascript
   const slowDown = require('express-slow-down');

   const speedLimiter = slowDown({
       windowMs: 1000, // 1 секунда
       delayAfter: 10, // замедлить после 10 запросов
       delayMs: 500 // задержка 500 мс
   });

   app.use(speedLimiter);
   ```

### Заключение
Используя одну из вышеперечисленных библиотек, вы можете легко внедрить механизм ограничения по IP и контроля частоты запросов в ваше приложение. В зависимости от использования и ваших предпочтений, вы можете выбрать библиотеку, которая лучше всего соответствует вашим требованиям.
User: Существуют ли аналоги express-slow-down и express-rate-limit для websocet соединений, как в моем коде
Assistant: