bot_tg/games/economy.py

634 lines
26 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
)
''')
# Вставка настроек по умолчанию
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('''
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('''
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 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,))
balance = cursor.fetchone()[0] if cursor.fetchone() 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()
conn.close()
# Получаем статистику сообщений за сегодня
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}%")
# Глобальный экземпляр
economy = EconomyManager()