forked from zovos/bot_tg
Compare commits
11 commits
1979ca3193
...
81af64c8b7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81af64c8b7 | ||
|
|
70e7d23064 | ||
| 78b42691b7 | |||
|
|
6f5c6c4809 | ||
| 0f8df91701 | |||
|
|
34ba32fefa | ||
|
|
3f416e9fe0 | ||
|
|
833ec9cf9a | ||
| 5847e0e06a | |||
| 41b5c001dc | |||
|
|
628099b443 |
6 changed files with 1268 additions and 88 deletions
106
DEPLOYMENT.md
Normal file
106
DEPLOYMENT.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# Инструкция по развертыванию бота
|
||||
|
||||
## 🚀 Быстрый старт
|
||||
|
||||
### 1. Клонирование и подготовка
|
||||
```bash
|
||||
git clone <репозиторий>
|
||||
cd bot_tg
|
||||
```
|
||||
|
||||
### 2. Настройка переменных окружения
|
||||
Создайте файл `.env` с необходимыми переменными:
|
||||
```env
|
||||
BOT_TOKEN=your_telegram_bot_token
|
||||
LLAMA_API_URL=http://your-llama-server:8080 # опционально
|
||||
ODDS_API_KEY=your_odds_api_key # опционально для ставок
|
||||
```
|
||||
|
||||
### 3. Запуск через Docker Compose
|
||||
```bash
|
||||
# Сборка и запуск
|
||||
docker compose up --build -d
|
||||
|
||||
# Просмотр логов
|
||||
docker compose logs -f bot
|
||||
|
||||
# Остановка
|
||||
docker compose down
|
||||
```
|
||||
|
||||
## 📁 Структура проекта
|
||||
|
||||
```
|
||||
bot_tg/
|
||||
├── main.py # основной файл бота
|
||||
├── games/
|
||||
│ ├── economy.py # экономическая система
|
||||
│ ├── casino.py # казино
|
||||
│ ├── betting.py # ставки на спорт
|
||||
│ └── ...
|
||||
├── AI/ # ИИ-модули
|
||||
├── maket/ # макеты для мемов
|
||||
├── db/ # базы данных (создаются автоматически)
|
||||
├── Dockerfile
|
||||
├── docker-compose.yaml
|
||||
├── requirements.txt
|
||||
└── .env # переменные окружения
|
||||
```
|
||||
|
||||
## 🗄️ Базы данных
|
||||
|
||||
Автоматически создаются в папке `./db`:
|
||||
- `penis_stats.sqlite3` - статистика игры /penis
|
||||
- `chat_history.sqlite3` - история для ИИ-общения
|
||||
- `polychaetsi_stats.json` - статистика слова "получается"
|
||||
- `bets.sqlite3` - ставки на спорт
|
||||
- `economy.sqlite3` - экономическая система (ЦБ, вклады, кредиты)
|
||||
|
||||
## 🏛️ Экономическая система
|
||||
|
||||
**Основная валюта:** сантиметры (см)
|
||||
|
||||
**Команды экономики:**
|
||||
- `/balance` - баланс и статистика
|
||||
- `/deposit [сумма] [дни]` - открыть вклад
|
||||
- `/loan [сумма] [дни]` - взять кредит
|
||||
- `/transfer @username [сумма]` - перевести деньги
|
||||
- `/cb_stats` - статистика Центрального Банка
|
||||
- `/central_bank` - детальная статистика ЦБ
|
||||
- `/cb_rules` - правила работы ЦБ
|
||||
|
||||
**Центральный Банк:** децентрализованная система с капиталом 1000 см
|
||||
|
||||
## 🐛 Поиск проблем
|
||||
|
||||
### Проверка логов
|
||||
```bash
|
||||
docker compose logs bot
|
||||
```
|
||||
|
||||
### Перезапуск бота
|
||||
```bash
|
||||
docker compose restart bot
|
||||
```
|
||||
|
||||
### Проверка статуса контейнера
|
||||
```bash
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
## ⚠️ Важные моменты
|
||||
|
||||
1. **Все базы данных хранятся в `./db`** - эта папка монтируется в контейнер
|
||||
2. **Бот работает в часовом поясе Europe/Moscow**
|
||||
3. **Центральный Банк полностью автоматический** - никакого ручного управления
|
||||
4. **Ежедневные выплаты за активность** - в 00:00 по МСК
|
||||
5. **Налоги 13%** - автоматически поступают в ЦБ
|
||||
|
||||
## 🔧 Первоначальная настройка
|
||||
|
||||
1. Получите токен бота у @BotFather
|
||||
2. Добавьте бота в чат (если нужен)
|
||||
3. Настройте `.env` файл
|
||||
4. Запустите через `docker compose up --build -d`
|
||||
|
||||
Бот готов к работе! 🎉
|
||||
44
README.md
44
README.md
|
|
@ -37,11 +37,12 @@ docker compose down
|
|||
Для ставок нужен `ODDS_API_KEY` (бесплатно на https://the-odds-api.com).
|
||||
|
||||
Контейнер хранит состояние в `./db`:
|
||||
- `penis_stats.sqlite3`
|
||||
- `chat_history.sqlite3`
|
||||
- `polychaetsi_stats.json`
|
||||
- `bets.sqlite3`
|
||||
- `nude_history.json`
|
||||
- `penis_stats.sqlite3` — статистика игры /penis
|
||||
- `chat_history.sqlite3` — история для ИИ-общения
|
||||
- `polychaetsi_stats.json` — статистика слова "получается"
|
||||
- `bets.sqlite3` — ставки на спорт
|
||||
- `economy.sqlite3` — экономическая система (балансы, вклады, кредиты)
|
||||
- `nude_history.json` — история запросов /nude
|
||||
|
||||
## Команды
|
||||
|
||||
|
|
@ -63,6 +64,39 @@ docker compose down
|
|||
- `/mybets` — мои активные ставки.
|
||||
- `/svodka` — сводка СВО.
|
||||
|
||||
## Экономика ZOV OS 💰
|
||||
|
||||
Основная валюта — сантиметры (см), получаемые из игры `/penis`.
|
||||
|
||||
**Команды экономики:**
|
||||
- `/balance` — показать баланс и статистику
|
||||
- `/deposit <сумма> <дни>` — открыть вклад по ставке ЦБ РФ
|
||||
- `/loan <сумма> <дни>` — взять кредит под 15% годовых
|
||||
- `/transfer @username <сумма>` — перевести деньги (лимит 20% от капитала)
|
||||
- `/cb_stats` — статистика Центрального Банка
|
||||
- `/central_bank` — детальная статистика ЦБ
|
||||
- `/cb_rules` — правила работы ЦБ
|
||||
- `/vanomasa` — полное обнуление всех балансов (админам)
|
||||
|
||||
**Фичи экономики:**
|
||||
- Вклады по ставке ЦБ РФ (7.5% годовых)
|
||||
- Кредиты под 15% годовых
|
||||
- Налог 13% с ежедневного дохода
|
||||
- Автоматическое регулирование инфляции
|
||||
- Фоновый сбор налогов каждый час
|
||||
- Заработок на сообщениях: 0.01 см за каждое сообщение
|
||||
- Ежедневные выплаты в 00:00 по МСК за активность
|
||||
|
||||
**Центральный Банк ZOV OS 🏛️ (децентрализованный):**
|
||||
- Учредительный капитал: 1000 см
|
||||
- Полностью автоматическая работа без вмешательства
|
||||
- Все налоги автоматически поступают в ЦБ
|
||||
- Алгоритмическое регулирование инфляции
|
||||
- Автоматическая эмиссия при дефляции
|
||||
- Автоматическая скупка денег при инфляции
|
||||
- Прозрачная статистика для всех пользователей
|
||||
- Никакого ручного управления - система работает по правилам
|
||||
|
||||
## Макеты и шрифт
|
||||
|
||||
- Шрифт `impact.ttf` лежит в корне.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ services:
|
|||
PENIS_DB_PATH: /db/penis_stats.sqlite3
|
||||
CHAT_HISTORY_DB_PATH: /db/chat_history.sqlite3
|
||||
POLYCHAETSI_STATS_PATH: /db/polychaetsi_stats.json
|
||||
BET_DB_PATH: /db/bets.sqlite3
|
||||
BETS_DB_PATH: /db/bets.sqlite3
|
||||
ECONOMY_DB_PATH: /db/economy.sqlite3
|
||||
LLAMA_API_URL: ${LLAMA_API_URL:-https://mirror.porno4free.ru/zovos-ai/}
|
||||
LLAMA_FORCE_DISABLE_THINKING: ${LLAMA_FORCE_DISABLE_THINKING:-0}
|
||||
LLAMA_LOG_THINKING: ${LLAMA_LOG_THINKING:-1}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import sqlite3
|
|||
import logging
|
||||
|
||||
import config
|
||||
from .economy import economy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -73,13 +74,7 @@ def update_user_length(user_id: int, delta: float) -> float | None:
|
|||
|
||||
|
||||
def play_casino(user_id: int, bet: float) -> str:
|
||||
current = get_user_length(user_id)
|
||||
if current is None:
|
||||
return "Ты ещё ни разу не крутил /penis, братуха. Сначала заведи счёт."
|
||||
|
||||
# Проверяем отрицательный баланс
|
||||
if current <= 0:
|
||||
return "🚫 С кредитом в казино не впускают, братуха. Иди наращивай через /penis."
|
||||
current = economy.get_user_balance(user_id)
|
||||
|
||||
# Проверяем максимум ставки
|
||||
if bet > config.CASINO_MAX_BET:
|
||||
|
|
@ -100,9 +95,11 @@ def play_casino(user_id: int, bet: float) -> str:
|
|||
min_mult, max_mult = MULTIPLIERS[matches]
|
||||
multiplier = round(random.uniform(min_mult, max_mult), 1)
|
||||
winnings = round(bet * multiplier, 1)
|
||||
new_length = update_user_length(user_id, winnings)
|
||||
if new_length is None:
|
||||
return "Ошибка БД. Попробуй позже."
|
||||
|
||||
# Обновляем баланс в экономической системе
|
||||
economy.update_balance(user_id, winnings, 'casino_win', f'Выигрыш в казино x{multiplier}')
|
||||
|
||||
new_balance = economy.get_user_balance(user_id)
|
||||
|
||||
if matches == 3:
|
||||
return (
|
||||
|
|
@ -110,22 +107,22 @@ def play_casino(user_id: int, bet: float) -> str:
|
|||
f"🎉 ДЖЕКПОТ!!! Три одинаковых!\n"
|
||||
f"Множитель: x{multiplier}\n"
|
||||
f"Выигрыш: +{winnings:.1f} см\n"
|
||||
f"📏 Текущий размер: {new_length:.1f} см"
|
||||
f"<EFBFBD> Баланс: {new_balance:.1f} см"
|
||||
)
|
||||
return (
|
||||
f"🎰 {slots_display}\n\n"
|
||||
f"✅ Выигрыш! Две совпали.\n"
|
||||
f"Множитель: x{multiplier}\n"
|
||||
f"Выигрыш: +{winnings:.1f} см\n"
|
||||
f"📏 Текущий размер: {new_length:.1f} см"
|
||||
f"<EFBFBD> Баланс: {new_balance:.1f} см"
|
||||
)
|
||||
else:
|
||||
new_length = update_user_length(user_id, -bet)
|
||||
if new_length is None:
|
||||
return "Ошибка БД. Попробуй позже."
|
||||
# Обновляем баланс в экономической системе
|
||||
economy.update_balance(user_id, -bet, 'casino_loss', f'Проигрыш в казино -{bet} см')
|
||||
new_balance = economy.get_user_balance(user_id)
|
||||
return (
|
||||
f"🎰 {slots_display}\n\n"
|
||||
f"❌ Мимо, братуха.\n"
|
||||
f"Проигрыш: -{bet:.1f} см\n"
|
||||
f"📏 Текущий размер: {new_length:.1f} см"
|
||||
f"<EFBFBD> Баланс: {new_balance:.1f} см"
|
||||
)
|
||||
|
|
|
|||
795
games/economy.py
Normal file
795
games/economy.py
Normal file
|
|
@ -0,0 +1,795 @@
|
|||
import random
|
||||
import sqlite3
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
import requests
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Пути к базам данных
|
||||
ECONOMY_DB_PATH = os.getenv("ECONOMY_DB_PATH", "/db/economy.sqlite3")
|
||||
|
||||
class EconomyManager:
|
||||
def __init__(self):
|
||||
self.init_db()
|
||||
|
||||
def init_db(self):
|
||||
"""Инициализация базы данных экономики"""
|
||||
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Таблица балансов пользователей
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS user_balances (
|
||||
user_id INTEGER PRIMARY KEY,
|
||||
balance REAL DEFAULT 0.0,
|
||||
daily_income REAL DEFAULT 0.0,
|
||||
last_daily_reset DATE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
|
||||
# Таблица вкладов
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS deposits (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER,
|
||||
amount REAL,
|
||||
interest_rate REAL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
matures_at TIMESTAMP,
|
||||
is_active BOOLEAN DEFAULT 1
|
||||
)
|
||||
''')
|
||||
|
||||
# Таблица кредитов
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS loans (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER,
|
||||
amount REAL,
|
||||
interest_rate REAL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
due_at TIMESTAMP,
|
||||
is_repaid BOOLEAN DEFAULT 0
|
||||
)
|
||||
''')
|
||||
|
||||
# Таблица транзакций
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS transactions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
from_user_id INTEGER,
|
||||
to_user_id INTEGER,
|
||||
amount REAL,
|
||||
transaction_type TEXT,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
|
||||
# Таблица налогов
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS taxes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER,
|
||||
amount REAL,
|
||||
tax_date DATE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
|
||||
# Таблица глобальных настроек экономики
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS economy_settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
|
||||
# Таблица ежедневной активности пользователей
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS daily_activity (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER,
|
||||
activity_date DATE,
|
||||
message_count INTEGER DEFAULT 0,
|
||||
last_activity_time TIMESTAMP,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(user_id, activity_date)
|
||||
)
|
||||
''')
|
||||
|
||||
# Таблица Центрального Банка
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS central_bank (
|
||||
id INTEGER PRIMARY KEY DEFAULT 1,
|
||||
capital REAL DEFAULT 1000.0,
|
||||
total_taxes_collected REAL DEFAULT 0.0,
|
||||
total_inflation_adjustments REAL DEFAULT 0.0,
|
||||
total_emissions REAL DEFAULT 0.0,
|
||||
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(id)
|
||||
)
|
||||
''')
|
||||
|
||||
# Кулдауны /penis (длина хранится в user_balances.balance)
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS penis_stats (
|
||||
user_id INTEGER PRIMARY KEY,
|
||||
display_name TEXT NOT NULL DEFAULT '',
|
||||
last_used_ts INTEGER
|
||||
)
|
||||
''')
|
||||
|
||||
# Вставка настроек по умолчанию
|
||||
default_settings = {
|
||||
'central_bank_rate': '7.5', # Ставка ЦБ РФ
|
||||
'deposit_min_amount': '10.0',
|
||||
'deposit_min_days': '7',
|
||||
'loan_max_amount': '100.0',
|
||||
'loan_interest_rate': '15.0',
|
||||
'tax_rate': '13.0',
|
||||
'transfer_limit_percent': '20.0',
|
||||
'inflation_rate': '5.0',
|
||||
'vanomasa_cooldown': '30', # дней между ваномаса
|
||||
'message_reward_rate': '0.01', # см за сообщение
|
||||
'last_activity_payout': '' # дата последней выплаты за активность
|
||||
}
|
||||
|
||||
for key, value in default_settings.items():
|
||||
cursor.execute('INSERT OR IGNORE INTO economy_settings (key, value) VALUES (?, ?)', (key, value))
|
||||
|
||||
# Инициализация Центрального Банка
|
||||
cursor.execute('INSERT OR IGNORE INTO central_bank (id, capital) VALUES (1, 1000.0)')
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def get_user_balance(self, user_id: int) -> float:
|
||||
"""Получить баланс пользователя"""
|
||||
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT balance FROM user_balances WHERE user_id = ?', (user_id,))
|
||||
result = cursor.fetchone()
|
||||
conn.close()
|
||||
return result[0] if result else 0.0
|
||||
|
||||
def update_balance(self, user_id: int, amount: float, transaction_type: str, description: str = ""):
|
||||
"""Обновить баланс пользователя"""
|
||||
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Вставляем или обновляем баланс
|
||||
cursor.execute('''
|
||||
INSERT INTO user_balances (user_id, balance, daily_income, last_daily_reset)
|
||||
VALUES (?, ?, 0.0, date('now'))
|
||||
ON CONFLICT(user_id) DO UPDATE SET balance = balance + ?
|
||||
''', (user_id, amount, amount))
|
||||
|
||||
# Записываем транзакцию
|
||||
cursor.execute('''
|
||||
INSERT INTO transactions (from_user_id, to_user_id, amount, transaction_type, description)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
''', (user_id, user_id, amount, transaction_type, description))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
logger.info(f"Balance updated for user {user_id}: +{amount} ({transaction_type})")
|
||||
|
||||
def transfer_money(self, from_user_id: int, to_user_id: int, amount: float, description: str = "") -> bool:
|
||||
"""Перевод денег между пользователями"""
|
||||
if amount <= 0:
|
||||
return False
|
||||
|
||||
from_balance = self.get_user_balance(from_user_id)
|
||||
transfer_limit = from_balance * 0.2 # 20% от капитала
|
||||
|
||||
if amount > transfer_limit:
|
||||
return False
|
||||
|
||||
if from_balance < amount:
|
||||
return False
|
||||
|
||||
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Обновляем балансы
|
||||
cursor.execute('''
|
||||
UPDATE user_balances SET balance = balance - ? WHERE user_id = ?
|
||||
''', (amount, from_user_id))
|
||||
|
||||
cursor.execute('''
|
||||
INSERT INTO user_balances (user_id, balance, daily_income, last_daily_reset)
|
||||
VALUES (?, ?, 0.0, date('now'))
|
||||
ON CONFLICT(user_id) DO UPDATE SET balance = balance + ?
|
||||
''', (to_user_id, amount, amount))
|
||||
|
||||
# Записываем транзакцию
|
||||
cursor.execute('''
|
||||
INSERT INTO transactions (from_user_id, to_user_id, amount, transaction_type, description)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
''', (from_user_id, to_user_id, amount, 'transfer', description))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
logger.info(f"Transfer: {amount} from user {from_user_id} to user {to_user_id}")
|
||||
return True
|
||||
|
||||
def create_deposit(self, user_id: int, amount: float, days: int) -> bool:
|
||||
"""Создать вклад"""
|
||||
if amount <= 0:
|
||||
return False
|
||||
|
||||
balance = self.get_user_balance(user_id)
|
||||
min_deposit = float(self.get_setting('deposit_min_amount'))
|
||||
|
||||
if amount < min_deposit:
|
||||
return False
|
||||
|
||||
if balance < amount:
|
||||
return False
|
||||
|
||||
central_bank_rate = float(self.get_setting('central_bank_rate'))
|
||||
matures_at = datetime.now() + timedelta(days=days)
|
||||
|
||||
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Списываем деньги со счета пользователя → передаём в ЦБ
|
||||
cursor.execute('UPDATE user_balances SET balance = balance - ? WHERE user_id = ?', (amount, user_id))
|
||||
cursor.execute('''
|
||||
UPDATE central_bank SET
|
||||
capital = capital + ?,
|
||||
last_updated = CURRENT_TIMESTAMP
|
||||
WHERE id = 1
|
||||
''', (amount,))
|
||||
|
||||
# Создаем вклад
|
||||
cursor.execute('''
|
||||
INSERT INTO deposits (user_id, amount, interest_rate, matures_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
''', (user_id, amount, central_bank_rate, matures_at))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
logger.info(f"Deposit created: user {user_id}, amount {amount}, rate {central_bank_rate}%")
|
||||
return True
|
||||
|
||||
def create_loan(self, user_id: int, amount: float, days: int) -> bool:
|
||||
"""Создать кредит"""
|
||||
if amount <= 0:
|
||||
return False
|
||||
|
||||
max_loan = float(self.get_setting('loan_max_amount'))
|
||||
loan_rate = float(self.get_setting('loan_interest_rate'))
|
||||
|
||||
if amount > max_loan:
|
||||
return False
|
||||
|
||||
due_at = datetime.now() + timedelta(days=days)
|
||||
|
||||
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# ЦБ выдаёт кредит: уменьшаем капитал ЦБ, зачисляем пользователю
|
||||
cursor.execute('''
|
||||
UPDATE central_bank SET
|
||||
capital = capital - ?,
|
||||
last_updated = CURRENT_TIMESTAMP
|
||||
WHERE id = 1
|
||||
''', (amount,))
|
||||
cursor.execute('''
|
||||
INSERT INTO user_balances (user_id, balance, daily_income, last_daily_reset)
|
||||
VALUES (?, ?, 0.0, date('now'))
|
||||
ON CONFLICT(user_id) DO UPDATE SET balance = balance + ?
|
||||
''', (user_id, amount, amount))
|
||||
|
||||
# Создаем кредит
|
||||
cursor.execute('''
|
||||
INSERT INTO loans (user_id, amount, interest_rate, due_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
''', (user_id, amount, loan_rate, due_at))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
logger.info(f"Loan created: user {user_id}, amount {amount}, rate {loan_rate}%")
|
||||
return True
|
||||
|
||||
def repay_loan(self, user_id: int, loan_id: int) -> bool:
|
||||
"""Погасить кредит"""
|
||||
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('SELECT amount, interest_rate, is_repaid FROM loans WHERE id = ? AND user_id = ?', (loan_id, user_id))
|
||||
loan = cursor.fetchone()
|
||||
|
||||
if not loan or loan[2]: # Кредит не найден или уже погашен
|
||||
conn.close()
|
||||
return False
|
||||
|
||||
amount, rate, _ = loan
|
||||
total_repayment = amount * (1 + rate / 100)
|
||||
|
||||
balance = self.get_user_balance(user_id)
|
||||
if balance < total_repayment:
|
||||
conn.close()
|
||||
return False
|
||||
|
||||
# Списываем деньги у пользователя → возвращаем в ЦБ
|
||||
cursor.execute('UPDATE user_balances SET balance = balance - ? WHERE user_id = ?', (total_repayment, user_id))
|
||||
cursor.execute('''
|
||||
UPDATE central_bank SET
|
||||
capital = capital + ?,
|
||||
last_updated = CURRENT_TIMESTAMP
|
||||
WHERE id = 1
|
||||
''', (total_repayment,))
|
||||
|
||||
# Отмечаем кредит как погашенный
|
||||
cursor.execute('UPDATE loans SET is_repaid = 1 WHERE id = ?', (loan_id,))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
logger.info(f"Loan repaid: user {user_id}, loan {loan_id}, amount {total_repayment}")
|
||||
return True
|
||||
|
||||
def collect_daily_taxes(self):
|
||||
"""Сбор ежедневных налогов"""
|
||||
tax_rate = float(self.get_setting('tax_rate')) / 100
|
||||
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Получаем всех пользователей с ежедневным доходом
|
||||
cursor.execute('''
|
||||
SELECT user_id, daily_income FROM user_balances
|
||||
WHERE daily_income > 0 AND (last_daily_reset != date('now') OR last_daily_reset IS NULL)
|
||||
''')
|
||||
|
||||
users = cursor.fetchall()
|
||||
|
||||
total_taxes_collected = 0.0
|
||||
|
||||
for user_id, daily_income in users:
|
||||
tax_amount = daily_income * tax_rate
|
||||
if tax_amount > 0:
|
||||
# Списываем налог
|
||||
cursor.execute(
|
||||
'UPDATE user_balances SET balance = balance - ?, daily_income = 0, last_daily_reset = date(\'now\') WHERE user_id = ?',
|
||||
(tax_amount, user_id)
|
||||
)
|
||||
|
||||
# Записываем налог
|
||||
cursor.execute('INSERT INTO taxes (user_id, amount, tax_date) VALUES (?, ?, date("now"))', (user_id, tax_amount))
|
||||
|
||||
total_taxes_collected += tax_amount
|
||||
|
||||
# Переводим собранные налоги в Центральный Банк
|
||||
if total_taxes_collected > 0:
|
||||
cursor.execute('UPDATE central_bank SET capital = capital + ?, total_taxes_collected = total_taxes_collected + ? WHERE id = 1',
|
||||
(total_taxes_collected, total_taxes_collected))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
logger.info(f"Daily taxes collected: {total_taxes_collected:.2f} cm from {len(users)} users")
|
||||
|
||||
def count_message(self, user_id: int):
|
||||
"""Учесть сообщение пользователя"""
|
||||
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
today = datetime.now().strftime('%Y-%m-%d')
|
||||
|
||||
# Вставляем или обновляем счетчик сообщений за сегодня
|
||||
cursor.execute('''
|
||||
INSERT INTO daily_activity (user_id, activity_date, message_count, last_activity_time)
|
||||
VALUES (?, ?, 1, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT(user_id, activity_date)
|
||||
DO UPDATE SET
|
||||
message_count = message_count + 1,
|
||||
last_activity_time = CURRENT_TIMESTAMP
|
||||
''', (user_id, today))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def pay_daily_activity_rewards(self) -> int:
|
||||
"""Начислить вознаграждения за ежедневную активность"""
|
||||
reward_rate = float(self.get_setting('message_reward_rate'))
|
||||
last_payout = self.get_setting('last_activity_payout')
|
||||
today = datetime.now().strftime('%Y-%m-%d')
|
||||
|
||||
# Проверяем, не выплачивали ли уже сегодня
|
||||
if last_payout == today:
|
||||
return 0
|
||||
|
||||
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Получаем всех пользователей с активностью за вчерашний день
|
||||
yesterday = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d')
|
||||
cursor.execute('''
|
||||
SELECT user_id, message_count FROM daily_activity
|
||||
WHERE activity_date = ? AND message_count > 0
|
||||
''', (yesterday,))
|
||||
|
||||
users = cursor.fetchall()
|
||||
total_paid = 0
|
||||
|
||||
for user_id, message_count in users:
|
||||
reward = message_count * reward_rate
|
||||
if reward > 0:
|
||||
# Обновляем баланс
|
||||
cursor.execute('''
|
||||
INSERT INTO user_balances (user_id, balance, daily_income, last_daily_reset)
|
||||
VALUES (?, ?, 0.0, date('now'))
|
||||
ON CONFLICT(user_id) DO UPDATE SET
|
||||
balance = balance + ?,
|
||||
daily_income = daily_income + ?
|
||||
''', (user_id, reward, reward, reward))
|
||||
|
||||
# Записываем транзакцию
|
||||
cursor.execute('''
|
||||
INSERT INTO transactions (from_user_id, to_user_id, amount, transaction_type, description)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
''', (user_id, user_id, reward, 'activity_reward', f'Вознаграждение за {message_count} сообщений'))
|
||||
|
||||
total_paid += reward
|
||||
|
||||
# Обновляем дату последней выплаты
|
||||
cursor.execute('UPDATE economy_settings SET value = ? WHERE key = ?', (today, 'last_activity_payout'))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
logger.info(f"Activity rewards paid: {total_paid:.2f} cm to {len(users)} users")
|
||||
return len(users)
|
||||
|
||||
def get_central_bank_stats(self) -> Dict:
|
||||
"""Получить статистику Центрального Банка"""
|
||||
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('SELECT capital, total_taxes_collected, total_inflation_adjustments, total_emissions FROM central_bank WHERE id = 1')
|
||||
result = cursor.fetchone()
|
||||
|
||||
conn.close()
|
||||
|
||||
if result:
|
||||
return {
|
||||
'capital': result[0],
|
||||
'total_taxes_collected': result[1],
|
||||
'total_inflation_adjustments': result[2],
|
||||
'total_emissions': result[3]
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'capital': 1000.0,
|
||||
'total_taxes_collected': 0.0,
|
||||
'total_inflation_adjustments': 0.0,
|
||||
'total_emissions': 0.0
|
||||
}
|
||||
|
||||
def update_central_bank(self, capital_change: float, transaction_type: str, description: str = ""):
|
||||
"""Обновить капитал Центрального Банка"""
|
||||
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Обновляем капитал
|
||||
cursor.execute('''
|
||||
UPDATE central_bank SET
|
||||
capital = capital + ?,
|
||||
last_updated = CURRENT_TIMESTAMP
|
||||
WHERE id = 1
|
||||
''', (capital_change,))
|
||||
|
||||
# Обновляем соответствующую статистику
|
||||
if transaction_type == 'tax':
|
||||
cursor.execute('UPDATE central_bank SET total_taxes_collected = total_taxes_collected + ? WHERE id = 1', (abs(capital_change),))
|
||||
elif transaction_type == 'inflation':
|
||||
cursor.execute('UPDATE central_bank SET total_inflation_adjustments = total_inflation_adjustments + ? WHERE id = 1', (abs(capital_change),))
|
||||
elif transaction_type == 'emission':
|
||||
cursor.execute('UPDATE central_bank SET total_emissions = total_emissions + ? WHERE id = 1', (abs(capital_change),))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
logger.info(f"Central bank updated: {capital_change} ({transaction_type})")
|
||||
|
||||
def emit_money(self, amount: float, reason: str = "") -> bool:
|
||||
"""Эмиссия денег - добавление денег в систему из ЦБ"""
|
||||
cb_stats = self.get_central_bank_stats()
|
||||
|
||||
if cb_stats['capital'] < amount:
|
||||
return False
|
||||
|
||||
# Уменьшаем капитал ЦБ
|
||||
self.update_central_bank(-amount, 'emission', reason)
|
||||
|
||||
# Здесь можно добавить логику распределения денег
|
||||
# Например, пропорционально текущим балансам или равными долями
|
||||
|
||||
logger.info(f"Money emitted: {amount} cm ({reason})")
|
||||
return True
|
||||
|
||||
def vanomasa(self) -> bool:
|
||||
"""Полное обнуление всех балансов"""
|
||||
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Проверяем кулдаун
|
||||
cursor.execute('SELECT value FROM economy_settings WHERE key = "last_vanomasa"')
|
||||
last_vanomasa = cursor.fetchone()
|
||||
|
||||
if last_vanomasa:
|
||||
last_date = datetime.strptime(last_vanomasa[0], '%Y-%m-%d')
|
||||
cooldown_days = int(self.get_setting('vanomasa_cooldown'))
|
||||
if datetime.now() - last_date < timedelta(days=cooldown_days):
|
||||
conn.close()
|
||||
return False
|
||||
|
||||
# Обнуляем все балансы
|
||||
cursor.execute('UPDATE user_balances SET balance = 0, daily_income = 0')
|
||||
|
||||
# Записываем дату ваномаса
|
||||
cursor.execute('INSERT OR REPLACE INTO economy_settings (key, value, updated_at) VALUES ("last_vanomasa", date("now"), CURRENT_TIMESTAMP)')
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
logger.warning("VANOMASA: All balances reset to zero!")
|
||||
return True
|
||||
|
||||
def get_setting(self, key: str) -> str:
|
||||
"""Получить настройку экономики"""
|
||||
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT value FROM economy_settings WHERE key = ?', (key,))
|
||||
result = cursor.fetchone()
|
||||
conn.close()
|
||||
return result[0] if result else '0'
|
||||
|
||||
def update_setting(self, key: str, value: str):
|
||||
"""Обновить настройку экономики"""
|
||||
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('INSERT OR REPLACE INTO economy_settings (key, value, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP)', (key, value))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def get_user_stats(self, user_id: int) -> Dict:
|
||||
"""Получить статистику пользователя"""
|
||||
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Баланс
|
||||
cursor.execute('SELECT balance FROM user_balances WHERE user_id = ?', (user_id,))
|
||||
row = cursor.fetchone()
|
||||
balance = row[0] if row else 0.0
|
||||
|
||||
# Активные вклады
|
||||
cursor.execute('SELECT COUNT(*), SUM(amount) FROM deposits WHERE user_id = ? AND is_active = 1', (user_id,))
|
||||
deposits = cursor.fetchone()
|
||||
|
||||
# Активные кредиты
|
||||
cursor.execute('SELECT COUNT(*), SUM(amount) FROM loans WHERE user_id = ? AND is_repaid = 0', (user_id,))
|
||||
loans = cursor.fetchone()
|
||||
|
||||
# Получаем статистику сообщений за сегодня
|
||||
today = datetime.now().strftime('%Y-%m-%d')
|
||||
cursor.execute('SELECT message_count FROM daily_activity WHERE user_id = ? AND activity_date = ?', (user_id, today))
|
||||
today_messages = cursor.fetchone()
|
||||
|
||||
# Получаем статистику сообщений за вчера
|
||||
yesterday = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d')
|
||||
cursor.execute('SELECT message_count FROM daily_activity WHERE user_id = ? AND activity_date = ?', (user_id, yesterday))
|
||||
yesterday_messages = cursor.fetchone()
|
||||
|
||||
conn.close()
|
||||
|
||||
return {
|
||||
'balance': balance,
|
||||
'active_deposits_count': deposits[0] or 0,
|
||||
'active_deposits_sum': deposits[1] or 0.0,
|
||||
'active_loans_count': loans[0] or 0,
|
||||
'active_loans_sum': loans[1] or 0.0,
|
||||
'today_messages': today_messages[0] if today_messages else 0,
|
||||
'yesterday_messages': yesterday_messages[0] if yesterday_messages else 0
|
||||
}
|
||||
|
||||
def regulate_inflation(self):
|
||||
"""Регулирование инфляции через Центральный Банк"""
|
||||
inflation_rate = float(self.get_setting('inflation_rate'))
|
||||
total_supply = 0.0
|
||||
user_count = 0
|
||||
|
||||
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Получаем общую массу денег и количество пользователей
|
||||
cursor.execute('SELECT SUM(balance), COUNT(*) FROM user_balances')
|
||||
result = cursor.fetchone()
|
||||
if result[0]:
|
||||
total_supply = result[0]
|
||||
user_count = result[1]
|
||||
|
||||
# Получаем статистику ЦБ
|
||||
cursor.execute('SELECT capital FROM central_bank WHERE id = 1')
|
||||
cb_result = cursor.fetchone()
|
||||
cb_capital = cb_result[0] if cb_result else 1000.0
|
||||
|
||||
conn.close()
|
||||
|
||||
# Если инфляция высокая, ЦБ может скупать деньги с рынка
|
||||
if inflation_rate > 10.0 and total_supply > 1000 and cb_capital > 100:
|
||||
# ЦБ скупает деньги для борьбы с инфляцией
|
||||
buyback_amount = min(total_supply * 0.05, cb_capital * 0.1) # 5% от массы или 10% от капитала ЦБ
|
||||
|
||||
# Уменьшаем капитал ЦБ (скупка денег)
|
||||
self.update_central_bank(-buyback_amount, 'inflation', f'Anti-inflation measures: buying back {buyback_amount:.2f} cm')
|
||||
|
||||
# Увеличиваем налоги для дополнительной борьбы с инфляцией
|
||||
new_tax_rate = min(20.0, 13.0 + (inflation_rate - 10.0) * 0.5)
|
||||
self.update_setting('tax_rate', str(new_tax_rate))
|
||||
|
||||
logger.info(f"Inflation regulation: CB bought back {buyback_amount:.2f} cm, tax rate increased to {new_tax_rate}%")
|
||||
|
||||
# Если дефляция (низкая инфляция) и много денег в ЦБ, можно делать эмиссию
|
||||
elif inflation_rate < 2.0 and total_supply < 500 and cb_capital > 200:
|
||||
emission_amount = cb_capital * 0.05 # 5% от капитала ЦБ
|
||||
|
||||
# Эмиссия денег через ЦБ
|
||||
self.update_central_bank(-emission_amount, 'emission', f'Economic stimulus: emitting {emission_amount:.2f} cm')
|
||||
|
||||
# Уменьшаем налоги для стимуляции экономики
|
||||
new_tax_rate = max(10.0, 13.0 - (2.0 - inflation_rate) * 1.5)
|
||||
self.update_setting('tax_rate', str(new_tax_rate))
|
||||
|
||||
logger.info(f"Economic stimulus: CB emitted {emission_amount:.2f} cm, tax rate decreased to {new_tax_rate}%")
|
||||
|
||||
def play_penis(self, user_id: int, display_name: str, now_ts: int,
|
||||
start_length: float, cooldown_seconds: int,
|
||||
min_delta: float, max_delta: float) -> dict:
|
||||
"""
|
||||
Возвращает dict:
|
||||
на кулдауне: {'allowed': False, 'left_seconds': N}
|
||||
успех: {'allowed': True, 'new_balance': X, 'delta': Y, 'sign': 1 или -1}
|
||||
"""
|
||||
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
||||
try:
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Текущий баланс (он же «длина») — стартуем с start_length для новых
|
||||
cursor.execute('SELECT balance FROM user_balances WHERE user_id = ?', (user_id,))
|
||||
row = cursor.fetchone()
|
||||
current_balance = row['balance'] if row else start_length
|
||||
|
||||
# Кулдаун из penis_stats
|
||||
cursor.execute('SELECT last_used_ts, display_name FROM penis_stats WHERE user_id = ?', (user_id,))
|
||||
ps_row = cursor.fetchone()
|
||||
last_used_ts = ps_row['last_used_ts'] if ps_row else None
|
||||
|
||||
if last_used_ts is not None:
|
||||
next_ts = int(last_used_ts) + cooldown_seconds
|
||||
if now_ts < next_ts:
|
||||
if ps_row and ps_row['display_name'] != display_name:
|
||||
cursor.execute(
|
||||
'UPDATE penis_stats SET display_name = ? WHERE user_id = ?',
|
||||
(display_name, user_id)
|
||||
)
|
||||
conn.commit()
|
||||
return {'allowed': False, 'left_seconds': next_ts - now_ts}
|
||||
|
||||
delta = round(random.uniform(min_delta, max_delta), 1)
|
||||
sign = random.choice([-1, 1])
|
||||
new_balance = round(max(0.1, current_balance + sign * delta), 1)
|
||||
|
||||
# Обновляем или создаём запись в user_balances
|
||||
cursor.execute('''
|
||||
INSERT INTO user_balances (user_id, balance, daily_income, last_daily_reset)
|
||||
VALUES (?, ?, 0.0, date('now'))
|
||||
ON CONFLICT(user_id) DO UPDATE SET balance = ?
|
||||
''', (user_id, new_balance, new_balance))
|
||||
|
||||
sign_label = f'+{delta:.1f}' if sign > 0 else f'-{delta:.1f}'
|
||||
cursor.execute('''
|
||||
INSERT INTO transactions (from_user_id, to_user_id, amount, transaction_type, description)
|
||||
VALUES (?, ?, ?, 'penis_game', ?)
|
||||
''', (user_id, user_id, sign * delta, f'Игра /penis: {sign_label} см'))
|
||||
|
||||
# Обновляем кулдаун
|
||||
if ps_row is None:
|
||||
cursor.execute(
|
||||
'INSERT INTO penis_stats (user_id, display_name, last_used_ts) VALUES (?, ?, ?)',
|
||||
(user_id, display_name, now_ts)
|
||||
)
|
||||
else:
|
||||
cursor.execute(
|
||||
'UPDATE penis_stats SET display_name = ?, last_used_ts = ? WHERE user_id = ?',
|
||||
(display_name, now_ts, user_id)
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
return {'allowed': True, 'new_balance': new_balance, 'delta': delta, 'sign': sign}
|
||||
except Exception:
|
||||
conn.rollback()
|
||||
raise
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def get_penis_top(self, limit: int) -> list:
|
||||
"""Топ пользователей по балансу (только игравшие в /penis)."""
|
||||
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
SELECT ub.user_id, ps.display_name, ub.balance
|
||||
FROM user_balances ub
|
||||
INNER JOIN penis_stats ps ON ub.user_id = ps.user_id
|
||||
ORDER BY ub.balance DESC, ub.user_id ASC
|
||||
LIMIT ?
|
||||
''', (limit,))
|
||||
rows = cursor.fetchall()
|
||||
conn.close()
|
||||
return [{'user_id': r['user_id'], 'display_name': r['display_name'], 'balance': r['balance']}
|
||||
for r in rows]
|
||||
|
||||
def process_matured_deposits(self) -> int:
|
||||
"""Выплачивает созревшие вклады из капитала ЦБ. Возвращает кол-во выплат."""
|
||||
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
||||
cursor = conn.cursor()
|
||||
now = datetime.now().isoformat()
|
||||
|
||||
cursor.execute('''
|
||||
SELECT id, user_id, amount, interest_rate
|
||||
FROM deposits
|
||||
WHERE is_active = 1 AND matures_at <= ?
|
||||
''', (now,))
|
||||
matured = cursor.fetchall()
|
||||
|
||||
paid = 0
|
||||
for dep_id, user_id, amount, rate in matured:
|
||||
payout = round(amount * (1 + rate / 100 * 1 / 365 * 30), 2) # ~месячный доход
|
||||
|
||||
# ЦБ выплачивает пользователю основную сумму + проценты
|
||||
cursor.execute('''
|
||||
INSERT INTO user_balances (user_id, balance, daily_income, last_daily_reset)
|
||||
VALUES (?, ?, 0.0, date('now'))
|
||||
ON CONFLICT(user_id) DO UPDATE SET balance = balance + ?
|
||||
''', (user_id, payout, payout))
|
||||
cursor.execute('''
|
||||
UPDATE central_bank SET
|
||||
capital = capital - ?,
|
||||
last_updated = CURRENT_TIMESTAMP
|
||||
WHERE id = 1
|
||||
''', (payout,))
|
||||
cursor.execute('UPDATE deposits SET is_active = 0 WHERE id = ?', (dep_id,))
|
||||
|
||||
cursor.execute('''
|
||||
INSERT INTO transactions (from_user_id, to_user_id, amount, transaction_type, description)
|
||||
VALUES (0, ?, ?, 'deposit_payout', ?)
|
||||
''', (user_id, payout, f'Выплата вклада #{dep_id}: {amount:.1f} + проценты = {payout:.2f} см'))
|
||||
|
||||
paid += 1
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
if paid:
|
||||
logger.info(f"Matured deposits paid out: {paid}")
|
||||
return paid
|
||||
|
||||
|
||||
# Глобальный экземпляр
|
||||
economy = EconomyManager()
|
||||
375
main.py
375
main.py
|
|
@ -39,6 +39,7 @@ from games.betting import (
|
|||
resolve_match_outcome,
|
||||
settle_bets,
|
||||
)
|
||||
from games.economy import economy
|
||||
from zparser import get_military_data
|
||||
import config
|
||||
|
||||
|
|
@ -729,61 +730,29 @@ def play_penis(user_id: int, user_name: str | None = None, now: datetime | None
|
|||
display_name = user_name.strip() if user_name and user_name.strip() else f"user_{user_id}"
|
||||
|
||||
try:
|
||||
with sqlite3.connect(config.PENIS_DB_PATH) as conn:
|
||||
conn.row_factory = sqlite3.Row
|
||||
row = conn.execute(
|
||||
"SELECT length, last_used_ts, display_name FROM penis_stats WHERE user_id = ?",
|
||||
(user_id,),
|
||||
).fetchone()
|
||||
|
||||
if row is None:
|
||||
current_length = config.PENIS_START_LENGTH
|
||||
last_used_ts = None
|
||||
else:
|
||||
current_length = float(row["length"])
|
||||
last_used_ts = row["last_used_ts"]
|
||||
stored_name = row["display_name"]
|
||||
if not user_name and isinstance(stored_name, str) and stored_name.strip():
|
||||
display_name = stored_name.strip()
|
||||
|
||||
if last_used_ts is not None:
|
||||
next_ts = int(last_used_ts) + config.PENIS_COOLDOWN_SECONDS
|
||||
if now_ts < next_ts:
|
||||
if row is not None and row["display_name"] != display_name:
|
||||
conn.execute(
|
||||
"UPDATE penis_stats SET display_name = ? WHERE user_id = ?",
|
||||
(display_name, user_id),
|
||||
result = economy.play_penis(
|
||||
user_id, display_name, now_ts,
|
||||
config.PENIS_START_LENGTH, config.PENIS_COOLDOWN_SECONDS,
|
||||
config.PENIS_MIN_DELTA, config.PENIS_MAX_DELTA,
|
||||
)
|
||||
left_seconds = next_ts - now_ts
|
||||
hours = left_seconds // 3600
|
||||
minutes = (left_seconds % 3600) // 60
|
||||
conn.commit()
|
||||
return False, f"Сегодня уже кидал. Попробуй через {hours} ч {minutes} мин."
|
||||
|
||||
delta = round(random.uniform(config.PENIS_MIN_DELTA, config.PENIS_MAX_DELTA), 1)
|
||||
sign = random.choice([-1, 1])
|
||||
new_length = round(max(0.1, current_length + sign * delta), 1)
|
||||
|
||||
if row is None:
|
||||
conn.execute(
|
||||
"INSERT INTO penis_stats(user_id, display_name, length, last_used_ts) VALUES(?, ?, ?, ?)",
|
||||
(user_id, display_name, new_length, now_ts),
|
||||
)
|
||||
else:
|
||||
conn.execute(
|
||||
"UPDATE penis_stats SET display_name = ?, length = ?, last_used_ts = ? WHERE user_id = ?",
|
||||
(display_name, new_length, now_ts, user_id),
|
||||
)
|
||||
conn.commit()
|
||||
except sqlite3.Error:
|
||||
except Exception:
|
||||
logger.exception("Failed to process /penis")
|
||||
return False, "Ошибка БД. Попробуй позже."
|
||||
|
||||
if not result['allowed']:
|
||||
left = result['left_seconds']
|
||||
hours = left // 3600
|
||||
minutes = (left % 3600) // 60
|
||||
return False, f"Сегодня уже кидал. Попробуй через {hours} ч {minutes} мин."
|
||||
|
||||
sign = result['sign']
|
||||
delta = result['delta']
|
||||
new_balance = result['new_balance']
|
||||
sign_text = "+" if sign > 0 else "-"
|
||||
return (
|
||||
True,
|
||||
f"📏 Изменение: {sign_text}{delta:.1f} см\n"
|
||||
f"Текущая длина: {new_length:.1f} см\n"
|
||||
f"Текущая длина: {new_balance:.1f} см\n"
|
||||
"Следующая попытка через 24 часа.",
|
||||
)
|
||||
|
||||
|
|
@ -796,18 +765,8 @@ def format_penis_user_name(user: types.User) -> str:
|
|||
|
||||
def build_penis_top(limit: int = config.PENIS_TOP_LIMIT) -> str:
|
||||
try:
|
||||
with sqlite3.connect(config.PENIS_DB_PATH) as conn:
|
||||
conn.row_factory = sqlite3.Row
|
||||
rows = conn.execute(
|
||||
"""
|
||||
SELECT user_id, display_name, length
|
||||
FROM penis_stats
|
||||
ORDER BY length DESC, user_id ASC
|
||||
LIMIT ?
|
||||
""",
|
||||
(limit,),
|
||||
).fetchall()
|
||||
except sqlite3.Error:
|
||||
rows = economy.get_penis_top(limit)
|
||||
except Exception:
|
||||
logger.exception("Failed to build /top_penis")
|
||||
return "Не удалось загрузить топ. Ошибка БД."
|
||||
|
||||
|
|
@ -818,11 +777,10 @@ def build_penis_top(limit: int = config.PENIS_TOP_LIMIT) -> str:
|
|||
lines = ["🏆 Топ по длине:"]
|
||||
for index, row in enumerate(rows, start=1):
|
||||
user_id = int(row["user_id"])
|
||||
length = float(row["length"])
|
||||
name = row["display_name"] if isinstance(row["display_name"], str) else f"user_{user_id}"
|
||||
name = name.strip() if name.strip() else f"user_{user_id}"
|
||||
balance = float(row["balance"])
|
||||
name = str(row["display_name"]).strip() or f"user_{user_id}"
|
||||
prefix = medals.get(index, f"{index}.")
|
||||
lines.append(f"{prefix} {name} — {length:.1f} см")
|
||||
lines.append(f"{prefix} {name} — {balance:.1f} см")
|
||||
return "\n".join(lines)
|
||||
|
||||
def _load_polychaetsi_stats() -> dict:
|
||||
|
|
@ -1201,6 +1159,11 @@ _autoreply_disabled_chats: set[int] = set()
|
|||
async def handle_keywords(message: Message):
|
||||
if not message.text or message.text.startswith('/'):
|
||||
return
|
||||
|
||||
# Считаем сообщение для экономической системы
|
||||
if message.from_user:
|
||||
await asyncio.to_thread(economy.count_message, message.from_user.id)
|
||||
|
||||
text_lower = message.text.lower()
|
||||
chat_id = message.chat.id
|
||||
user_name = (message.from_user.first_name or "кент") if message.from_user else "кент"
|
||||
|
|
@ -1256,6 +1219,244 @@ async def handle_penis_casino_cmd(message: Message):
|
|||
result = await asyncio.to_thread(play_casino, message.from_user.id, bet)
|
||||
await message.reply(result, parse_mode=None)
|
||||
|
||||
# --- Экономические команды ---
|
||||
|
||||
async def handle_balance_cmd(message: Message):
|
||||
"""Показать баланс пользователя"""
|
||||
if not message.from_user:
|
||||
await message.answer("Команда доступна только пользователям.")
|
||||
return
|
||||
|
||||
balance = economy.get_user_balance(message.from_user.id)
|
||||
stats = await asyncio.to_thread(economy.get_user_stats, message.from_user.id)
|
||||
cb_stats = await asyncio.to_thread(economy.get_central_bank_stats)
|
||||
|
||||
text = f"""💰 **Твой баланс:** {balance:.1f} см
|
||||
|
||||
📊 **Статистика:**
|
||||
• Активные вклады: {stats['active_deposits_count']} шт. ({stats['active_deposits_sum']:.1f} см)
|
||||
• Активные кредиты: {stats['active_loans_count']} шт. ({stats['active_loans_sum']:.1f} см)
|
||||
• Сообщений сегодня: {stats['today_messages']} шт.
|
||||
• Сообщений вчера: {stats['yesterday_messages']} шт.
|
||||
• Ожидаемый доход: {stats['yesterday_messages'] * 0.01:.2f} см
|
||||
|
||||
🏛️ **Центральный Банк:**
|
||||
• Капитал ЦБ: {cb_stats['capital']:.1f} см
|
||||
• Собрано налогов: {cb_stats['total_taxes_collected']:.1f} см
|
||||
|
||||
💡 **Команды экономики:**
|
||||
/balance - показать баланс
|
||||
/deposit [сумма] [дни] - открыть вклад
|
||||
/loans - показать кредиты
|
||||
/loan [сумма] [дни] - взять кредит
|
||||
/repay [ID] - погасить кредит
|
||||
/transfer @username [сумма] - перевести деньги
|
||||
/cb_stats - статистика ЦБ
|
||||
/vanomasa - полное обнуление (для админов)
|
||||
|
||||
💬 **Заработок на сообщениях:**
|
||||
0.01 см за каждое сообщение
|
||||
Выплата в 00:00 по МСК
|
||||
"""
|
||||
await message.answer(text, parse_mode=None)
|
||||
|
||||
async def handle_deposit_cmd(message: Message):
|
||||
"""Открыть вклад"""
|
||||
if not message.from_user:
|
||||
await message.answer("Команда доступна только пользователям.")
|
||||
return
|
||||
|
||||
parts = message.text.split(maxsplit=2)
|
||||
if len(parts) < 3:
|
||||
await message.reply("Формат: /deposit [сумма] [дни]\nПример: /deposit 50 30", parse_mode=None)
|
||||
return
|
||||
|
||||
try:
|
||||
amount = float(parts[1].replace(",", "."))
|
||||
days = int(parts[2])
|
||||
except ValueError:
|
||||
await message.reply("Сумма должна быть числом, дни - целым числом.", parse_mode=None)
|
||||
return
|
||||
|
||||
if amount <= 0 or days <= 0:
|
||||
await message.reply("Сумма и дни должны быть положительными.", parse_mode=None)
|
||||
return
|
||||
|
||||
success = await asyncio.to_thread(economy.create_deposit, message.from_user.id, amount, days)
|
||||
if success:
|
||||
rate = economy.get_setting('central_bank_rate')
|
||||
await message.reply(f"✅ Вклад открыт!\n💰 Сумма: {amount:.1f} см\n📅 Срок: {days} дней\n📈 Ставка: {rate}% годовых", parse_mode=None)
|
||||
else:
|
||||
await message.reply("❌ Не удалось открыть вклад. Проверь баланс и минимальную сумму.", parse_mode=None)
|
||||
|
||||
async def handle_loans_cmd(message: Message):
|
||||
"""Показать кредиты"""
|
||||
if not message.from_user:
|
||||
await message.answer("Команда доступна только пользователям.")
|
||||
return
|
||||
|
||||
# Здесь можно добавить логику показа кредитов пользователя
|
||||
await message.reply("📋 Твои кредиты:\n\n(функция в разработке)", parse_mode=None)
|
||||
|
||||
async def handle_loan_cmd(message: Message):
|
||||
"""Взять кредит"""
|
||||
if not message.from_user:
|
||||
await message.answer("Команда доступна только пользователям.")
|
||||
return
|
||||
|
||||
parts = message.text.split(maxsplit=2)
|
||||
if len(parts) < 3:
|
||||
await message.reply("Формат: /loan [сумма] [дни]\nПример: /loan 50 30", parse_mode=None)
|
||||
return
|
||||
|
||||
try:
|
||||
amount = float(parts[1].replace(",", "."))
|
||||
days = int(parts[2])
|
||||
except ValueError:
|
||||
await message.reply("Сумма должна быть числом, дни - целым числом.", parse_mode=None)
|
||||
return
|
||||
|
||||
if amount <= 0 or days <= 0:
|
||||
await message.reply("Сумма и дни должны быть положительными.", parse_mode=None)
|
||||
return
|
||||
|
||||
success = await asyncio.to_thread(economy.create_loan, message.from_user.id, amount, days)
|
||||
if success:
|
||||
rate = economy.get_setting('loan_interest_rate')
|
||||
await message.reply(f"✅ Кредит выдан!\n💰 Сумма: {amount:.1f} см\n📅 Срок: {days} дней\n📈 Ставка: {rate}% годовых", parse_mode=None)
|
||||
else:
|
||||
await message.reply("❌ Не удалось взять кредит. Проверь лимиты.", parse_mode=None)
|
||||
|
||||
async def handle_transfer_cmd(message: Message):
|
||||
"""Перевод денег другому пользователю"""
|
||||
if not message.from_user:
|
||||
await message.answer("Команда доступна только пользователям.")
|
||||
return
|
||||
|
||||
parts = message.text.split(maxsplit=2)
|
||||
if len(parts) < 3:
|
||||
await message.reply("Формат: /transfer @username [сумма]\nПример: /transfer @user123 10.5", parse_mode=None)
|
||||
return
|
||||
|
||||
username = parts[1].lstrip('@')
|
||||
try:
|
||||
amount = float(parts[2].replace(",", "."))
|
||||
except ValueError:
|
||||
await message.reply("Сумма должна быть числом.", parse_mode=None)
|
||||
return
|
||||
|
||||
if amount <= 0:
|
||||
await message.reply("Сумма должна быть положительной.", parse_mode=None)
|
||||
return
|
||||
|
||||
# Здесь нужно найти user_id по username
|
||||
# Для простоты примера, пока покажем сообщение об ошибке
|
||||
await message.reply("🔍 Поиск пользователя...\n(функция поиска пользователей будет добавлена)", parse_mode=None)
|
||||
|
||||
async def handle_vanomasa_cmd(message: Message):
|
||||
"""Полное обнуление (только для админов)"""
|
||||
if not message.from_user:
|
||||
await message.answer("Команда доступна только пользователям.")
|
||||
return
|
||||
|
||||
# Проверка на админа (простая реализация)
|
||||
if message.from_user.id not in [123456789]: # Заменить на реальные ID админов
|
||||
await message.reply("❌ Команда доступна только администраторам!", parse_mode=None)
|
||||
return
|
||||
|
||||
success = await asyncio.to_thread(economy.vanomasa)
|
||||
if success:
|
||||
await message.reply("💥 **ВАНОМАСА!** 💥\n\nВсе балансы обнулены!", parse_mode=None)
|
||||
else:
|
||||
await message.reply("❌ Ваномаса еще не доступна. Подождите.", parse_mode=None)
|
||||
|
||||
async def handle_central_bank_cmd(message: Message):
|
||||
"""Статистика Центрального Банка"""
|
||||
if not message.from_user:
|
||||
await message.answer("Команда доступна только пользователям.")
|
||||
return
|
||||
|
||||
cb_stats = await asyncio.to_thread(economy.get_central_bank_stats)
|
||||
|
||||
text = f"""🏛️ **Центральный Банк ZOV OS** (децентрализованный)
|
||||
|
||||
💰 **Капитал ЦБ:** {cb_stats['capital']:.2f} см
|
||||
📊 **Статистика операций:**
|
||||
• Всего собрано налогов: {cb_stats['total_taxes_collected']:.2f} см
|
||||
• Регулирование инфляции: {cb_stats['total_inflation_adjustments']:.2f} см
|
||||
• Эмиссия денег: {cb_stats['total_emissions']:.2f} см
|
||||
|
||||
🤖 **Автоматическое регулирование:**
|
||||
• ЦБ работает по алгоритмам без вмешательства
|
||||
• Налоги автоматически поступают в ЦБ
|
||||
• Инфляция регулируется автоматически
|
||||
• Эмиссия при дефляции, скупка при инфляции
|
||||
|
||||
💡 **Команды ЦБ:**
|
||||
/central_bank - детальная статистика
|
||||
/cb_stats - краткая статистика
|
||||
/cb_rules - правила работы ЦБ
|
||||
"""
|
||||
await message.answer(text, parse_mode=None)
|
||||
|
||||
async def handle_cb_stats_cmd(message: Message):
|
||||
"""Статистика ЦБ для всех пользователей"""
|
||||
if not message.from_user:
|
||||
await message.answer("Команда доступна только пользователям.")
|
||||
return
|
||||
|
||||
cb_stats = await asyncio.to_thread(economy.get_central_bank_stats)
|
||||
|
||||
text = f"""🏛️ **Центральный Банк ZOV OS** (децентрализованный)
|
||||
|
||||
💰 **Капитал ЦБ:** {cb_stats['capital']:.2f} см
|
||||
📊 **Операции ЦБ:**
|
||||
• Собрано налогов: {cb_stats['total_taxes_collected']:.2f} см
|
||||
• Антиинфляция: {cb_stats['total_inflation_adjustments']:.2f} см
|
||||
• Эмиссия: {cb_stats['total_emissions']:.2f} см
|
||||
|
||||
🔹 ЦБ работает автоматически по алгоритмам
|
||||
🔹 Учредительный капитал: 1000 см
|
||||
🔹 Все налоги поступают в ЦБ
|
||||
🔹 Никакого ручного управления
|
||||
"""
|
||||
await message.answer(text, parse_mode=None)
|
||||
|
||||
async def handle_cb_rules_cmd(message: Message):
|
||||
"""Правила работы Центрального Банка"""
|
||||
if not message.from_user:
|
||||
await message.answer("Команда доступна только пользователям.")
|
||||
return
|
||||
|
||||
text = f"""📖 **Правила Центрального Банка ZOV OS**
|
||||
|
||||
🤖 **Децентрализованная система:**
|
||||
• ЦБ работает полностью автоматически
|
||||
• Никакого ручного вмешательства
|
||||
• Все решения принимаются по алгоритмам
|
||||
|
||||
<EFBFBD> **Сбор налогов:**
|
||||
• 13% налог автоматически поступает в ЦБ
|
||||
• Налоги собираются каждый час
|
||||
• Капитал ЦБ растет за счет налогов
|
||||
|
||||
📈 **Борьба с инфляцией:**
|
||||
• Если инфляция > 10%: ЦБ скупает деньги
|
||||
• Увеличивает налоги до 20% максимум
|
||||
• Скупает до 5% от массы денег
|
||||
|
||||
<EFBFBD> **Стимуляция экономики:**
|
||||
• Если инфляция < 2%: ЦБ проводит эмиссию
|
||||
• Уменьшает налоги до 10% минимум
|
||||
• Эмиссия до 5% от капитала ЦБ
|
||||
|
||||
⚖️ **Баланс системы:**
|
||||
• ЦБ не может обанкротиться (учредительный капитал)
|
||||
• Автоматическая стабилизация экономики
|
||||
• Прозрачная статистика для всех
|
||||
"""
|
||||
await message.answer(text, parse_mode=None)
|
||||
|
||||
async def handle_gadanie_cmd(message: Message):
|
||||
parts = message.text.split(maxsplit=1)
|
||||
if len(parts) < 2 or not parts[1].strip():
|
||||
|
|
@ -1434,7 +1635,6 @@ def main():
|
|||
token = os.getenv("BOT_TOKEN")
|
||||
if not token: raise RuntimeError("BOT_TOKEN не задан!")
|
||||
|
||||
init_penis_db()
|
||||
init_polychaetsi_storage()
|
||||
bot = Bot(token=token, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
|
||||
dp = Dispatcher()
|
||||
|
|
@ -1467,6 +1667,19 @@ def main():
|
|||
dp.message.register(handle_uwu_cmd, Command("uwu"))
|
||||
dp.message.register(handle_nude_cmd, Command("nude"))
|
||||
dp.message.register(handle_photo_message, _is_watch_image_message)
|
||||
|
||||
# Экономические команды
|
||||
dp.message.register(handle_balance_cmd, Command("balance"))
|
||||
dp.message.register(handle_deposit_cmd, Command("deposit"))
|
||||
dp.message.register(handle_loans_cmd, Command("loans"))
|
||||
dp.message.register(handle_loan_cmd, Command("loan"))
|
||||
dp.message.register(handle_transfer_cmd, Command("transfer"))
|
||||
dp.message.register(handle_vanomasa_cmd, Command("vanomasa"))
|
||||
|
||||
# Команды Центрального Банка (децентрализованные)
|
||||
dp.message.register(handle_central_bank_cmd, Command("central_bank"))
|
||||
dp.message.register(handle_cb_stats_cmd, Command("cb_stats"))
|
||||
dp.message.register(handle_cb_rules_cmd, Command("cb_rules"))
|
||||
dp.message.register(handle_keywords, F.text)
|
||||
dp.message.register(handle_unmatched_message_debug)
|
||||
|
||||
|
|
@ -1499,6 +1712,13 @@ def main():
|
|||
BotCommand(command="svodka", description="СВО: итоги"),
|
||||
BotCommand(command="uwu", description="Случайная картинка с e621"),
|
||||
BotCommand(command="nude", description="Голые женщины из открытых источников"),
|
||||
BotCommand(command="balance", description="💰 баланс и статистика"),
|
||||
BotCommand(command="deposit", description="💎 открыть вклад"),
|
||||
BotCommand(command="loan", description="💵 взять кредит"),
|
||||
BotCommand(command="transfer", description="💸 перевести деньги"),
|
||||
BotCommand(command="cb_stats", description="🏛️ статистика ЦБ"),
|
||||
BotCommand(command="central_bank", description="🏛️ детальная статистика ЦБ"),
|
||||
BotCommand(command="cb_rules", description="📖 правила ЦБ"),
|
||||
]
|
||||
scopes = (
|
||||
BotCommandScopeDefault(),
|
||||
|
|
@ -1510,6 +1730,9 @@ def main():
|
|||
except Exception:
|
||||
logger.warning("Не удалось выставить команды, но бот продолжит работу.")
|
||||
|
||||
# Запуск фоновых задач экономики
|
||||
asyncio.create_task(economy_background_tasks())
|
||||
|
||||
dp.startup.register(on_startup)
|
||||
|
||||
async def start_settle(bot: Bot):
|
||||
|
|
@ -1519,5 +1742,29 @@ def main():
|
|||
logger.info("Бот запущен...")
|
||||
asyncio.run(dp.start_polling(bot, allowed_updates=dp.resolve_used_update_types()))
|
||||
|
||||
|
||||
async def economy_background_tasks():
|
||||
"""Фоновые задачи для экономики"""
|
||||
while True:
|
||||
try:
|
||||
moscow_time = datetime.now(ZoneInfo("Europe/Moscow"))
|
||||
current_hour = moscow_time.hour
|
||||
current_minute = moscow_time.minute
|
||||
|
||||
await asyncio.to_thread(economy.collect_daily_taxes)
|
||||
await asyncio.to_thread(economy.regulate_inflation)
|
||||
await asyncio.to_thread(economy.process_matured_deposits)
|
||||
|
||||
if current_hour == 0 and current_minute == 0:
|
||||
users_paid = await asyncio.to_thread(economy.pay_daily_activity_rewards)
|
||||
if users_paid > 0:
|
||||
logger.info(f"Activity rewards paid to {users_paid} users at 00:00 MSK")
|
||||
|
||||
logger.info(f"Economy background tasks completed at {moscow_time.strftime('%H:%M:%S MSK')}")
|
||||
except Exception as e:
|
||||
logger.exception(f"Economy background task failed: {e}")
|
||||
|
||||
await asyncio.sleep(60)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
|||
Loading…
Reference in a new issue