716 lines
30 KiB
Python
716 lines
30 KiB
Python
import logging
|
||
import random
|
||
from datetime import datetime, timedelta
|
||
from typing import Dict
|
||
|
||
import psycopg2.extras
|
||
|
||
from db import get_conn
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class EconomyManager:
|
||
def __init__(self):
|
||
self.init_db()
|
||
|
||
def init_db(self):
|
||
with get_conn() as conn:
|
||
cur = conn.cursor()
|
||
|
||
cur.execute('''
|
||
CREATE TABLE IF NOT EXISTS user_balances (
|
||
user_id BIGINT PRIMARY KEY,
|
||
balance REAL DEFAULT 0.0,
|
||
daily_income REAL DEFAULT 0.0,
|
||
last_daily_reset DATE,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
)
|
||
''')
|
||
|
||
cur.execute('''
|
||
CREATE TABLE IF NOT EXISTS deposits (
|
||
id SERIAL PRIMARY KEY,
|
||
user_id BIGINT,
|
||
amount REAL,
|
||
interest_rate REAL,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
matures_at TIMESTAMP,
|
||
is_active BOOLEAN DEFAULT TRUE
|
||
)
|
||
''')
|
||
|
||
cur.execute('''
|
||
CREATE TABLE IF NOT EXISTS loans (
|
||
id SERIAL PRIMARY KEY,
|
||
user_id BIGINT,
|
||
amount REAL,
|
||
interest_rate REAL,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
due_at TIMESTAMP,
|
||
is_repaid BOOLEAN DEFAULT FALSE
|
||
)
|
||
''')
|
||
|
||
cur.execute('''
|
||
CREATE TABLE IF NOT EXISTS transactions (
|
||
id SERIAL PRIMARY KEY,
|
||
from_user_id BIGINT,
|
||
to_user_id BIGINT,
|
||
amount REAL,
|
||
transaction_type TEXT,
|
||
description TEXT,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
)
|
||
''')
|
||
|
||
cur.execute('''
|
||
CREATE TABLE IF NOT EXISTS taxes (
|
||
id SERIAL PRIMARY KEY,
|
||
user_id BIGINT,
|
||
amount REAL,
|
||
tax_date DATE,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
)
|
||
''')
|
||
|
||
cur.execute('''
|
||
CREATE TABLE IF NOT EXISTS economy_settings (
|
||
key TEXT PRIMARY KEY,
|
||
value TEXT,
|
||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
)
|
||
''')
|
||
|
||
cur.execute('''
|
||
CREATE TABLE IF NOT EXISTS daily_activity (
|
||
id SERIAL PRIMARY KEY,
|
||
user_id BIGINT,
|
||
activity_date DATE,
|
||
message_count INTEGER DEFAULT 0,
|
||
last_activity_time TIMESTAMP,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
UNIQUE (user_id, activity_date)
|
||
)
|
||
''')
|
||
|
||
cur.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)
|
||
)
|
||
''')
|
||
|
||
cur.execute('''
|
||
CREATE TABLE IF NOT EXISTS penis_stats (
|
||
user_id BIGINT PRIMARY KEY,
|
||
display_name TEXT NOT NULL DEFAULT '',
|
||
last_used_ts BIGINT
|
||
)
|
||
''')
|
||
|
||
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():
|
||
cur.execute('''
|
||
INSERT INTO economy_settings (key, value) VALUES (%s, %s)
|
||
ON CONFLICT (key) DO NOTHING
|
||
''', (key, value))
|
||
|
||
cur.execute('''
|
||
INSERT INTO central_bank (id, capital) VALUES (1, 1000.0)
|
||
ON CONFLICT (id) DO NOTHING
|
||
''')
|
||
|
||
def get_user_balance(self, user_id: int) -> float:
|
||
with get_conn() as conn:
|
||
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||
cur.execute('SELECT balance FROM user_balances WHERE user_id = %s', (user_id,))
|
||
result = cur.fetchone()
|
||
return result['balance'] if result else 0.0
|
||
|
||
def update_balance(self, user_id: int, amount: float, transaction_type: str, description: str = ""):
|
||
with get_conn() as conn:
|
||
cur = conn.cursor()
|
||
cur.execute('''
|
||
INSERT INTO user_balances (user_id, balance, daily_income, last_daily_reset)
|
||
VALUES (%s, %s, 0.0, CURRENT_DATE)
|
||
ON CONFLICT (user_id) DO UPDATE SET balance = user_balances.balance + %s
|
||
''', (user_id, amount, amount))
|
||
|
||
cur.execute('''
|
||
INSERT INTO transactions (from_user_id, to_user_id, amount, transaction_type, description)
|
||
VALUES (%s, %s, %s, %s, %s)
|
||
''', (user_id, user_id, amount, transaction_type, description))
|
||
|
||
logger.info(f"Balance updated for user {user_id}: +{amount} ({transaction_type})")
|
||
|
||
def find_user_id_by_username(self, username: str) -> int | None:
|
||
"""Ищет user_id по @username в penis_stats (display_name хранится как '@username')."""
|
||
name = username.lstrip('@').lower()
|
||
with get_conn() as conn:
|
||
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||
cur.execute(
|
||
"SELECT user_id FROM penis_stats WHERE LOWER(display_name) = %s OR LOWER(display_name) = %s",
|
||
(f'@{name}', name),
|
||
)
|
||
row = cur.fetchone()
|
||
return int(row['user_id']) if row else None
|
||
|
||
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)
|
||
limit = round(from_balance * 0.2, 2)
|
||
if round(amount, 2) > limit or round(from_balance, 2) < round(amount, 2):
|
||
return False
|
||
|
||
with get_conn() as conn:
|
||
cur = conn.cursor()
|
||
cur.execute(
|
||
'UPDATE user_balances SET balance = balance - %s WHERE user_id = %s',
|
||
(amount, from_user_id),
|
||
)
|
||
cur.execute('''
|
||
INSERT INTO user_balances (user_id, balance, daily_income, last_daily_reset)
|
||
VALUES (%s, %s, 0.0, CURRENT_DATE)
|
||
ON CONFLICT (user_id) DO UPDATE SET balance = user_balances.balance + %s
|
||
''', (to_user_id, amount, amount))
|
||
cur.execute('''
|
||
INSERT INTO transactions (from_user_id, to_user_id, amount, transaction_type, description)
|
||
VALUES (%s, %s, %s, 'transfer', %s)
|
||
''', (from_user_id, to_user_id, amount, description))
|
||
|
||
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) -> tuple[bool, str]:
|
||
if amount <= 0:
|
||
return False, "Сумма вклада должна быть больше нуля."
|
||
|
||
balance = self.get_user_balance(user_id)
|
||
min_deposit = float(self.get_setting('deposit_min_amount'))
|
||
|
||
if round(amount, 2) < round(min_deposit, 2):
|
||
return False, f"Минимальная сумма вклада — {min_deposit:.1f} см. Твой баланс: {balance:.1f} см."
|
||
if round(balance, 2) < round(amount, 2):
|
||
return False, f"Недостаточно средств. Твой баланс: {balance:.1f} см, а сумма вклада: {amount:.1f} см."
|
||
|
||
central_bank_rate = float(self.get_setting('central_bank_rate'))
|
||
matures_at = datetime.now() + timedelta(days=days)
|
||
|
||
with get_conn() as conn:
|
||
cur = conn.cursor()
|
||
cur.execute(
|
||
'UPDATE user_balances SET balance = balance - %s WHERE user_id = %s',
|
||
(amount, user_id),
|
||
)
|
||
cur.execute('''
|
||
UPDATE central_bank SET capital = capital + %s, last_updated = CURRENT_TIMESTAMP
|
||
WHERE id = 1
|
||
''', (amount,))
|
||
cur.execute('''
|
||
INSERT INTO deposits (user_id, amount, interest_rate, matures_at)
|
||
VALUES (%s, %s, %s, %s)
|
||
''', (user_id, amount, central_bank_rate, matures_at))
|
||
|
||
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 round(amount, 2) > round(max_loan, 2):
|
||
return False
|
||
|
||
with get_conn() as conn:
|
||
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||
|
||
# Лимит суммарного долга на юзера = max_loan
|
||
cur.execute(
|
||
"SELECT COALESCE(SUM(amount),0) AS total FROM loans WHERE user_id=%s AND is_repaid=FALSE",
|
||
(user_id,),
|
||
)
|
||
outstanding = float(cur.fetchone()['total'])
|
||
if round(outstanding + amount, 2) > round(max_loan, 2):
|
||
return False
|
||
|
||
# ЦБ должен иметь достаточно капитала
|
||
cur.execute("SELECT capital FROM central_bank WHERE id=1")
|
||
cb_capital = float(cur.fetchone()['capital'])
|
||
if round(cb_capital, 2) < round(amount, 2):
|
||
return False
|
||
|
||
due_at = datetime.now() + timedelta(days=days)
|
||
|
||
with get_conn() as conn:
|
||
cur = conn.cursor()
|
||
cur.execute('''
|
||
UPDATE central_bank SET capital = capital - %s, last_updated = CURRENT_TIMESTAMP
|
||
WHERE id = 1
|
||
''', (amount,))
|
||
cur.execute('''
|
||
INSERT INTO user_balances (user_id, balance, daily_income, last_daily_reset)
|
||
VALUES (%s, %s, 0.0, CURRENT_DATE)
|
||
ON CONFLICT (user_id) DO UPDATE SET balance = user_balances.balance + %s
|
||
''', (user_id, amount, amount))
|
||
cur.execute('''
|
||
INSERT INTO loans (user_id, amount, interest_rate, due_at)
|
||
VALUES (%s, %s, %s, %s)
|
||
''', (user_id, amount, loan_rate, due_at))
|
||
|
||
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:
|
||
with get_conn() as conn:
|
||
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||
cur.execute(
|
||
'SELECT amount, interest_rate, is_repaid FROM loans WHERE id = %s AND user_id = %s',
|
||
(loan_id, user_id),
|
||
)
|
||
loan = cur.fetchone()
|
||
|
||
if not loan or loan['is_repaid']:
|
||
return False
|
||
|
||
total_repayment = round(loan['amount'] * (1 + loan['interest_rate'] / 100), 2)
|
||
|
||
balance = self.get_user_balance(user_id)
|
||
if round(balance, 2) < total_repayment:
|
||
return False
|
||
|
||
wcur = conn.cursor()
|
||
wcur.execute(
|
||
'UPDATE user_balances SET balance = balance - %s WHERE user_id = %s',
|
||
(total_repayment, user_id),
|
||
)
|
||
wcur.execute('''
|
||
UPDATE central_bank SET capital = capital + %s, last_updated = CURRENT_TIMESTAMP
|
||
WHERE id = 1
|
||
''', (total_repayment,))
|
||
wcur.execute('UPDATE loans SET is_repaid = TRUE WHERE id = %s', (loan_id,))
|
||
|
||
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
|
||
|
||
with get_conn() as conn:
|
||
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||
cur.execute('''
|
||
SELECT user_id, daily_income FROM user_balances
|
||
WHERE daily_income > 0 AND (last_daily_reset != CURRENT_DATE OR last_daily_reset IS NULL)
|
||
''')
|
||
users = cur.fetchall()
|
||
|
||
total_taxes_collected = 0.0
|
||
wcur = conn.cursor()
|
||
|
||
for row in users:
|
||
user_id = row['user_id']
|
||
daily_income = row['daily_income']
|
||
tax_amount = daily_income * tax_rate
|
||
if tax_amount <= 0:
|
||
continue
|
||
|
||
wcur.execute('''
|
||
UPDATE user_balances
|
||
SET balance = balance - %s, daily_income = 0, last_daily_reset = CURRENT_DATE
|
||
WHERE user_id = %s
|
||
''', (tax_amount, user_id))
|
||
wcur.execute(
|
||
'INSERT INTO taxes (user_id, amount, tax_date) VALUES (%s, %s, CURRENT_DATE)',
|
||
(user_id, tax_amount),
|
||
)
|
||
total_taxes_collected += tax_amount
|
||
|
||
if total_taxes_collected > 0:
|
||
wcur.execute('''
|
||
UPDATE central_bank
|
||
SET capital = capital + %s, total_taxes_collected = total_taxes_collected + %s
|
||
WHERE id = 1
|
||
''', (total_taxes_collected, total_taxes_collected))
|
||
|
||
logger.info(f"Daily taxes collected: {total_taxes_collected:.2f} cm from {len(users)} users")
|
||
|
||
def count_message(self, user_id: int):
|
||
today = datetime.now().strftime('%Y-%m-%d')
|
||
with get_conn() as conn:
|
||
cur = conn.cursor()
|
||
cur.execute('''
|
||
INSERT INTO daily_activity (user_id, activity_date, message_count, last_activity_time)
|
||
VALUES (%s, %s, 1, CURRENT_TIMESTAMP)
|
||
ON CONFLICT (user_id, activity_date) DO UPDATE SET
|
||
message_count = daily_activity.message_count + 1,
|
||
last_activity_time = CURRENT_TIMESTAMP
|
||
''', (user_id, today))
|
||
|
||
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
|
||
|
||
yesterday = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d')
|
||
|
||
with get_conn() as conn:
|
||
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||
cur.execute('''
|
||
SELECT user_id, message_count FROM daily_activity
|
||
WHERE activity_date = %s AND message_count > 0
|
||
''', (yesterday,))
|
||
users = cur.fetchall()
|
||
|
||
total_paid = 0.0
|
||
wcur = conn.cursor()
|
||
|
||
# Проверяем что у ЦБ есть деньги на выплаты
|
||
wcur = conn.cursor()
|
||
wcur.execute("SELECT capital FROM central_bank WHERE id=1")
|
||
cb_row = wcur.fetchone()
|
||
cb_capital = float(cb_row[0]) if cb_row else 0.0
|
||
|
||
for row in users:
|
||
user_id = row['user_id']
|
||
message_count = row['message_count']
|
||
reward = message_count * reward_rate
|
||
if reward <= 0:
|
||
continue
|
||
if cb_capital < reward:
|
||
logger.warning("CB out of funds for activity rewards, stopping early")
|
||
break
|
||
|
||
wcur.execute('''
|
||
INSERT INTO user_balances (user_id, balance, daily_income, last_daily_reset)
|
||
VALUES (%s, %s, 0.0, CURRENT_DATE)
|
||
ON CONFLICT (user_id) DO UPDATE SET
|
||
balance = user_balances.balance + %s,
|
||
daily_income = user_balances.daily_income + %s
|
||
''', (user_id, reward, reward, reward))
|
||
wcur.execute('''
|
||
UPDATE central_bank SET capital = capital - %s, last_updated = CURRENT_TIMESTAMP
|
||
WHERE id = 1
|
||
''', (reward,))
|
||
wcur.execute('''
|
||
INSERT INTO transactions (from_user_id, to_user_id, amount, transaction_type, description)
|
||
VALUES (0, %s, %s, 'activity_reward', %s)
|
||
''', (user_id, reward, f'Вознаграждение за {message_count} сообщений'))
|
||
cb_capital -= reward
|
||
total_paid += reward
|
||
|
||
wcur.execute(
|
||
'UPDATE economy_settings SET value = %s WHERE key = %s',
|
||
(today, 'last_activity_payout'),
|
||
)
|
||
|
||
logger.info(f"Activity rewards paid: {total_paid:.2f} cm to {len(users)} users")
|
||
return len(users)
|
||
|
||
def get_central_bank_stats(self) -> Dict:
|
||
with get_conn() as conn:
|
||
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||
cur.execute('''
|
||
SELECT capital, total_taxes_collected, total_inflation_adjustments, total_emissions
|
||
FROM central_bank WHERE id = 1
|
||
''')
|
||
result = cur.fetchone()
|
||
|
||
if result:
|
||
return {
|
||
'capital': result['capital'],
|
||
'total_taxes_collected': result['total_taxes_collected'],
|
||
'total_inflation_adjustments': result['total_inflation_adjustments'],
|
||
'total_emissions': result['total_emissions'],
|
||
}
|
||
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 = ""):
|
||
with get_conn() as conn:
|
||
cur = conn.cursor()
|
||
cur.execute('''
|
||
UPDATE central_bank SET capital = capital + %s, last_updated = CURRENT_TIMESTAMP
|
||
WHERE id = 1
|
||
''', (capital_change,))
|
||
|
||
if transaction_type == 'tax':
|
||
cur.execute('''
|
||
UPDATE central_bank
|
||
SET total_taxes_collected = total_taxes_collected + %s WHERE id = 1
|
||
''', (abs(capital_change),))
|
||
elif transaction_type == 'inflation':
|
||
cur.execute('''
|
||
UPDATE central_bank
|
||
SET total_inflation_adjustments = total_inflation_adjustments + %s WHERE id = 1
|
||
''', (abs(capital_change),))
|
||
elif transaction_type == 'emission':
|
||
cur.execute('''
|
||
UPDATE central_bank
|
||
SET total_emissions = total_emissions + %s WHERE id = 1
|
||
''', (abs(capital_change),))
|
||
|
||
logger.info(f"Central bank updated: {capital_change} ({transaction_type})")
|
||
|
||
def emit_money(self, amount: float, reason: str = "") -> bool:
|
||
if round(self.get_central_bank_stats()['capital'], 2) < round(amount, 2):
|
||
return False
|
||
self.update_central_bank(-amount, 'emission', reason)
|
||
logger.info(f"Money emitted: {amount} cm ({reason})")
|
||
return True
|
||
|
||
def vanomasa(self) -> bool:
|
||
with get_conn() as conn:
|
||
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||
cur.execute("SELECT value FROM economy_settings WHERE key = %s", ('last_vanomasa',))
|
||
last_vanomasa = cur.fetchone()
|
||
|
||
if last_vanomasa and last_vanomasa['value']:
|
||
try:
|
||
last_date = datetime.strptime(last_vanomasa['value'], '%Y-%m-%d')
|
||
cooldown_days = int(self.get_setting('vanomasa_cooldown'))
|
||
if datetime.now() - last_date < timedelta(days=cooldown_days):
|
||
return False
|
||
except ValueError:
|
||
pass
|
||
|
||
today = datetime.now().strftime('%Y-%m-%d')
|
||
wcur = conn.cursor()
|
||
wcur.execute('UPDATE user_balances SET balance = 0, daily_income = 0')
|
||
wcur.execute('''
|
||
INSERT INTO economy_settings (key, value, updated_at) VALUES (%s, %s, CURRENT_TIMESTAMP)
|
||
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = CURRENT_TIMESTAMP
|
||
''', ('last_vanomasa', today))
|
||
|
||
logger.warning("VANOMASA: All balances reset to zero!")
|
||
return True
|
||
|
||
def get_setting(self, key: str) -> str:
|
||
with get_conn() as conn:
|
||
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||
cur.execute('SELECT value FROM economy_settings WHERE key = %s', (key,))
|
||
result = cur.fetchone()
|
||
return result['value'] if result else '0'
|
||
|
||
def update_setting(self, key: str, value: str):
|
||
with get_conn() as conn:
|
||
cur = conn.cursor()
|
||
cur.execute('''
|
||
INSERT INTO economy_settings (key, value, updated_at) VALUES (%s, %s, CURRENT_TIMESTAMP)
|
||
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = CURRENT_TIMESTAMP
|
||
''', (key, value))
|
||
|
||
def get_user_stats(self, user_id: int) -> Dict:
|
||
today = datetime.now().strftime('%Y-%m-%d')
|
||
yesterday = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d')
|
||
|
||
with get_conn() as conn:
|
||
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||
|
||
cur.execute('SELECT balance FROM user_balances WHERE user_id = %s', (user_id,))
|
||
row = cur.fetchone()
|
||
balance = row['balance'] if row else 0.0
|
||
|
||
cur.execute('''
|
||
SELECT COUNT(*) AS cnt, COALESCE(SUM(amount), 0) AS total
|
||
FROM deposits WHERE user_id = %s AND is_active = TRUE
|
||
''', (user_id,))
|
||
deposits = cur.fetchone()
|
||
|
||
cur.execute('''
|
||
SELECT COUNT(*) AS cnt, COALESCE(SUM(amount), 0) AS total
|
||
FROM loans WHERE user_id = %s AND is_repaid = FALSE
|
||
''', (user_id,))
|
||
loans = cur.fetchone()
|
||
|
||
cur.execute(
|
||
'SELECT message_count FROM daily_activity WHERE user_id = %s AND activity_date = %s',
|
||
(user_id, today),
|
||
)
|
||
today_row = cur.fetchone()
|
||
|
||
cur.execute(
|
||
'SELECT message_count FROM daily_activity WHERE user_id = %s AND activity_date = %s',
|
||
(user_id, yesterday),
|
||
)
|
||
yesterday_row = cur.fetchone()
|
||
|
||
return {
|
||
'balance': balance,
|
||
'active_deposits_count': deposits['cnt'] or 0,
|
||
'active_deposits_sum': deposits['total'] or 0.0,
|
||
'active_loans_count': loans['cnt'] or 0,
|
||
'active_loans_sum': loans['total'] or 0.0,
|
||
'today_messages': today_row['message_count'] if today_row else 0,
|
||
'yesterday_messages': yesterday_row['message_count'] if yesterday_row else 0,
|
||
}
|
||
|
||
def regulate_inflation(self):
|
||
inflation_rate = float(self.get_setting('inflation_rate'))
|
||
|
||
with get_conn() as conn:
|
||
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||
cur.execute('SELECT SUM(balance) AS total_balance, COUNT(*) AS user_count FROM user_balances')
|
||
result = cur.fetchone()
|
||
total_supply = result['total_balance'] or 0.0
|
||
|
||
cur.execute('SELECT capital FROM central_bank WHERE id = 1')
|
||
cb_result = cur.fetchone()
|
||
cb_capital = cb_result['capital'] if cb_result else 1000.0
|
||
|
||
if inflation_rate > 10.0 and total_supply > 1000 and cb_capital > 100:
|
||
buyback_amount = min(total_supply * 0.05, cb_capital * 0.1)
|
||
self.update_central_bank(-buyback_amount, 'inflation', f'Anti-inflation: {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: bought back {buyback_amount:.2f} cm, tax={new_tax_rate}%")
|
||
|
||
elif inflation_rate < 2.0 and total_supply < 500 and cb_capital > 200:
|
||
emission_amount = cb_capital * 0.05
|
||
self.update_central_bank(-emission_amount, 'emission', f'Stimulus: {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: emitted {emission_amount:.2f} cm, tax={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:
|
||
with get_conn() as conn:
|
||
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||
|
||
cur.execute('SELECT balance FROM user_balances WHERE user_id = %s', (user_id,))
|
||
row = cur.fetchone()
|
||
current_balance = row['balance'] if row else start_length
|
||
|
||
cur.execute(
|
||
'SELECT last_used_ts, display_name FROM penis_stats WHERE user_id = %s',
|
||
(user_id,),
|
||
)
|
||
ps_row = cur.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:
|
||
wcur = conn.cursor()
|
||
wcur.execute(
|
||
'UPDATE penis_stats SET display_name = %s WHERE user_id = %s',
|
||
(display_name, user_id),
|
||
)
|
||
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)
|
||
|
||
wcur = conn.cursor()
|
||
wcur.execute('''
|
||
INSERT INTO user_balances (user_id, balance, daily_income, last_daily_reset)
|
||
VALUES (%s, %s, 0.0, CURRENT_DATE)
|
||
ON CONFLICT (user_id) DO UPDATE SET balance = %s
|
||
''', (user_id, new_balance, new_balance))
|
||
|
||
sign_label = f'+{delta:.1f}' if sign > 0 else f'-{delta:.1f}'
|
||
wcur.execute('''
|
||
INSERT INTO transactions (from_user_id, to_user_id, amount, transaction_type, description)
|
||
VALUES (%s, %s, %s, 'penis_game', %s)
|
||
''', (user_id, user_id, sign * delta, f'Игра /penis: {sign_label} см'))
|
||
|
||
if ps_row is None:
|
||
wcur.execute(
|
||
'INSERT INTO penis_stats (user_id, display_name, last_used_ts) VALUES (%s, %s, %s)',
|
||
(user_id, display_name, now_ts),
|
||
)
|
||
else:
|
||
wcur.execute(
|
||
'UPDATE penis_stats SET display_name = %s, last_used_ts = %s WHERE user_id = %s',
|
||
(display_name, now_ts, user_id),
|
||
)
|
||
|
||
return {'allowed': True, 'new_balance': new_balance, 'delta': delta, 'sign': sign}
|
||
|
||
def get_penis_top(self, limit: int) -> list:
|
||
with get_conn() as conn:
|
||
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||
cur.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 %s
|
||
''', (limit,))
|
||
rows = cur.fetchall()
|
||
return [{'user_id': r['user_id'], 'display_name': r['display_name'], 'balance': r['balance']}
|
||
for r in rows]
|
||
|
||
def process_matured_deposits(self) -> int:
|
||
with get_conn() as conn:
|
||
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||
cur.execute('''
|
||
SELECT id, user_id, amount, interest_rate
|
||
FROM deposits
|
||
WHERE is_active = TRUE AND matures_at <= NOW()
|
||
''')
|
||
matured = cur.fetchall()
|
||
|
||
paid = 0
|
||
wcur = conn.cursor()
|
||
for row in matured:
|
||
dep_id = row['id']
|
||
user_id = row['user_id']
|
||
amount = row['amount']
|
||
rate = row['interest_rate']
|
||
payout = round(amount * (1 + rate / 100 * 1 / 365 * 30), 2)
|
||
|
||
wcur.execute('''
|
||
INSERT INTO user_balances (user_id, balance, daily_income, last_daily_reset)
|
||
VALUES (%s, %s, 0.0, CURRENT_DATE)
|
||
ON CONFLICT (user_id) DO UPDATE SET balance = user_balances.balance + %s
|
||
''', (user_id, payout, payout))
|
||
wcur.execute('''
|
||
UPDATE central_bank SET capital = capital - %s, last_updated = CURRENT_TIMESTAMP
|
||
WHERE id = 1
|
||
''', (payout,))
|
||
wcur.execute('UPDATE deposits SET is_active = FALSE WHERE id = %s', (dep_id,))
|
||
wcur.execute('''
|
||
INSERT INTO transactions (from_user_id, to_user_id, amount, transaction_type, description)
|
||
VALUES (0, %s, %s, 'deposit_payout', %s)
|
||
''', (user_id, payout, f'Выплата вклада #{dep_id}: {amount:.1f} + проценты = {payout:.2f} см'))
|
||
paid += 1
|
||
|
||
if paid:
|
||
logger.info(f"Matured deposits paid out: {paid}")
|
||
return paid
|
||
|
||
|
||
economy = EconomyManager()
|