diff --git a/bot.py b/bot.py index 467a5909..3e03eb39 100644 --- a/bot.py +++ b/bot.py @@ -34,6 +34,7 @@ from models.user import UserAdmin from processing.processing import processing_router from repositories.button_media import ButtonMediaRepository +from services.media import MediaService from services.notification import NotificationService from services.wallet import WalletService from utils.utils import validate_i18n @@ -91,16 +92,20 @@ async def on_startup(): if photos.total_count == 0: photo_id_list = [] for admin_id in config.ADMIN_ID_LIST: - msg = await bot.send_photo(chat_id=admin_id, - photo=URLInputFile(url="https://i.imgur.com/CxWRPwY.png", - filename="no_image.png")) - bot_photo_id = msg.photo[-1].file_id - photo_id_list.append(bot_photo_id) + try: + msg = await bot.send_photo(chat_id=admin_id, + photo=URLInputFile(url="https://img.freepik.com/premium-vector/no-photo-available-vector-icon-default-image-symbol-picture-coming-soon-web-site-mobile-app_87543-18055.jpg", + filename="no_image.png")) + bot_photo_id = msg.photo[-1].file_id + photo_id_list.append(bot_photo_id) + except Exception as _: + pass bot_photo_id = photo_id_list[0] else: bot_photo_id = photos.photos[0][-1].file_id with open("static/no_image.jpeg", "w") as f: f.write(bot_photo_id) + await MediaService.update_inaccessible_media(bot) validate_i18n() await ButtonMediaRepository.init_buttons_media() if config.CRYPTO_FORWARDING_MODE: diff --git a/crypto_api/CryptoApiWrapper.py b/crypto_api/CryptoApiWrapper.py index 5200000f..0c9e2571 100644 --- a/crypto_api/CryptoApiWrapper.py +++ b/crypto_api/CryptoApiWrapper.py @@ -20,13 +20,13 @@ async def fetch_api_request(url: str, params: dict | None = None, method: str = async def get_crypto_prices() -> dict: url = f"https://api.coingecko.com/api/v3/simple/price" params = { - "ids": "bitcoin,litecoin,solana,ethereum,binancecoin", + "ids": "bitcoin,litecoin,solana,ethereum,binancecoin,tether,usd-coin", "vs_currencies": "usd,eur,gbp,jpy,cad" } return await CryptoApiWrapper.fetch_api_request(url, params) @staticmethod - async def get_wallet_balance() -> dict: + async def get_wallet_balance() -> dict[Cryptocurrency, float]: url = f"{config.KRYPTO_EXPRESS_API_URL}/wallet" headers = { "X-Api-Key": config.KRYPTO_EXPRESS_API_KEY @@ -35,7 +35,7 @@ async def get_wallet_balance() -> dict: url, headers=headers ) - return {k: v for k, v in response.items() if v > 0} + return {Cryptocurrency(k): v for k, v in response.items()} @staticmethod async def withdrawal(cryptocurrency: Cryptocurrency, diff --git a/handlers/admin/announcement.py b/handlers/admin/announcement.py index 51779aeb..e0d56f7e 100644 --- a/handlers/admin/announcement.py +++ b/handlers/admin/announcement.py @@ -59,12 +59,8 @@ async def send_confirmation(**kwargs): callback_data: AnnouncementCallback = kwargs.get("callback_data") session: AsyncSession = kwargs.get("session") language: Language = kwargs.get("language") - msg = await AnnouncementService.send_announcement(callback, callback_data, session, language) - if callback.message.caption: - await callback.message.delete() - await callback.message.answer(text=msg) - elif callback.message.text: - await callback.message.edit_text(text=msg) + await AnnouncementService.send_announcement(callback, callback_data, session, language) + await callback.message.delete() @announcement_router.callback_query(AdminIdFilter(), AnnouncementCallback.filter()) diff --git a/i18n/de.json b/i18n/de.json index 4c74218f..f1e975af 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -31,10 +31,8 @@ "credit_management_request_user_entity": "👤 Sende die Telegram-ID oder den Benutzernamen des Nutzers:", "credit_management_user_not_found": "⚠️ Nutzer nicht gefunden.", "crypto_withdraw": "👛 Wallet", - "crypto_wallet": "\n\n🤖 Bot-Guthaben\n\n⚖️ BTC-Saldo: {btc_balance} BTC\n⚖️ LTC-Saldo: {ltc_balance} LTC\n⚖️ ETH-Saldo: {eth_balance} ETH\n⚖️ SOL-Saldo: {sol_balance} SOL\n⚖️ BNB-Saldo: {bnb_balance} BNB", "current_stock_header": "🗂️ Aktueller Lagerbestand\n", "deposits_statistics": "📊 Einzahlungsstatistik", - "deposits_statistics_msg": "📊 Einzahlungsstatistiken der letzten {timedelta}.\n\n💸 Gesamteinzahlungen: {deposits_count}\n\n💰 BTC-Einzahlungen in Höhe von: {btc_amount} BTC\n💰 LTC-Einzahlungen in Höhe von: {ltc_amount} LTC\n💰 SOL-Einzahlungen in Höhe von: {sol_amount} SOL\n💰 ETH-Einzahlungen in Höhe von: {eth_amount} ETH\n💰 BNB-Einzahlungen in Höhe von: {bnb_amount} BNB\n\n💼 Gesamtbetrag in Fiat: {fiat_amount:.2f} {currency_text}", "delete_entity": "🗑️ {entity} löschen", "delete_entity_confirmation": "❓ Möchtest du {entity} mit dem Namen {entity_name} wirklich löschen?", "get_database_file": "💾 Datenbankdatei herunterladen", @@ -55,7 +53,6 @@ "restocking_message_header": "🆕 Neue Lagerbestände! 🆕\n", "sales_statistics": "📊 Verkaufsstatistiken der letzten {timedelta}.\n💰 Gesamtgewinn: {currency_sym}{total_profit:.2f}\n🛍️ Verkaufte Artikel: {items_sold}\n💼 Gesamtkäufe: {buys_count}", "send_everyone": "📢 An alle senden", - "sending_result": "✅ Nachricht an {counter} von {len} aktiven Nutzern gesendet.\nGesamtzahl der Nutzer: {users_count}", "sending_started": "🚀 Versand gestartet", "stock": "📦 Vollständige Lagerbestandsmeldung", "statistics": "📊 Analytik und Berichte", @@ -135,7 +132,14 @@ "remove_review_image_confirmation": "❓ Sind Sie sicher, dass Sie das Bewertungsbild entfernen möchten?", "reviews_management": "⭐ Bewertungsverwaltung", "notification_new_deposit_id": "💰 Neue Einzahlung von Benutzer mit ID {telegram_id} für {currency_sym}{deposit_amount_fiat:.2f} mit 💰 {value} {crypto_name}\n💰 Empfehlungsbonus: {currency_sym}{referral_bonus:.2f}\n💰 Empfehlerbonus: {currency_sym}{referrer_bonus:.2f}", - "notification_new_deposit_username": "💰 Neue Einzahlung von Benutzer mit Benutzername @{username} für {currency_sym}{deposit_amount_fiat:.2f} mit 💰 {value} {crypto_name}\n💰 Empfehlungsbonus: {currency_sym}{referral_bonus:.2f}\n💰 Empfehlerbonus: {currency_sym}{referrer_bonus:.2f}" + "notification_new_deposit_username": "💰 Neue Einzahlung von Benutzer mit Benutzername @{username} für {currency_sym}{deposit_amount_fiat:.2f} mit 💰 {value} {crypto_name}\n💰 Empfehlungsbonus: {currency_sym}{referral_bonus:.2f}\n💰 Empfehlerbonus: {currency_sym}{referrer_bonus:.2f}", + "crypto_wallet": "\n\n🤖 Bot-Guthaben\n\n{wallet_content}", + "crypto_wallet_line": "⚖️ {crypto_name} Guthaben: {crypto_balance:.8f} {crypto_name}", + "deposits_statistics_msg": "📊 Einzahlungsstatistiken der letzten {timedelta}.\n\n💰 Gesamteinzahlungen: {deposits_count}\n\n{deposits_content}\n\n💼 Gesamte Kryptowährungseinzahlungen im Wert: {fiat_amount:.2f} {currency_text}", + "deposits_statistics_line": "💰 Gesamte {crypto_name}-Einzahlungen im Wert: {crypto_amount:.8f} {crypto_name}", + "sending_result": "✅ Nachricht an {counter} von {len} aktiven Benutzern gesendet.\n👤 Benutzer insgesamt: {users_count}\nStatus: {status}", + "in_progress": "🟡 In Bearbeitung...", + "finished": "🟢 Abgeschlossen." }, "common": { "back_button": "⬅️ Zurück", diff --git a/i18n/en.json b/i18n/en.json index 01646975..ee680358 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -31,10 +31,8 @@ "credit_management_request_user_entity": "👤 Send the user's telegram id or their telegram username:", "credit_management_user_not_found": "⚠️ The user with this data is not found in the database.", "crypto_withdraw": "👛 Wallet", - "crypto_wallet": "\n\n\uD83E\uDD16 Bot balances\n\n⚖\uFE0F BTC Balance: {btc_balance} BTC\n⚖\uFE0F LTC Balance: {ltc_balance} LTC\n⚖\uFE0F ETH Balance: {eth_balance} ETH\n⚖\uFE0F SOL Balance: {sol_balance} SOL\n⚖\uFE0F BNB Balance: {bnb_balance} BNB", "current_stock_header": "🗂️ Current Stock\n", "deposits_statistics": "📊 Deposits statistics", - "deposits_statistics_msg": "📊 Deposit statistics for the last {timedelta}.\n\n\uD83D\uDCB8 Total deposits: {deposits_count}\n\n\uD83D\uDCB0 Total BTC deposits for the amount: {btc_amount} BTC\n\uD83D\uDCB0 Total LTC deposits for the amount: {ltc_amount} LTC\n\uD83D\uDCB0 Total SOL deposits for the amount: {sol_amount} SOL\n\uD83D\uDCB0 Total ETH deposits in amount: {eth_amount} ETH\n\uD83D\uDCB0 Total BNB deposits in amount: {bnb_amount} BNB\n\n\uD83D\uDCBC Total cryptocurrency deposits for the amount: {fiat_amount:.2f} {currency_text}", "delete_entity": "🗑️ Delete {entity}", "delete_entity_confirmation": "❓ Do you really want to delete the {entity} with name {entity_name}?", "get_database_file": "💾 Get database file", @@ -55,7 +53,6 @@ "restocking_message_header": "🆕 New Stock Alert! 🆕\n", "sales_statistics": "📊 Sales statistics for the last {timedelta}.\n\uD83D\uDCB0 Total profit: {currency_sym}{total_profit:.2f}\n\uD83D\uDECD\uFE0F Items sold: {items_sold}\n\uD83D\uDCBC Total buys: {buys_count}", "send_everyone": "📢 Send to Everyone", - "sending_result": "✅ Message sent to {counter} out of {len} active users.\nTotal users:{users_count}", "sending_started": "🚀 Sending started", "stock": "📦 Full Inventory Message", "statistics": "📊 Analytics & Reports ", @@ -135,7 +132,14 @@ "remove_review_image_confirmation": "❓ Are you sure you want to remove the review image?", "reviews_management": "⭐ Reviews Management", "notification_new_deposit_id": "💰 New deposit by user with ID {telegram_id} for {currency_sym}{deposit_amount_fiat:.2f} with \uD83D\uDCB0 {value} {crypto_name}\n💰 Referral bonus: {currency_sym}{referral_bonus:.2f}\n💰 Referrer bonus: {currency_sym}{referrer_bonus:.2f}", - "notification_new_deposit_username": "💰 New deposit by user with username @{username} for {currency_sym}{deposit_amount_fiat:.2f} with \uD83D\uDCB0 {value} {crypto_name}\n💰 Referral bonus: {currency_sym}{referral_bonus:.2f}\n💰 Referrer bonus: {currency_sym}{referrer_bonus:.2f}" + "notification_new_deposit_username": "💰 New deposit by user with username @{username} for {currency_sym}{deposit_amount_fiat:.2f} with \uD83D\uDCB0 {value} {crypto_name}\n💰 Referral bonus: {currency_sym}{referral_bonus:.2f}\n💰 Referrer bonus: {currency_sym}{referrer_bonus:.2f}", + "crypto_wallet": "\n\n\uD83E\uDD16 Bot balances\n\n{wallet_content}", + "crypto_wallet_line": "⚖\uFE0F {crypto_name} Balance: {crypto_balance:.8f} {crypto_name}", + "deposits_statistics_msg": "📊 Deposit statistics for the last {timedelta}.\n\n\uD83D\uDCB8 Total deposits: {deposits_count}\n\n{deposits_content}\n\n\uD83D\uDCBC Total cryptocurrency deposits for the amount: {fiat_amount:.2f} {currency_text}", + "deposits_statistics_line": "💰 Total {crypto_name} deposits for the amount: {crypto_amount:.8f} {crypto_name}", + "sending_result": "✅ Message sent to {counter} out of {len} active users.\n\uD83D\uDC64 Total users:{users_count}\nStatus: {status}", + "in_progress": "🟡 In progress...", + "finished": "🟢 Finished." }, "common": { "back_button": "⬅️ Back", diff --git a/i18n/es.json b/i18n/es.json index 12759db5..73b58ea6 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -31,10 +31,8 @@ "credit_management_request_user_entity": "👤 Envía el telegram id del usuario o su nombre de usuario:", "credit_management_user_not_found": "⚠️ No se encontró al usuario con estos datos.", "crypto_withdraw": "👛 Billetera", - "crypto_wallet": "\n\n🤖 Saldos del bot\n\n⚖️ Saldo BTC: {btc_balance} BTC\n⚖️ Saldo LTC: {ltc_balance} LTC\n⚖️ Saldo ETH: {eth_balance} ETH\n⚖️ Saldo SOL: {sol_balance} SOL\n⚖️ Saldo BNB: {bnb_balance} BNB", "current_stock_header": "🗂️ Inventario actual\n", "deposits_statistics": "📊 Estadísticas de depósitos", - "deposits_statistics_msg": "📊 Estadísticas de depósitos de los últimos {timedelta}.\n\n💸 Depósitos totales: {deposits_count}\n\n💰 Depósitos BTC por un monto de: {btc_amount} BTC\n💰 Depósitos LTC por un monto de: {ltc_amount} LTC\n💰 Depósitos SOL por un monto de: {sol_amount} SOL\n💰 Depósitos ETH por un monto de: {eth_amount} ETH\n💰 Depósitos BNB por un monto de: {bnb_amount} BNB\n\n💼 Total en moneda fiduciaria: {fiat_amount:.2f} {currency_text}", "delete_entity": "🗑️ Eliminar {entity}", "delete_entity_confirmation": "❓ ¿Realmente deseas eliminar {entity} con el nombre {entity_name}?", "get_database_file": "💾 Obtener archivo de base de datos", @@ -55,7 +53,6 @@ "restocking_message_header": "🆕 ¡Nueva llegada de stock! 🆕\n", "sales_statistics": "📊 Estadísticas de ventas de los últimos {timedelta}.\n💰 Beneficio total: {currency_sym}{total_profit:.2f}\n🛍️ Artículos vendidos: {items_sold}\n💼 Total de compras: {buys_count}", "send_everyone": "📢 Enviar a todos", - "sending_result": "✅ Mensaje enviado a {counter} de {len} usuarios activos.\nUsuarios totales: {users_count}", "sending_started": "🚀 Envío iniciado", "stock": "📦 Mensaje de inventario completo", "statistics": "📊 Analítica y reportes", @@ -135,7 +132,14 @@ "remove_review_image_confirmation": "❓ ¿Estás seguro de que quieres eliminar la imagen de la reseña?", "reviews_management": "⭐ Gestión de reseñas", "notification_new_deposit_id": "💰 Nuevo depósito del usuario con ID {telegram_id} por {currency_sym}{deposit_amount_fiat:.2f} con 💰 {value} {crypto_name}\n💰 Bono por referido: {currency_sym}{referral_bonus:.2f}\n💰 Bono del referidor: {currency_sym}{referrer_bonus:.2f}", - "notification_new_deposit_username": "💰 Nuevo depósito del usuario con nombre de usuario @{username} por {currency_sym}{deposit_amount_fiat:.2f} con 💰 {value} {crypto_name}\n💰 Bono por referido: {currency_sym}{referral_bonus:.2f}\n💰 Bono del referidor: {currency_sym}{referrer_bonus:.2f}" + "notification_new_deposit_username": "💰 Nuevo depósito del usuario con nombre de usuario @{username} por {currency_sym}{deposit_amount_fiat:.2f} con 💰 {value} {crypto_name}\n💰 Bono por referido: {currency_sym}{referral_bonus:.2f}\n💰 Bono del referidor: {currency_sym}{referrer_bonus:.2f}", + "crypto_wallet": "\n\n🤖 Saldos del Bot\n\n{wallet_content}", + "crypto_wallet_line": "⚖️ Saldo de {crypto_name}: {crypto_balance:.8f} {crypto_name}", + "deposits_statistics_msg": "📊 Estadísticas de depósitos de los últimos {timedelta}.\n\n💰 Depósitos totales: {deposits_count}\n\n{deposits_content}\n\n💼 Depósitos totales de criptomonedas por valor: {fiat_amount:.2f} {currency_text}", + "deposits_statistics_line": "💰 Depósitos totales de {crypto_name} por valor: {crypto_amount:.8f} {crypto_name}", + "sending_result": "✅ Mensaje enviado a {counter} de {len} usuarios activos.\n👤 Usuarios totales: {users_count}\nEstado: {status}", + "in_progress": "🟡 En progreso...", + "finished": "🟢 Finalizado." }, "common": { "back_button": "⬅️ Atrás", diff --git a/i18n/fr.json b/i18n/fr.json index 3a67930d..a791cf56 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -31,10 +31,8 @@ "credit_management_request_user_entity": "👤 Envoyez l’ID Telegram ou le nom d’utilisateur :", "credit_management_user_not_found": "⚠️ Aucun utilisateur trouvé.", "crypto_withdraw": "👛 Portefeuille", - "crypto_wallet": "\n\n🤖 Soldes du bot\n\n⚖️ Solde BTC : {btc_balance} BTC\n⚖️ Solde LTC : {ltc_balance} LTC\n⚖️ Solde ETH : {eth_balance} ETH\n⚖️ Solde SOL : {sol_balance} SOL\n⚖️ Solde BNB : {bnb_balance} BNB", "current_stock_header": "🗂️ Stock actuel\n", "deposits_statistics": "📊 Statistiques de dépôts", - "deposits_statistics_msg": "📊 Statistiques des dépôts pour les {timedelta} derniers jours.\n\n💸 Nombre total de dépôts : {deposits_count}\n\n💰 Dépôts BTC pour un montant de : {btc_amount} BTC\n💰 Dépôts LTC pour un montant de : {ltc_amount} LTC\n💰 Dépôts SOL pour un montant de : {sol_amount} SOL\n💰 Dépôts ETH pour un montant de : {eth_amount} ETH\n💰 Dépôts BNB pour un montant de : {bnb_amount} BNB\n\n💼 Total en monnaie fiduciaire : {fiat_amount:.2f} {currency_text}", "delete_entity": "🗑️ Supprimer {entity}", "delete_entity_confirmation": "❓ Voulez-vous vraiment supprimer {entity} nommé {entity_name} ?", "get_database_file": "💾 Télécharger la base de données", @@ -55,7 +53,6 @@ "restocking_message_header": "🆕 Nouveau stock disponible ! 🆕\n", "sales_statistics": "📊 Statistiques des ventes des {timedelta} derniers jours.\n💰 Profit total : {currency_sym}{total_profit:.2f}\n🛍️ Articles vendus : {items_sold}\n💼 Nombre total d’achats : {buys_count}", "send_everyone": "📢 Envoyer à tout le monde", - "sending_result": "✅ Message envoyé à {counter} sur {len} utilisateurs actifs.\nTotal des utilisateurs : {users_count}", "sending_started": "🚀 Envoi lancé", "stock": "📦 Annonce complète du stock", "statistics": "📊 Analyses et rapports", @@ -135,7 +132,14 @@ "remove_review_image_confirmation": "❓ Êtes-vous sûr de vouloir supprimer l'image de l'avis?", "reviews_management": "⭐ Gestion des avis", "notification_new_deposit_id": "💰 Nouveau dépôt de l'utilisateur avec ID {telegram_id} pour {currency_sym}{deposit_amount_fiat:.2f} avec 💰 {value} {crypto_name}\n💰 Bonus parrainé: {currency_sym}{referral_bonus:.2f}\n💰 Bonus parrain: {currency_sym}{referrer_bonus:.2f}", - "notification_new_deposit_username": "💰 Nouveau dépôt de l'utilisateur avec nom d'utilisateur @{username} pour {currency_sym}{deposit_amount_fiat:.2f} avec 💰 {value} {crypto_name}\n💰 Bonus parrainé: {currency_sym}{referral_bonus:.2f}\n💰 Bonus parrain: {currency_sym}{referrer_bonus:.2f}" + "notification_new_deposit_username": "💰 Nouveau dépôt de l'utilisateur avec nom d'utilisateur @{username} pour {currency_sym}{deposit_amount_fiat:.2f} avec 💰 {value} {crypto_name}\n💰 Bonus parrainé: {currency_sym}{referral_bonus:.2f}\n💰 Bonus parrain: {currency_sym}{referrer_bonus:.2f}", + "crypto_wallet": "\n\n🤖 Soldes du Bot\n\n{wallet_content}", + "crypto_wallet_line": "⚖️ Solde {crypto_name}: {crypto_balance:.8f} {crypto_name}", + "deposits_statistics_msg": "📊 Statistiques des dépôts des derniers {timedelta}.\n\n💰 Dépôts totaux: {deposits_count}\n\n{deposits_content}\n\n💼 Dépôts totaux de cryptomonnaies d'une valeur de: {fiat_amount:.2f} {currency_text}", + "deposits_statistics_line": "💰 Dépôts totaux de {crypto_name} d'une valeur de: {crypto_amount:.8f} {crypto_name}", + "sending_result": "✅ Message envoyé à {counter} sur {len} utilisateurs actifs.\n👤 Utilisateurs totaux: {users_count}\nStatut: {status}", + "in_progress": "🟡 En cours...", + "finished": "🟢 Terminé." }, "common": { "back_button": "⬅️ Retour", diff --git a/i18n/it.json b/i18n/it.json index b67459fc..07e3e682 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -31,10 +31,8 @@ "credit_management_request_user_entity": "👤 Invia l’ID Telegram o username:", "credit_management_user_not_found": "⚠️ Nessun utente trovato.", "crypto_withdraw": "👛 Portafoglio", - "crypto_wallet": "\n\n🤖 Saldi del bot\n\n⚖️ Saldo BTC: {btc_balance} BTC\n⚖️ Saldo LTC: {ltc_balance} LTC\n⚖️ Saldo ETH: {eth_balance} ETH\n⚖️ Saldo SOL: {sol_balance} SOL\n⚖️ Saldo BNB: {bnb_balance} BNB", "current_stock_header": "🗂️ Magazzino attuale\n", "deposits_statistics": "📊 Statistiche depositi", - "deposits_statistics_msg": "📊 Statistiche dei depositi degli ultimi {timedelta}.\n\n💸 Depositi totali: {deposits_count}\n\n💰 Depositi BTC per un importo di: {btc_amount} BTC\n💰 Depositi LTC per un importo di: {ltc_amount} LTC\n💰 Depositi SOL per un importo di: {sol_amount} SOL\n💰 Depositi ETH per un importo di: {eth_amount} ETH\n💰 Depositi BNB per un importo di: {bnb_amount} BNB\n\n💼 Totale in valuta fiat: {fiat_amount:.2f} {currency_text}", "delete_entity": "🗑️ Elimina {entity}", "delete_entity_confirmation": "❓ Vuoi davvero eliminare {entity} chiamato {entity_name}?", "get_database_file": "💾 Scarica database", @@ -55,7 +53,6 @@ "restocking_message_header": "🆕 Nuovo stock disponibile! 🆕\n", "sales_statistics": "📊 Statistiche delle vendite degli ultimi {timedelta}.\n💰 Profitto totale: {currency_sym}{total_profit:.2f}\n🛍️ Articoli venduti: {items_sold}\n💼 Numero totale di acquisti: {buys_count}", "send_everyone": "📢 Invia a tutti", - "sending_result": "✅ Messaggio inviato a {counter} di {len} utenti attivi.\nTotale utenti: {users_count}", "sending_started": "🚀 Invio avviato", "stock": "📦 Annuncio completo magazzino", "statistics": "📊 Statistiche e report", @@ -135,7 +132,14 @@ "remove_review_image_confirmation": "❓ Sei sicuro di voler rimuovere l'immagine della recensione?", "reviews_management": "⭐ Gestione recensioni", "notification_new_deposit_id": "💰 Nuovo deposito dall'utente con ID {telegram_id} per {currency_sym}{deposit_amount_fiat:.2f} con 💰 {value} {crypto_name}\n💰 Bonus referral: {currency_sym}{referral_bonus:.2f}\n💰 Bonus referrer: {currency_sym}{referrer_bonus:.2f}", - "notification_new_deposit_username": "💰 Nuovo deposito dall'utente con username @{username} per {currency_sym}{deposit_amount_fiat:.2f} con 💰 {value} {crypto_name}\n💰 Bonus referral: {currency_sym}{referral_bonus:.2f}\n💰 Bonus referrer: {currency_sym}{referrer_bonus:.2f}" + "notification_new_deposit_username": "💰 Nuovo deposito dall'utente con username @{username} per {currency_sym}{deposit_amount_fiat:.2f} con 💰 {value} {crypto_name}\n💰 Bonus referral: {currency_sym}{referral_bonus:.2f}\n💰 Bonus referrer: {currency_sym}{referrer_bonus:.2f}", + "crypto_wallet": "\n\n🤖 Saldi del Bot\n\n{wallet_content}", + "crypto_wallet_line": "⚖️ Saldo {crypto_name}: {crypto_balance:.8f} {crypto_name}", + "deposits_statistics_msg": "📊 Statistiche depositi degli ultimi {timedelta}.\n\n💰 Depositi totali: {deposits_count}\n\n{deposits_content}\n\n💼 Depositi totali di criptovalute per un valore di: {fiat_amount:.2f} {currency_text}", + "deposits_statistics_line": "💰 Depositi totali di {crypto_name} per un valore di: {crypto_amount:.8f} {crypto_name}", + "sending_result": "✅ Messaggio inviato a {counter} su {len} utenti attivi.\n👤 Utenti totali: {users_count}\nStato: {status}", + "in_progress": "🟡 In corso...", + "finished": "🟢 Completato." }, "common": { "back_button": "⬅️ Indietro", diff --git a/i18n/zh.json b/i18n/zh.json index ed8b62fc..d54077d5 100644 --- a/i18n/zh.json +++ b/i18n/zh.json @@ -31,10 +31,8 @@ "credit_management_request_user_entity": "👤 请输入 Telegram ID 或用户名:", "credit_management_user_not_found": "⚠️ 未找到该用户。", "crypto_withdraw": "👛 钱包", - "crypto_wallet": "\n\n🤖 机器人余额\n\n⚖️ BTC 余额:{btc_balance} BTC\n⚖️ LTC 余额:{ltc_balance} LTC\n⚖️ ETH 余额:{eth_balance} ETH\n⚖️ SOL 余额:{sol_balance} SOL\n⚖️ BNB 余额:{bnb_balance} BNB", "current_stock_header": "🗂️ 当前库存\n", "deposits_statistics": "📊 充值统计", - "deposits_statistics_msg": "📊 过去 {timedelta} 的充值统计。\n\n💸 总充值次数:{deposits_count}\n\n💰 BTC 充值金额:{btc_amount} BTC\n💰 LTC 充值金额:{ltc_amount} LTC\n💰 SOL 充值金额:{sol_amount} SOL\n💰 ETH 充值金额:{eth_amount} ETH\n💰 BNB 充值金额:{bnb_amount} BNB\n\n💼 加密货币折合法币总额:{fiat_amount:.2f} {currency_text}", "delete_entity": "🗑️ 删除 {entity}", "delete_entity_confirmation": "❓ 确定要删除名为 {entity_name} 的 {entity} 吗?", "get_database_file": "💾 下载数据库", @@ -55,7 +53,6 @@ "restocking_message_header": "🆕 新库存已上架! 🆕\n", "sales_statistics": "📊 过去 {timedelta} 的销售统计。\n💰 总利润:{currency_sym}{total_profit:.2f}\n🛍️ 售出商品:{items_sold}\n💼 总购买次数:{buys_count}", "send_everyone": "📢 群发消息", - "sending_result": "✅ 消息已成功发送给 {counter}/{len} 名活跃用户。\n总用户数:{users_count}", "sending_started": "🚀 开始发送", "stock": "📦 全部库存公告", "statistics": "📊 分析与统计", @@ -135,7 +132,14 @@ "remove_review_image_confirmation": "❓ 确定要删除评价图片吗?", "reviews_management": "⭐ 评价管理", "notification_new_deposit_id": "💰 用户ID {telegram_id} 新存款 {currency_sym}{deposit_amount_fiat:.2f},使用 💰 {value} {crypto_name}\n💰 被邀请人奖金: {currency_sym}{referral_bonus:.2f}\n💰 邀请人奖金: {currency_sym}{referrer_bonus:.2f}", - "notification_new_deposit_username": "💰 用户名 @{username} 新存款 {currency_sym}{deposit_amount_fiat:.2f},使用 💰 {value} {crypto_name}\n💰 被邀请人奖金: {currency_sym}{referral_bonus:.2f}\n💰 邀请人奖金: {currency_sym}{referrer_bonus:.2f}" + "notification_new_deposit_username": "💰 用户名 @{username} 新存款 {currency_sym}{deposit_amount_fiat:.2f},使用 💰 {value} {crypto_name}\n💰 被邀请人奖金: {currency_sym}{referral_bonus:.2f}\n💰 邀请人奖金: {currency_sym}{referrer_bonus:.2f}", + "crypto_wallet": "\n\n🤖 机器人余额\n\n{wallet_content}", + "crypto_wallet_line": "⚖️ {crypto_name} 余额: {crypto_balance:.8f} {crypto_name}", + "deposits_statistics_msg": "📊 最近 {timedelta} 存款统计\n\n💰 总存款次数: {deposits_count}\n\n{deposits_content}\n\n💼 加密货币存款总价值: {fiat_amount:.2f} {currency_text}", + "deposits_statistics_line": "💰 {crypto_name} 存款总额: {crypto_amount:.8f} {crypto_name}", + "sending_result": "✅ 消息已发送给 {len} 位活跃用户中的 {counter} 位\n👤 总用户数: {users_count}\n状态: {status}", + "in_progress": "🟡 进行中...", + "finished": "🟢 已完成。" }, "common": { "back_button": "⬅️ 返回", diff --git a/repositories/button_media.py b/repositories/button_media.py index a37c5569..31733ec7 100644 --- a/repositories/button_media.py +++ b/repositories/button_media.py @@ -1,9 +1,12 @@ -from sqlalchemy import select, update +from sqlalchemy import select, update, distinct, union_all from sqlalchemy.ext.asyncio import AsyncSession from db import get_db_session, session_execute, session_commit from enums.keyboard_button import KeyboardButton from models.button_media import ButtonMedia, ButtonMediaDTO +from models.category import Category +from models.review import Review +from models.subcategory import Subcategory from utils.utils import get_bot_photo_id @@ -35,3 +38,49 @@ async def update(button_media_dto: ButtonMediaDTO, session: AsyncSession): .where(ButtonMedia.button == button_media_dto.button) .values(**button_media_dto.model_dump())) await session_execute(stmt, session) + + @staticmethod + async def get_all_file_ids(session: AsyncSession) -> list[str]: + stmt = select( + distinct( + union_all( + select(Category.media_id).where(Category.media_id != None), + select(Subcategory.media_id).where(Subcategory.media_id != None), + select(ButtonMedia.media_id).where(ButtonMedia.media_id != None), + select(Review.image_id).where(Review.image_id != None), + ).subquery().c[0] + ) + ) + result = await session_execute(stmt, session) + return result.scalars().all() + + @staticmethod + async def update_media_id(old_media_id: str, new_media_id: str, session: AsyncSession): + stmt_category = ( + update(Category) + .where(Category.media_id == old_media_id) + .values(media_id=new_media_id) + ) + + stmt_subcategory = ( + update(Subcategory) + .where(Subcategory.media_id == old_media_id) + .values(media_id=new_media_id) + ) + + stmt_buttonmedia = ( + update(ButtonMedia) + .where(ButtonMedia.media_id == old_media_id) + .values(media_id=new_media_id) + ) + + stmt_review = ( + update(Review) + .where(Review.image_id == old_media_id) + .values(image_id=new_media_id) + ) + await session_execute(stmt_category, session) + await session_execute(stmt_subcategory, session) + await session_execute(stmt_buttonmedia, session) + await session_execute(stmt_review, session) + diff --git a/services/announcement.py b/services/announcement.py index f740de9d..f85b0c87 100644 --- a/services/announcement.py +++ b/services/announcement.py @@ -14,6 +14,7 @@ from handlers.admin.constants import AdminConstants from repositories.item import ItemRepository from repositories.user import UserRepository +from services.notification import NotificationService from utils.utils import get_text @@ -40,6 +41,15 @@ async def send_announcement(callback: CallbackQuery, active_users = await UserRepository.get_active(session) all_users_count = await UserRepository.get_all_count(session) counter = 0 + message_template = get_text(language, BotEntity.ADMIN, "sending_result") + message = await callback.message.answer( + text=message_template.format( + counter=counter, + len=len(active_users), + users_count=all_users_count, + status=get_text(language, BotEntity.ADMIN, "in_progress") + ) + ) for user in active_users: try: await callback.message.copy_to(user.telegram_id, reply_markup=None) @@ -55,9 +65,25 @@ async def send_announcement(callback: CallbackQuery, except Exception as e: logging.error(e) finally: + if counter % 5 == 0: + msg = message_template.format( + counter=counter, + len=len(active_users), + users_count=all_users_count, + status=get_text(language, BotEntity.ADMIN, "in_progress") + ) + await NotificationService.edit_message(message=msg, + source_message_id=message.message_id, + chat_id=message.chat.id) if callback_data.announcement_type == AnnouncementType.RESTOCKING: await ItemRepository.set_not_new(session) await session_commit(session) - return get_text(language, BotEntity.ADMIN, "sending_result").format(counter=counter, - len=len(active_users), - users_count=all_users_count) + await NotificationService.edit_message( + message=message_template.format( + counter=counter, + len=len(active_users), + users_count=all_users_count, + status=get_text(language, BotEntity.ADMIN, "finished") + ), + source_message_id=message.message_id, + chat_id=message.chat.id) diff --git a/services/media.py b/services/media.py index 3fa21e51..6d9b0e71 100644 --- a/services/media.py +++ b/services/media.py @@ -1,10 +1,12 @@ +from aiogram import Bot +from aiogram.exceptions import TelegramBadRequest from aiogram.fsm.context import FSMContext from aiogram.types import Message, InputMediaPhoto, InputMediaVideo, InputMediaAnimation from aiogram.utils.keyboard import InlineKeyboardBuilder from sqlalchemy.ext.asyncio import AsyncSession from callbacks import MediaManagementCallback, AdminMenuCallback -from db import session_commit +from db import session_commit, get_db_session from enums.bot_entity import BotEntity from enums.entity_type import EntityType from enums.keyboard_button import KeyboardButton @@ -15,7 +17,7 @@ from repositories.category import CategoryRepository from repositories.subcategory import SubcategoryRepository from services.notification import NotificationService -from utils.utils import get_text +from utils.utils import get_text, get_bot_photo_id class MediaService: @@ -150,3 +152,21 @@ def convert_to_media(media_id: str, caption: str) -> InputMediaPhoto | InputMedi else: media = InputMediaAnimation(media=category_media_id, caption=caption) return media + + @staticmethod + async def update_inaccessible_media(bot: Bot): + bot_photo_id = f"0{get_bot_photo_id()}" + async with get_db_session() as session: + unique_file_ids = await ButtonMediaRepository.get_all_file_ids(session) + inaccessible_media_list = [] + for unique_id in unique_file_ids: + parsed_unique_id = unique_id + if parsed_unique_id[0] in ["0", "1", "2"]: + parsed_unique_id = unique_id[1:] + try: + await bot.get_file(parsed_unique_id) + except TelegramBadRequest as _: + inaccessible_media_list.append(unique_id) + for media_id in inaccessible_media_list: + await ButtonMediaRepository.update_media_id(media_id, bot_photo_id, session) + await session_commit(session) diff --git a/services/notification.py b/services/notification.py index f3966c5f..f13aa2df 100644 --- a/services/notification.py +++ b/services/notification.py @@ -182,9 +182,9 @@ async def new_buy(buy: BuyDTO, user: UserDTO, session: AsyncSession | Session): category = await CategoryRepository.get_by_id(item_example.category_id, session) subcategory = await SubcategoryRepository.get_by_id(item_example.subcategory_id, session) if user.telegram_username: - msg = get_text(Language.EN, BotEntity.ADMIN, "notification_purchase_with_tgid") - else: msg = get_text(Language.EN, BotEntity.ADMIN, "notification_purchase_with_username") + else: + msg = get_text(Language.EN, BotEntity.ADMIN, "notification_purchase_with_tgid") cart_content.append(msg.format( username=user.telegram_username, telegram_id=user.telegram_id, diff --git a/services/statistics.py b/services/statistics.py index 50293208..b8be30df 100644 --- a/services/statistics.py +++ b/services/statistics.py @@ -199,40 +199,28 @@ async def get_statistics(callback_data: StatisticsCallback, prices = await CryptoApiWrapper.get_crypto_prices() chart = StatisticsService.build_statistics_chart(deposits, callback_data.timedelta, language, prices) fiat_amount = 0.0 - btc_amount = sum( - [btc_deposit.amount if btc_deposit.network == Cryptocurrency.BTC else 0 for btc_deposit in - deposits]) - ltc_amount = sum( - [ltc_deposit.amount if ltc_deposit.network == Cryptocurrency.LTC else 0 for ltc_deposit in - deposits]) - sol_amount = sum( - [sol_deposit.amount if sol_deposit.network == Cryptocurrency.SOL else 0 for sol_deposit in - deposits]) - eth_amount = sum( - [eth_deposit.amount if eth_deposit.network == Cryptocurrency.ETH else 0 for eth_deposit in - deposits]) - bnb_amount = sum( - [bnb_deposit.amount if bnb_deposit.network == Cryptocurrency.BNB else 0 for bnb_deposit in - deposits]) - btc_amount = btc_amount / pow(10, Cryptocurrency.BTC.get_decimals()) - ltc_amount = ltc_amount / pow(10, Cryptocurrency.LTC.get_decimals()) - sol_amount = sol_amount / pow(10, Cryptocurrency.SOL.get_decimals()) - eth_amount = eth_amount / pow(10, Cryptocurrency.ETH.get_decimals()) - bnb_amount = bnb_amount / pow(10, Cryptocurrency.BNB.get_decimals()) - btc_price = prices[Cryptocurrency.BTC.get_coingecko_name()][config.CURRENCY.value.lower()] - ltc_price = prices[Cryptocurrency.LTC.get_coingecko_name()][config.CURRENCY.value.lower()] - sol_price = prices[Cryptocurrency.SOL.get_coingecko_name()][config.CURRENCY.value.lower()] - eth_price = prices[Cryptocurrency.ETH.get_coingecko_name()][config.CURRENCY.value.lower()] - bnb_price = prices[Cryptocurrency.BNB.get_coingecko_name()][config.CURRENCY.value.lower()] - fiat_amount += ((btc_amount * btc_price) + (ltc_amount * ltc_price) + (sol_amount * sol_price) - + (eth_amount * eth_price) + (bnb_amount * bnb_price)) + crypto_amount_dict = {crypto: 0 for crypto in Cryptocurrency} + for deposit in deposits: + crypto_price = prices[deposit.network.get_coingecko_name()][config.CURRENCY.value.lower()] + crypto_friendly_amount = deposit.amount / pow(10, deposit.network.get_decimals()) + crypto_amount_dict[deposit.network] += crypto_friendly_amount + fiat_amount += crypto_friendly_amount * crypto_price + fiat_amount = sum(crypto_amount_dict.values()) kb_builder.row(AdminConstants.back_to_main_button(language), callback_data.get_back_button(language)) + deposits_content_list = [] + for cryptocurrency, crypto_amount in crypto_amount_dict.items(): + deposits_content_list.append(get_text(language, + BotEntity.ADMIN, + "deposits_statistics_line").format( + crypto_name=cryptocurrency.name.replace("_", " "), + crypto_amount=crypto_amount + )) caption = get_text(language, BotEntity.ADMIN, "deposits_statistics_msg").format( - timedelta=timedelta_localized, deposits_count=len(deposits), - btc_amount=btc_amount, ltc_amount=ltc_amount, - sol_amount=sol_amount, eth_amount=eth_amount, - bnb_amount=bnb_amount, - fiat_amount=fiat_amount, currency_text=config.CURRENCY.get_localized_text()) + timedelta=timedelta_localized, + deposits_count=len(deposits), + deposits_content="\n".join(deposits_content_list), + fiat_amount=fiat_amount, + currency_text=config.CURRENCY.get_localized_text()) media = InputMediaPhoto( media=BufferedInputFile(file=chart, filename="chart.png"), diff --git a/services/wallet.py b/services/wallet.py index bc814903..5f9dc7f2 100644 --- a/services/wallet.py +++ b/services/wallet.py @@ -28,18 +28,21 @@ async def get_wallet_menu(language: Language) -> tuple[str, InlineKeyboardBuilde async def get_withdraw_menu(language: Language) -> tuple[str, InlineKeyboardBuilder]: kb_builder = InlineKeyboardBuilder() wallet_balance = await CryptoApiWrapper.get_wallet_balance() - [kb_builder.button( - text=get_text(language, BotEntity.COMMON, f"{key.lower()}_top_up"), - callback_data=WalletCallback.create(1, Cryptocurrency(key)) - ) for key in wallet_balance.keys()] + wallet_content = [] + for cryptocurrency, amount in wallet_balance.items(): + wallet_content.append(get_text(language, BotEntity.ADMIN, "crypto_wallet_line").format( + crypto_name=cryptocurrency.name.replace('_', " "), + crypto_balance=amount + )) + if amount > 0: + kb_builder.button( + text=get_text(language, BotEntity.COMMON, f"{cryptocurrency.name.lower()}_top_up"), + callback_data=WalletCallback.create(1, cryptocurrency) + ) kb_builder.adjust(1) kb_builder.row(AdminConstants.back_to_main_button(language)) msg_text = get_text(language, BotEntity.ADMIN, "crypto_wallet").format( - btc_balance=wallet_balance.get('BTC') or 0.0, - ltc_balance=wallet_balance.get('LTC') or 0.0, - sol_balance=wallet_balance.get('SOL') or 0.0, - eth_balance=wallet_balance.get('ETH') or 0.0, - bnb_balance=wallet_balance.get('BNB') or 0.0 + wallet_content="\n".join(wallet_content) ) if sum(wallet_balance.values()) > 0: msg_text += get_text(language, BotEntity.ADMIN, "choose_crypto_to_withdraw")