forked from zovos/bot_tg
feat: единая экономика — penis game, вклады/кредиты через ЦБ
- penis_stats переехал в economy.sqlite3, длина хранится в user_balances.balance - /top_penis читает из user_balances, /penis пишет туда же - create_deposit: деньги уходят в капитал ЦБ - create_loan: деньги берутся из капитала ЦБ - repay_loan: деньги возвращаются в ЦБ - добавлен process_matured_deposits: ЦБ выплачивает вклады с процентами - init_penis_db() больше не вызывается Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
70e7d23064
commit
81af64c8b7
2 changed files with 196 additions and 83 deletions
186
games/economy.py
186
games/economy.py
|
|
@ -1,3 +1,4 @@
|
||||||
|
import random
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
@ -116,6 +117,15 @@ class EconomyManager:
|
||||||
UNIQUE(id)
|
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 = {
|
default_settings = {
|
||||||
|
|
@ -233,18 +243,24 @@ class EconomyManager:
|
||||||
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
# Списываем деньги со счета
|
# Списываем деньги со счета пользователя → передаём в ЦБ
|
||||||
cursor.execute('UPDATE user_balances SET balance = balance - ? WHERE user_id = ?', (amount, user_id))
|
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('''
|
cursor.execute('''
|
||||||
INSERT INTO deposits (user_id, amount, interest_rate, matures_at)
|
INSERT INTO deposits (user_id, amount, interest_rate, matures_at)
|
||||||
VALUES (?, ?, ?, ?)
|
VALUES (?, ?, ?, ?)
|
||||||
''', (user_id, amount, central_bank_rate, matures_at))
|
''', (user_id, amount, central_bank_rate, matures_at))
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
logger.info(f"Deposit created: user {user_id}, amount {amount}, rate {central_bank_rate}%")
|
logger.info(f"Deposit created: user {user_id}, amount {amount}, rate {central_bank_rate}%")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
@ -264,22 +280,28 @@ class EconomyManager:
|
||||||
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
conn = sqlite3.connect(ECONOMY_DB_PATH)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
# Начисляем деньги на счет
|
# ЦБ выдаёт кредит: уменьшаем капитал ЦБ, зачисляем пользователю
|
||||||
|
cursor.execute('''
|
||||||
|
UPDATE central_bank SET
|
||||||
|
capital = capital - ?,
|
||||||
|
last_updated = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = 1
|
||||||
|
''', (amount,))
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
INSERT INTO user_balances (user_id, balance, daily_income, last_daily_reset)
|
INSERT INTO user_balances (user_id, balance, daily_income, last_daily_reset)
|
||||||
VALUES (?, ?, 0.0, date('now'))
|
VALUES (?, ?, 0.0, date('now'))
|
||||||
ON CONFLICT(user_id) DO UPDATE SET balance = balance + ?
|
ON CONFLICT(user_id) DO UPDATE SET balance = balance + ?
|
||||||
''', (user_id, amount, amount))
|
''', (user_id, amount, amount))
|
||||||
|
|
||||||
# Создаем кредит
|
# Создаем кредит
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
INSERT INTO loans (user_id, amount, interest_rate, due_at)
|
INSERT INTO loans (user_id, amount, interest_rate, due_at)
|
||||||
VALUES (?, ?, ?, ?)
|
VALUES (?, ?, ?, ?)
|
||||||
''', (user_id, amount, loan_rate, due_at))
|
''', (user_id, amount, loan_rate, due_at))
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
logger.info(f"Loan created: user {user_id}, amount {amount}, rate {loan_rate}%")
|
logger.info(f"Loan created: user {user_id}, amount {amount}, rate {loan_rate}%")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
@ -303,15 +325,21 @@ class EconomyManager:
|
||||||
conn.close()
|
conn.close()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Списываем деньги
|
# Списываем деньги у пользователя → возвращаем в ЦБ
|
||||||
cursor.execute('UPDATE user_balances SET balance = balance - ? WHERE user_id = ?', (total_repayment, user_id))
|
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,))
|
cursor.execute('UPDATE loans SET is_repaid = 1 WHERE id = ?', (loan_id,))
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
logger.info(f"Loan repaid: user {user_id}, loan {loan_id}, amount {total_repayment}")
|
logger.info(f"Loan repaid: user {user_id}, loan {loan_id}, amount {total_repayment}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
@ -629,5 +657,139 @@ class EconomyManager:
|
||||||
|
|
||||||
logger.info(f"Economic stimulus: CB emitted {emission_amount:.2f} cm, tax rate decreased to {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()
|
economy = EconomyManager()
|
||||||
|
|
|
||||||
93
main.py
93
main.py
|
|
@ -730,67 +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}"
|
display_name = user_name.strip() if user_name and user_name.strip() else f"user_{user_id}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with sqlite3.connect(config.PENIS_DB_PATH) as conn:
|
result = economy.play_penis(
|
||||||
conn.row_factory = sqlite3.Row
|
user_id, display_name, now_ts,
|
||||||
row = conn.execute(
|
config.PENIS_START_LENGTH, config.PENIS_COOLDOWN_SECONDS,
|
||||||
"SELECT length, last_used_ts, display_name FROM penis_stats WHERE user_id = ?",
|
config.PENIS_MIN_DELTA, config.PENIS_MAX_DELTA,
|
||||||
(user_id,),
|
)
|
||||||
).fetchone()
|
except Exception:
|
||||||
|
|
||||||
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),
|
|
||||||
)
|
|
||||||
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()
|
|
||||||
|
|
||||||
# Обновляем баланс в экономической системе
|
|
||||||
if sign > 0:
|
|
||||||
economy.update_balance(user_id, delta, 'penis_game', f'Выигрыш в игре +{delta} см')
|
|
||||||
else:
|
|
||||||
economy.update_balance(user_id, delta, 'penis_game', f'Проигрыш в игре {delta} см')
|
|
||||||
except sqlite3.Error:
|
|
||||||
logger.exception("Failed to process /penis")
|
logger.exception("Failed to process /penis")
|
||||||
return False, "Ошибка БД. Попробуй позже."
|
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 "-"
|
sign_text = "+" if sign > 0 else "-"
|
||||||
return (
|
return (
|
||||||
True,
|
True,
|
||||||
f"📏 Изменение: {sign_text}{delta:.1f} см\n"
|
f"📏 Изменение: {sign_text}{delta:.1f} см\n"
|
||||||
f"Текущая длина: {new_length:.1f} см\n"
|
f"Текущая длина: {new_balance:.1f} см\n"
|
||||||
"Следующая попытка через 24 часа.",
|
"Следующая попытка через 24 часа.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -803,18 +765,8 @@ def format_penis_user_name(user: types.User) -> str:
|
||||||
|
|
||||||
def build_penis_top(limit: int = config.PENIS_TOP_LIMIT) -> str:
|
def build_penis_top(limit: int = config.PENIS_TOP_LIMIT) -> str:
|
||||||
try:
|
try:
|
||||||
with sqlite3.connect(config.PENIS_DB_PATH) as conn:
|
rows = economy.get_penis_top(limit)
|
||||||
conn.row_factory = sqlite3.Row
|
except Exception:
|
||||||
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:
|
|
||||||
logger.exception("Failed to build /top_penis")
|
logger.exception("Failed to build /top_penis")
|
||||||
return "Не удалось загрузить топ. Ошибка БД."
|
return "Не удалось загрузить топ. Ошибка БД."
|
||||||
|
|
||||||
|
|
@ -825,11 +777,10 @@ def build_penis_top(limit: int = config.PENIS_TOP_LIMIT) -> str:
|
||||||
lines = ["🏆 Топ по длине:"]
|
lines = ["🏆 Топ по длине:"]
|
||||||
for index, row in enumerate(rows, start=1):
|
for index, row in enumerate(rows, start=1):
|
||||||
user_id = int(row["user_id"])
|
user_id = int(row["user_id"])
|
||||||
length = float(row["length"])
|
balance = float(row["balance"])
|
||||||
name = row["display_name"] if isinstance(row["display_name"], str) else f"user_{user_id}"
|
name = str(row["display_name"]).strip() or f"user_{user_id}"
|
||||||
name = name.strip() if name.strip() else f"user_{user_id}"
|
|
||||||
prefix = medals.get(index, f"{index}.")
|
prefix = medals.get(index, f"{index}.")
|
||||||
lines.append(f"{prefix} {name} — {length:.1f} см")
|
lines.append(f"{prefix} {name} — {balance:.1f} см")
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
def _load_polychaetsi_stats() -> dict:
|
def _load_polychaetsi_stats() -> dict:
|
||||||
|
|
@ -1684,7 +1635,6 @@ def main():
|
||||||
token = os.getenv("BOT_TOKEN")
|
token = os.getenv("BOT_TOKEN")
|
||||||
if not token: raise RuntimeError("BOT_TOKEN не задан!")
|
if not token: raise RuntimeError("BOT_TOKEN не задан!")
|
||||||
|
|
||||||
init_penis_db()
|
|
||||||
init_polychaetsi_storage()
|
init_polychaetsi_storage()
|
||||||
bot = Bot(token=token, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
|
bot = Bot(token=token, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
|
||||||
dp = Dispatcher()
|
dp = Dispatcher()
|
||||||
|
|
@ -1803,6 +1753,7 @@ async def economy_background_tasks():
|
||||||
|
|
||||||
await asyncio.to_thread(economy.collect_daily_taxes)
|
await asyncio.to_thread(economy.collect_daily_taxes)
|
||||||
await asyncio.to_thread(economy.regulate_inflation)
|
await asyncio.to_thread(economy.regulate_inflation)
|
||||||
|
await asyncio.to_thread(economy.process_matured_deposits)
|
||||||
|
|
||||||
if current_hour == 0 and current_minute == 0:
|
if current_hour == 0 and current_minute == 0:
|
||||||
users_paid = await asyncio.to_thread(economy.pay_daily_activity_rewards)
|
users_paid = await asyncio.to_thread(economy.pay_daily_activity_rewards)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue