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: 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);
}

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

```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 scheduleStatisticsReset() {
    // Запланировать первую запись через 2 минуты (120000 мс)
    setTimeout(() => {
        resetStatistics(); // первая запись статистики
        // Устанавливаем интервал на 5 минут (300000 мс) для последующих записей
        setInterval(resetStatistics, 5 * 60 * 1000);
    }, 2 * 60 * 1000); // 120000 мс = 2 минуты
}

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

### Изменения:
1. **Первая запись через 2 минуты**: `setTimeout` теперь подождет 2 минуты перед первой записью статистики.
2. **Запись каждые 5 минут**: `setInterval` будет вызываться каждые 5 минут (300000 мс) после первой записи.

Теперь ваш код будет производить первую запись статистики через 2 минуты после запуска сервера, а затем каждые 5 минут.
User: Какую статистику можно получить из Яндекс метрикс и писать код для ее сбора отдельно излишне?
Assistant: ндекс.Метрика предоставляет множество возможностей для сбора и анализа статистики. Вот некоторые из ключевых метрик и показателей, которые вы можете отслеживать:

### 1. Основные метрики:
- **Посетители**: общее количество уникальных посетителей за выбранный период.
- **Просмотры страниц**: общее количество просмотров страниц.
- **Время на сайте**: среднее время, проведенное пользователями на сайте.
- **Отказы**: процент пользователей, покинувших сайт после просмотра только одной страницы.
- **Трафик**: информация о том, как пользователи попали на сайт (органический поиск, реклама, социальные сети и т.д.).

### 2. Подробные данные о поведении пользователей:
- **События**: отслеживание действий пользователей (клики на кнопках, заполнение форм и т.д.).
- **Целевые действия**: анализ заданных целей (например, оформление заказа, подписка на новости).
- **Тепловые карты**: визуализация того, куда пользователи кликают и как прокручивают страницы.

### 3. Данные о трафике:
- **Источники трафика**: какие каналы (поиск, соцсети, прямой заход и т.д.) приводят к посещениям.
- **География**: информация о том, откуда пришли ваши пользователи (страны, города).

### 4. Анализ устройств и посетителей:
- **Устройства и браузеры**: информация о том, какими устройствами (мобильные, десктопные) и браузерами пользовались посетители.
- **Экранные разрешения**: данные о разрешениях экранов, которые используют ваши пользователи, что может помочь в адаптации дизайна.

### 5. Время и частота посещений:
- **Часы и дни посещений**: когда ваши пользователи чаще всего посещают сайт.
- **Повторные посещения**: информация о том, сколько пользователей возвращаются на сайт.

### Код для сбора статистики:
Если у вас есть необходимость в сборе определенной статистики из Яндекс.Метрики, возможно, вам пригодится использовать API Яндекс.Метрики. Примерный код для получения статистики может выглядеть так:

```javascript
const axios = require('axios');

async function getYaMetricData() {
    const token = 'YOUR_YANDEX_METRIX_TOKEN'; // Ваш API токен
    const counterId = 'YOUR_COUNTER_ID'; // ID счетчика Яндекс.Метрики

    const url = `https://api-metrika.yandex.net/stat/v1/data?ids=${counterId}&metrics=ym:s:visits,ym:s:pageviews&date1=7daysAgo&date2=today`;
    
    try {
        const response = await axios.get(url, {
            headers: {
                'Authorization': `OAuth ${token}`
            }
        });
        console.log(response.data); // Отобразить полученные данные
    } catch (error) {
        console.error('Ошибка при получении данных Яндекс.Метрики:', error);
    }
}

