├── requirements.txt
├── static
├── captcha
│ ├── captcha-bg.png
│ ├── script.js
│ └── style.css
├── qrCode
│ ├── script.js
│ └── styles.css
├── captchaV2
│ └── styles.css
├── telegram.svg
└── form-template
│ └── style.css
├── .env
├── templates
├── qrCode.html
├── captchav2.html
├── captcha.html
└── demoForm.html
├── README.md
├── LICENSE
├── bot.py
└── server.py
/requirements.txt:
--------------------------------------------------------------------------------
1 | Flask
2 | requests
3 | python-dotenv
4 | pyTelegramBotAPI
5 |
--------------------------------------------------------------------------------
/static/captcha/captcha-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tech-savvy-guy/telegram-web-apps/HEAD/static/captcha/captcha-bg.png
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | API_TOKEN = '1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZ' # Token from @BotFather
2 | SITE_KEY = "re-CAPTCHA-site-key" # Site key from https://www.google.com/recaptcha/admin
3 | SECRET_KEY = "re-CAPTCHA-secret-key" # Secret key from https://www.google.com/recaptcha/admin
4 |
--------------------------------------------------------------------------------
/static/qrCode/script.js:
--------------------------------------------------------------------------------
1 | Telegram.WebApp.ready()
2 | Telegram.WebApp.expand()
3 |
4 | function handleQRTextReceived(event) {
5 | var qrText = event.data;
6 |
7 | fetch('/qrCodeResponse', {
8 | method: 'POST',
9 | headers: {
10 | 'Accept': 'application/json',
11 | 'Content-Type': 'application/json'
12 | },
13 | body: JSON.stringify({ qr: qrText, initData: window.Telegram.WebApp.initData })
14 | });
15 |
16 | Telegram.WebApp.offEvent('qrTextReceived', handleQRTextReceived);
17 |
18 | setTimeout(Telegram.WebApp.closeScanQrPopup(), 1000);
19 | }
20 |
21 | Telegram.WebApp.onEvent('qrTextReceived', handleQRTextReceived);
--------------------------------------------------------------------------------
/templates/qrCode.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | QR SCANNER
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | SCAN CODE
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Telegram Web Apps ➜ ***[@pytba_web_app_bot](https://t.me/pytba_web_app_bot/)***
2 |
3 | This is a collection of some Web Apps that I designed for Telegram.
4 |
5 | All the Web Apps are designed using **[pyTelegramBotAPI](https://github.com/eternnoir/pyTelegramBotAPI)**.
6 |
7 | ## List of Web Apps included here:
8 |
9 | - **Demo Forms** : An easy way to collect inputs from users supporting a lot of input types (dates, files, etc.)
10 |
11 | - **CAPTCHA** : An simple verification system to keep spammers away!
12 |
13 | - **re-CAPTCHA** : Google's re-CAPTCHA system integrated within Telegram to filter unwanted spammers. This can be implemented within large groups.
14 |
15 | - **QR-Code** : A built in QR Code scanner within Telegram.
16 |
17 | The main purpose for this project is to help new-comers to start designing their own Mini Apps!
18 |
19 | **If you like this project, kindly give a star! ⭐**
20 |
21 | **Contributions are welcome! 😇**
22 |
--------------------------------------------------------------------------------
/templates/captchav2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Google Captcha
10 |
11 |
12 |
13 |
Google reCAPTCHA
14 |
19 |
20 |
26 |
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Soham Datta
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/static/captchaV2/styles.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Poppins:400,500,600,700&display=swap');
2 |
3 | * {
4 | margin: 0;
5 | padding: 0;
6 | font-family: 'Poppins', sans-serif;
7 | max-width: 100%;
8 | max-height: 100%;
9 | }
10 |
11 | body {
12 | height: 100vh;
13 | display: flex;
14 | flex-direction: column;
15 | align-items: center;
16 | justify-content: center;
17 | background: linear-gradient(135deg, #8c00ff, #00ffd0);
18 | }
19 |
20 | .container {
21 | max-width: 50%;
22 | max-height: 75%;
23 | background-color: #fff;
24 | display: flex;
25 | flex-direction: column;
26 | align-items: center;
27 | justify-content: center;
28 | padding: 50px;
29 | border-radius: 25px;
30 | }
31 |
32 | h1 {
33 | margin-bottom: 30px;
34 | }
35 |
36 | input {
37 | width: 100%;
38 | height: 50px;
39 | border: none;
40 | border-radius: 10px;
41 | padding: 0 15px;
42 | margin-bottom: 20px;
43 | font-size: 16px;
44 | background-color: #00b7ff;
45 | color: #fff;
46 | font-weight: 600;
47 | }
48 |
49 | .g-recaptcha {
50 | margin-bottom: 20px;
51 | }
--------------------------------------------------------------------------------
/templates/captcha.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | CAPTCHA
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/static/qrCode/styles.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Noto+Serif:ital@1&family=Poppins:wght@400;500;625;700;800&display=swap');
2 |
3 | * {
4 | margin: 0;
5 | padding: 0;
6 | box-sizing: border-box;
7 | font-family: "Poppins", sans-serif;
8 | }
9 |
10 | ::selection {
11 | color: #fff;
12 | background: #4db2ec;
13 | }
14 |
15 | body {
16 | display: flex;
17 | align-items: center;
18 | justify-content: center;
19 | min-height: 100vh;
20 | background: #4db2ec;
21 | }
22 |
23 | .wrapper {
24 | margin: 20px;
25 | max-width: 400px;
26 | width: 100%;
27 | background: #fff;
28 | padding: 45px 20px 30px 20px;
29 | border-radius: 10px;
30 | box-shadow: 8px 8px 8px rgba(0, 0, 0, 0.05);
31 | display: flex;
32 | flex-direction: column;
33 | align-items: center;
34 | justify-content: center;
35 | }
36 |
37 | .wrapper header {
38 | color: #237db2;
39 | font-size: 35px;
40 | font-weight: 625;
41 | text-align: center;
42 | margin-bottom: 45px;
43 | }
44 |
45 | .wrapper button {
46 | align-items: center;
47 | outline: none;
48 | border: none;
49 | color: #fff;
50 | cursor: pointer;
51 | background: #4db2ec;
52 | border-radius: 5px;
53 | transition: all 0.3s ease;
54 | padding: 10px 20px;
55 | font-weight: 450;
56 | font-size: 25px;
57 | }
58 |
59 | .wrapper button:hover {
60 | background: #2fa5e9;
61 | }
--------------------------------------------------------------------------------
/static/telegram.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bot.py:
--------------------------------------------------------------------------------
1 | import os
2 | import dotenv
3 | dotenv.load_dotenv()
4 |
5 | import telebot
6 | from telebot.types import WebAppInfo
7 | from telebot.types import InlineKeyboardMarkup
8 | from telebot.types import InlineKeyboardButton
9 |
10 | bot = telebot.TeleBot(os.getenv('API_TOKEN'), parse_mode="HTML")
11 |
12 | web_apps = [
13 | {"label" : "Demo Forms", "link" : "https://your-server.domain/demoForm"},
14 | {"label" : "CAPTCHA", "link" : "https://your-server.domain/captcha"},
15 | {"label" : "QR Code", "link" : "https://your-server.domain/qrCode"},
16 | {"label" : "re-CAPTCHA", "link" : "https://your-server.domain/captchav2"},
17 | ]
18 |
19 | @bot.message_handler(commands=["start"])
20 | def start(message):
21 |
22 | markup = InlineKeyboardMarkup()
23 | markup.row(InlineKeyboardButton(web_apps[0]["label"],
24 | web_app=WebAppInfo(web_apps[0]["link"])))
25 | markup.row(
26 | InlineKeyboardButton("⬅️", callback_data="web-app:0"),
27 | InlineKeyboardButton("➡️", callback_data="web-app:2")
28 | )
29 |
30 | bot.send_message(message.chat.id, "Hey there! "
31 | "Wanna see some cool Telegram Web Apps? 🔥\n\n"
32 | "Browse using the butons below 👇🏻 ", reply_markup=markup)
33 |
34 |
35 | @bot.callback_query_handler(func=lambda call: True)
36 | def callback_listener(call):
37 |
38 | _id, data = call.id, call.data
39 |
40 | if data[:7] == "web-app":
41 | index = int(data[8:])
42 |
43 | if index == 0:
44 | bot.answer_callback_query(_id, "Oops! Start of list!", show_alert=True)
45 | elif index > len(web_apps):
46 | bot.answer_callback_query(_id, "Oops! End of list!", show_alert=True)
47 | else:
48 | prev_button = InlineKeyboardButton("⬅️", callback_data=f"web-app:{index-1}")
49 | next_button = InlineKeyboardButton("➡️", callback_data=f"web-app:{index+1}")
50 | web_app_btn = InlineKeyboardButton(web_apps[index-1]["label"],
51 | web_app=WebAppInfo(web_apps[index-1]["link"]))
52 |
53 | markup = InlineKeyboardMarkup()
54 | markup.row(web_app_btn)
55 | markup.row(prev_button, next_button)
56 |
57 | bot.edit_message_reply_markup(
58 | call.message.chat.id,
59 | call.message.id, reply_markup=markup)
60 |
61 | elif data[:7] == "confirm":
62 | bot.send_message(int(data[8:]), "Thanks for trying out the WebApp.\n\
63 | \nIn case you liked it, kindly star ⭐ the project on\
64 | GitHub .\n\
65 | \nContributions are welcome! 😇 ")
66 | bot.edit_message_reply_markup(inline_message_id=call.inline_message_id)
67 |
68 | @bot.message_handler(commands=["help"])
69 | def help(message):
70 | bot.send_message(message.chat.id,
71 | "To report a bug, please visit the\
72 | issues page on\
73 | GitHub .\n\
74 | \nFor any other questions, contact developer ➜ @tech_savvy_guy ")
75 |
76 | if __name__ == "__main__":
77 | print(f'@{bot.get_me().username} is up and running! 🚀')
78 | bot.infinity_polling(skip_pending=True)
79 |
--------------------------------------------------------------------------------
/templates/demoForm.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Form Demo
13 |
34 |
35 |
36 |
37 |
38 |
67 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/static/captcha/script.js:
--------------------------------------------------------------------------------
1 | const captcha = document.querySelector(".captcha"),
2 | reloadBtn = document.querySelector(".reload-btn"),
3 | inputField = document.querySelector(".input-area input"),
4 | checkBtn = document.querySelector(".check-btn"),
5 | statusTxt = document.querySelector(".status-text"),
6 | maxtries = document.querySelector(".error");
7 |
8 | var attempts = 0;
9 |
10 | Telegram.WebApp.ready()
11 |
12 | let allCharacters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
13 | 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
14 | 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
15 | 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
16 | function getCaptcha(){
17 | for (let i = 0; i < 6; i++) {
18 | let randomCharacter = allCharacters[Math.floor(Math.random() * allCharacters.length)];
19 | captcha.innerText += ` ${randomCharacter}`;
20 | }
21 | }
22 | getCaptcha();
23 | reloadBtn.addEventListener("click", ()=>{
24 | removeContent();
25 | getCaptcha();
26 | });
27 |
28 | checkBtn.addEventListener("click", e =>{
29 | e.preventDefault();
30 | statusTxt.style.display = "block";
31 | let inputVal = inputField.value.split('').join(' ');
32 | if(inputVal == captcha.innerText){
33 | statusTxt.style.color = "#4db2ec";
34 | statusTxt.innerText = "Nice! You don't appear to be a robot.";
35 | checkBtn.style.display = "none";
36 | fetch('/captchaResponse', {
37 | method: 'POST',
38 | headers: {
39 | 'Accept': 'application/json',
40 | 'Content-Type': 'application/json'
41 | },
42 | body: JSON.stringify({
43 | isbot: false, attempts: attempts + 1,
44 | initData: window.Telegram.WebApp.initData
45 | })
46 | });
47 | attempts = 0;
48 |
49 | }else{
50 | if (attempts < 2) {
51 | statusTxt.style.color = "#ff0000";
52 | statusTxt.innerText = "Error! Remaining attempts: " + (2 - attempts);
53 | fetch('/captchaResponse', {
54 | method: 'POST',
55 | headers: {
56 | 'Accept': 'application/json',
57 | 'Content-Type': 'application/json'
58 | },
59 | body: JSON.stringify({
60 | isbot: true, attempts: attempts + 1,
61 | initData: window.Telegram.WebApp.initData
62 | })
63 | });
64 | inputField.value = "";
65 | captcha.innerText = "";
66 | attempts = attempts + 1;
67 | getCaptcha();
68 | }
69 |
70 | else {
71 | maxtries.style.display = "block";
72 | statusTxt.style.display = "none";
73 | maxtries.innerText = "Maximum attempts reached!";
74 | document.querySelector(".input-area").style.display = "none";
75 | document.querySelector(".captcha-area").style.display = "none";
76 | fetch('/captchaResponse', {
77 | method: 'POST',
78 | headers: {
79 | 'Accept': 'application/json',
80 | 'Content-Type': 'application/json'
81 | },
82 | body: JSON.stringify({
83 | isbot: true, attempts: attempts + 1,
84 | initData: window.Telegram.WebApp.initData
85 | })
86 | });
87 | }
88 | }
89 | });
90 |
91 | function removeContent(){
92 | inputField.value = "";
93 | captcha.innerText = "";
94 | statusTxt.style.display = "none";
95 | }
--------------------------------------------------------------------------------
/static/form-template/style.css:
--------------------------------------------------------------------------------
1 | /* ===== Google Font Import - Poformsins ===== */
2 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
3 |
4 | *{
5 | margin: 0;
6 | padding: 0;
7 | box-sizing: border-box;
8 | font-family: 'Poppins', sans-serif;
9 | }
10 |
11 | body{
12 | height: 100vh;
13 | display: flex;
14 | align-items: center;
15 | justify-content: center;
16 | background-color: #4070f4;
17 | }
18 |
19 | .container{
20 | position: relative;
21 | max-width: 430px;
22 | width: 100%;
23 | background: #fff;
24 | border-radius: 10px;
25 | box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
26 | overflow: hidden;
27 | margin: 0 20px;
28 | }
29 |
30 | .container .forms{
31 | display: flex;
32 | align-items: center;
33 | height: 440px;
34 | width: 200%;
35 | transition: height 0.2s ease;
36 | }
37 |
38 |
39 | .container .form{
40 | width: 50%;
41 | padding: 30px;
42 | background-color: #fff;
43 | transition: margin-left 0.18s ease;
44 | }
45 |
46 |
47 | .container .signup{
48 | opacity: 0;
49 | transition: opacity 0.09s ease;
50 | }
51 | .container.active .signup{
52 | opacity: 1;
53 | transition: opacity 0.2s ease;
54 | }
55 |
56 | .container.active .forms{
57 | height: 600px;
58 | }
59 | .container .form .title{
60 | position: relative;
61 | font-size: 27px;
62 | font-weight: 600;
63 | }
64 |
65 | .form .title::before{
66 | content: '';
67 | position: absolute;
68 | left: 0;
69 | bottom: 0;
70 | height: 3px;
71 | width: 30px;
72 | background-color: #4070f4;
73 | border-radius: 25px;
74 | }
75 |
76 | .form .input-field{
77 | position: relative;
78 | height: 50px;
79 | width: 100%;
80 | margin-top: 30px;
81 | }
82 |
83 | .input-field input{
84 | position: absolute;
85 | height: 100%;
86 | width: 100%;
87 | padding: 0 35px;
88 | border: none;
89 | outline: none;
90 | font-size: 16px;
91 | border-bottom: 2px solid #ccc;
92 | border-top: 2px solid transparent;
93 | transition: all 0.2s ease;
94 | }
95 |
96 | .input-field input:is(:focus, :valid){
97 | border-bottom-color: #4070f4;
98 | }
99 |
100 | .input-field i{
101 | position: absolute;
102 | top: 50%;
103 | transform: translateY(-50%);
104 | color: #999;
105 | font-size: 23px;
106 | transition: all 0.2s ease;
107 | }
108 |
109 | .input-field input:is(:focus, :valid) ~ i{
110 | color: #4070f4;
111 | }
112 |
113 | .input-field i.icon{
114 | left: 0;
115 | }
116 | .input-field i.showHidePw{
117 | right: 0;
118 | cursor: pointer;
119 | padding: 10px;
120 | }
121 |
122 | .form .checkbox-text{
123 | display: flex;
124 | align-items: center;
125 | justify-content: space-between;
126 | margin-top: 20px;
127 | }
128 |
129 | .checkbox-text .checkbox-content{
130 | display: flex;
131 | align-items: center;
132 | }
133 |
134 | .checkbox-content input{
135 | margin: 0 8px -2px 4px;
136 | accent-color: #4070f4;
137 | }
138 |
139 | .form .text{
140 | color: #333;
141 | font-size: 14px;
142 | }
143 |
144 | .form a.text{
145 | color: #4070f4;
146 | text-decoration: none;
147 | }
148 | .form a:hover{
149 | text-decoration: underline;
150 | }
151 |
152 | .form .button{
153 | margin-top: 35px;
154 | }
155 |
156 | .form .button input{
157 | border: none;
158 | color: #fff;
159 | font-size: 17px;
160 | font-weight: 500;
161 | letter-spacing: 1px;
162 | border-radius: 6px;
163 | background-color: #4070f4;
164 | cursor: pointer;
165 | transition: all 0.3s ease;
166 | }
167 |
168 | .button input:hover{
169 | background-color: #265df2;
170 | }
171 |
172 | .form .login-signup{
173 | margin-top: 30px;
174 | text-align: center;
175 | }
--------------------------------------------------------------------------------
/static/captcha/style.css:
--------------------------------------------------------------------------------
1 | /* Import Google font - Poppins & Noto */
2 | @import url('https://fonts.googleapis.com/css2?family=Noto+Serif:ital@1&family=Poppins:wght@400;500;600&display=swap');
3 | *{
4 | margin: 0;
5 | padding: 0;
6 | box-sizing: border-box;
7 | font-family: "Poppins", sans-serif;
8 | }
9 | ::selection{
10 | color: #fff;
11 | background: #4db2ec;
12 | }
13 | body{
14 | display: flex;
15 | align-items: center;
16 | justify-content: center;
17 | min-height: 100vh;
18 | background: #4db2ec;
19 | }
20 | .wrapper{
21 | max-width: 485px;
22 | width: 100%;
23 | background: #fff;
24 | padding: 22px 30px 40px;
25 | border-radius: 10px;
26 | box-shadow: 8px 8px 8px rgba(0, 0, 0, 0.05);
27 | }
28 |
29 | .wrapper header{
30 | color: #4db2ec;
31 | font-size: 33px;
32 | font-weight: 500;
33 | text-align: center;
34 | }
35 |
36 | .wrapper .error{
37 | display: none;
38 | color: #ff0000;
39 | font-size: 25px;
40 | font-weight: 500;
41 | text-align: center;
42 | }
43 | .wrapper .captcha-area{
44 | display: flex;
45 | height: 65px;
46 | margin: 30px 0 20px;
47 | align-items: center;
48 | justify-content: space-between;
49 | }
50 | .captcha-area .captcha-img{
51 | height: 100%;
52 | width: 345px;
53 | user-select: none;
54 | background: #000;
55 | border-radius: 5px;
56 | position: relative;
57 | }
58 | .captcha-img img{
59 | width: 100%;
60 | height: 100%;
61 | object-fit: cover;
62 | border-radius: 5px;
63 | opacity: 0.95;
64 | }
65 | .captcha-img .captcha{
66 | position: absolute;
67 | left: 50%;
68 | top: 50%;
69 | width: 100%;
70 | color: #fff;
71 | font-size: 35px;
72 | text-align: center;
73 | letter-spacing: 10px;
74 | transform: translate(-50%, -50%);
75 | text-shadow: 0px 0px 2px #b1b1b1;
76 | font-family: 'Noto Serif', serif;
77 | }
78 | .wrapper button{
79 | outline: none;
80 | border: none;
81 | color: #fff;
82 | cursor: pointer;
83 | background: #4db2ec;
84 | border-radius: 5px;
85 | transition: all 0.3s ease;
86 | }
87 | .wrapper button:hover{
88 | background: #2fa5e9;
89 | }
90 | .captcha-area .reload-btn{
91 | width: 75px;
92 | height: 100%;
93 | font-size: 25px;
94 | }
95 | .captcha-area .reload-btn i{
96 | transition: transform 0.3s ease;
97 | }
98 | .captcha-area .reload-btn:hover i{
99 | transform: rotate(15deg);
100 | }
101 | .wrapper .input-area{
102 | height: 60px;
103 | width: 100%;
104 | position: relative;
105 | }
106 | .input-area input{
107 | width: 100%;
108 | height: 100%;
109 | outline: none;
110 | padding-left: 20px;
111 | font-size: 20px;
112 | border-radius: 5px;
113 | border: 1px solid #bfbfbf;
114 | }
115 | .input-area input:is(:focus, :valid){
116 | padding-left: 19px;
117 | border: 2px solid #4db2ec;
118 | }
119 | .input-area input::placeholder{
120 | color: #bfbfbf;
121 | }
122 | .input-area .check-btn{
123 | position: absolute;
124 | right: 7px;
125 | top: 50%;
126 | font-size: 17px;
127 | height: 45px;
128 | padding: 0 20px;
129 | opacity: 0;
130 | pointer-events: none;
131 | transform: translateY(-50%);
132 | }
133 | .input-area input:valid + .check-btn{
134 | opacity: 1;
135 | pointer-events: auto;
136 | }
137 | .wrapper .status-text{
138 | display: none;
139 | font-size: 18px;
140 | text-align: center;
141 | margin: 20px 0 -5px;
142 | }
143 |
144 | @media (max-width: 506px){
145 | body{
146 | padding: 0 10px;
147 | }
148 | .wrapper{
149 | padding: 22px 25px 35px;
150 | }
151 | .wrapper header{
152 | font-size: 25px;
153 | }
154 | .wrapper .captcha-area{
155 | height: 60px;
156 | }
157 | .captcha-area .captcha{
158 | font-size: 28px;
159 | letter-spacing: 5px;
160 | }
161 | .captcha-area .reload-btn{
162 | width: 60px;
163 | margin-left: 5px;
164 | font-size: 20px;
165 | }
166 | .wrapper .input-area{
167 | height: 55px;
168 | }
169 | .input-area .check-btn{
170 | height: 40px;
171 | }
172 | .wrapper .status-text{
173 | font-size: 15px;
174 | }
175 | .captcha-area .captcha-img{
176 | width: 250px;
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/server.py:
--------------------------------------------------------------------------------
1 | import os
2 | import dotenv
3 | import requests
4 | dotenv.load_dotenv()
5 |
6 | import flask
7 | from flask import request
8 | from flask import redirect
9 | from flask import render_template
10 |
11 | import telebot
12 | from telebot.types import InlineKeyboardMarkup
13 | from telebot.types import InlineKeyboardButton
14 | from telebot.types import InputTextMessageContent
15 | from telebot.types import InlineQueryResultArticle
16 |
17 | from telebot.util import parse_web_app_data
18 | from telebot.util import validate_web_app_data
19 |
20 | app = flask.Flask(__name__, static_url_path="/static")
21 | bot = telebot.TeleBot(os.getenv("API_TOKEN"), parse_mode="HTML")
22 |
23 | @app.route('/')
24 | def index():
25 | return "A collection of Telegram Mini Apps"
26 |
27 | # ------------------- Demo Form ------------------- #
28 |
29 | @app.route('/demoForm')
30 | def demoForm():
31 | return flask.render_template("demoForm.html")
32 |
33 | @app.route('/demoFormResponse', methods=["POST"])
34 | def demoFormResponse():
35 | raw_data = request.json
36 | name = raw_data["name"]
37 | date = raw_data["date"]
38 | email = raw_data["email"]
39 | country = raw_data["country"]
40 | initData = raw_data["initData"]
41 |
42 | isValid = validate_web_app_data(bot.token, initData)
43 |
44 | if isValid:
45 | web_app_data = parse_web_app_data(bot.token, initData)
46 | query_id = web_app_data["query_id"]
47 | bot.answer_web_app_query(query_id, InlineQueryResultArticle(
48 | id=query_id, title="VERIFICATION FAILED!",
49 | input_message_content=InputTextMessageContent(
50 | f"Demo Form:\n\nName: {name}\n\nBorn: {date}\n\
51 | \nEmail: {email}\n\nCountry: {country} ", parse_mode="HTML"),
52 | reply_markup=InlineKeyboardMarkup().row(InlineKeyboardButton(
53 | "CLICK TO CONTINUE ✅", callback_data=f"confirm-{web_app_data['user']['id']}"))))
54 |
55 | return redirect("/")
56 |
57 | # ------------------- Demo Form ------------------- #
58 |
59 |
60 | # ------------------- Text Captcha ------------------- #
61 |
62 | @app.route('/captcha')
63 | def captcha():
64 | return flask.render_template("captcha.html")
65 |
66 | @app.route('/captchaResponse', methods=['POST'])
67 | def captchaResponse():
68 | raw_data = flask.request.json
69 | isbot = raw_data["isbot"]
70 | initData = raw_data["initData"]
71 | attempts = raw_data["attempts"]
72 |
73 | isValid = validate_web_app_data(bot.token, initData)
74 |
75 | if isValid:
76 | if not isbot:
77 | web_app_data = parse_web_app_data(bot.token, initData)
78 | query_id = web_app_data["query_id"]
79 | bot.answer_web_app_query(query_id, InlineQueryResultArticle(
80 | id=query_id, title="VERIFICATION PASSED!",
81 | input_message_content=InputTextMessageContent(
82 | "Captcha verification passed ✅\n\
83 | \nIt seems that you're indeed a human! 😉 ",
84 | parse_mode="HTML"), reply_markup=InlineKeyboardMarkup().row(
85 | InlineKeyboardButton("CLICK TO CONTINUE ✅",
86 | callback_data=f"confirm-{web_app_data['user']['id']}"))))
87 | else:
88 | if attempts == 3:
89 | web_app_data = parse_web_app_data(bot.token, initData)
90 | query_id = web_app_data["query_id"]
91 | bot.answer_web_app_query(query_id, InlineQueryResultArticle(
92 | id=query_id, title="VERIFICATION FAILED!",
93 | input_message_content=InputTextMessageContent(
94 | "Captcha verification failed ❌\n\
95 | \nI don't trust your human side! 🤔 ",
96 | parse_mode="HTML"), reply_markup=InlineKeyboardMarkup().row(
97 | InlineKeyboardButton("CLICK TO CONTINUE ✅",
98 | callback_data=f"confirm-{web_app_data['user']['id']}"))))
99 |
100 | return redirect("/")
101 |
102 | # ------------------- Text Captcha ------------------- #
103 |
104 |
105 | # ------------------- QR Code Scanner ------------------- #
106 |
107 | @app.route('/qrCode')
108 | def qrCode():
109 | return flask.render_template("qrCode.html")
110 |
111 | @app.route('/qrCodeResponse', methods=["POST"])
112 | def qrCodeResponse():
113 | raw_data = flask.request.json
114 | initData = raw_data["initData"]
115 |
116 | isValid = validate_web_app_data(bot.token, initData)
117 |
118 | if isValid:
119 | web_app_data = parse_web_app_data(bot.token, initData)
120 |
121 | query_id = web_app_data["query_id"]
122 |
123 | bot.answer_web_app_query(query_id, InlineQueryResultArticle(
124 | id=query_id, title="QR DETECTED!",
125 | input_message_content=InputTextMessageContent(
126 | f"QR Code scanned successfully! 👇🏻\n\
127 | \n{raw_data['qr']} ", parse_mode="HTML"),
128 | reply_markup=InlineKeyboardMarkup().row(
129 | InlineKeyboardButton("CLICK TO CONTINUE ✅",
130 | callback_data=f"confirm-{web_app_data['user']['id']}"))))
131 |
132 | return redirect("/")
133 |
134 | # ------------------- QR Code Scanner ------------------- #
135 |
136 |
137 | # ------------------- Google re-CAPTCHA ------------------- #
138 |
139 | @app.route('/captchav2', methods=["GET", "POST"])
140 | def captchaV2():
141 |
142 | if request.method == 'POST':
143 |
144 | recaptcha_response = request.form.get('g-recaptcha-response')
145 |
146 | data = {
147 | 'secret': os.getenv("SECRET_KEY"),
148 | 'response': recaptcha_response
149 | }
150 | response = requests.post(
151 | 'https://www.google.com/recaptcha/api/siteverify', data=data)
152 | result = response.json()
153 |
154 | raw_data = request.form
155 | initData = raw_data["initData"]
156 |
157 | isValid = validate_web_app_data(bot.token, initData)
158 |
159 | if isValid:
160 |
161 | web_app_data = parse_web_app_data(bot.token, initData)
162 | query_id = web_app_data["query_id"]
163 |
164 | if result['success']:
165 | bot.answer_web_app_query(query_id, InlineQueryResultArticle(
166 | id=query_id, title="VERIFICATION PASSED!",
167 | input_message_content=InputTextMessageContent(
168 | "Captcha verification passed ✅\n\
169 | \nIt seems that you're indeed a human! 😉 ",
170 | parse_mode="HTML"), reply_markup=InlineKeyboardMarkup().row(
171 | InlineKeyboardButton("CLICK TO CONTINUE ✅",
172 | callback_data=f"confirm-{web_app_data['user']['id']}"))))
173 | else:
174 |
175 | bot.answer_web_app_query(query_id, InlineQueryResultArticle(
176 | id=query_id, title="VERIFICATION FAILED!",
177 | input_message_content=InputTextMessageContent(
178 | "Captcha verification failed ❌\n\
179 | \nI don't trust your human side! 🤔 ",
180 | parse_mode="HTML"), reply_markup=InlineKeyboardMarkup().row(
181 | InlineKeyboardButton("CLICK TO CONTINUE ✅",
182 | callback_data=f"confirm-{web_app_data['user']['id']}"))))
183 |
184 | return redirect("/")
185 |
186 | return render_template('captchav2.html')
187 |
188 | # ------------------- Google re-CAPTCHA ------------------- #
189 |
190 |
191 | if __name__ == '__main__':
192 | app.run(host='0.0.0.0')
193 |
--------------------------------------------------------------------------------