forked from zovos/bot_tg
- Все 4 SQLite базы объединены в одну PostgreSQL - Новый db.py с ThreadedConnectionPool (psycopg2) - docker-compose: сервис postgres с healthcheck, данные в ./db/postgres - scripts/migrate_sqlite_to_pg.py — миграция существующих данных - Убраны мёртвый код и дублирующий import в main.py и talk_handler.py - .env удалён из git-индекса Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
669 lines
28 KiB
Python
669 lines
28 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 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)
|
|
if amount > from_balance * 0.2 or from_balance < amount:
|
|
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) -> 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 or balance < amount:
|
|
return False
|
|
|
|
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 amount > max_loan:
|
|
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 = loan['amount'] * (1 + loan['interest_rate'] / 100)
|
|
|
|
balance = self.get_user_balance(user_id)
|
|
if balance < 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()
|
|
|
|
for row in users:
|
|
user_id = row['user_id']
|
|
message_count = row['message_count']
|
|
reward = message_count * reward_rate
|
|
if reward <= 0:
|
|
continue
|
|
|
|
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('''
|
|
INSERT INTO transactions (from_user_id, to_user_id, amount, transaction_type, description)
|
|
VALUES (%s, %s, %s, 'activity_reward', %s)
|
|
''', (user_id, user_id, reward, f'Вознаграждение за {message_count} сообщений'))
|
|
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 self.get_central_bank_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:
|
|
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()
|