Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,60 +1,82 @@
|
|
1 |
from flask import Flask, render_template_string, jsonify, request
|
2 |
-
import
|
|
|
3 |
import math
|
|
|
4 |
|
5 |
app = Flask(__name__)
|
6 |
|
7 |
-
# Инициализация
|
8 |
def init_db():
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
|
|
23 |
def get_all_markers():
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
|
|
|
|
|
|
|
|
43 |
def remove_marker_from_db(marker_id):
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
|
59 |
# HTML-шаблон с улучшенным дизайном
|
60 |
html_template = '''
|
@@ -66,109 +88,201 @@ html_template = '''
|
|
66 |
<title>GeoGram</title>
|
67 |
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css">
|
68 |
<style>
|
|
|
69 |
body {
|
70 |
font-family: 'Roboto', sans-serif;
|
71 |
-
background
|
72 |
margin: 0;
|
73 |
padding: 0;
|
74 |
-
color: #
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
75 |
}
|
76 |
#map {
|
77 |
height: 90vh;
|
78 |
width: 100%;
|
79 |
margin-bottom: 20px;
|
80 |
}
|
81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
82 |
text-align: center;
|
|
|
|
|
|
|
|
|
|
|
|
|
83 |
padding: 20px;
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
font-size: 24px;
|
88 |
}
|
89 |
-
.
|
90 |
-
|
91 |
-
|
92 |
-
padding: 10px;
|
93 |
-
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
|
94 |
}
|
95 |
-
.
|
96 |
-
color: #
|
97 |
font-size: 18px;
|
98 |
-
display: block;
|
99 |
-
margin-bottom: 5px;
|
100 |
}
|
101 |
-
.
|
102 |
-
color: #
|
|
|
|
|
|
|
|
|
103 |
text-decoration: none;
|
|
|
104 |
}
|
105 |
-
.
|
106 |
text-decoration: underline;
|
107 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
108 |
.popup-form {
|
109 |
-
|
110 |
-
|
111 |
-
|
|
|
|
|
112 |
}
|
113 |
-
.popup-form input,
|
114 |
-
|
|
|
115 |
margin-bottom: 10px;
|
116 |
-
border:
|
117 |
-
border-radius:
|
118 |
font-size: 14px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
119 |
}
|
120 |
.popup-form button {
|
121 |
-
background
|
122 |
color: white;
|
123 |
border: none;
|
124 |
-
padding:
|
125 |
-
border-radius:
|
126 |
cursor: pointer;
|
|
|
|
|
|
|
127 |
}
|
128 |
.popup-form button:hover {
|
129 |
-
background
|
130 |
}
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
|
|
|
|
136 |
}
|
137 |
-
|
138 |
-
|
139 |
-
width: 300px;
|
140 |
-
border: 1px solid #ccc;
|
141 |
-
border-radius: 5px;
|
142 |
-
margin-right: 10px;
|
143 |
-
font-size: 14px;
|
144 |
}
|
145 |
-
|
146 |
-
|
147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
148 |
color: white;
|
149 |
border: none;
|
150 |
-
|
|
|
151 |
cursor: pointer;
|
152 |
font-size: 14px;
|
|
|
|
|
153 |
}
|
154 |
-
|
155 |
-
background
|
156 |
-
}
|
157 |
-
#search-results {
|
158 |
-
margin: 20px;
|
159 |
-
text-align: center;
|
160 |
-
}
|
161 |
-
.result-item {
|
162 |
-
background: white;
|
163 |
-
border: 1px solid #ccc;
|
164 |
-
border-radius: 5px;
|
165 |
-
margin: 10px auto;
|
166 |
-
padding: 10px;
|
167 |
-
transition: background 0.3s;
|
168 |
-
max-width: 500px;
|
169 |
}
|
170 |
-
|
171 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
172 |
}
|
173 |
</style>
|
174 |
</head>
|
@@ -183,7 +297,7 @@ html_template = '''
|
|
183 |
|
184 |
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
|
185 |
<script>
|
186 |
-
const map = L.map('map').setView([55.75, 37.61], 13);
|
187 |
|
188 |
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
189 |
maxZoom: 19,
|
@@ -192,16 +306,14 @@ html_template = '''
|
|
192 |
let userLatitude = 55.75;
|
193 |
let userLongitude = 37.61;
|
194 |
|
195 |
-
// Проверка поддержки геолокации и установка центра карты на текущее местоположение пользователя
|
196 |
if (navigator.geolocation) {
|
197 |
navigator.geolocation.getCurrentPosition(function(position) {
|
198 |
userLatitude = position.coords.latitude;
|
199 |
userLongitude = position.coords.longitude;
|
200 |
map.setView([userLatitude, userLongitude], 13);
|
201 |
|
202 |
-
// Добавление маркера для местоположения пользователя
|
203 |
L.marker([userLatitude, userLongitude]).addTo(map)
|
204 |
-
.bindPopup('
|
205 |
}, function() {
|
206 |
alert('Не удалось получить ваше местоположение.');
|
207 |
});
|
@@ -209,18 +321,78 @@ html_template = '''
|
|
209 |
alert('Ваш браузер не поддерживает геолокацию.');
|
210 |
}
|
211 |
|
212 |
-
|
|
|
213 |
fetch('/get_markers')
|
214 |
.then(response => response.json())
|
215 |
.then(data => {
|
216 |
data.forEach(marker => {
|
217 |
-
const
|
218 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
219 |
});
|
220 |
});
|
221 |
|
222 |
-
|
223 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
224 |
fetch('/add_marker', {
|
225 |
method: 'POST',
|
226 |
headers: {
|
@@ -230,43 +402,81 @@ html_template = '''
|
|
230 |
name: name,
|
231 |
description: description,
|
232 |
telegram_link: telegram_link,
|
233 |
-
|
|
|
|
|
234 |
})
|
235 |
})
|
236 |
.then(response => response.json())
|
237 |
.then(data => {
|
238 |
-
const
|
239 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
240 |
});
|
241 |
}
|
242 |
|
243 |
-
// Событие клика по карте для добавления формы
|
244 |
map.on('click', function(e) {
|
245 |
const popupContent = document.createElement('div');
|
246 |
popupContent.classList.add('popup-form');
|
247 |
|
248 |
const inputName = document.createElement('input');
|
249 |
inputName.type = 'text';
|
250 |
-
inputName.placeholder = 'Название
|
251 |
popupContent.appendChild(inputName);
|
252 |
|
253 |
const inputDescription = document.createElement('textarea');
|
254 |
-
inputDescription.placeholder = '
|
255 |
popupContent.appendChild(inputDescription);
|
256 |
|
257 |
const inputTelegram = document.createElement('input');
|
258 |
inputTelegram.type = 'text';
|
259 |
-
inputTelegram.placeholder = 'Telegram
|
260 |
popupContent.appendChild(inputTelegram);
|
261 |
|
|
|
|
|
|
|
|
|
|
|
262 |
const submitButton = document.createElement('button');
|
263 |
submitButton.textContent = 'Добавить метку';
|
264 |
submitButton.onclick = function() {
|
265 |
const name = inputName.value;
|
266 |
const description = inputDescription.value;
|
267 |
const telegram_link = inputTelegram.value;
|
268 |
-
|
269 |
-
|
|
|
270 |
}
|
271 |
map.closePopup();
|
272 |
};
|
@@ -278,20 +488,26 @@ html_template = '''
|
|
278 |
.openOn(map);
|
279 |
});
|
280 |
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
292 |
}
|
293 |
|
294 |
-
//
|
295 |
document.getElementById('search-button').addEventListener('click', function() {
|
296 |
const searchTerm = document.getElementById('search-input').value.toLowerCase();
|
297 |
fetch('/get_markers')
|
@@ -305,35 +521,81 @@ html_template = '''
|
|
305 |
marker.distance = calculateDistance(userLatitude, userLongitude, marker.latitude, marker.longitude);
|
306 |
});
|
307 |
|
308 |
-
//
|
309 |
-
data.
|
310 |
-
|
311 |
-
const results = data.filter(marker =>
|
312 |
-
marker.name.toLowerCase().includes(searchTerm) ||
|
313 |
marker.description.toLowerCase().includes(searchTerm)
|
314 |
);
|
315 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
316 |
results.forEach(marker => {
|
317 |
const resultItem = document.createElement('div');
|
318 |
resultItem.classList.add('result-item');
|
319 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
320 |
resultsContainer.appendChild(resultItem);
|
321 |
});
|
322 |
|
323 |
if (results.length === 0) {
|
324 |
-
resultsContainer.innerHTML = '<p>Нет результатов</p>';
|
325 |
}
|
326 |
});
|
327 |
});
|
328 |
|
329 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
330 |
function calculateDistance(lat1, lon1, lat2, lon2) {
|
331 |
-
const R = 6371;
|
332 |
const dLat = (lat2 - lat1) * Math.PI / 180;
|
333 |
const dLon = (lon2 - lon1) * Math.PI / 180;
|
334 |
-
const a =
|
335 |
-
0.5 - Math.cos(dLat)/2 +
|
336 |
-
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
|
337 |
(1 - Math.cos(dLon)) / 2;
|
338 |
|
339 |
return R * 2 * Math.asin(Math.sqrt(a));
|
@@ -346,20 +608,18 @@ html_template = '''
|
|
346 |
# Маршрут для главной страницы
|
347 |
@app.route('/')
|
348 |
def index():
|
|
|
349 |
return render_template_string(html_template)
|
350 |
|
351 |
# Маршрут для получения всех меток
|
352 |
@app.route('/get_markers')
|
353 |
-
def
|
354 |
markers = get_all_markers()
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
'latitude': marker[4],
|
361 |
-
'longitude': marker[5]
|
362 |
-
} for marker in markers])
|
363 |
|
364 |
# Маршрут для добавления метки
|
365 |
@app.route('/add_marker', methods=['POST'])
|
@@ -368,26 +628,41 @@ def add_marker():
|
|
368 |
name = data['name']
|
369 |
description = data['description']
|
370 |
telegram_link = data['telegram_link']
|
|
|
371 |
latitude, longitude = data['position']
|
|
|
372 |
|
373 |
-
marker_id = add_marker_to_db(name, description, telegram_link, latitude, longitude)
|
|
|
374 |
return jsonify({
|
375 |
'id': marker_id,
|
376 |
'name': name,
|
377 |
'description': description,
|
378 |
'telegram_link': telegram_link,
|
|
|
379 |
'latitude': latitude,
|
380 |
-
'longitude': longitude
|
|
|
|
|
|
|
381 |
})
|
382 |
|
383 |
# Маршрут для удаления метки
|
384 |
@app.route('/remove_marker', methods=['POST'])
|
385 |
-
def
|
386 |
data = request.json
|
387 |
marker_id = data['id']
|
388 |
remove_marker_from_db(marker_id)
|
389 |
return jsonify({'success': True})
|
390 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
391 |
if __name__ == '__main__':
|
392 |
init_db()
|
393 |
-
app.run(debug=True, host='0.0.0.0', port=7860)
|
|
|
1 |
from flask import Flask, render_template_string, jsonify, request
|
2 |
+
import json
|
3 |
+
import os
|
4 |
import math
|
5 |
+
from datetime import datetime, timedelta
|
6 |
|
7 |
app = Flask(__name__)
|
8 |
|
9 |
+
# Инициализация файла JSON (если файла нет – создаётся пустой список)
|
10 |
def init_db():
|
11 |
+
if not os.path.exists('markers.json'):
|
12 |
+
with open('markers.json', 'w', encoding='utf-8') as f:
|
13 |
+
json.dump([], f, ensure_ascii=False, indent=4)
|
14 |
+
|
15 |
+
# Загрузка меток из файла JSON
|
16 |
+
def load_markers():
|
17 |
+
with open('markers.json', 'r', encoding='utf-8') as f:
|
18 |
+
return json.load(f)
|
19 |
+
|
20 |
+
# Сохранение меток в файл JSON
|
21 |
+
def save_markers(markers):
|
22 |
+
with open('markers.json', 'w', encoding='utf-8') as f:
|
23 |
+
json.dump(markers, f, ensure_ascii=False, indent=4)
|
24 |
+
|
25 |
+
# Получение всех меток
|
26 |
def get_all_markers():
|
27 |
+
return load_markers()
|
28 |
+
|
29 |
+
# Добавление новой метки в "базу" (JSON-файл)
|
30 |
+
def add_marker_to_db(name, description, telegram_link, logo_link, latitude, longitude, delete_password):
|
31 |
+
markers = load_markers()
|
32 |
+
new_id = max((marker["id"] for marker in markers), default=0) + 1
|
33 |
+
new_marker = {
|
34 |
+
"id": new_id,
|
35 |
+
"name": name,
|
36 |
+
"description": description,
|
37 |
+
"telegram_link": telegram_link,
|
38 |
+
"logo_link": logo_link,
|
39 |
+
"latitude": latitude,
|
40 |
+
"longitude": longitude,
|
41 |
+
"delete_password": delete_password,
|
42 |
+
"premium": 0,
|
43 |
+
"premium_start_date": None
|
44 |
+
}
|
45 |
+
markers.append(new_marker)
|
46 |
+
save_markers(markers)
|
47 |
+
return new_id
|
48 |
+
|
49 |
+
# Удаление метки из JSON-файла
|
50 |
def remove_marker_from_db(marker_id):
|
51 |
+
markers = load_markers()
|
52 |
+
markers = [marker for marker in markers if marker["id"] != marker_id]
|
53 |
+
save_markers(markers)
|
54 |
+
|
55 |
+
# Активация премиум-статуса для метки
|
56 |
+
def activate_premium(marker_id):
|
57 |
+
markers = load_markers()
|
58 |
+
current_date = datetime.now().isoformat()
|
59 |
+
for marker in markers:
|
60 |
+
if marker["id"] == marker_id:
|
61 |
+
marker["premium"] = 1
|
62 |
+
marker["premium_start_date"] = current_date
|
63 |
+
break
|
64 |
+
save_markers(markers)
|
65 |
+
|
66 |
+
# Проверка и сброс премиум-статуса по истечении 30 дней
|
67 |
+
def check_premium_status():
|
68 |
+
markers = load_markers()
|
69 |
+
updated = False
|
70 |
+
for marker in markers:
|
71 |
+
if marker["premium"] == 1 and marker["premium_start_date"]:
|
72 |
+
premium_start_date = datetime.fromisoformat(marker["premium_start_date"])
|
73 |
+
current_date = datetime.now()
|
74 |
+
if (current_date - premium_start_date).days > 30:
|
75 |
+
marker["premium"] = 0
|
76 |
+
marker["premium_start_date"] = None
|
77 |
+
updated = True
|
78 |
+
if updated:
|
79 |
+
save_markers(markers)
|
80 |
|
81 |
# HTML-шаблон с улучшенным дизайном
|
82 |
html_template = '''
|
|
|
88 |
<title>GeoGram</title>
|
89 |
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css">
|
90 |
<style>
|
91 |
+
/* Общие стили */
|
92 |
body {
|
93 |
font-family: 'Roboto', sans-serif;
|
94 |
+
background: linear-gradient(135deg, #483D8B, #2c3e50);
|
95 |
margin: 0;
|
96 |
padding: 0;
|
97 |
+
color: #fff; /* Белый текст */
|
98 |
+
overflow-x: hidden;
|
99 |
+
}
|
100 |
+
h1 {
|
101 |
+
text-align: center;
|
102 |
+
padding: 20px;
|
103 |
+
margin: 0;
|
104 |
+
background: linear-gradient(135deg, #6a0dad, #483D8B);
|
105 |
+
color: white;
|
106 |
+
font-size: 28px;
|
107 |
+
letter-spacing: 2px;
|
108 |
+
text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.7);
|
109 |
}
|
110 |
#map {
|
111 |
height: 90vh;
|
112 |
width: 100%;
|
113 |
margin-bottom: 20px;
|
114 |
}
|
115 |
+
|
116 |
+
/* Поиск */
|
117 |
+
#search-container {
|
118 |
+
display: flex;
|
119 |
+
justify-content: center;
|
120 |
+
align-items: center;
|
121 |
+
margin: 20px;
|
122 |
+
}
|
123 |
+
#search-input {
|
124 |
+
padding: 15px;
|
125 |
+
width: 300px;
|
126 |
+
border: none;
|
127 |
+
border-radius: 30px 0 0 30px;
|
128 |
+
font-size: 16px;
|
129 |
+
background: #34495e;
|
130 |
+
color: #fff;
|
131 |
+
outline: none;
|
132 |
+
transition: background 0.3s ease;
|
133 |
+
}
|
134 |
+
#search-input:focus {
|
135 |
+
background: #2c3e50;
|
136 |
+
}
|
137 |
+
#search-button {
|
138 |
+
padding: 15px 30px;
|
139 |
+
background: linear-gradient(135deg, #6a0dad, #483D8B);
|
140 |
+
color: white;
|
141 |
+
border: none;
|
142 |
+
border-radius: 0 30px 30px 0;
|
143 |
+
cursor: pointer;
|
144 |
+
font-size: 16px;
|
145 |
+
transition: background 0.3s ease;
|
146 |
+
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.5);
|
147 |
+
}
|
148 |
+
#search-button:hover {
|
149 |
+
background: linear-gradient(135deg, #483D8B, #6a0dad);
|
150 |
+
}
|
151 |
+
|
152 |
+
/* Результаты поиска */
|
153 |
+
#search-results {
|
154 |
+
margin: 20px;
|
155 |
text-align: center;
|
156 |
+
}
|
157 |
+
.result-item {
|
158 |
+
background: linear-gradient(135deg, #34495e, #2c3e50);
|
159 |
+
border: 1px solid rgba(106, 13, 173, 0.5);
|
160 |
+
border-radius: 15px;
|
161 |
+
margin: 10px auto;
|
162 |
padding: 20px;
|
163 |
+
max-width: 500px;
|
164 |
+
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
165 |
+
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.5);
|
|
|
166 |
}
|
167 |
+
.result-item:hover {
|
168 |
+
transform: translateY(-5px);
|
169 |
+
box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.7);
|
|
|
|
|
170 |
}
|
171 |
+
.result-item b {
|
172 |
+
color: #fff;
|
173 |
font-size: 18px;
|
|
|
|
|
174 |
}
|
175 |
+
.result-item p {
|
176 |
+
color: #ecf0f1;
|
177 |
+
margin: 5px 0;
|
178 |
+
}
|
179 |
+
.result-item a {
|
180 |
+
color: #fff;
|
181 |
text-decoration: none;
|
182 |
+
font-weight: bold;
|
183 |
}
|
184 |
+
.result-item a:hover {
|
185 |
text-decoration: underline;
|
186 |
}
|
187 |
+
.result-item button {
|
188 |
+
background: linear-gradient(135deg, #6a0dad, #483D8B);
|
189 |
+
color: white;
|
190 |
+
border: none;
|
191 |
+
padding: 10px 20px;
|
192 |
+
border-radius: 30px;
|
193 |
+
cursor: pointer;
|
194 |
+
font-size: 14px;
|
195 |
+
transition: background 0.3s ease;
|
196 |
+
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.5);
|
197 |
+
}
|
198 |
+
.result-item button:hover {
|
199 |
+
background: linear-gradient(135deg, #483D8B, #6a0dad);
|
200 |
+
}
|
201 |
+
|
202 |
+
/* Окно добавления метки */
|
203 |
.popup-form {
|
204 |
+
background: linear-gradient(135deg, #34495e, #2c3e50);
|
205 |
+
border-radius: 15px;
|
206 |
+
padding: 20px;
|
207 |
+
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.5);
|
208 |
+
width: 300px;
|
209 |
}
|
210 |
+
.popup-form input,
|
211 |
+
.popup-form textarea {
|
212 |
+
padding: 12px;
|
213 |
margin-bottom: 10px;
|
214 |
+
border: none;
|
215 |
+
border-radius: 10px;
|
216 |
font-size: 14px;
|
217 |
+
background: #483D8B;
|
218 |
+
color: #fff;
|
219 |
+
outline: none;
|
220 |
+
transition: background 0.3s ease;
|
221 |
+
}
|
222 |
+
.popup-form input:focus,
|
223 |
+
.popup-form textarea:focus {
|
224 |
+
background: #6a0dad;
|
225 |
}
|
226 |
.popup-form button {
|
227 |
+
background: linear-gradient(135deg, #6a0dad, #483D8B);
|
228 |
color: white;
|
229 |
border: none;
|
230 |
+
padding: 12px;
|
231 |
+
border-radius: 30px;
|
232 |
cursor: pointer;
|
233 |
+
font-size: 14px;
|
234 |
+
transition: background 0.3s ease;
|
235 |
+
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.5);
|
236 |
}
|
237 |
.popup-form button:hover {
|
238 |
+
background: linear-gradient(135deg, #483D8B, #6a0dad);
|
239 |
}
|
240 |
+
|
241 |
+
/* Попапы на карте */
|
242 |
+
.leaflet-popup-content-wrapper {
|
243 |
+
background: linear-gradient(135deg, #34495e, #2c3e50);
|
244 |
+
border-radius: 15px;
|
245 |
+
padding: 15px;
|
246 |
+
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.5);
|
247 |
}
|
248 |
+
.leaflet-popup-content {
|
249 |
+
color: #fff;
|
|
|
|
|
|
|
|
|
|
|
250 |
}
|
251 |
+
.leaflet-popup-content b {
|
252 |
+
color: #fff;
|
253 |
+
font-size: 18px;
|
254 |
+
display: block;
|
255 |
+
margin-bottom: 5px;
|
256 |
+
}
|
257 |
+
.leaflet-popup-content a {
|
258 |
+
color: #fff;
|
259 |
+
text-decoration: none;
|
260 |
+
}
|
261 |
+
.leaflet-popup-content a:hover {
|
262 |
+
text-decoration: underline;
|
263 |
+
}
|
264 |
+
.leaflet-popup-content button {
|
265 |
+
background: linear-gradient(135deg, #6a0dad, #483D8B);
|
266 |
color: white;
|
267 |
border: none;
|
268 |
+
padding: 10px 20px;
|
269 |
+
border-radius: 30px;
|
270 |
cursor: pointer;
|
271 |
font-size: 14px;
|
272 |
+
transition: background 0.3s ease;
|
273 |
+
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.5);
|
274 |
}
|
275 |
+
.leaflet-popup-content button:hover {
|
276 |
+
background: linear-gradient(135deg, #483D8B, #6a0dad);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
277 |
}
|
278 |
+
|
279 |
+
/* Маркеры */
|
280 |
+
.marker-logo {
|
281 |
+
width: 25px;
|
282 |
+
height: 25px;
|
283 |
+
border-radius: 50%;
|
284 |
+
border: 2px solid #6a0dad;
|
285 |
+
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.5);
|
286 |
}
|
287 |
</style>
|
288 |
</head>
|
|
|
297 |
|
298 |
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
|
299 |
<script>
|
300 |
+
const map = L.map('map').setView([55.75, 37.61], 13);
|
301 |
|
302 |
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
303 |
maxZoom: 19,
|
|
|
306 |
let userLatitude = 55.75;
|
307 |
let userLongitude = 37.61;
|
308 |
|
|
|
309 |
if (navigator.geolocation) {
|
310 |
navigator.geolocation.getCurrentPosition(function(position) {
|
311 |
userLatitude = position.coords.latitude;
|
312 |
userLongitude = position.coords.longitude;
|
313 |
map.setView([userLatitude, userLongitude], 13);
|
314 |
|
|
|
315 |
L.marker([userLatitude, userLongitude]).addTo(map)
|
316 |
+
.bindPopup('<span style="color:white;">Вы находитесь здесь</span>').openPopup();
|
317 |
}, function() {
|
318 |
alert('Не удалось получить ваше местоположение.');
|
319 |
});
|
|
|
321 |
alert('Ваш браузер не поддерживает геолокацию.');
|
322 |
}
|
323 |
|
324 |
+
let markersOnMap = {};
|
325 |
+
|
326 |
fetch('/get_markers')
|
327 |
.then(response => response.json())
|
328 |
.then(data => {
|
329 |
data.forEach(marker => {
|
330 |
+
const customIcon = L.divIcon({
|
331 |
+
className: 'custom-icon',
|
332 |
+
html: `<img src="${marker.logo_link || 'https://simpleicon.com/wp-content/uploads/map-marker-1.png'}" class="marker-logo"/>`,
|
333 |
+
iconSize: [25, 25],
|
334 |
+
iconAnchor: [12.5, 25]
|
335 |
+
});
|
336 |
+
|
337 |
+
const markerObj = L.marker([marker.latitude, marker.longitude], {icon: customIcon}).addTo(map);
|
338 |
+
markersOnMap[marker.id] = markerObj;
|
339 |
+
|
340 |
+
let premiumLabel = '';
|
341 |
+
if (marker.premium) {
|
342 |
+
const premiumStartDate = new Date(marker.premium_start_date);
|
343 |
+
const currentDate = new Date();
|
344 |
+
const daysLeft = Math.max(0, Math.floor((premiumStartDate.getTime() + (30 * 24 * 60 * 60 * 1000) - currentDate.getTime()) / (1000 * 60 * 60 * 24)));
|
345 |
+
premiumLabel = `<span style="color: gold;">Премиум метка (${daysLeft} дней осталось)</span><br>`;
|
346 |
+
}
|
347 |
+
|
348 |
+
markerObj.bindPopup(`
|
349 |
+
<div class="leaflet-popup-content">
|
350 |
+
${premiumLabel}
|
351 |
+
<b>${marker.name}</b>
|
352 |
+
<p>${marker.description}</p>
|
353 |
+
<button style="background: linear-gradient(135deg, #6a0dad, #483D8B);
|
354 |
+
color: white;
|
355 |
+
border: none;
|
356 |
+
padding: 10px 20px;
|
357 |
+
border-radius: 30px;
|
358 |
+
cursor: pointer;
|
359 |
+
font-size: 14px;
|
360 |
+
transition: background 0.3s ease;
|
361 |
+
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.5);"
|
362 |
+
onclick="window.open('https://t.me/${marker.telegram_link}', '_blank')">
|
363 |
+
Перейти в Telegram
|
364 |
+
</button><br>
|
365 |
+
<button onclick="buyPremium(${marker.id})">Купить премиум</button>
|
366 |
+
<button onclick="removeMarker(${marker.id}, '${marker.delete_password}')">Удалить</button>
|
367 |
+
</div>
|
368 |
+
`);
|
369 |
});
|
370 |
});
|
371 |
|
372 |
+
function buyPremium(markerId) {
|
373 |
+
const activationCode = prompt("Введите код активации премиума:");
|
374 |
+
|
375 |
+
if (activationCode === "morshenadmin87132") {
|
376 |
+
fetch('/activate_premium', {
|
377 |
+
method: 'POST',
|
378 |
+
headers: {
|
379 |
+
'Content-Type': 'application/json',
|
380 |
+
},
|
381 |
+
body: JSON.stringify({id: markerId})
|
382 |
+
}).then(() => {
|
383 |
+
alert("Премиум активирован!");
|
384 |
+
location.reload();
|
385 |
+
});
|
386 |
+
} else {
|
387 |
+
alert("Неверный код активации!");
|
388 |
+
}
|
389 |
+
}
|
390 |
+
|
391 |
+
function addMarker(name, description, telegram_link, logo_link, position) {
|
392 |
+
const deletePassword = prompt("Введите пароль для удаления метки:");
|
393 |
+
|
394 |
+
const finalLogoLink = logo_link || 'https://icons.veryicon.com/png/o/miscellaneous/high-icon-library/geo-fence.png';
|
395 |
+
|
396 |
fetch('/add_marker', {
|
397 |
method: 'POST',
|
398 |
headers: {
|
|
|
402 |
name: name,
|
403 |
description: description,
|
404 |
telegram_link: telegram_link,
|
405 |
+
logo_link: finalLogoLink,
|
406 |
+
position: [position.lat, position.lng],
|
407 |
+
delete_password: deletePassword
|
408 |
})
|
409 |
})
|
410 |
.then(response => response.json())
|
411 |
.then(data => {
|
412 |
+
const customIcon = L.divIcon({
|
413 |
+
className: 'custom-icon',
|
414 |
+
html: `<img src="${data.logo_link}" class="marker-logo"/>`,
|
415 |
+
iconSize: [25, 25],
|
416 |
+
iconAnchor: [12.5, 25]
|
417 |
+
});
|
418 |
+
|
419 |
+
const markerObj = L.marker([data.latitude, data.longitude], {icon: customIcon}).addTo(map);
|
420 |
+
markersOnMap[data.id] = markerObj;
|
421 |
+
|
422 |
+
let premiumLabel = data.premium ? '<span style="color: gold;">Премиум метка</span><br>' : '';
|
423 |
+
|
424 |
+
markerObj.bindPopup(`
|
425 |
+
<div class="leaflet-popup-content">
|
426 |
+
${premiumLabel}
|
427 |
+
<b>${data.name}</b>
|
428 |
+
<p>${data.description}</p>
|
429 |
+
<button style="background: linear-gradient(135deg, #6a0dad, #483D8B);
|
430 |
+
color: white;
|
431 |
+
border: none;
|
432 |
+
padding: 10px 20px;
|
433 |
+
border-radius: 30px;
|
434 |
+
cursor: pointer;
|
435 |
+
font-size: 14px;
|
436 |
+
transition: background 0.3s ease;
|
437 |
+
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.5);"
|
438 |
+
onclick="window.open('https://t.me/${data.telegram_link}', '_blank')">
|
439 |
+
Перейти в Telegram
|
440 |
+
</button><br>
|
441 |
+
<button onclick="buyPremium(${data.id})">Купить премиум</button>
|
442 |
+
<button onclick="removeMarker(${data.id}, '${data.delete_password}')">Удалить</button>
|
443 |
+
</div>
|
444 |
+
`);
|
445 |
});
|
446 |
}
|
447 |
|
|
|
448 |
map.on('click', function(e) {
|
449 |
const popupContent = document.createElement('div');
|
450 |
popupContent.classList.add('popup-form');
|
451 |
|
452 |
const inputName = document.createElement('input');
|
453 |
inputName.type = 'text';
|
454 |
+
inputName.placeholder = 'Название ';
|
455 |
popupContent.appendChild(inputName);
|
456 |
|
457 |
const inputDescription = document.createElement('textarea');
|
458 |
+
inputDescription.placeholder = 'Ваши товары/услуги';
|
459 |
popupContent.appendChild(inputDescription);
|
460 |
|
461 |
const inputTelegram = document.createElement('input');
|
462 |
inputTelegram.type = 'text';
|
463 |
+
inputTelegram.placeholder = 'Telegram канал (без @)';
|
464 |
popupContent.appendChild(inputTelegram);
|
465 |
|
466 |
+
const inputLogoLink = document.createElement('input');
|
467 |
+
inputLogoLink.type = 'text';
|
468 |
+
inputLogoLink.placeholder = 'Ссылка на логотип (необязательно)';
|
469 |
+
popupContent.appendChild(inputLogoLink);
|
470 |
+
|
471 |
const submitButton = document.createElement('button');
|
472 |
submitButton.textContent = 'Добавить метку';
|
473 |
submitButton.onclick = function() {
|
474 |
const name = inputName.value;
|
475 |
const description = inputDescription.value;
|
476 |
const telegram_link = inputTelegram.value;
|
477 |
+
const logo_link = inputLogoLink.value;
|
478 |
+
if(name && description && telegram_link) {
|
479 |
+
addMarker(name, description, telegram_link, logo_link, e.latlng);
|
480 |
}
|
481 |
map.closePopup();
|
482 |
};
|
|
|
488 |
.openOn(map);
|
489 |
});
|
490 |
|
491 |
+
function removeMarker(markerId, markerPassword) {
|
492 |
+
const universalPassword = "morshenadmin87132";
|
493 |
+
const passwordInput = prompt("Введите пароль для удаления ��етки:");
|
494 |
+
|
495 |
+
if (passwordInput === universalPassword || passwordInput === markerPassword) {
|
496 |
+
fetch('/remove_marker', {
|
497 |
+
method: 'POST',
|
498 |
+
headers: {
|
499 |
+
'Content-Type': 'application/json',
|
500 |
+
},
|
501 |
+
body: JSON.stringify({id: markerId})
|
502 |
+
}).then(() => {
|
503 |
+
location.reload();
|
504 |
+
});
|
505 |
+
} else {
|
506 |
+
alert("Неверный пароль!");
|
507 |
+
}
|
508 |
}
|
509 |
|
510 |
+
// Обновленная функция поиска с приоритетом премиум меток
|
511 |
document.getElementById('search-button').addEventListener('click', function() {
|
512 |
const searchTerm = document.getElementById('search-input').value.toLowerCase();
|
513 |
fetch('/get_markers')
|
|
|
521 |
marker.distance = calculateDistance(userLatitude, userLongitude, marker.latitude, marker.longitude);
|
522 |
});
|
523 |
|
524 |
+
// Фильтрация результатов по поисковому запросу
|
525 |
+
const results = data.filter(marker =>
|
526 |
+
marker.name.toLowerCase().includes(searchTerm) ||
|
|
|
|
|
527 |
marker.description.toLowerCase().includes(searchTerm)
|
528 |
);
|
529 |
|
530 |
+
// Сортировка результатов: сначала премиум метки, затем обычные,
|
531 |
+
// и внутри каждой категории сортировка по расстоянию
|
532 |
+
results.sort((a, b) => {
|
533 |
+
if (a.premium !== b.premium) {
|
534 |
+
return b.premium - a.premium; // Премиум метки идут первыми
|
535 |
+
}
|
536 |
+
return a.distance - b.distance; // Сортировка по расстоянию
|
537 |
+
});
|
538 |
+
|
539 |
+
// Отображение результатов
|
540 |
results.forEach(marker => {
|
541 |
const resultItem = document.createElement('div');
|
542 |
resultItem.classList.add('result-item');
|
543 |
+
|
544 |
+
let premiumLabel = '';
|
545 |
+
if (marker.premium) {
|
546 |
+
const premiumStartDate = new Date(marker.premium_start_date);
|
547 |
+
const currentDate = new Date();
|
548 |
+
const daysLeft = Math.max(0, Math.floor((premiumStartDate.getTime() + (30 * 24 * 60 * 60 * 1000) - currentDate.getTime()) / (1000 * 60 * 60 * 24)));
|
549 |
+
premiumLabel = ` (${daysLeft} дней осталось)`;
|
550 |
+
}
|
551 |
+
|
552 |
+
resultItem.innerHTML = `
|
553 |
+
<b>${marker.name}</b>
|
554 |
+
<p>${marker.description}</p>
|
555 |
+
<p>Расстояние: ${marker.distance.toFixed(2)} км</p>
|
556 |
+
<p>Статус: ${marker.premium ? 'Премиум' + premiumLabel : 'Обычная'}</p>
|
557 |
+
<button style="background: linear-gradient(135deg, #6a0dad, #483D8B);
|
558 |
+
color: white;
|
559 |
+
border: none;
|
560 |
+
padding: 10px 20px;
|
561 |
+
border-radius: 30px;
|
562 |
+
cursor: pointer;
|
563 |
+
font-size: 14px;
|
564 |
+
transition: background 0.3s ease;
|
565 |
+
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.5);"
|
566 |
+
onclick="window.open('https://t.me/${marker.telegram_link}', '_blank')">
|
567 |
+
Перейти в Telegram
|
568 |
+
</button>
|
569 |
+
<button onclick="showOnMap(${marker.id}, ${marker.latitude}, ${marker.longitude}); hideResults()">Показать на карте</button>`;
|
570 |
+
|
571 |
resultsContainer.appendChild(resultItem);
|
572 |
});
|
573 |
|
574 |
if (results.length === 0) {
|
575 |
+
resultsContainer.innerHTML = '<p style="color:white;">Нет результатов</p>';
|
576 |
}
|
577 |
});
|
578 |
});
|
579 |
|
580 |
+
function showOnMap(markerId, lat, lng) {
|
581 |
+
map.setView([lat, lng], 13);
|
582 |
+
if (markersOnMap[markerId]) {
|
583 |
+
markersOnMap[markerId].openPopup();
|
584 |
+
}
|
585 |
+
}
|
586 |
+
|
587 |
+
function hideResults() {
|
588 |
+
const resultsContainer = document.getElementById('search-results');
|
589 |
+
resultsContainer.innerHTML = '';
|
590 |
+
}
|
591 |
+
|
592 |
function calculateDistance(lat1, lon1, lat2, lon2) {
|
593 |
+
const R = 6371;
|
594 |
const dLat = (lat2 - lat1) * Math.PI / 180;
|
595 |
const dLon = (lon2 - lon1) * Math.PI / 180;
|
596 |
+
const a =
|
597 |
+
0.5 - Math.cos(dLat) / 2 +
|
598 |
+
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
|
599 |
(1 - Math.cos(dLon)) / 2;
|
600 |
|
601 |
return R * 2 * Math.asin(Math.sqrt(a));
|
|
|
608 |
# Маршрут для главной страницы
|
609 |
@app.route('/')
|
610 |
def index():
|
611 |
+
check_premium_status()
|
612 |
return render_template_string(html_template)
|
613 |
|
614 |
# Маршрут для получения всех меток
|
615 |
@app.route('/get_markers')
|
616 |
+
def get_markers_route():
|
617 |
markers = get_all_markers()
|
618 |
+
# Если для метки не указан логотип, используем изображение по умолчанию
|
619 |
+
for marker in markers:
|
620 |
+
if not marker.get("logo_link"):
|
621 |
+
marker["logo_link"] = "https://simpleicon.com/wp-content/uploads/map-marker-1.png"
|
622 |
+
return jsonify(markers)
|
|
|
|
|
|
|
623 |
|
624 |
# Маршрут для добавления метки
|
625 |
@app.route('/add_marker', methods=['POST'])
|
|
|
628 |
name = data['name']
|
629 |
description = data['description']
|
630 |
telegram_link = data['telegram_link']
|
631 |
+
logo_link = data['logo_link'] if data['logo_link'] else 'https://simpleicon.com/wp-content/uploads/map-marker-1.png'
|
632 |
latitude, longitude = data['position']
|
633 |
+
delete_password = data['delete_password']
|
634 |
|
635 |
+
marker_id = add_marker_to_db(name, description, telegram_link, logo_link, latitude, longitude, delete_password)
|
636 |
+
|
637 |
return jsonify({
|
638 |
'id': marker_id,
|
639 |
'name': name,
|
640 |
'description': description,
|
641 |
'telegram_link': telegram_link,
|
642 |
+
'logo_link': logo_link,
|
643 |
'latitude': latitude,
|
644 |
+
'longitude': longitude,
|
645 |
+
'delete_password': delete_password,
|
646 |
+
'premium': 0,
|
647 |
+
'premium_start_date': None
|
648 |
})
|
649 |
|
650 |
# Маршрут для удаления метки
|
651 |
@app.route('/remove_marker', methods=['POST'])
|
652 |
+
def remove_marker_route():
|
653 |
data = request.json
|
654 |
marker_id = data['id']
|
655 |
remove_marker_from_db(marker_id)
|
656 |
return jsonify({'success': True})
|
657 |
|
658 |
+
# Маршрут для активации премиум-статуса
|
659 |
+
@app.route('/activate_premium', methods=['POST'])
|
660 |
+
def activate_premium_route():
|
661 |
+
data = request.json
|
662 |
+
marker_id = data['id']
|
663 |
+
activate_premium(marker_id)
|
664 |
+
return jsonify({'success': True})
|
665 |
+
|
666 |
if __name__ == '__main__':
|
667 |
init_db()
|
668 |
+
app.run(debug=True, host='0.0.0.0', port=7860)
|