Update app.py
Browse files
app.py
CHANGED
|
@@ -156,8 +156,11 @@ TEMPLATE = """
|
|
| 156 |
<meta charset="UTF-8">
|
| 157 |
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no, user-scalable=no, viewport-fit=cover">
|
| 158 |
<title>Morshen Group</title>
|
|
|
|
| 159 |
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
| 160 |
-
<script
|
|
|
|
|
|
|
| 161 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 162 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 163 |
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
@@ -596,6 +599,7 @@ TEMPLATE = """
|
|
| 596 |
}
|
| 597 |
|
| 598 |
async function fetchTonBalance(address) {
|
|
|
|
| 599 |
const rpcUrl = 'https://rpc.toncenter.com/api/v2/jsonRPC';
|
| 600 |
try {
|
| 601 |
const response = await fetch(rpcUrl, {
|
|
@@ -605,7 +609,7 @@ TEMPLATE = """
|
|
| 605 |
},
|
| 606 |
body: JSON.stringify({
|
| 607 |
jsonrpc: '2.0',
|
| 608 |
-
id: 1,
|
| 609 |
method: 'getBalance',
|
| 610 |
params: {
|
| 611 |
address: address
|
|
@@ -616,32 +620,36 @@ TEMPLATE = """
|
|
| 616 |
const data = await response.json();
|
| 617 |
if (data.error) throw new Error(`RPC error: ${data.error.message}`);
|
| 618 |
const balanceNanoTon = parseInt(data.result);
|
| 619 |
-
const balanceTon = balanceNanoTon / 1e9;
|
| 620 |
-
return balanceTon.toFixed(4);
|
| 621 |
} catch (error) {
|
| 622 |
console.error('Error fetching TON balance:', error);
|
| 623 |
document.getElementById('ton-error').textContent = `Ошибка получения баланса: ${error.message}`;
|
| 624 |
document.getElementById('ton-error').style.display = 'block';
|
| 625 |
-
return 'N/A';
|
| 626 |
}
|
| 627 |
}
|
| 628 |
|
| 629 |
|
| 630 |
function initializeWebApp() {
|
|
|
|
| 631 |
if (!tg || !tg.initData) {
|
| 632 |
console.error("Telegram WebApp script not loaded or initData is missing.");
|
| 633 |
const greetingElement = document.getElementById('greeting');
|
| 634 |
if(greetingElement) greetingElement.textContent = 'Не удалось связаться с Telegram.';
|
| 635 |
-
document.body.style.visibility = 'visible';
|
| 636 |
return;
|
| 637 |
}
|
| 638 |
|
|
|
|
| 639 |
tg.ready();
|
| 640 |
tg.expand();
|
| 641 |
|
|
|
|
| 642 |
applyTheme(tg.themeParams);
|
| 643 |
tg.onEvent('themeChanged', () => applyTheme(tg.themeParams));
|
| 644 |
|
|
|
|
| 645 |
fetch('/verify', {
|
| 646 |
method: 'POST',
|
| 647 |
headers: {
|
|
@@ -665,6 +673,7 @@ TEMPLATE = """
|
|
| 665 |
console.error('Error sending initData for verification:', error);
|
| 666 |
});
|
| 667 |
|
|
|
|
| 668 |
const user = tg.initDataUnsafe?.user;
|
| 669 |
const greetingElement = document.getElementById('greeting');
|
| 670 |
if (user) {
|
|
@@ -674,14 +683,17 @@ TEMPLATE = """
|
|
| 674 |
greetingElement.textContent = 'Добро пожаловать!';
|
| 675 |
}
|
| 676 |
|
|
|
|
| 677 |
const contactButtons = document.querySelectorAll('.contact-link');
|
| 678 |
contactButtons.forEach(button => {
|
| 679 |
button.addEventListener('click', (e) => {
|
| 680 |
e.preventDefault();
|
|
|
|
| 681 |
tg.openTelegramLink('https://t.me/morshenkhan');
|
| 682 |
});
|
| 683 |
});
|
| 684 |
|
|
|
|
| 685 |
const modal = document.getElementById("saveModal");
|
| 686 |
const saveCardBtn = document.getElementById("save-card-btn");
|
| 687 |
const closeBtn = document.getElementById("modal-close-btn");
|
|
@@ -706,8 +718,10 @@ TEMPLATE = """
|
|
| 706 |
});
|
| 707 |
}
|
| 708 |
|
|
|
|
| 709 |
document.body.style.visibility = 'visible';
|
| 710 |
|
|
|
|
| 711 |
// --- TON Connect Integration ---
|
| 712 |
const connectTonBtn = document.getElementById('connect-ton-btn');
|
| 713 |
const disconnectTonBtn = document.getElementById('disconnect-ton-btn');
|
|
@@ -717,39 +731,45 @@ TEMPLATE = """
|
|
| 717 |
const tonInfoDiv = document.getElementById('ton-info');
|
| 718 |
const tonErrorDiv = document.getElementById('ton-error');
|
| 719 |
|
|
|
|
| 720 |
if (connectTonBtn && disconnectTonBtn && tonWalletStatus && tonWalletAddress && tonBalance && tonInfoDiv && tonErrorDiv) {
|
| 721 |
-
// Ensure TonConnectSDK is available globally
|
| 722 |
if (!window.TonConnectSDK) {
|
| 723 |
-
console.error("TON Connect SDK script not loaded correctly.");
|
| 724 |
tonErrorDiv.textContent = 'Ошибка: TON Connect SDK не загружен.';
|
| 725 |
tonErrorDiv.style.display = 'block';
|
| 726 |
-
connectTonBtn.disabled = true;
|
| 727 |
-
return;
|
| 728 |
}
|
| 729 |
|
| 730 |
try {
|
|
|
|
| 731 |
tonConnectSDK = new window.TonConnectSDK.TonConnectSDK({
|
| 732 |
manifestUrl: window.location.origin + '/tonconnect-manifest.json',
|
| 733 |
actionsConfiguration: {
|
| 734 |
-
|
|
|
|
|
|
|
| 735 |
}
|
| 736 |
});
|
| 737 |
|
|
|
|
| 738 |
tonConnectSDK.onStatusChange(async wallet => {
|
| 739 |
tonErrorDiv.style.display = 'none'; // Clear previous errors
|
| 740 |
if (wallet) {
|
|
|
|
| 741 |
tonWalletStatus.textContent = 'Статус: Подключен ✅';
|
| 742 |
tonWalletAddress.textContent = wallet.account.address;
|
| 743 |
tonInfoDiv.style.display = 'block';
|
| 744 |
connectTonBtn.style.display = 'none';
|
| 745 |
disconnectTonBtn.style.display = 'inline-flex';
|
| 746 |
-
tonBalance.textContent = 'Загрузка...';
|
| 747 |
const balance = await fetchTonBalance(wallet.account.address);
|
| 748 |
tonBalance.textContent = balance;
|
| 749 |
if (tg.HapticFeedback) {
|
| 750 |
tg.HapticFeedback.notificationOccurred('success');
|
| 751 |
}
|
| 752 |
} else {
|
|
|
|
| 753 |
tonWalletStatus.textContent = 'Статус: Не подключен';
|
| 754 |
tonWalletAddress.textContent = '';
|
| 755 |
tonBalance.textContent = '';
|
|
@@ -760,37 +780,41 @@ TEMPLATE = """
|
|
| 760 |
tg.HapticFeedback.notificationOccurred('warning');
|
| 761 |
}
|
| 762 |
}
|
| 763 |
-
//
|
|
|
|
| 764 |
});
|
| 765 |
|
|
|
|
| 766 |
connectTonBtn.addEventListener('click', () => {
|
| 767 |
tonErrorDiv.style.display = 'none'; // Clear error on button click
|
| 768 |
-
tonConnectSDK.connectWallet();
|
| 769 |
});
|
| 770 |
|
| 771 |
disconnectTonBtn.addEventListener('click', () => {
|
| 772 |
tonErrorDiv.style.display = 'none'; // Clear error on button click
|
| 773 |
-
tonConnectSDK.disconnect();
|
| 774 |
});
|
| 775 |
|
|
|
|
| 776 |
tonConnectSDK.restoreConnection();
|
| 777 |
|
| 778 |
} catch (e) {
|
|
|
|
| 779 |
console.error("Error initializing TON Connect SDK:", e);
|
| 780 |
tonErrorDiv.textContent = `Ошибка инициализации TON Connect: ${e.message}`;
|
| 781 |
tonErrorDiv.style.display = 'block';
|
| 782 |
-
connectTonBtn.disabled = true; // Disable button on error
|
| 783 |
}
|
| 784 |
} else {
|
| 785 |
-
console.error("TON Wallet section elements not found!");
|
| 786 |
}
|
| 787 |
// --- End TON Connect Integration ---
|
| 788 |
}
|
| 789 |
|
| 790 |
-
// Wait for the window to fully load (including
|
| 791 |
window.addEventListener('load', initializeWebApp);
|
| 792 |
|
| 793 |
-
// Fallback
|
| 794 |
setTimeout(() => {
|
| 795 |
if (document.body.style.visibility !== 'visible') {
|
| 796 |
console.error("WebApp initialization fallback timeout triggered.");
|
|
@@ -798,7 +822,7 @@ TEMPLATE = """
|
|
| 798 |
if(greetingElement) greetingElement.textContent = 'Ошибка загрузки интерфейса.';
|
| 799 |
document.body.style.visibility = 'visible';
|
| 800 |
}
|
| 801 |
-
}, 5000); // Increased timeout slightly
|
| 802 |
</script>
|
| 803 |
</body>
|
| 804 |
</html>
|
|
@@ -1064,10 +1088,13 @@ def verify_data():
|
|
| 1064 |
|
| 1065 |
@app.route('/tonconnect-manifest.json')
|
| 1066 |
def tonconnect_manifest():
|
|
|
|
|
|
|
| 1067 |
manifest = {
|
| 1068 |
"url": request.url_root.rstrip('/') + "/tonconnect-manifest.json",
|
| 1069 |
"name": "Morshen Group Mini App",
|
| 1070 |
-
"iconUrl":
|
|
|
|
| 1071 |
"termsOfUseUrl": "https://example.com/terms",
|
| 1072 |
"privacyPolicyUrl": "https://example.com/privacy"
|
| 1073 |
}
|
|
@@ -1096,16 +1123,23 @@ def admin_trigger_upload():
|
|
| 1096 |
|
| 1097 |
@app.route('/static/<path:filename>')
|
| 1098 |
def static_files(filename):
|
| 1099 |
-
|
| 1100 |
-
|
| 1101 |
-
|
| 1102 |
-
|
| 1103 |
-
|
| 1104 |
-
|
| 1105 |
-
|
|
|
|
|
|
|
| 1106 |
|
| 1107 |
|
| 1108 |
if __name__ == '__main__':
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1109 |
download_data_from_hf()
|
| 1110 |
load_visitor_data()
|
| 1111 |
|
|
|
|
| 156 |
<meta charset="UTF-8">
|
| 157 |
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no, user-scalable=no, viewport-fit=cover">
|
| 158 |
<title>Morshen Group</title>
|
| 159 |
+
<!-- Telegram WebApp script MUST be loaded first -->
|
| 160 |
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
| 161 |
+
<!-- TON Connect SDK script with defer to avoid blocking -->
|
| 162 |
+
<script src="https://unpkg.com/@tonconnect/sdk@2.0.0/dist/tonconnect-sdk.min.js" defer></script>
|
| 163 |
+
|
| 164 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 165 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 166 |
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
|
|
| 599 |
}
|
| 600 |
|
| 601 |
async function fetchTonBalance(address) {
|
| 602 |
+
// Using public RPC endpoint provided by TON Center
|
| 603 |
const rpcUrl = 'https://rpc.toncenter.com/api/v2/jsonRPC';
|
| 604 |
try {
|
| 605 |
const response = await fetch(rpcUrl, {
|
|
|
|
| 609 |
},
|
| 610 |
body: JSON.stringify({
|
| 611 |
jsonrpc: '2.0',
|
| 612 |
+
id: 1, // Request ID, can be anything
|
| 613 |
method: 'getBalance',
|
| 614 |
params: {
|
| 615 |
address: address
|
|
|
|
| 620 |
const data = await response.json();
|
| 621 |
if (data.error) throw new Error(`RPC error: ${data.error.message}`);
|
| 622 |
const balanceNanoTon = parseInt(data.result);
|
| 623 |
+
const balanceTon = balanceNanoTon / 1e9; // Convert NanoTON to TON
|
| 624 |
+
return balanceTon.toFixed(4); // Format to 4 decimal places
|
| 625 |
} catch (error) {
|
| 626 |
console.error('Error fetching TON balance:', error);
|
| 627 |
document.getElementById('ton-error').textContent = `Ошибка получения баланса: ${error.message}`;
|
| 628 |
document.getElementById('ton-error').style.display = 'block';
|
| 629 |
+
return 'N/A'; // Return N/A on error
|
| 630 |
}
|
| 631 |
}
|
| 632 |
|
| 633 |
|
| 634 |
function initializeWebApp() {
|
| 635 |
+
// Check if Telegram WebApp is ready
|
| 636 |
if (!tg || !tg.initData) {
|
| 637 |
console.error("Telegram WebApp script not loaded or initData is missing.");
|
| 638 |
const greetingElement = document.getElementById('greeting');
|
| 639 |
if(greetingElement) greetingElement.textContent = 'Не удалось связаться с Telegram.';
|
| 640 |
+
document.body.style.visibility = 'visible'; // Show body even on error
|
| 641 |
return;
|
| 642 |
}
|
| 643 |
|
| 644 |
+
// Initialize Telegram WebApp features
|
| 645 |
tg.ready();
|
| 646 |
tg.expand();
|
| 647 |
|
| 648 |
+
// Apply theme based on Telegram app theme
|
| 649 |
applyTheme(tg.themeParams);
|
| 650 |
tg.onEvent('themeChanged', () => applyTheme(tg.themeParams));
|
| 651 |
|
| 652 |
+
// Send initData to backend for verification and logging
|
| 653 |
fetch('/verify', {
|
| 654 |
method: 'POST',
|
| 655 |
headers: {
|
|
|
|
| 673 |
console.error('Error sending initData for verification:', error);
|
| 674 |
});
|
| 675 |
|
| 676 |
+
// Display user greeting
|
| 677 |
const user = tg.initDataUnsafe?.user;
|
| 678 |
const greetingElement = document.getElementById('greeting');
|
| 679 |
if (user) {
|
|
|
|
| 683 |
greetingElement.textContent = 'Добро пожаловать!';
|
| 684 |
}
|
| 685 |
|
| 686 |
+
// Setup Contact Links to open Telegram chat
|
| 687 |
const contactButtons = document.querySelectorAll('.contact-link');
|
| 688 |
contactButtons.forEach(button => {
|
| 689 |
button.addEventListener('click', (e) => {
|
| 690 |
e.preventDefault();
|
| 691 |
+
// Replace 'morshenkhan' with the actual Telegram username
|
| 692 |
tg.openTelegramLink('https://t.me/morshenkhan');
|
| 693 |
});
|
| 694 |
});
|
| 695 |
|
| 696 |
+
// Setup Save Card Modal
|
| 697 |
const modal = document.getElementById("saveModal");
|
| 698 |
const saveCardBtn = document.getElementById("save-card-btn");
|
| 699 |
const closeBtn = document.getElementById("modal-close-btn");
|
|
|
|
| 718 |
});
|
| 719 |
}
|
| 720 |
|
| 721 |
+
// Show body after initial setup is complete
|
| 722 |
document.body.style.visibility = 'visible';
|
| 723 |
|
| 724 |
+
|
| 725 |
// --- TON Connect Integration ---
|
| 726 |
const connectTonBtn = document.getElementById('connect-ton-btn');
|
| 727 |
const disconnectTonBtn = document.getElementById('disconnect-ton-btn');
|
|
|
|
| 731 |
const tonInfoDiv = document.getElementById('ton-info');
|
| 732 |
const tonErrorDiv = document.getElementById('ton-error');
|
| 733 |
|
| 734 |
+
// Check if TON Connect elements exist and TON Connect SDK is loaded
|
| 735 |
if (connectTonBtn && disconnectTonBtn && tonWalletStatus && tonWalletAddress && tonBalance && tonInfoDiv && tonErrorDiv) {
|
|
|
|
| 736 |
if (!window.TonConnectSDK) {
|
| 737 |
+
console.error("TON Connect SDK script not loaded correctly or window.TonConnectSDK is not defined.");
|
| 738 |
tonErrorDiv.textContent = 'Ошибка: TON Connect SDK не загружен.';
|
| 739 |
tonErrorDiv.style.display = 'block';
|
| 740 |
+
connectTonBtn.disabled = true; // Disable connect button
|
| 741 |
+
return; // Stop TON Connect initialization
|
| 742 |
}
|
| 743 |
|
| 744 |
try {
|
| 745 |
+
// Initialize TON Connect SDK
|
| 746 |
tonConnectSDK = new window.TonConnectSDK.TonConnectSDK({
|
| 747 |
manifestUrl: window.location.origin + '/tonconnect-manifest.json',
|
| 748 |
actionsConfiguration: {
|
| 749 |
+
// !!! IMPORTANT: Replace with your actual bot username and Mini App shortname
|
| 750 |
+
// Example: 'https://t.me/MyAwesomeBot/MyMiniApp'
|
| 751 |
+
twaReturnUrl: 'https://t.me/YOUR_BOT_USERNAME/YOUR_MINIAPP_SHORTNAME'
|
| 752 |
}
|
| 753 |
});
|
| 754 |
|
| 755 |
+
// Subscribe to wallet status changes
|
| 756 |
tonConnectSDK.onStatusChange(async wallet => {
|
| 757 |
tonErrorDiv.style.display = 'none'; // Clear previous errors
|
| 758 |
if (wallet) {
|
| 759 |
+
// Wallet is connected
|
| 760 |
tonWalletStatus.textContent = 'Статус: Подключен ✅';
|
| 761 |
tonWalletAddress.textContent = wallet.account.address;
|
| 762 |
tonInfoDiv.style.display = 'block';
|
| 763 |
connectTonBtn.style.display = 'none';
|
| 764 |
disconnectTonBtn.style.display = 'inline-flex';
|
| 765 |
+
tonBalance.textContent = 'Загрузка...'; // Show loading state for balance
|
| 766 |
const balance = await fetchTonBalance(wallet.account.address);
|
| 767 |
tonBalance.textContent = balance;
|
| 768 |
if (tg.HapticFeedback) {
|
| 769 |
tg.HapticFeedback.notificationOccurred('success');
|
| 770 |
}
|
| 771 |
} else {
|
| 772 |
+
// Wallet is disconnected
|
| 773 |
tonWalletStatus.textContent = 'Статус: Не подключен';
|
| 774 |
tonWalletAddress.textContent = '';
|
| 775 |
tonBalance.textContent = '';
|
|
|
|
| 780 |
tg.HapticFeedback.notificationOccurred('warning');
|
| 781 |
}
|
| 782 |
}
|
| 783 |
+
// Hide Telegram's MainButton if it might be interfering
|
| 784 |
+
// tg.MainButton.hide();
|
| 785 |
});
|
| 786 |
|
| 787 |
+
// Add event listeners for connect/disconnect buttons
|
| 788 |
connectTonBtn.addEventListener('click', () => {
|
| 789 |
tonErrorDiv.style.display = 'none'; // Clear error on button click
|
| 790 |
+
tonConnectSDK.connectWallet(); // Trigger wallet connection flow
|
| 791 |
});
|
| 792 |
|
| 793 |
disconnectTonBtn.addEventListener('click', () => {
|
| 794 |
tonErrorDiv.style.display = 'none'; // Clear error on button click
|
| 795 |
+
tonConnectSDK.disconnect(); // Trigger wallet disconnection
|
| 796 |
});
|
| 797 |
|
| 798 |
+
// Attempt to restore previous connection on startup
|
| 799 |
tonConnectSDK.restoreConnection();
|
| 800 |
|
| 801 |
} catch (e) {
|
| 802 |
+
// Catch errors specifically during SDK instantiation
|
| 803 |
console.error("Error initializing TON Connect SDK:", e);
|
| 804 |
tonErrorDiv.textContent = `Ошибка инициализации TON Connect: ${e.message}`;
|
| 805 |
tonErrorDiv.style.display = 'block';
|
| 806 |
+
connectTonBtn.disabled = true; // Disable button on initialization error
|
| 807 |
}
|
| 808 |
} else {
|
| 809 |
+
console.error("TON Wallet section HTML elements not found!");
|
| 810 |
}
|
| 811 |
// --- End TON Connect Integration ---
|
| 812 |
}
|
| 813 |
|
| 814 |
+
// Wait for the window to fully load all resources (including deferred scripts)
|
| 815 |
window.addEventListener('load', initializeWebApp);
|
| 816 |
|
| 817 |
+
// Fallback timeout in case something prevents 'load' or Telegram script
|
| 818 |
setTimeout(() => {
|
| 819 |
if (document.body.style.visibility !== 'visible') {
|
| 820 |
console.error("WebApp initialization fallback timeout triggered.");
|
|
|
|
| 822 |
if(greetingElement) greetingElement.textContent = 'Ошибка загрузки интерфейса.';
|
| 823 |
document.body.style.visibility = 'visible';
|
| 824 |
}
|
| 825 |
+
}, 5000); // Increased timeout slightly to 5 seconds
|
| 826 |
</script>
|
| 827 |
</body>
|
| 828 |
</html>
|
|
|
|
| 1088 |
|
| 1089 |
@app.route('/tonconnect-manifest.json')
|
| 1090 |
def tonconnect_manifest():
|
| 1091 |
+
# You need to serve your icon file, e.g., place morshengroup.png in a 'static' folder
|
| 1092 |
+
icon_url = request.url_root.rstrip('/') + "/static/morshengroup.png"
|
| 1093 |
manifest = {
|
| 1094 |
"url": request.url_root.rstrip('/') + "/tonconnect-manifest.json",
|
| 1095 |
"name": "Morshen Group Mini App",
|
| 1096 |
+
"iconUrl": icon_url,
|
| 1097 |
+
# !!! IMPORTANT: Replace with actual URLs for your terms of use and privacy policy
|
| 1098 |
"termsOfUseUrl": "https://example.com/terms",
|
| 1099 |
"privacyPolicyUrl": "https://example.com/privacy"
|
| 1100 |
}
|
|
|
|
| 1123 |
|
| 1124 |
@app.route('/static/<path:filename>')
|
| 1125 |
def static_files(filename):
|
| 1126 |
+
# Assuming you place your icon file (e.g., morshengroup.png) in a 'static' folder
|
| 1127 |
+
# relative to your app.py file.
|
| 1128 |
+
# For the manifest, a PNG is usually required, but the original code used JPG.
|
| 1129 |
+
# Let's assume you have a morshengroup.png in a static folder.
|
| 1130 |
+
static_dir = os.path.join(app.root_path, 'static')
|
| 1131 |
+
return send_from_directory(static_dir, filename)
|
| 1132 |
+
|
| 1133 |
+
# Import send_from_directory for the static route
|
| 1134 |
+
from flask import send_from_directory
|
| 1135 |
|
| 1136 |
|
| 1137 |
if __name__ == '__main__':
|
| 1138 |
+
# Ensure the static directory exists if you're using it for icons
|
| 1139 |
+
if not os.path.exists('static'):
|
| 1140 |
+
os.makedirs('static')
|
| 1141 |
+
# You should place your morshengroup.png file inside the new 'static' folder
|
| 1142 |
+
|
| 1143 |
download_data_from_hf()
|
| 1144 |
load_visitor_data()
|
| 1145 |
|