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 { RateLimiterMemory } = require('rate-limiter-flexible');

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

// Настройки для ограничений по IP
const rateLimiter = new RateLimiterMemory({
    points: 20, // разрешается 10 запросов
    duration: 1, // за 1 секунду
	blockDuration: 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'); // Получаем 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 для каждого клиента (ограничение на количество запросов по Id)
      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;
			
			rateLimiter.consume(userIP)
                .then(() => {
					// Используем Bottleneck для управления количеством запросов
					limiters[userId].schedule(() => {
						// Отправка запроса на сервер чата
						return call(messages, ws);
					}).catch(error => {
						//console.error('Rate limit exceeded, message ignored:', error);
						const response = { code: 2, message: "Превышен лимит запросов. Пожалуйста, попробуйте позже." };
						// Отправка ответа клиенту
						sendMessage(ws, response);
					});
				})
                .catch(() => {
                    const response = { code: 2, message: "Вы временно заблокированы за превышение лимита запросов." };
                    // Отправка ответа клиенту
					sendMessage(ws, response);
                });
			
		} catch (error) {
				console.error('Error parsing JSON:', error);
				const response = { code: 2, message: "Произошла ошибка. Попробуйте еще раз."};
				console.log(response);
				// Отправка ответа клиенту
				sendMessage(ws, response);
		}
		
    });

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


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

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

// Очистка старых соединений раз в минуту (по желанию)
setInterval(() => {
  Object.keys(clientActions).forEach(userId => {
    if (!clientActions[userId].connected) {
      delete clientActions[userId]; // Удаляем пользователя из памяти, если он отключен
    }
  });
}, 60000); // Очистка каждые 60 секунд


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

// Отправляет ответ клиенту
function sendMessage(ws, message) {
    if (ws.readyState === WebSocket.OPEN) {
        ws.send(JSON.stringify(message));
    } else {
        console.warn(`Trying to send message to a closed WebSocket connection.`);
    }
}

Нужно добавить сбор статистики. Приложение должно собирать статистику об общем количестве запросов за день. Эти данные нужно сохранять ежедневно для последующего анализа.
Assistant:  добавить сбор статистики об общем количестве запросов за день, предлагаю реализовать следующую логику:

1. Хранить общее количество запросов в памяти.
2. Каждый день обнулять значение счетчика.
3. Сохранять статистику в файл или базу данных для последующего анализа.

Вот пример реализации:

### Шаг 1: Настройка переменных для хранения статистики

Добавьте переменные для хранения общего количества запросов и даты последнего обновления статистики.

```javascript
const stats = {
    totalRequests: 0,
    date: new Date().toISOString().split('T')[0] // Начальная дата - текущая
};
```

### Шаг 2: Обновление статистики при каждом запросе

Каждый раз, когда клиент отправляет сообщение, увеличивайте счетчик запросов.

Измените ваш код внутри обработчика `ws.on('message', ...)`:

```javascript
ws.on('message', (message) => {
    // (ваш существующий код)

    // Увеличиваем общий счетчик запросов
    stats.totalRequests++;

    rateLimiter.consume(userIP)
        .then(() => {
            // (ваш существующий код)
        })
        .catch(() => {
            // (ваш существующий код)
        });
});
```

### Шаг 3: Обнуляем статистику каждый день

Вы можете использовать `setInterval` для проверки даты каждый день и сброса счетчика.

```javascript
setInterval(() => {
    const today = new Date().toISOString().split('T')[0];
    if (today !== stats.date) {
        // Сохраняем статистику в файл
        saveStatistics(stats.totalRequests, stats.date);

        // Обнуляем счетчик и обновляем дату
        stats.totalRequests = 0;
        stats.date = today;
    }
}, 24 * 60 * 60 * 1000); // Проверка каждый день
```

### Шаг 4: Сохранение статистики в файл

Создайте функцию для сохранения статистики в файл. В этом примере мы будем использовать JSON-файл.

```javascript
const fs = require('fs');
const path = require('path');

function saveStatistics(totalRequests, date) {
    const statsFilePath = path.join(__dirname, 'statistics.json');
    const statsData = {
        date: date,
        totalRequests: totalRequests
    };

    fs.appendFile(statsFilePath, JSON.stringify(statsData) + '\n', (err) => {
        if (err) {
            console.error('Ошибка сохранения статистики:', err);
        } else {
            console.log('Статистика сохранена:', statsData);
        }
    });
}
```

### Итоговый код

Теперь ваш код будет управлять сбором статистики запросов, обнулять счетчик каждый день и записывать данные в файл. Полный пример может выглядеть следующим образом:

```javascript
const fs = require('fs');
const path = require('path');

// Добавьте статистику
const stats = {
    totalRequests: 0,
    date: new Date().toISOString().split('T')[0] // Начальная дата - текущая
};

// ... ваш существующий код

ws.on('message', (message) => {
    // (ваш существующий код)
    stats.totalRequests++; // Увеличиваем общий счетчик запросов
    // ...
});

// Обнуляем статистику каждый день
setInterval(() => {
    const today = new Date().toISOString().split('T')[0];
    if (today !== stats.date) {
        saveStatistics(stats.totalRequests, stats.date);
        stats.totalRequests = 0;
        stats.date = today;
    }
}, 24 * 60 * 60 * 1000); // Проверка каждый день

// Функция для сохранения статистики в файл
function saveStatistics(totalRequests, date) {
    const statsFilePath = path.join(__dirname, 'statistics.json');
    const statsData = {
        date: date,
        totalRequests: totalRequests
    };

    fs.appendFile(statsFilePath, JSON.stringify(statsData) + '\n', (err) => {
        if (err) {
            console.error('Ошибка сохранения статистики:', err);
        } else {
            console.log('Статистика сохранена:', statsData);
        }
    });
}

// Остальная часть вашего кода...
```

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

### Шаг 1: Добавление переменной для хранения общего количества подключений

Добавьте переменную для хранения общего количества подключений:

```javascript
const stats = {
    totalRequests: 0,
    totalConnections: 0, // Общее количество подключений
    date: new Date().toISOString().split('T')[0] // Начальная дата - текущая
};
```

### Шаг 2: Увеличение счетчика подключений

Каждый раз, когда новый клиент подключается к вашему серверу WebSocket, увеличьте счетчик подключений в обработчике `wss.on('connection', ...)`:

```javascript
wss.on('connection', (ws, req) => {
    // (ваш существующий код)

    // Увеличиваем общий счетчик подключений
    stats.totalConnections++;
    
    console.log('Client connected with user ID:', userId, 'Домен - ', userHeaders.origin);
    console.log('Общее количество подключений:', stats.totalConnections);

    // (оставшаяся часть кода)
});
```

### Шаг 3: Сохранение статистики

Измените функции сохранения статистики, чтобы также сохранять общее количество подключений:

```javascript
function saveStatistics(totalRequests, totalConnections, date) {
    const statsFilePath = path.join(__dirname, 'statistics.json');
    const statsData = {
        date: date,
        totalRequests: totalRequests,
        totalConnections: totalConnections // Добавление общего количества подключений
    };

    fs.appendFile(statsFilePath, JSON.stringify(statsData) + '\n', (err) => {
        if (err) {
            console.error('Ошибка сохранения статистики:', err);
        } else {
            console.log('Статистика сохранена:', statsData);
        }
    });
}
```

### Шаг 4: Обновление проверки на дату

Обновите периодическую проверку на обнуление счетчиков, чтобы она учитывала общее количество подключений:

```javascript
setInterval(() => {
    const today = new Date().toISOString().split('T')[0];
    if (today !== stats.date) {
        saveStatistics(stats.totalRequests, stats.totalConnections, stats.date);
        
        // Обнуляем счетчики и обновляем дату
        stats.totalRequests = 0;
        stats.totalConnections = 0; // Обнуляем счетчик подключений
        stats.date = today;
    }
}, 24 * 60 * 60 * 1000); // Проверка каждый день
```

### Итоговый код

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

```javascript
const fs = require('fs');
const path = require('path');

// Добавьте статистику
const stats = {
    totalRequests: 0,
    totalConnections: 0, // Общее количество подключений
    date: new Date().toISOString().split('T')[0] // Начальная дата - текущая
};

// Ваш существующий код ...

wss.on('connection', (ws, req) => {
    // (ваш существующий код)
    
    // Увеличиваем общий счетчик подключений
    stats.totalConnections++;
    
    console.log('Client connected with user ID:', userId, 'Домен - ', userHeaders.origin);
    console.log('Общее количество подключений:', stats.totalConnections);
    
    // (оставшаяся часть кода)
});

// Обновление статистики при каждом запросе
ws.on('message', (message) => {
    // (ваш существующий код)
    
    stats.totalRequests++; // Увеличиваем общий счетчик запросов
    // ...
});

// Обнуляем статистику каждый день
setInterval(() => {
    const today = new Date().toISOString().split('T')[0];
    if (today !== stats.date) {
        saveStatistics(stats.totalRequests, stats.totalConnections, stats.date);
        stats.totalRequests = 0;
        stats.totalConnections = 0; // Обнуляем счетчик подключений
        stats.date = today;
    }
}, 24 * 60 * 60 * 1000); // Проверка каждый день

// Функция для сохранения статистики в файл
function saveStatistics(totalRequests, totalConnections, date) {
    const statsFilePath = path.join(__dirname, 'statistics.json');
    const statsData = {
        date: date,
        totalRequests: totalRequests,
        totalConnections: totalConnections // Добавляем общее количество подключений
    };

    fs.appendFile(statsFilePath, JSON.stringify(statsData) + '\n', (err) => {
        if (err) {
            console.error('Ошибка сохранения статистики:', err);
        } else {
            console.log('Статистика сохранена:', statsData);
        }
    });
}

// Остальная часть вашего кода...
```

Теперь ваше приложение будет также собирать статистику по общему количеству подключений, и эта информация будет сохраняться для будущего анализа.
User: // Обнуляем статистику каждый день
setInterval(() => {
    const today = new Date().toISOString().split('T')[0];
    if (today !== stats.date) {
        saveStatistics(stats.totalRequests, stats.totalConnections, stats.date);
        stats.totalRequests = 0;
        stats.totalConnections = 0; // Обнуляем счетчик подключений
        stats.date = today;
    }
}, 24 * 60 * 60 * 1000); // Проверка каждый день

В какое время это происходит?
Assistant: ункция, отвечающая за обнуление статистики, запускается каждые 24 часа с помощью `setInterval`. Однако в текущей реализации, ее начало будет зависеть от времени, когда сервер был запущен.

### Как это работает:
1. **Время запуска**: `setInterval` начинает отсчет с момента, когда ваш Node.js сервер был запущен. То есть, если вы запустите сервер, например, в 15:00, проверка даты будет выполняться каждые 24 часа после этого, и обнуление произойдет каждый день в 15:00.

2. **Проверка времени**: Скрипт сравнивает дату, полученную через `new Date().toISOString().split('T')[0]`, с сохраненной датой (`stats.date`). Если даты не совпадают (то есть наступил новый день), происходит обнуление статистики и сохранение данных.

### Пример системы обнуления
Если сервер запущен в 15:00 1 января:

- Статистика будет сброшена и сохранена в 15:00 2 января.
- Затем следующий сброс будет происходить в 15:00 3 января, и так далее.

### Если необходимо контролировать время обнуления
Если вам нужно обнуление статистики в конкретное время каждого дня (например, в полночь), вы можете использовать `setTimeout` в комбинации с `setInterval` для установки времени первого срабатывания. Пример:

```javascript
function resetStatistics() {
    saveStatistics(stats.totalRequests, stats.totalConnections, stats.date);
    stats.totalRequests = 0;
    stats.totalConnections = 0; // Обнуляем счетчик подключений
    stats.date = new Date().toISOString().split('T')[0]; // Обновляем дату на текущую
}

// Функция для вычисления времени до следующего обнуления (в полночь)
function scheduleDailyReset() {
    const now = new Date();
    const nextReset = new Date();
    nextReset.setHours(24, 0, 0, 0); // Устанавливаем на полночь следующего дня
    
    const delay = nextReset - now;
    setTimeout(() => {
        resetStatistics(); // Обнуляем статистику в полночь
        setInterval(resetStatistics, 24 * 60 * 60 * 1000); // Затем устанавливаем интервал
    }, delay);
}

// Запускаем планировщик
scheduleDailyReset();
```

### Итог
Таким образом, с текущей реализацией обнуление происходит в то время, когда сервер был запущен. Если требуется обнуление в определенное время, можно использовать предложенную функцию планировщика для реализации такого поведения.
User: перепиши весь код с учетом обнуления и сохранения статистики в полночь
Assistant: исанный код с учетом обнуления и сохранения статистики в полночь. Используется `setTimeout` для первого срабатывания, а затем `setInterval` для ежедневного обнуления.

```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 { RateLimiterMemory } = require('rate-limiter-flexible');
const fs = require('fs');
const path = require('path');

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

// Настройки для ограничений по IP
const rateLimiter = new RateLimiterMemory({
    points: 20, // разрешается 20 запросов
    duration: 1, // за 1 секунду
    blockDuration: 60 // время блокировки в секундах
});

// Статистика
const stats = {
    totalRequests: 0,
    totalConnections: 0, // Общее количество подключений
    date: new Date().toISOString().split('T')[0] // Начальная дата - текущая
};

// Создайте WebSocket-сервер
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; // Если отклонено, выходим из обработчика
    }
        
    // Увеличиваем общий счетчик подключений
    stats.totalConnections++;
    console.log('Client connected with user ID:', userId, 'Домен - ', userHeaders.origin);
    console.log('Общее количество подключений:', stats.totalConnections);
        
    // Инициализируем состояние клиента, если его еще нет
    if (!clientActions[userId]) {
        clientActions[userId] = {
            actions: [], // Массив для хранения действий
            connected: true, // Статус подключения
        };

        // Создаем экземпляр Bottleneck для каждого клиента
        limiters[userId] = new Bottleneck({
            maxConcurrent: 1, // Максимальное количество одновременных задач
            minTime: 1000, // Минимальное время между запросами в миллисекундах
            highWater: 0 // Не разрешать в очереди больше 0 запросов
        });
    }

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

            // Увеличиваем общий счетчик запросов
            stats.totalRequests++;

            rateLimiter.consume(userIP)
                .then(() => {
                    limiters[userId].schedule(() => {
                        return call(messages, ws);
                    }).catch(error => {
                        const response = { code: 2, message: "Превышен лимит запросов. Пожалуйста, попробуйте позже." };
                        sendMessage(ws, response);
                    });
                })
                .catch(() => {
                    const response = { code: 2, message: "Вы временно заблокированы за превышение лимита запросов." };
                    sendMessage(ws, response);
                });

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

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

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

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

// Функция для сохранения статистики в файл
function saveStatistics(totalRequests, totalConnections, date) {
    const statsFilePath = path.join(__dirname, 'statistics.json');
    const statsData = {
        date: date,
        totalRequests: totalRequests,
        totalConnections: totalConnections
    };

    fs.appendFile(statsFilePath, JSON.stringify(statsData) + '\n', (err) => {
        if (err) {
            console.error('Ошибка сохранения статистики:', err);
        } else {
            console.log('Статистика сохранена:', statsData);
        }
    });
}

// Функция для обнуления статистики и сохранения её в файле
function resetStatistics() {
    saveStatistics(stats.totalRequests, stats.totalConnections, stats.date);
    stats.totalRequests = 0;
    stats.totalConnections = 0;
    stats.date = new Date().toISOString().split('T')[0]; // Обновляем дату
}

// Функция для вычисления времени до следующего обнуления (в полночь)
function scheduleDailyReset() {
    const now = new Date();
    const nextReset = new Date();
    nextReset.setHours(24, 0, 0, 0); // Устанавливаем на полночь следующего дня
    
    const delay = nextReset - now;
    setTimeout(() => {
        resetStatistics(); // Обнуляем статистику в полночь
        setInterval(resetStatistics, 24 * 60 * 60 * 1000); // Затем устанавливаем интервал
    }, delay);
}

// Запускаем планировщик
scheduleDailyReset();

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

// Отправляет ответ клиенту
function sendMessage(ws, message) {
    if (ws.readyState === WebSocket.OPEN) {
        ws.send(JSON.stringify(message));
    } else {
        console.warn(`Trying to send message to a closed WebSocket connection.`);
    }
}
```

### Изменения и улучшения:
1. **Статистика**:
   - Добавлены переменные для хранения общего количества подключений и запросов.
   - Логика обнуления и сохранения статистики теперь осуществляется в полночь.

2. **Функция сохранения и обнуления**:
   - Функция `saveStatistics` теперь принимает количество подключений.
   - Функция `resetStatistics` обнуляет счета и сохраняет данные.

3. **Находится планировщик на полночь**:
   - Функция `scheduleDailyReset` настраивает первый вызов `resetStatistics` в полночь и продолжает вызывать её ежедневно.

Теперь код полностью поддерживает запросы и подключения статистики, что позволяет вам вести учет и сохранять эти данные для последующего анализа.
User: Почему при подключении не срабатывает console.log('Общее количество подключений:', stats.totalConnections);
Assistant: