· 6 years ago · Feb 16, 2020, 11:08 AM
1from math import cos, sqrt, radians
2import pymorphy2
3import vk_api
4from vk_api.keyboard import VkKeyboard, VkKeyboardColor
5from vk_api.utils import get_random_id
6import sqlite3
7import json
8from requests import post
9from vk import API, Session
10
11buildcoords = []
12res = []
13resnear = ['<']
14commands = ['/radius', '/geo', '/help'] # Commands
15buildtype = ''
16describe = ''
17choose = ''
18rad = ''
19chng = True # Radius status
20cmd_flag = None
21
22d = json.loads(open('Users.json', 'r').read()) # User information
23
24# Bot init
25token = '55825b97e49214ec39aa1c4be004ac3188edce2e24e465fefb2db7ebdfe1981ca185f62988d16eb680a51'
26vk_session = vk_api.VkApi(token=token)
27vk = vk_session.get_api()
28
29VK_API_VERSION, GROUP_ID = '5.95', 189072320
30session = Session(access_token=token)
31api = API(session, v=VK_API_VERSION)
32longPoll = api.groups.getLongPollServer(group_id=GROUP_ID)
33server, key, ts = longPoll['server'], longPoll['key'], longPoll['ts']
34
35
36def geo_update(message, user_id): # Updating geoposition function
37 if message == 'отменить':
38 vk.messages.send(
39 user_id=user_id,
40 message='Изменение геопозиции отменено.',
41 keyboard=types_of_places(),
42 random_id=get_random_id())
43
44
45def change_radius(message, user_id): # Changing radius function
46 global chng, d
47
48 try:
49 if float(message) > 0:
50 try:
51 d['user_ids'][str(user_id)]['radius'] = int(message)
52 except Exception:
53 d['user_ids'][str(user_id)]['radius'] = float(message)
54
55 info_update()
56 if d['user_ids'][str(user_id)]['radius'] >= 1:
57 vk.messages.send(
58 user_id=uid,
59 message=f'Радиус поиска изменён на'
60 f" {d['user_ids'][str(user_id)]['radius']} км.", keyboard=types_of_places(),
61 random_id=get_random_id())
62 else:
63 vk.messages.send(
64 user_id=uid,
65 message=f'Радиус поиска изменён на'
66 f" {int(d['user_ids'][str(user_id)]['radius'] * 1000)} м.",
67 keyboard=types_of_places(),
68 random_id=get_random_id())
69 chng = False
70 else:
71 vk.messages.send(
72 user_id=uid,
73 message='Произошла ошибка. Попробуйте еще раз.',
74 keyboard=types_of_places(),
75 random_id=get_random_id())
76 except Exception:
77 if message == 'отменить':
78 vk.messages.send(
79 user_id=uid,
80 message='Изменение радиуса отменено.', keyboard=types_of_places(),
81 random_id=get_random_id())
82 else:
83 vk.messages.send(
84 user_id=uid,
85 message='Произошла ошибка. Попробуйте еще раз.',
86 keyboard=types_of_places(),
87 random_id=get_random_id())
88
89
90def buildings(message, user_id): # Getting type of buildings
91 global buildtype, describe
92 if message == 'Ближайшие гостиницы':
93 buildtype = 3
94 describe = 'гостиница'
95 near(user_id)
96 elif message == 'Ближайшие кафе':
97 buildtype = 2
98 describe = 'кафе'
99 near(user_id)
100 elif message == 'Ближайшие достопримечательности':
101 buildtype = 1
102 describe = 'достопримечательность'
103 near(user_id)
104
105
106def near(user_id): # Showing the nearest buildings of the selected type
107
108 global res, buildtype, buildcoords, resnear, rad, d, chng
109
110 con = sqlite3.connect('Buildings.db')
111 cur = con.cursor()
112 buildcoords = cur.execute(f"SELECT coords, id FROM places WHERE cttype LIKE '{buildtype}'").fetchall()
113
114 if not chng: # If the radius hasn't changed
115 resnear = ['<']
116 chng = True
117
118 try:
119 # Selection of coordinates and id of the nearest places
120 for i in buildcoords:
121 coord = i[0].split(', ')
122 a = lonlat_distance(d['user_ids'][str(user_id)]['geo'], tuple(map(float, coord)))
123 if a <= d['user_ids'][str(user_id)]['radius']:
124 resnear.append(i[-1])
125 except Exception:
126 vk.messages.send(
127 user_id=user_id,
128 message="Геопозиция не получена."
129 " Предоставьте доступ заново,"
130 " используя команду /geo",
131 keyboard=location_kb(), random_id=get_random_id())
132
133 if len(resnear) > 1:
134 try:
135 # Getting more info about nearest places
136 res = [i for i in cur.execute(f"SELECT name, info, coords FROM places WHERE id IN {tuple(resnear)}"
137 f" AND cttype LIKE '{buildtype}'").fetchall()]
138
139 # Sorting
140 res = sorted([(i[0], i[1], (lonlat_distance(d['user_ids'][str(user_id)]['geo'],
141 tuple(map(float, i[-1].split(', '))))))
142 for i in res], key=lambda x: x[2])[:6]
143 if d['user_ids'][str(user_id)]['radius'] >= 1:
144 rad = f"{d['user_ids'][str(user_id)]['radius']} км"
145 else:
146 rad = f"{int(d['user_ids'][str(user_id)]['radius'] * 1000)} м"
147
148 if len(res) > 0:
149 morph = pymorphy2.MorphAnalyzer()
150 word = morph.parse(describe)[0]
151 word2 = morph.parse("Нашлось")[0]
152
153 # Places output on the keyboard
154 keyboard = VkKeyboard(inline=True)
155 for i in res:
156 if res.index(i) == 0:
157 keyboard.add_button(i[0])
158 else:
159 keyboard.add_line()
160 keyboard.add_button(i[0])
161 kb = keyboard.get_keyboard()
162
163 vk.messages.send(
164 user_id=user_id,
165 message=f"""{word2.inflect(
166 {'VERB', 'perf', 'intr', 'past', 'femn' if len(res) % 10 == 1
167 and len(res) != 11 else 'indc'}).word.capitalize()
168 } {len(res)} {word.make_agree_with_number(len(res)).word} в радиусе {rad}:""",
169 keyboard=kb, random_id=get_random_id())
170
171 else:
172 vk.messages.send(
173 user_id=user_id,
174 message='Ничего не найдено.\nПопробуйте увеличить радиус поиска'
175 ' или обновить текущее местоположение.', random_id=get_random_id())
176 except Exception:
177 vk.messages.send(
178 user_id=user_id,
179 message='Ничего не найдено.\nПопробуйте увеличить радиус поиска'
180 ' или обновить текущее местоположение.', random_id=get_random_id())
181 else:
182 vk.messages.send(
183 user_id=user_id,
184 message='Ничего не найдено.\nПопробуйте увеличить радиус поиска'
185 ' или обновить текущее местоположение.', random_id=get_random_id())
186
187
188def sending_info(call, user_id): # Sending info about place
189 global choose, buildcoords, res
190 if call != '':
191 choose = call
192
193 con = sqlite3.connect('Buildings.db')
194 cur = con.cursor()
195 buildcoords = cur.execute(
196 f"SELECT coords FROM places WHERE name LIKE '{choose}'"
197 f" AND cttype LIKE '{buildtype}'").fetchall()[0][0]
198
199 a = round(lonlat_distance(d['user_ids'][str(user_id)]['geo'],
200 tuple(map(float, buildcoords.split(', ')))), 2)
201 s = f'{a} км' if a >= 1 else f'{a * 1000} м'
202
203 keyboard = VkKeyboard(inline=True)
204 keyboard.add_button('Узнать описание')
205 keyboard.add_line()
206 keyboard.add_button('Построить маршрут')
207 keyboard.add_line()
208 keyboard.add_button('Показать на карте')
209 kb = keyboard.get_keyboard()
210
211 vk.messages.send(
212 user_id=user_id,
213 message=f'Вы выбрали {choose} ({s} от вас):',
214 keyboard=kb, random_id=get_random_id())
215 else:
216 vk.messages.send(
217 user_id=user_id,
218 message=f'Что-то пошло не так. Попробуйте перезапустить бота, используя команду /start',
219 random_id=get_random_id())
220
221
222def about(update):
223 global buildcoords
224
225 user_id = update['object']['from_id']
226 text = update['object']['text']
227
228 if text == 'Узнать описание': # Description of the selected place
229 con = sqlite3.connect('Buildings.db')
230 cur = con.cursor()
231 img = cur.execute(f"SELECT info, img FROM places WHERE name LIKE '{choose}'"
232 f" AND cttype LIKE '{buildtype}'").fetchall()[0]
233
234 api.messages.markAsRead(peer_id=update['object']['from_id'])
235 pfile = post(api.photos.getMessagesUploadServer(peer_id=update['object']['from_id'])['upload_url'],
236 files={'photo': open(f'/home/amirelkanov1337/Photos/{img[-1]}', 'rb')}).json()
237
238 api.messages.send(user_id=update['object']['from_id'], random_id=get_random_id(),
239 message='Секундочку...', )
240
241 photo = api.photos.saveMessagesPhoto(server=pfile['server'], photo=pfile['photo'], hash=pfile['hash'])[
242 0]
243
244 vk.messages.send(user_id=user_id,
245 message=f'{choose}:\n{img[0]}',
246 attachment=f'photo{photo["owner_id"]}_{photo["id"]}',
247 random_id=get_random_id())
248
249 elif text == 'Построить маршрут': # Building a route to the selected place
250 x, y = ([float(i) for i in (''.join([j for i in buildcoords for j in i]).split(', '))])
251
252 vk.messages.send(
253 user_id=user_id,
254 message=f'Маршрут до места можно посмотреть по ссылке:\n'
255 f"https://yandex.ru/maps/?mode=routes&rtext="
256 f"{d['user_ids'][str(user_id)]['geo'][0]}%2C"
257 f"{d['user_ids'][str(user_id)]['geo'][-1]}~{x}"
258 f"%2C{y}",
259 random_id=get_random_id())
260
261 elif text == 'Показать на карте': # Showing selected place on the map
262 con = sqlite3.connect('Buildings.db')
263 cur = con.cursor()
264 short_url = cur.execute(f"SELECT mapurl FROM places WHERE name LIKE '{choose}'"
265 f" AND cttype LIKE '{buildtype}'").fetchall()[0][0]
266 vk.messages.send(
267 user_id=user_id,
268 message=f'Увидеть место на карте можно по ссылке:\n'
269 f'https://yandex.ru/maps/-/{short_url}',
270 random_id=get_random_id())
271
272
273def lonlat_distance(first_coords, second_coords): # Getting distance between points in km
274 first_lon, first_lat = first_coords
275 second_lon, second_lat = second_coords
276 radians_latitude = radians((first_lat + second_lat) / 2.)
277 lat_lon_factor = cos(radians_latitude)
278 dx = abs(first_lon - second_lon) * 111 * lat_lon_factor
279 dy = abs(first_lat - second_lat) * 111
280 distance = sqrt(dx * dx + dy * dy)
281 return distance
282
283
284def user_check(s): # Is there a user in the database
285 global d
286 if s not in [i for i in d['user_ids'].keys()]:
287 d['user_ids'][s] = {'radius': 3, 'geo': None}
288
289
290def info_update(): # User Information Update
291 open('Users.json', 'w').write(json.dumps(d, ensure_ascii=False, indent=2))
292
293
294def types_of_places():
295 keyboard = VkKeyboard()
296 keyboard.add_button('Ближайшие гостиницы')
297 keyboard.add_line()
298 keyboard.add_button('Ближайшие кафе')
299 keyboard.add_line()
300 keyboard.add_button('Ближайшие достопримечательности')
301 kb = keyboard.get_keyboard()
302 return kb
303
304
305def location_kb():
306 keyboard = VkKeyboard()
307 keyboard.add_location_button()
308 kb = keyboard.get_keyboard()
309 return kb
310
311
312while True:
313 try:
314 longPoll = post('%s' % server, data={'act': 'a_check',
315 'key': key,
316 'ts': ts,
317 'wait': 25}).json()
318
319 if longPoll['updates'] and len(longPoll['updates']) != 0:
320 for update in longPoll['updates']:
321 if update['type'] == 'message_new':
322
323 uid = update['object']['from_id']
324 text = update['object']['text']
325 user_check(str(uid))
326
327 if 'geo' in update['object'].keys(): # Location processing
328 geo = update['object']['geo']['coordinates']
329 d['user_ids'][str(uid)]['geo'] = geo['latitude'], geo['longitude']
330 info_update()
331 buildcoords = []
332 res = []
333 resnear = ['<']
334
335 vk.messages.send(
336 user_id=uid,
337 message='Геопозиция получена! С чего начнем?',
338 keyboard=types_of_places(), random_id=get_random_id())
339
340 elif text.lower() == 'начать' or text.lower() == '/start': # First message
341
342 vk.messages.send(
343 user_id=uid,
344 message='Привет! Я бот, призванный помогать туристам!\n'
345 'Чтобы увидеть полный список команд, используйте "/help"\n\n'
346 'Для начала нужно дать доступ к местоположению.',
347 keyboard=location_kb(), random_id=get_random_id())
348
349 d['user_ids'][str(uid)] = {'radius': 3, 'geo': None}
350 buildcoords = []
351 res = []
352 resnear = ['<']
353 info_update()
354
355 elif text in ['Ближайшие гостиницы', 'Ближайшие кафе',
356 'Ближайшие достопримечательности']:
357 buildings(text, uid)
358
359 elif text in [i[0].strip() for i in res]:
360 sending_info(text, uid)
361
362 elif text in ['Узнать описание', 'Построить маршрут', 'Показать на карте']:
363 about(update)
364
365 elif text.lower() in commands:
366 if text.lower() == '/radius': # Changing radius command
367 if d['user_ids'][str(uid)]['radius'] >= 1:
368 vk.messages.send(user_id=uid,
369 message=f"Текущий радуис равен "
370 f"{d['user_ids'][str(uid)]['radius']} км.",
371 random_id=get_random_id())
372 else:
373 vk.messages.send(user_id=uid,
374 message=f"Текущий радуис равен "
375 f"{int(d['user_ids'][str(uid)]['radius'] * 1000)} м.",
376 random_id=get_random_id())
377
378 keyboard = VkKeyboard()
379 keyboard.add_button('Отменить', color=VkKeyboardColor.NEGATIVE)
380 kb = keyboard.get_keyboard()
381
382 vk.messages.send(user_id=uid, message='Введите желаемый радиус поиска.',
383 keyboard=kb, random_id=get_random_id())
384 cmd_flag = 1
385
386 elif text.lower() == '/geo': # Updating geoposition command
387
388 keyboard = VkKeyboard()
389 keyboard.add_location_button()
390 keyboard.add_line()
391 keyboard.add_button('Отменить', color=VkKeyboardColor.NEGATIVE)
392 kb = keyboard.get_keyboard()
393
394 vk.messages.send(user_id=uid, message=f'Выбрано изменение геопозиции.',
395 keyboard=kb, random_id=get_random_id())
396 cmd_flag = 2
397
398 elif text.lower() == '/help': # Help command
399 vk.messages.send(user_id=uid,
400 message='Список команд:\n'
401 '/help — получение списка команд;\n'
402 '/start — перезапуск бота;\n'
403 '/radius — изменение радиуса поиска;\n'
404 '/geo — обновление текущего местоположения.',
405 random_id=get_random_id())
406 continue
407
408 if text.lower() != '/radius' and cmd_flag == 1:
409 change_radius(text.lower(), uid)
410
411 elif text.lower() != '/geo' and cmd_flag == 2:
412 geo_update(text.lower(), uid)
413
414 cmd_flag = None
415
416 ts = longPoll['ts']
417 except Exception:
418 longPoll = post('%s' % server, data={'act': 'a_check',
419 'key': key,
420 'ts': ts,
421 'wait': 25}).json()