// Пример вызова функции
getYaMetricData();
```

### Заключение:
Яндекс.Метрика предоставляет мощные инструменты для более глубокого анализа и понимания посещаемости вашего сайта. Если ваши потребности в статистике совпадают с функциональностью Яндекс.Метрики, писать отдельный код для ее сбора может быть излишним, так как вы сможете получить множество необходимых метрик с ее помощью.
User: Допустим я собрал статистику за несколько дней
{"date":"2025-01-04","totalRequests":5,"totalConnections":3}
{"date":"2025-01-04","totalRequests":8,"totalConnections":1}
{"date":"2025-01-04","totalRequests":2,"totalConnections":0}

Как ее анализировать? Писать отдельный код или есть инструменты для этого?
Assistant: нализ собранной статистики можно проводить различными способами: с помощью написания собственного кода или с использованием готовых инструментов. Рассмотрим оба подхода.

### 1. Использование собственного кода для анализа

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

- **Суммирование значений**: Собрать общее количество запросов и подключений за определенный период.
- **Выявление закономерностей**: Определить, в какие дни было наибольшее количество запросов или подключений.
- **Визуализация**: Использовать библиотеки для создания графиков (например, Chart.js или D3.js) и визуализировать данные.

Пример простого анализа с использованием JavaScript:

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

const statsFilePath = path.join(__dirname, 'statistics.json');

function analyzeStatistics() {
    fs.readFile(statsFilePath, 'utf8', (err, data) => {
        if (err) {
            console.error('Ошибка при чтении файла статистики:', err);
            return;
        }

        const lines = data.trim().split('\n');
        const results = lines.map(line => JSON.parse(line));
        
        const summary = results.reduce((acc, current) => {
            acc.totalRequests += current.totalRequests;
            acc.totalConnections += current.totalConnections;
            return acc;
        }, { totalRequests: 0, totalConnections: 0 });

        console.log('Общее количество запросов:', summary.totalRequests);
        console.log('Общее количество подключений:', summary.totalConnections);
    });
}

analyzeStatistics();
```

### 2. Использование готовых инструментов

Если вы не хотите писать собственный код, есть множество готовых инструментов и библиотек для анализа и визуализации данных:

#### Таблицы и графики

- **Google Sheets** или **Microsoft Excel**: Импортируйте данные в таблицы и используйте встроенные возможности для анализа и построения графиков. Вы можете легко получить сводные таблицы, графики, визуализации и т.д.
  
- **Системы BI (Business Intelligence)**: Такие инструменты, как **Tableau**, **Power BI** или **Google Data Studio**, позволяют более глубоко анализировать данные и визуализировать полученные результаты. Интеграция с различными источниками данных (включая файлы JSON) делает эти инструменты очень мощными для анализа больших объемов информации.

#### Агрегация и анализа данных

- **Python с библиотеками Pandas и Matplotlib**: Если вы предпочитаете программный анализ, язык Python с библиотеками для обработки данных (например, Pandas) и визуализации (например, Matplotlib) может быть отличным выбором.

Пример на Python:

```python
import json
import pandas as pd
import matplotlib.pyplot as plt

# Загружаем данные
data = []
with open('statistics.json', 'r') as f:
    for line in f:
        data.append(json.loads(line))

# Преобразуем в DataFrame
df = pd.DataFrame(data)

# Суммируем запросы и подключения
summary = df.groupby('date').sum()

print(summary)

# Строим график
summary.plot(kind='bar')
plt.title('Общее количество запросов и подключений')
plt.xlabel('Дата')
plt.ylabel('Количество')
plt.show()
```

### Итог

Выбор между написанием собственного кода и использованием готовых инструментов зависит от ваших навыков и предпочтений. Если у вас есть специфические требования и вам нужно больше контроля над процессом, стоит рассмотреть написание кода. В противном случае, готовые инструменты могут упростить вашу задачу и значительно ускорить процесс анализа данных.
User: как импортировать такой json в excel
Assistant: портировать данные из JSON-файла в Excel можно несколькими способами. Вот несколько основных методов:

### Способ 1: Использование "Получение данных" в Excel

1. **Откройте Excel**.
2. Перейдите на вкладку **"Данные"** в ленте.
3. Нажмите на **"Получить данные"** (или **"Импортировать данные"**) -> **"Из файла"** -> **"Из JSON"**.
4. Выберите ваш JSON-файл и нажмите **"Импортировать"**.
5. Excel откроет **"Проверка данных"**. Здесь вы сможете увидеть структуру данных. Выберите интересующую вас таблицу и нажмите **"Загрузить"**.
6. Данные будут импортированы в новый лист Excel.

### Способ 2: Использование Power Query

1. **Откройте Excel**.
2. Перейдите на вкладку **"Данные"**.
3. Выберите **"Получить данные"** -> **"Из других источников"** -> **"Пустой запрос"**.
4. В редакторе Power Query выберите **"Дополнительно"**.
5. Введите следующий код в редакторе:

    ```m
    let
        Source = Json.Document(File.Contents("C:\path\to\your\file.json")),
        Data = Source
    in
        Data
    ```

   Замените `"C:\path\to\your\file.json"` на путь к вашему файлу JSON.
   
6. Нажмите **"Закрыть и загрузить"**.

### Способ 3: Конвертация JSON в CSV с помощью онлайн сервиса

1. Посетите один из онлайн конвертеров, например [json-csv.com](https://www.json-csv.com/).
2. Выберите или загрузите ваш JSON-файл.
3. Конвертируйте его в формат CSV.
4. Скачайте CSV-файл.
5. Откройте CSV-файл в Excel, выбрав **"Файл"** -> **"Открыть"**.

### Способ 4: Использование Python для преобразования JSON в CSV

Если вам удобен Python, можно использовать его для преобразования JSON в CSV:

```python
import pandas as pd
import json

# Чтение файла JSON
with open('statistics.json') as f:
    data = [json.loads(line) for line in f]

# Преобразование в DataFrame
df = pd.DataFrame(data)

# Сохранение в CSV
df.to_csv('statistics.csv', index=False)
```

1. Убедитесь, что у вас установлен Python и необходимые библиотеки (`pandas`).
2. Запустите скрипт, чтобы преобразовать ваш JSON в CSV.
3. Загрузите полученный CSV-файл в Excel.

### Итог

Вы можете использовать различные методы для импорта JSON в Excel в зависимости от того, какая из процедур вам больше подходит. Наиболее удобным и простым способом может быть использование встроенных функций Excel или Power Query, если у вас последняя версия Office.
User: В редакторе Power Query выберите "Дополнительно".

Где это
Assistant:  Power Query "Дополнительный редактор" называется "Дополнительный интерфейс" или просто "Расширенный редактор". Вот шаги, чтобы попасть туда и использовать его:

### Как открыть "Дополнительный редактор" в Power Query

1. **Откройте Microsoft Excel**.

2. **Перейдите на вкладку "Данные".**
   - Нажмите на кнопку **"Получить данные"**.
   - Выберите **"Из других источников"**.

3. **Создайте новый запрос:**
   - Выберите **"Пустой запрос"**.

4. **Откроется редактор Power Query.**
   - В верхней части окна, на вкладке "Главная", вы увидите различные параметры.

5. **Откройте "Расширенный редактор":**
   - Нажмите на кнопку **"Расширенный редактор"** на вкладке "Главная". 
   - Это откроет интерфейс, где вы можете ввести свои собственные запросы на языке M.

### Пример запроса для загрузки JSON

Когда вы находитесь в "Расширенном редакторе", введите следующий код:

```m
let
    Source = Json.Document(File.Contents("C:\path\to\your\file.json")),
    Data = Source
in
    Data
```

Обязательно замените `"C:\path\to\your\file.json"` на фактический путь к вашему JSON-файлу на компьютере.

### После ввода кода:

1. Нажмите **"Готово"**.
2. Теперь вы увидите данные из вашего JSON-файла в Power Query.
3. Если надо, вы можете отредактировать структуру данных и изменить их при помощи функций Power Query.
4. Чтобы загрузить данные в Excel, нажмите **"Закрыть и загрузить"** в верхнем левом углу.

### Примечание

Если у вас возникли сложности с поиском, убедитесь, что у вас установлена последняя версия Excel, так как интерфейс может немного отличаться в разных версиях. Кроме того, некоторые версии Office могут иметь слегка отличные последовательности действий или названия меню. В последних версиях Office (таких как Office 365) все должно работать так, как описано выше.
User: DataFormat.Error: В конце входного JSON-файла были обнаружены лишние символы.
Сведения:
    Value={
    Position=61
Assistant: , которую вы видите (DataFormat.Error: В конце входного JSON-файла были обнаружены лишние символы), обычно возникает, когда Power Query пытается импортировать данные из файла, но обнаруживает, что формат данных не соответствует ожидаемому. В данном случае это может происходить по нескольким причинам, когда файл JSON невалиден. Рассмотрим возможные источники проблемы и способы ее решения:

### Причины ошибки

1. **Несоответствующий формат JSON**: Убедитесь, что JSON следит строгим правилам формата. Например, если вы пытаетесь загрузить несколько объектов JSON подряд, это может вызвать ошибку. Стандартный JSON должен быть представлен в формате массива или единого объекта.

2. **Лишние символы**: Проверяйте файл на наличие лишних символов или пробелов в конце, что может привести к неправильному анализу.

3. **Неправильная структура JSON**: JSON-объекты должны быть корректно структурированы и не должны содержать ошибок синтаксиса.

### Способы исправления

1. **Используйте массив для нескольких объектов**: Если у вас в файле несколько JSON-объектов:

    ```json
    [
        {"date": "2025-01-04", "totalRequests": 5, "totalConnections": 3},
        {"date": "2025-01-04", "totalRequests": 8, "totalConnections": 1},
        {"date": "2025-01-04", "totalRequests": 2, "totalConnections": 0}
    ]
    ```

   Это требует, чтобы все объекты были заключены в квадратные скобки `[]`, представляя собой массив.

2. **Проверьте файл на наличие лишних символов**: Убедитесь, что нет лишних символов, пробелов или новых строк после последнего JSON-объекта.

3. **Используйте онлайн-валидатор**: Пройдите ваш JSON через [JSONLint](https://jsonlint.com/) или другой валидатор, чтобы убедиться, что JSON является валидным.

### Импортирование исправленного JSON в Power Query

После исправления файла JSON:

1. Повторите попытку загрузить файл в Power Query:
   - Перейдите на **Вкладка "Данные"** > **"Получить данные"** > **"Из файла"** > **"Из JSON"**.
   - Выберите исправленный JSON-файл и нажмите **"Импортировать"**.

2. В Power Query.
   - Если данные корректно загружаются, вы сможете видеть их в редакторе Power Query.
   - Нажмите **"Закрыть и загрузить"** для помещения данных в Excel.

### Заключение

Следуя этим шагам, вы сможете устранить ошибку и успешно импортировать свой JSON-файл в Excel. Важно помнить о правильном формате JSON и проверять файл перед импортом. Если проблема сохраняется, проверьте файл еще раз на наличие неочевидных ошибок.
User: Ну в таком случае в моем код который ты написал для ведения статистики неправильно ее сохраняет в моем проекте. Перепиши 
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 fs = require('fs');
const path = require('path');

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

// Настройки для ограничений по IP
const rateLimiter = new RateLimiterMemory({
    points: 20, // разрешается 10 запросов
    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 для каждого клиента (ограничение на количество запросов по 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;
			
			// Увеличиваем общий счетчик запросов
            stats.totalRequests++;
			
			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');

// Функция для сохранения статистики в файл
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);
}

/*function scheduleDailyReset() {
    // Запланировать первую запись через 2 минуты (120000 мс)
    setTimeout(() => {
        resetStatistics(); // первая запись статистики
        // Устанавливаем интервал на 5 минут (300000 мс) для последующих записей
        setInterval(resetStatistics, 5 * 60 * 1000);
    }, 2 * 60 * 1000); // 120000 мс = 2 минуты
}*/



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

// Очистка старых соединений раз в минуту (по желанию)
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.`);
    }
}

Перепиши. Статистика должна сохраняться так, чтобы файл статистики в итоге имел вид

[
    {"date": "2025-01-04", "totalRequests": 5, "totalConnections": 3},
    {"date": "2025-01-04", "totalRequests": 8, "totalConnections": 1},
    {"date": "2025-01-04", "totalRequests": 2, "totalConnections": 0}
]
Assistant:  изменитьуру сохранения статистики в вашем проекте, так чтобы файл имел формат массива JSON, как указано, необходимо адаптировать функции сохранения и обнуления статистики. Вместо записи каждого объекта в файл по отдельности, мы будем собирать все записи в массив и сохранять его в одном JSON-файле. Вот переписанный код с учетом ваших требований:

```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,
    duration: 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
        });
    }

    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 {
                    const response = { code: 1, message: fullResponse};
                    console.log('Ответ чата: ', response);
                    sendMessage(ws, response);
                }
            } catch (callbackError) {
                console.error('Error processing callback result:', callbackError);
                const response = { code: 2, message: "Произошла ошибка. Попробуйте еще раз." };
                sendMessage(ws, response);
            }
        });
    } catch (error) {
        console.error('Error:', error);
        const response = { code: 2, message: "Произошла ошибка. Попробуйте еще раз."};
        sendMessage(ws, response);
    }
}

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

    // Считываем существующие данные из файла
    let existingData = [];
    if (fs.existsSync(statsFilePath)) {
        const fileContent = fs.readFileSync(statsFilePath, 'utf8');
        existingData = JSON.parse(fileContent); // Можно использовать JSON.parse с массивом
    }

    // Добавляем текущую статистику
    existingData.push({
        date: stats.date,
        totalRequests: stats.totalRequests,
        totalConnections: stats.totalConnections
    });

    // Сохраняем обновленные данные
    fs.writeFileSync(statsFilePath, JSON.stringify(existingData, null, 4)); // Форматируем JSON для удобочитаемости
    console.log('Статистика сохранена:', existingData);
}

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

// Функция для планирования записи статистики
function scheduleStatisticsReset() {
    // Запланировать первую запись через 2 минуты
    setTimeout(() => {
        resetStatistics(); // первая запись статистики
        // Устанавливаем интервал на 5 минут для последующих записей
        setInterval(resetStatistics, 5 * 60 * 1000);
    }, 2 * 60 * 1000); // 120000 мс = 2 минуты
}

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

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

// Проверка источника
function checkOrigin(origin, socket) {
    if (origin !== allowedOrigin) {
        console.log('Недопустимый домен. Соединение разорвано: ', origin);
        socket.close(); 
        return false; 
    }
    return 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.`);
    }
}

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

### Изменения:
1. **Форматирование файловой записи**: Теперь статистика сохраняется в формате массива JSON, где каждый объект представляет собой запись с датой, общим количеством запросов и подключений.
2. **Чтение существующих данных**: При сохранении статистики сначала считывается текущий содержимое файла, затем новая запись добавляется в массив, и весь массив затем сохраняется обратно в файл.
3. **Форматированный вывод**: При записи в файл используется `JSON.stringify(..., null, 4)`, чтобы сделать JSON более читабельным, добавляя отступы.

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

// Функция для вычисления времени до следующего обнуления (в полночь)
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();
Assistant:  переписанный код, в котором статистика будет сохраняться раз в сутки в полночь. Код включает функцию `scheduleDailyReset`, которая планирует обнуление и сохранение статистики, а также сохраняет данные в формате массива JSON.

```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,
    duration: 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
        });
    }

    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 {
                    const response = { code: 1, message: fullResponse};
                    console.log('Ответ чата: ', response);
                    sendMessage(ws, response);
                }
            } catch (callbackError) {
                console.error('Error processing callback result:', callbackError);
                const response = { code: 2, message: "Произошла ошибка. Попробуйте еще раз." };
                sendMessage(ws, response);
            }
        });
    } catch (error) {
        console.error('Error:', error);
        const response = { code: 2, message: "Произошла ошибка. Попробуйте еще раз."};
        sendMessage(ws, response);
    }
}

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

    // Считываем существующие данные из файла
    let existingData = [];
    if (fs.existsSync(statsFilePath)) {
        const fileContent = fs.readFileSync(statsFilePath, 'utf8');
        existingData = JSON.parse(fileContent); // Можно использовать JSON.parse с массивом
    }

    // Добавляем текущую статистику
    existingData.push({
        date: stats.date,
        totalRequests: stats.totalRequests,
        totalConnections: stats.totalConnections
    });

    // Сохраняем обновленные данные
    fs.writeFileSync(statsFilePath, JSON.stringify(existingData, null, 4)); // Форматируем JSON для удобочитаемости
    console.log('Статистика сохранена:', existingData);
}

// Функция для обнуления статистики
function resetStatistics() {
    saveStatistics(); // Сохраняем текущую статистику перед обнулением
    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();

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

// Проверка источника
function checkOrigin(origin, socket) {
    if (origin !== allowedOrigin) {
        console.log('Недопустимый домен. Соединение разорвано: ', origin);
        socket.close(); 
        return false; 
    }
    return 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.`);
    }
}

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

### Изменения:
1. **Сохранение статистики в полночь**: Теперь функция `scheduleDailyReset` устанавливает время следующего обнуления на полночь. В указанное время функция `resetStatistics` будет вызываться для сохранения текущей статистики и обнуления счетчиков.
2. **Формат записи**: Статистика сохраняется в виде массива JSON, как вы указали ранее.

Теперь, когда вы запустите проект, он будет сохранять статистику раз в сутки в полночь и обновлять файл `statistics.json` в указанном формате.
User: Перепиши. Надо чтобы сохранялась статистика только за последние statsdays дней, где statsdays - глобальная переменная.
Assistant:  того чтобы сохранять статистику только за последние `statsDays` дней, мы можем добавить логику, которая будет очищать старые записи в файле, оставляя только последние `statsDays` записей после каждого сохранения. Давайте добавим соответствующие изменения в код.

Вот переписанный код:

```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'; 

// Глобальная переменная для хранения количества дней для статистики
const statsDays = 7; // Измените здесь на нужное количество дней

// Настройки для ограничений по IP
const rateLimiter = new RateLimiterMemory({
    points: 20,
    duration: 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
        });
    }

    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 {
                    const response = { code: 1, message: fullResponse};
                    console.log('Ответ чата: ', response);
                    sendMessage(ws, response);
                }
            } catch (callbackError) {
                console.error('Error processing callback result:', callbackError);
                const response = { code: 2, message: "Произошла ошибка. Попробуйте еще раз." };
                sendMessage(ws, response);
            }
        });
    } catch (error) {
        console.error('Error:', error);
        const response = { code: 2, message: "Произошла ошибка. Попробуйте еще раз."};
        sendMessage(ws, response);
    }
}

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

    // Считываем существующие данные из файла
    let existingData = [];
    if (fs.existsSync(statsFilePath)) {
        const fileContent = fs.readFileSync(statsFilePath, 'utf8');
        existingData = JSON.parse(fileContent);
    }

    // Добавляем текущую статистику
    existingData.push({
        date: stats.date,
        totalRequests: stats.totalRequests,
        totalConnections: stats.totalConnections
    });

    // Оставляем только последние statsDays записей
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - statsDays);
    existingData = existingData.filter(entry => new Date(entry.date) >= cutoffDate);

    // Сохраняем обновленные данные
    fs.writeFileSync(statsFilePath, JSON.stringify(existingData, null, 4)); // Форматируем JSON для удобочитаемости
    console.log('Статистика сохранена:', existingData);
}

// Функция для обнуления статистики
function resetStatistics() {
    saveStatistics(); // Сохраняем текущую статистику перед обнулением
    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();

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

// Проверка источника
function checkOrigin(origin, socket) {
    if (origin !== allowedOrigin) {
        console.log('Недопустимый домен. Соединение разорвано: ', origin);
        socket.close(); 
        return false; 
    }
    return 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.`);
    }
}

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

### Изменения:
1. **Глобальная переменная `statsDays`**: Теперь вы можете установить количество дней, за которые хотите сохранять статистику, изменив значение `statsDays`.
2. **Фильтрация старых записей**: При сохранении статистики добавлена логика, которая удаляет записи, которые старше `statsDays` дней. 

Теперь ваш код будет сохранять только последние `statsDays` записей в файле `statistics.json`, обеспечивая его компактность и актуальность.
User: function saveStatistics() {
    const statsFilePath = path.join(__dirname, 'statistics.json');

    // Считываем существующие данные из файла
    let existingData = [];
    if (fs.existsSync(statsFilePath)) {
        const fileContent = fs.readFileSync(statsFilePath, 'utf8');
        existingData = JSON.parse(fileContent);
    }

    // Добавляем текущую статистику
    existingData.push({
        date: stats.date,
        totalRequests: stats.totalRequests,
        totalConnections: stats.totalConnections
    });

    // Оставляем только последние statsDays записей
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - statsDays);
    existingData = existingData.filter(entry => new Date(entry.date) >= cutoffDate);

    // Сохраняем обновленные данные
    fs.writeFileSync(statsFilePath, JSON.stringify(existingData, null, 4)); // Форматируем JSON для удобочитаемости
    console.log('Статистика сохранена:', existingData);
}

Перепиши, в логах надо выводить только статистику текущего дня а не всю статистику
Assistant:  выводить в логах только статистику текущего дня, мы можем изменить функцию `saveStatistics`, добавив логирование только новых данных, которые сохраняются, вместо логирования всей статистики. Вот обновленный код функции `saveStatistics`:

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

    // Считываем существующие данные из файла
    let existingData = [];
    if (fs.existsSync(statsFilePath)) {
        const fileContent = fs.readFileSync(statsFilePath, 'utf8');
        existingData = JSON.parse(fileContent);
    }

    // Создаем объект с текущей статистикой
    const currentStat = {
        date: stats.date,
        totalRequests: stats.totalRequests,
        totalConnections: stats.totalConnections
    };

    // Добавляем текущую статистику
    existingData.push(currentStat);

    // Оставляем только последние statsDays записей
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - statsDays);
    existingData = existingData.filter(entry => new Date(entry.date) >= cutoffDate);

    // Сохраняем обновленные данные
    fs.writeFileSync(statsFilePath, JSON.stringify(existingData, null, 4)); // Форматируем JSON для удобочитаемости

    // Логируем только текущую статистику
    console.log('Статистика сохранена:', currentStat);
}
```

### Изменения:
1. **Логирование текущей статистики**: Мы создаем объект `currentStat`, который содержит только текущие значения статистики (`totalRequests` и `totalConnections` за сегодня), и логируем именно его.
2. **Общая логика**: Весь процесс сохранения статистики остается без изменений. Мы считываем существующую статистику, добавляем новые данные и фильтруем старые записи.

Теперь, когда вы запустите код, в логе будет отображаться только статистика текущего дня, что позволит лучше контролировать рабочий процесс и выявлять изменения в текущих запросах и подключениях.
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 fs = require('fs');
const path = require('path');

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

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

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

// Количество дней для статистики
const statsDays = 7; // Измените здесь на нужное количество дней

// Создайте 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 для каждого клиента (ограничение на количество запросов по 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;
			
			// Увеличиваем общий счетчик запросов
            stats.totalRequests++;
			
			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);
    }
}

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

    // Считываем существующие данные из файла
    let existingData = [];
    if (fs.existsSync(statsFilePath)) {
        const fileContent = fs.readFileSync(statsFilePath, 'utf8');
        existingData = JSON.parse(fileContent);
    }

    // Создаем объект с текущей статистикой
    const currentStat = {
        date: stats.date,
        totalRequests: stats.totalRequests,
        totalConnections: stats.totalConnections
    };

    // Добавляем текущую статистику
    existingData.push(currentStat);

    // Оставляем только последние statsDays записей
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - statsDays);
    existingData = existingData.filter(entry => new Date(entry.date) >= cutoffDate);

    // Сохраняем обновленные данные
    fs.writeFileSync(statsFilePath, JSON.stringify(existingData, null, 4)); // Форматируем JSON для удобочитаемости

    // Логируем только текущую статистику
    console.log('Статистика сохранена:', currentStat);
}

// Функция для обнуления статистики и сохранения её в файле
function resetStatistics() {
    saveStatistics();
    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);
}*/

function scheduleDailyReset() {
    // Запланировать первую запись через 2 минуты (120000 мс)
    setTimeout(() => {
        resetStatistics(); // первая запись статистики
        // Устанавливаем интервал на 2 минут для последующих записей
        setInterval(resetStatistics, 2 * 60 * 1000);
    }, 2 * 60 * 1000); // 120000 мс = 2 минуты
}

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

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

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

Перепиши. Нужно вынести сбор статистики в отдельный модуль (файл)
Assistant:  того чтобы вынести статистики в отдельный модуль, мы создадим новый файл, который будет содержать функции для сохранения и обнуления статистики. Основной код будет использовать этот модуль. Давайте рассмотрим, как это сделать.

### Шаг 1: Создание модуля для статистики

Создайте новый файл, например, `stats.js`, и поместите в него следующую логику:

```javascript
// stats.js
const fs = require('fs');
const path = require('path');

// Количество дней для статистики
const statsDays = 7; // Можно сделать глобальной переменной, если нужно передавать со стороны

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

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

    // Считываем существующие данные из файла
    let existingData = [];
    if (fs.existsSync(statsFilePath)) {
        const fileContent = fs.readFileSync(statsFilePath, 'utf8');
        existingData = JSON.parse(fileContent);
    }

    // Создаем объект с текущей статистикой
    const currentStat = {
        date: stats.date,
        totalRequests: stats.totalRequests,
        totalConnections: stats.totalConnections
    };

    // Добавляем текущую статистику
    existingData.push(currentStat);

    // Оставляем только последние statsDays записей
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - statsDays);
    existingData = existingData.filter(entry => new Date(entry.date) >= cutoffDate);

    // Сохраняем обновленные данные
    fs.writeFileSync(statsFilePath, JSON.stringify(existingData, null, 4)); // Форматируем JSON для удобочитаемости

    // Логируем только текущую статистику
    console.log('Статистика сохранена:', currentStat);
}

// Функция для обнуления статистики и сохранения её в файле
function resetStatistics() {
    saveStatistics();
    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);
}

// Экспортируем функции и переменные
module.exports = {
    stats,
    scheduleDailyReset,
    resetStatistics,
    saveStatistics,
};
```

### Шаг 2: Изменить основной код

Теперь изменим основной код, чтобы использовать новый модуль статистики, который мы создали.

```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 { stats, scheduleDailyReset, resetStatistics } = require('./stats'); // Импортируем модуль статистики
const fs = require('fs');
const path = require('path');

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

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

// Создайте 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 для каждого клиента (ограничение на количество запросов по Id)
        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 {
                    const response = { code: 1, message: fullResponse};
                    console.log('Ответ чата: ', response);
                    sendMessage(ws, response);
                }
            } catch (callbackError) {
                console.error('Error processing callback result:', callbackError);
                const response = { code: 2, message: "Произошла ошибка. Попробуйте еще раз." };
                sendMessage(ws, response);
            }
        });
    } catch (error) {
        console.error('Error:', error);
        const response = { code: 2, message: "Произошла ошибка. Попробуйте еще раз."};
        sendMessage(ws, response);
    }
}

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

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

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

### Изменения:
1. **Создание `stats.js`**: Весь код, связанный с сохранением и обработкой статистики, был вынесен в отдельный файл (`stats.js`), который теперь можно использовать для работы с данными статистики.
2. **Импорт модуля статистики**: В основном файле была добавлена строка для импорта созданного модуля вместе с его функциями.
3. **Чистота основного кода**: Основной код теперь выглядит чище и понятнее, так как логика работы со статистикой была вынесена в отдельный модуль.
4. **Поддержка глобальных переменных**: Вы можете настроить `statsDays` как глобальную переменную в `stats.js`, если это необходимо сделать динамически.

Теперь ваш проект организован лучше и статическая логика вынесена в отдельный модуль, который может быть легко развиваем и поддерживается отдельно.
User: const fs = require('fs');
const path = require('path');

Эти строки присутствуют в обоих файлах. Это обязательно?
Assistant: