Queer European MD passionate about IT

ciclopi.py 50 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616
  1. """Get information about bike sharing in Pisa.
  2. Available bikes in bike sharing stations.
  3. """
  4. # Standard library modules
  5. import asyncio
  6. import datetime
  7. import math
  8. # Third party modules
  9. from davtelepot.utilities import (
  10. async_wrapper, CachedPage, extract, get_cleaned_text,
  11. line_drawing_unordered_list, make_button, make_inline_keyboard,
  12. make_lines_of_buttons
  13. )
  14. _URL = "http://www.ciclopi.eu/frmLeStazioni.aspx"
  15. ciclopi_webpage = CachedPage.get(
  16. _URL,
  17. datetime.timedelta(seconds=15),
  18. mode='html'
  19. )
  20. UNIT_TO_KM = {
  21. 'km': 1,
  22. 'm': 1000,
  23. 'mi': 0.621371192,
  24. 'nmi': 0.539956803,
  25. 'ft': 3280.839895013,
  26. 'in': 39370.078740158
  27. }
  28. CICLOPI_SORTING_CHOICES = {
  29. 0: dict(
  30. id='center',
  31. symbol='🏛'
  32. ),
  33. 1: dict(
  34. id='alphabetical',
  35. symbol='🔤'
  36. ),
  37. 2: dict(
  38. id='position',
  39. symbol='🧭'
  40. ),
  41. 3: dict(
  42. id='custom',
  43. symbol='⭐️'
  44. )
  45. }
  46. CICLOPI_STATIONS_TO_SHOW = {
  47. -1: dict(
  48. id='fav',
  49. symbol='⭐️'
  50. ),
  51. 0: dict(
  52. id='all',
  53. symbol='💯'
  54. ),
  55. 3: dict(
  56. id='3',
  57. symbol='3️⃣'
  58. ),
  59. 5: dict(
  60. id='5',
  61. symbol='5️⃣'
  62. ),
  63. 10: dict(
  64. id='10',
  65. symbol='🔟'
  66. )
  67. }
  68. def haversine_distance(lat1, lon1, lat2, lon2, degrees='dec', unit='m'):
  69. """
  70. Calculate the great circle distance between two points on Earth.
  71. (specified in decimal degrees)
  72. """
  73. assert unit in UNIT_TO_KM, "Invalid distance unit of measurement!"
  74. assert degrees in ['dec', 'rad'], "Invalid angle unit of measurement!"
  75. # Convert decimal degrees to radians
  76. if degrees == 'dec':
  77. lon1, lat1, lon2, lat2 = map(
  78. math.radians,
  79. [lon1, lat1, lon2, lat2]
  80. )
  81. average_earth_radius = 6371.0088 * UNIT_TO_KM[unit]
  82. return (
  83. 2
  84. * average_earth_radius
  85. * math.asin(
  86. math.sqrt(
  87. math.sin((lat2 - lat1) * 0.5) ** 2
  88. + math.cos(lat1)
  89. * math.cos(lat2)
  90. * math.sin((lon2 - lon1) * 0.5) ** 2
  91. )
  92. )
  93. )
  94. class Location():
  95. """Location in world map."""
  96. def __init__(self, coordinates):
  97. """Check and set instance attributes."""
  98. assert type(coordinates) is tuple, "`coordinates` must be a tuple"
  99. assert (
  100. len(coordinates) == 2
  101. and all(type(c) is float for c in coordinates)
  102. ), "`coordinates` must be two floats"
  103. self._coordinates = coordinates
  104. @property
  105. def coordinates(self):
  106. """Return a tuple (latitude, longitude)."""
  107. return self._coordinates
  108. @property
  109. def latitude(self):
  110. """Return latitude."""
  111. return self._coordinates[0]
  112. @property
  113. def longitude(self):
  114. """Return longitude."""
  115. return self._coordinates[1]
  116. def get_distance(self, other, *args, **kwargs):
  117. """Return the distance between two `Location`s."""
  118. return haversine_distance(
  119. self.latitude, self.longitude,
  120. other.latitude, other.longitude,
  121. *args, **kwargs
  122. )
  123. class Station(Location):
  124. """CicloPi bike sharing station."""
  125. stations = {
  126. 1: dict(
  127. name='Aeroporto',
  128. coordinates=(43.699455, 10.400075),
  129. ),
  130. 2: dict(
  131. name='Stazione F.S.',
  132. coordinates=(43.708627, 10.399051),
  133. ),
  134. 3: dict(
  135. name='Comune Palazzo Blu',
  136. coordinates=(43.715541, 10.400505),
  137. ),
  138. 4: dict(
  139. name='Teatro Tribunale',
  140. coordinates=(43.716391, 10.405136),
  141. ),
  142. 5: dict(
  143. name='Borgo Stretto',
  144. coordinates=(43.718518, 10.402165),
  145. ),
  146. 6: dict(
  147. name='Polo Marzotto',
  148. coordinates=(43.719772, 10.407291),
  149. ),
  150. 7: dict(
  151. name='Duomo',
  152. coordinates=(43.722855, 10.391977),
  153. ),
  154. 8: dict(
  155. name='Pietrasantina',
  156. coordinates=(43.729020, 10.392726),
  157. ),
  158. 9: dict(
  159. name='Paparelli',
  160. coordinates=(43.724449, 10.410438),
  161. ),
  162. 10: dict(
  163. name='Pratale',
  164. coordinates=(43.7212554, 10.4180257),
  165. ),
  166. 11: dict(
  167. name='Ospedale Cisanello',
  168. coordinates=(43.705752, 10.441740),
  169. ),
  170. 12: dict(
  171. name='Sms Biblioteca',
  172. coordinates=(43.706565, 10.419136),
  173. ),
  174. 13: dict(
  175. name='Vittorio Emanuele',
  176. coordinates=(43.710182, 10.398751),
  177. ),
  178. 14: dict(
  179. name='Palacongressi',
  180. coordinates=(43.710014, 10.410232),
  181. ),
  182. 15: dict(
  183. name='Porta a Lucca',
  184. coordinates=(43.724247, 10.402269),
  185. ),
  186. 16: dict(
  187. name='Solferino',
  188. coordinates=(43.715698, 10.394999),
  189. ),
  190. 17: dict(
  191. name='San Rossore F.S.',
  192. coordinates=(43.718992, 10.384391),
  193. ),
  194. 18: dict(
  195. name='Guerrazzi',
  196. coordinates=(43.710358, 10.405337),
  197. ),
  198. 19: dict(
  199. name='Livornese',
  200. coordinates=(43.708114, 10.384021),
  201. ),
  202. 20: dict(
  203. name='Cavalieri',
  204. coordinates=(43.719856, 10.400194),
  205. ),
  206. 21: dict(
  207. name='M. Libertà',
  208. coordinates=(43.719821, 10.403021),
  209. ),
  210. 22: dict(
  211. name='Galleria Gerace',
  212. coordinates=(43.710791, 10.420456),
  213. ),
  214. 23: dict(
  215. name='C. Marchesi',
  216. coordinates=(43.714971, 10.419322),
  217. ),
  218. 24: dict(
  219. name='CNR-Praticelli',
  220. coordinates=(43.719256, 10.424012),
  221. ),
  222. 25: dict(
  223. name='Sesta Porta',
  224. coordinates=(43.709162, 10.395837),
  225. ),
  226. 26: dict(
  227. name='Qualconia',
  228. coordinates=(43.713011, 10.394458),
  229. ),
  230. 27: dict(
  231. name='Donatello',
  232. coordinates=(43.711715, 10.372480),
  233. ),
  234. 28: dict(
  235. name='Spadoni',
  236. coordinates=(43.716850, 10.391347),
  237. ),
  238. 29: dict(
  239. name='Nievo',
  240. coordinates=(43.738286, 10.400865),
  241. ),
  242. 30: dict(
  243. name='Cisanello',
  244. coordinates=(43.701159, 10.438863),
  245. ),
  246. 31: dict(
  247. name='Edificio 3',
  248. coordinates=(43.707869, 10.441698),
  249. ),
  250. 32: dict(
  251. name='Edificio 6',
  252. coordinates=(43.709046, 10.442541),
  253. ),
  254. 33: dict(
  255. name='Frascani',
  256. coordinates=(43.710157, 10.433339),
  257. ),
  258. 34: dict(
  259. name='Chiarugi',
  260. coordinates=(43.726244, 10.412882),
  261. ),
  262. 35: dict(
  263. name='Praticelli 2',
  264. coordinates=(43.719619, 10.427469),
  265. ),
  266. 36: dict(
  267. name='Carducci',
  268. coordinates=(43.726700, 10.420562),
  269. ),
  270. 37: dict(
  271. name='Garibaldi',
  272. coordinates=(43.718077, 10.418168),
  273. ),
  274. 38: dict(
  275. name='Silvestro',
  276. coordinates=(43.714128, 10.409065),
  277. ),
  278. 39: dict(
  279. name='Pardi',
  280. coordinates=(43.702273, 10.399793),
  281. ),
  282. }
  283. def __init__(self, id=0, name='unknown', coordinates=(91.0, 181.0)):
  284. """Check and set instance attributes."""
  285. if id in self.__class__.stations:
  286. coordinates = self.__class__.stations[id]['coordinates']
  287. name = self.__class__.stations[id]['name']
  288. Location.__init__(self, coordinates)
  289. self._id = id
  290. self._name = name
  291. self._active = True
  292. self._location = None
  293. self._description = ''
  294. self._distance = None
  295. self._bikes = 0
  296. self._free = 0
  297. @property
  298. def id(self):
  299. """Return station identification number."""
  300. return self._id
  301. @property
  302. def name(self):
  303. """Return station name."""
  304. return self._name
  305. @property
  306. def description(self):
  307. """Return station description."""
  308. return self._description
  309. @property
  310. def is_active(self):
  311. """Return True if station is active."""
  312. return self._active
  313. @property
  314. def location(self):
  315. """Return location from which distance should be evaluated."""
  316. if self._location is None:
  317. return default_location
  318. return self._location
  319. @property
  320. def distance(self):
  321. """Return distance from `self.location`.
  322. If distance is not evaluated yet, do it and store the result.
  323. Otherwise, return stored value.
  324. """
  325. if self._distance is None:
  326. self._distance = self.get_distance(self.location)
  327. return self._distance
  328. @property
  329. def bikes(self):
  330. """Return number of available bikes."""
  331. return self._bikes
  332. @property
  333. def free(self):
  334. """Return number of free slots."""
  335. return self._free
  336. def set_active(self, active):
  337. """Change station status to `active`.
  338. `active` should be either `True` or `False`.
  339. """
  340. assert type(active) is bool, "`active` should be a boolean."
  341. self._active = active
  342. def set_description(self, description):
  343. """Change station description to `description`.
  344. `description` should be a string.
  345. """
  346. assert type(description) is str, "`description` should be a boolean."
  347. self._description = description
  348. def set_location(self, location):
  349. """Change station location to `location`.
  350. `location` should be a Location object.
  351. """
  352. assert (
  353. isinstance(location, Location)
  354. ), "`location` should be a Location."
  355. self._location = location
  356. def set_bikes(self, bikes):
  357. """Change number of available `bikes`.
  358. `bikes` should be an int.
  359. """
  360. assert (
  361. type(bikes) is int
  362. ), "`bikes` should be an int."
  363. self._bikes = bikes
  364. def set_free(self, free):
  365. """Change number of `free` bike parking slots.
  366. `free` should be an int.
  367. """
  368. assert (
  369. type(free) is int
  370. ), "`free` should be an int."
  371. self._free = free
  372. @property
  373. def status(self):
  374. """Return station status to be shown to users.
  375. It includes distance, location, available bikes and free stalls.
  376. """
  377. if self.bikes + self.free == 0:
  378. bikes_and_stalls = "<i>⚠️ {{not_available}}</i>"
  379. else:
  380. bikes_and_stalls = f"🚲 {self.bikes} | 🅿️ {self.free}"
  381. return (
  382. f"<b>{self.name}</b> | <i>{self.description}</i>\n"
  383. f"<code> </code>{bikes_and_stalls} | 📍 {self.distance:.0f} m"
  384. ).format(
  385. s=self
  386. )
  387. def ciclopi_custom_sorter(custom_order):
  388. """Return a function to sort stations by a `custom_order`."""
  389. custom_values = {
  390. record['station']: record['value']
  391. for record in custom_order
  392. }
  393. def sorter(station):
  394. """Take a station and return its queue value.
  395. Stations will be sorted by queue value in ascending order.
  396. """
  397. if station.id in custom_values:
  398. return (custom_values[station.id], station.name)
  399. return (100, station.name)
  400. return sorter
  401. def _get_stations(data, location):
  402. stations = []
  403. for _station in data.find_all(
  404. "li",
  405. attrs={"class": "rrItem"}
  406. ):
  407. station_name = _station.find(
  408. "span",
  409. attrs={"class": "Stazione"}
  410. ).text
  411. if 'Non operativa' in station_name:
  412. active = False
  413. else:
  414. active = True
  415. station_id = _station.find(
  416. "div",
  417. attrs={"class": "cssNumero"}
  418. ).text
  419. if (
  420. station_id is None
  421. or type(station_id) is not str
  422. or not station_id.isnumeric()
  423. ):
  424. station_id = 0
  425. else:
  426. station_id = int(station_id)
  427. station = Station(station_id)
  428. station.set_active(active)
  429. station.set_description(
  430. _station.find(
  431. "span",
  432. attrs={"class": "TableComune"}
  433. ).text.replace(
  434. 'a`',
  435. 'à'
  436. ).replace(
  437. 'e`',
  438. 'è'
  439. )
  440. )
  441. bikes_text = _station.find(
  442. "span",
  443. attrs={"class": "Red"}
  444. ).get_text('\t')
  445. if bikes_text.count('\t') < 1:
  446. bikes = 0
  447. free = 0
  448. else:
  449. bikes, free, *other = [
  450. int(
  451. ''.join(
  452. char
  453. for char in s
  454. if char.isnumeric()
  455. )
  456. )
  457. for s in bikes_text.split('\t')
  458. ]
  459. station.set_bikes(bikes)
  460. station.set_free(free)
  461. station.set_location(location)
  462. stations.append(
  463. station
  464. )
  465. return stations
  466. async def set_ciclopi_location(bot, update, user_record):
  467. """Take a location update and store it as CicloPi place.
  468. CicloPi stations will be sorted by distance from this place.
  469. """
  470. location = update['location']
  471. chat_id = update['chat']['id']
  472. telegram_id = update['from']['id']
  473. with bot.db as db:
  474. db['ciclopi'].upsert(
  475. dict(
  476. chat_id=chat_id,
  477. latitude=location['latitude'],
  478. longitude=location['longitude']
  479. ),
  480. ['chat_id']
  481. )
  482. await bot.send_message(
  483. chat_id=chat_id,
  484. text=bot.get_message(
  485. 'ciclopi', 'set_position', 'success',
  486. update=update, user_record=user_record
  487. )
  488. )
  489. # Remove individual text message handler which was set to catch `/cancel`
  490. bot.remove_individual_text_message_handler(telegram_id)
  491. return await _ciclopi_command(bot, update, user_record)
  492. async def cancel_ciclopi_location(bot, update, user_record):
  493. """Handle the situation in which a user does not send location on request.
  494. This function is set as individual text message handler when the bot
  495. requests user's location and is removed if user does send one.
  496. If not, return a proper message.
  497. """
  498. text = get_cleaned_text(bot=bot, update=update)
  499. # If user cancels operation, confirm that it was cancelled
  500. if text.lower() == 'annulla':
  501. return bot.get_message(
  502. 'ciclopi', 'set_position', 'cancel',
  503. update=update, user_record=user_record
  504. )
  505. # If user writes something else, remind them how to set position later
  506. return bot.get_message(
  507. 'ciclopi', 'set_position', 'cancel_and_remind',
  508. update=update, user_record=user_record
  509. )
  510. async def _ciclopi_command(bot, update, user_record, sent_message=None,
  511. show_all=False):
  512. chat_id = update['chat']['id']
  513. default_stations_to_show = 5
  514. if sent_message is None:
  515. await bot.sendChatAction(
  516. chat_id=chat_id,
  517. action='typing'
  518. )
  519. else:
  520. await bot.edit_message_text(
  521. update=sent_message,
  522. text="<i>{message}...</i>".format(
  523. message=bot.get_message(
  524. 'ciclopi', 'command', 'updating',
  525. update=update, user_record=user_record
  526. )
  527. ),
  528. parse_mode='HTML',
  529. reply_markup=None
  530. )
  531. ciclopi_data = await ciclopi_webpage.get_page()
  532. if ciclopi_data is None or isinstance(ciclopi_data, Exception):
  533. text = bot.get_message(
  534. 'ciclopi', 'command', 'unavailable_website',
  535. update=update, user_record=user_record
  536. )
  537. else:
  538. with bot.db as db:
  539. ciclopi_record = db['ciclopi'].find_one(
  540. chat_id=chat_id
  541. )
  542. custom_order = list(
  543. db['ciclopi_custom_order'].find(
  544. chat_id=chat_id
  545. )
  546. )
  547. if (
  548. ciclopi_record is not None
  549. and isinstance(ciclopi_record, dict)
  550. and 'sorting' in ciclopi_record
  551. and ciclopi_record['sorting'] in CICLOPI_SORTING_CHOICES
  552. ):
  553. sorting_code = ciclopi_record['sorting']
  554. if (
  555. 'latitude' in ciclopi_record
  556. and ciclopi_record['latitude'] is not None
  557. and 'longitude' in ciclopi_record
  558. and ciclopi_record['longitude'] is not None
  559. ):
  560. saved_place = Location(
  561. (
  562. ciclopi_record['latitude'],
  563. ciclopi_record['longitude']
  564. )
  565. )
  566. else:
  567. saved_place = default_location
  568. else:
  569. sorting_code = 0
  570. if (
  571. ciclopi_record is not None
  572. and isinstance(ciclopi_record, dict)
  573. and 'stations_to_show' in ciclopi_record
  574. and ciclopi_record[
  575. 'stations_to_show'
  576. ] in CICLOPI_STATIONS_TO_SHOW
  577. ):
  578. stations_to_show = ciclopi_record[
  579. 'stations_to_show'
  580. ]
  581. else:
  582. stations_to_show = default_stations_to_show
  583. location = (
  584. saved_place if sorting_code != 0
  585. else default_location
  586. )
  587. sorting_method = (
  588. (lambda station: station.distance) if sorting_code in [0, 2]
  589. else (lambda station: station.name) if sorting_code == 1
  590. else ciclopi_custom_sorter(custom_order) if sorting_code == 3
  591. else (lambda station: 0)
  592. )
  593. stations = sorted(
  594. _get_stations(
  595. ciclopi_data,
  596. location
  597. ),
  598. key=sorting_method
  599. )
  600. if (
  601. stations_to_show == -1
  602. and not show_all
  603. ):
  604. stations = list(
  605. filter(
  606. lambda station: station.id in [
  607. record['station']
  608. for record in custom_order
  609. ],
  610. stations
  611. )
  612. )
  613. if (
  614. stations_to_show > 0
  615. and sorting_code != 1
  616. and not show_all
  617. ):
  618. stations = stations[:stations_to_show]
  619. filter_label = ""
  620. if stations_to_show == -1:
  621. filter_label = bot.get_message(
  622. 'ciclopi', 'filters', 'fav', 'all' if show_all else 'only',
  623. update=update, user_record=user_record
  624. )
  625. elif len(stations) < len(Station.stations):
  626. filter_label = bot.get_message(
  627. 'ciclopi', 'filters', 'num',
  628. update=update, user_record=user_record,
  629. n=stations_to_show
  630. )
  631. if filter_label:
  632. filter_label = ' ({label})'.format(
  633. label=filter_label
  634. )
  635. text = (
  636. "🚲 {title} {order}"
  637. "{filter} {sort[symbol]}\n"
  638. "\n"
  639. "{stations_list}"
  640. ).format(
  641. title=bot.get_message(
  642. 'ciclopi', 'command', 'title',
  643. update=update, user_record=user_record
  644. ),
  645. sort=CICLOPI_SORTING_CHOICES[sorting_code],
  646. order=bot.get_message(
  647. 'ciclopi', 'sorting',
  648. CICLOPI_SORTING_CHOICES[sorting_code]['id'],
  649. 'short_description',
  650. update=update, user_record=user_record
  651. ),
  652. filter=filter_label,
  653. stations_list=(
  654. '\n\n'.join(
  655. station.status.format(
  656. not_available=bot.get_message(
  657. 'ciclopi', 'status', 'not_available',
  658. update=update, user_record=user_record
  659. )
  660. )
  661. for station in stations
  662. ) if len(stations)
  663. else "<i>- {message} -</i>".format(
  664. message=bot.get_message(
  665. 'ciclopi', 'command', 'no_station_available',
  666. update=update, user_record=user_record
  667. )
  668. )
  669. ),
  670. )
  671. if not text:
  672. return
  673. reply_markup = make_inline_keyboard(
  674. (
  675. [
  676. make_button(
  677. text="💯 {message}".format(
  678. message=bot.get_message(
  679. 'ciclopi', 'command', 'buttons', 'all',
  680. update=update, user_record=user_record
  681. )
  682. ),
  683. prefix='ciclopi:///',
  684. data=['show', 'all']
  685. )
  686. ] if len(stations) < len(Station.stations)
  687. else [
  688. make_button(
  689. "{sy} {message}".format(
  690. message=(
  691. bot.get_message(
  692. 'ciclopi', 'command', 'buttons', 'only_fav',
  693. update=update, user_record=user_record
  694. ) if stations_to_show == -1
  695. else bot.get_message(
  696. 'ciclopi', 'command', 'buttons', 'first_n',
  697. update=update, user_record=user_record,
  698. n=stations_to_show
  699. )
  700. ),
  701. sy=CICLOPI_STATIONS_TO_SHOW[stations_to_show]['symbol']
  702. ),
  703. prefix='ciclopi:///',
  704. data=['show']
  705. )
  706. ] if show_all
  707. else []
  708. ) + [
  709. make_button(
  710. text=bot.get_message(
  711. 'ciclopi', 'command', 'buttons', 'update',
  712. update=update, user_record=user_record
  713. ),
  714. prefix='ciclopi:///',
  715. data=(
  716. ['show'] + (
  717. [] if len(stations) < len(Station.stations)
  718. else ['all']
  719. )
  720. )
  721. ),
  722. make_button(
  723. text=bot.get_message(
  724. 'ciclopi', 'command', 'buttons', 'legend',
  725. update=update, user_record=user_record
  726. ),
  727. prefix='ciclopi:///',
  728. data=['legend']
  729. ),
  730. make_button(
  731. text=bot.get_message(
  732. 'ciclopi', 'command', 'buttons', 'settings',
  733. update=update, user_record=user_record
  734. ),
  735. prefix='ciclopi:///',
  736. data=['main']
  737. )
  738. ],
  739. 2
  740. )
  741. parameters = dict(
  742. update=update,
  743. text=text,
  744. parse_mode='HTML',
  745. reply_markup=reply_markup
  746. )
  747. method = (
  748. bot.send_message
  749. if sent_message is None
  750. else bot.edit_message_text
  751. )
  752. await method(**parameters)
  753. return
  754. def get_menu_back_buttons(bot, update, user_record,
  755. include_back_to_settings=True):
  756. """Return a list of menu buttons to navigate back in the menu.
  757. `include_back_to_settings` : Bool
  758. Set it to True to include a 'back to settings' menu button.
  759. """
  760. if include_back_to_settings:
  761. buttons = [
  762. make_button(
  763. text="⚙️ {message}".format(
  764. message=bot.get_message(
  765. 'ciclopi', 'button', 'back_to_settings',
  766. update=update, user_record=user_record
  767. )
  768. ),
  769. prefix='ciclopi:///',
  770. data=['main']
  771. )
  772. ]
  773. else:
  774. buttons = []
  775. buttons += [
  776. make_button(
  777. text="🚲 {message}".format(
  778. message=bot.get_message(
  779. 'ciclopi', 'button', 'back_to_stations',
  780. update=update, user_record=user_record
  781. )
  782. ),
  783. prefix='ciclopi:///',
  784. data=['show']
  785. )
  786. ]
  787. return buttons
  788. async def _ciclopi_button_main(bot, update, user_record, arguments):
  789. result, text, reply_markup = '', '', None
  790. text = (
  791. "⚙️ {settings_title} 🚲\n"
  792. "\n"
  793. "{settings_list}"
  794. ).format(
  795. settings_title=bot.get_message(
  796. 'ciclopi', 'button', 'title',
  797. update=update, user_record=user_record
  798. ),
  799. settings_list='\n'.join(
  800. "- {symbol} {name}: {description}".format(
  801. symbol=bot.get_message(
  802. 'ciclopi', 'settings', setting, 'symbol',
  803. update=update, user_record=user_record
  804. ),
  805. name=bot.get_message(
  806. 'ciclopi', 'settings', setting, 'name',
  807. update=update, user_record=user_record
  808. ),
  809. description=bot.get_message(
  810. 'ciclopi', 'settings', setting, 'description',
  811. update=update, user_record=user_record
  812. )
  813. )
  814. for setting in bot.messages['ciclopi']['settings']
  815. )
  816. )
  817. reply_markup = make_inline_keyboard(
  818. [
  819. make_button(
  820. text="{symbol} {name}".format(
  821. symbol=bot.get_message(
  822. 'ciclopi', 'settings', setting, 'symbol',
  823. update=update, user_record=user_record
  824. ),
  825. name=bot.get_message(
  826. 'ciclopi', 'settings', setting, 'name',
  827. update=update, user_record=user_record
  828. )
  829. ),
  830. prefix='ciclopi:///',
  831. data=[setting]
  832. )
  833. for setting in bot.messages['ciclopi']['settings']
  834. ] + get_menu_back_buttons(
  835. bot=bot, update=update, user_record=user_record,
  836. include_back_to_settings=False
  837. )
  838. )
  839. return result, text, reply_markup
  840. async def _ciclopi_button_sort(bot, update, user_record, arguments):
  841. result, text, reply_markup = '', '', None
  842. chat_id = (
  843. update['message']['chat']['id'] if 'message' in update
  844. else update['chat']['id'] if 'chat' in update
  845. else 0
  846. )
  847. with bot.db as db:
  848. ciclopi_record = db['ciclopi'].find_one(
  849. chat_id=chat_id
  850. )
  851. if ciclopi_record is None:
  852. ciclopi_record = dict(
  853. chat_id=chat_id,
  854. sorting=0
  855. )
  856. if len(arguments) == 1:
  857. new_choice = (
  858. int(arguments[0])
  859. if arguments[0].isnumeric()
  860. else 0
  861. )
  862. if new_choice == ciclopi_record['sorting']:
  863. return bot.get_message(
  864. 'ciclopi', 'button', 'no_change',
  865. update=update, user_record=user_record
  866. ), '', None
  867. elif new_choice not in CICLOPI_SORTING_CHOICES:
  868. return bot.get_message(
  869. 'ciclopi', 'button', 'unknown_option',
  870. update=update, user_record=user_record
  871. ), '', None
  872. db['ciclopi'].upsert(
  873. dict(
  874. chat_id=chat_id,
  875. sorting=new_choice
  876. ),
  877. ['chat_id'],
  878. ensure=True
  879. )
  880. ciclopi_record['sorting'] = new_choice
  881. result = bot.get_message(
  882. 'ciclopi', 'button', 'done',
  883. update=update, user_record=user_record
  884. )
  885. text = bot.get_message(
  886. 'ciclopi', 'button', 'sorting_header',
  887. update=update, user_record=user_record
  888. ).format(
  889. options='\n'.join(
  890. "- {symbol} {name}: {description}".format(
  891. symbol=choice['symbol'],
  892. name=bot.get_message(
  893. 'ciclopi', 'sorting', choice['id'], 'name',
  894. update=update, user_record=user_record
  895. ),
  896. description=bot.get_message(
  897. 'ciclopi', 'sorting', choice['id'], 'description',
  898. update=update, user_record=user_record
  899. )
  900. )
  901. for choice in CICLOPI_SORTING_CHOICES.values()
  902. )
  903. )
  904. reply_markup = make_inline_keyboard(
  905. [
  906. make_button(
  907. text="{s} {name} {c[symbol]}".format(
  908. c=choice,
  909. s=(
  910. '✅'
  911. if code == ciclopi_record['sorting']
  912. else '☑️'
  913. ),
  914. name=bot.get_message(
  915. 'ciclopi', 'sorting', choice['id'], 'name',
  916. update=update, user_record=user_record
  917. )
  918. ),
  919. prefix='ciclopi:///',
  920. data=['sort', code]
  921. )
  922. for code, choice in CICLOPI_SORTING_CHOICES.items()
  923. ] + get_menu_back_buttons(
  924. bot=bot, update=update, user_record=user_record,
  925. include_back_to_settings=True
  926. )
  927. )
  928. return result, text, reply_markup
  929. async def _ciclopi_button_limit(bot, update, user_record, arguments):
  930. result, text, reply_markup = '', '', None
  931. chat_id = (
  932. update['message']['chat']['id'] if 'message' in update
  933. else update['chat']['id'] if 'chat' in update
  934. else 0
  935. )
  936. with bot.db as db:
  937. ciclopi_record = db['ciclopi'].find_one(
  938. chat_id=chat_id
  939. )
  940. if ciclopi_record is None or 'stations_to_show' not in ciclopi_record:
  941. ciclopi_record = dict(
  942. chat_id=chat_id,
  943. stations_to_show=5
  944. )
  945. if len(arguments) == 1:
  946. new_choice = (
  947. int(arguments[0])
  948. if arguments[0].lstrip('+-').isnumeric()
  949. else 0
  950. )
  951. if new_choice == ciclopi_record['stations_to_show']:
  952. return bot.get_message(
  953. 'ciclopi', 'button', 'no_change',
  954. update=update, user_record=user_record
  955. ), '', None
  956. elif new_choice not in CICLOPI_STATIONS_TO_SHOW:
  957. return bot.get_message(
  958. 'ciclopi', 'button', 'unknown_option',
  959. update=update, user_record=user_record
  960. ), '', None
  961. db['ciclopi'].upsert(
  962. dict(
  963. chat_id=chat_id,
  964. stations_to_show=new_choice
  965. ),
  966. ['chat_id'],
  967. ensure=True
  968. )
  969. ciclopi_record['stations_to_show'] = new_choice
  970. result = bot.get_message(
  971. 'ciclopi', 'button', 'done',
  972. update=update, user_record=user_record
  973. )
  974. text = bot.get_message(
  975. 'ciclopi', 'button', 'limit_header',
  976. update=update, user_record=user_record
  977. ).format(
  978. options='\n'.join(
  979. "- {symbol} {name}".format(
  980. symbol=choice['symbol'],
  981. name=bot.get_message(
  982. 'ciclopi', 'filters', choice['id'], 'name',
  983. update=update, user_record=user_record
  984. )
  985. )
  986. for choice in CICLOPI_STATIONS_TO_SHOW.values()
  987. )
  988. )
  989. reply_markup = make_inline_keyboard(
  990. [
  991. make_button(
  992. text="{s} {name} {symbol}".format(
  993. symbol=choice['symbol'],
  994. name=bot.get_message(
  995. 'ciclopi', 'filters', choice['id'], 'name',
  996. update=update, user_record=user_record
  997. ),
  998. s=(
  999. '✅'
  1000. if code == ciclopi_record['stations_to_show']
  1001. else '☑️'
  1002. )
  1003. ),
  1004. prefix='ciclopi:///',
  1005. data=['limit', code]
  1006. )
  1007. for code, choice in CICLOPI_STATIONS_TO_SHOW.items()
  1008. ] + get_menu_back_buttons(
  1009. bot=bot, update=update, user_record=user_record,
  1010. include_back_to_settings=True
  1011. )
  1012. )
  1013. return result, text, reply_markup
  1014. async def _ciclopi_button_show(bot, update, user_record, arguments):
  1015. result, text, reply_markup = '', '', None
  1016. fake_update = update['message']
  1017. fake_update['from'] = update['from']
  1018. asyncio.ensure_future(
  1019. _ciclopi_command(
  1020. bot=bot,
  1021. update=fake_update,
  1022. user_record=user_record,
  1023. sent_message=fake_update,
  1024. show_all=(
  1025. True if len(arguments) == 1 and arguments[0] == 'all'
  1026. else False
  1027. )
  1028. )
  1029. )
  1030. return result, text, reply_markup
  1031. async def _ciclopi_button_legend(bot, update, user_record, arguments):
  1032. result, text, reply_markup = '', '', None
  1033. text = (
  1034. "<b>{s[name]}</b> | <i>{s[description]}</i>\n"
  1035. "<code> </code>🚲 {s[bikes]} | 🅿️ {s[free]} | 📍 {s[distance]}"
  1036. ).format(
  1037. s={
  1038. key: bot.get_message(
  1039. 'ciclopi', 'button', 'legend', key,
  1040. update=update, user_record=user_record
  1041. )
  1042. for key in ('name', 'distance', 'description', 'bikes', 'free')
  1043. }
  1044. )
  1045. reply_markup = make_inline_keyboard(
  1046. get_menu_back_buttons(
  1047. bot=bot, update=update, user_record=user_record,
  1048. include_back_to_settings=True
  1049. )
  1050. )
  1051. return result, text, reply_markup
  1052. async def _ciclopi_button_favourites_add(bot, update, user_record, arguments,
  1053. order_record, ordered_stations):
  1054. result, text, reply_markup = '', '', None
  1055. result = bot.get_message(
  1056. 'ciclopi', 'button', 'favourites', 'popup',
  1057. update=update, user_record=user_record
  1058. )
  1059. if len(arguments) == 2 and arguments[1].isnumeric():
  1060. station_id = int(arguments[1])
  1061. chat_id = (
  1062. update['message']['chat']['id'] if 'message' in update
  1063. else update['chat']['id'] if 'chat' in update
  1064. else 0
  1065. )
  1066. with bot.db as db:
  1067. if station_id in (s.id for s in ordered_stations): # Remove
  1068. # Find `old_record` to be removed
  1069. for old_record in order_record:
  1070. if old_record['station'] == station_id:
  1071. break
  1072. db.query(
  1073. """UPDATE ciclopi_custom_order
  1074. SET value = value - 1
  1075. WHERE chat_id = {chat_id}
  1076. AND value > {val}
  1077. """.format(
  1078. chat_id=chat_id,
  1079. val=old_record['value']
  1080. )
  1081. )
  1082. db['ciclopi_custom_order'].delete(
  1083. id=old_record['id']
  1084. )
  1085. order_record = list(
  1086. filter(
  1087. (lambda r: r['station'] != station_id),
  1088. order_record
  1089. )
  1090. )
  1091. ordered_stations = list(
  1092. filter(
  1093. (lambda s: s.id != station_id),
  1094. ordered_stations
  1095. )
  1096. )
  1097. else: # Add
  1098. new_record = dict(
  1099. chat_id=chat_id,
  1100. station=station_id,
  1101. value=(len(order_record) + 1)
  1102. )
  1103. db['ciclopi_custom_order'].upsert(
  1104. new_record,
  1105. ['chat_id', 'station'],
  1106. ensure=True
  1107. )
  1108. order_record.append(new_record)
  1109. ordered_stations.append(
  1110. Station(station_id)
  1111. )
  1112. text = bot.get_message(
  1113. 'ciclopi', 'button', 'favourites', 'header',
  1114. update=update, user_record=user_record,
  1115. options=line_drawing_unordered_list(
  1116. [
  1117. station.name
  1118. for station in ordered_stations
  1119. ]
  1120. )
  1121. )
  1122. reply_markup = dict(
  1123. inline_keyboard=make_lines_of_buttons(
  1124. [
  1125. make_button(
  1126. text=(
  1127. "{sy} {n}"
  1128. ).format(
  1129. sy=(
  1130. '✅' if station_id in [
  1131. s.id for s in ordered_stations
  1132. ]
  1133. else '☑️'
  1134. ),
  1135. n=station['name']
  1136. ),
  1137. prefix='ciclopi:///',
  1138. data=['fav', 'add', station_id]
  1139. )
  1140. for station_id, station in sorted(
  1141. Station.stations.items(),
  1142. key=lambda t: t[1]['name'] # Sort by station_name
  1143. )
  1144. ],
  1145. 3
  1146. ) + make_lines_of_buttons(
  1147. [
  1148. make_button(
  1149. text=bot.get_message(
  1150. 'ciclopi', 'button', 'favourites', 'sort', 'buttons',
  1151. 'change_order',
  1152. update=update, user_record=user_record
  1153. ),
  1154. prefix="ciclopi:///",
  1155. data=["fav"]
  1156. )
  1157. ] + get_menu_back_buttons(
  1158. bot=bot, update=update, user_record=user_record,
  1159. include_back_to_settings=True
  1160. ),
  1161. 3
  1162. )
  1163. )
  1164. return result, text, reply_markup
  1165. def move_favorite_station(
  1166. bot, chat_id, action, station_id,
  1167. order_record
  1168. ):
  1169. """Move a station in `chat_id`-associated custom order.
  1170. `bot`: Bot object, having a `.db` property.
  1171. `action`: should be `up` or `down`
  1172. `order_record`: list of records about `chat_id`-associated custom order.
  1173. """
  1174. assert action in ('up', 'down'), "Invalid action!"
  1175. for old_record in order_record:
  1176. if old_record['station'] == station_id:
  1177. break
  1178. with bot.db as db:
  1179. if action == 'down':
  1180. db.query(
  1181. """UPDATE ciclopi_custom_order
  1182. SET value = 500
  1183. WHERE chat_id = {chat_id}
  1184. AND value = {val} + 1
  1185. """.format(
  1186. chat_id=chat_id,
  1187. val=old_record['value']
  1188. )
  1189. )
  1190. db.query(
  1191. """UPDATE ciclopi_custom_order
  1192. SET value = value + 1
  1193. WHERE chat_id = {chat_id}
  1194. AND value = {val}
  1195. """.format(
  1196. chat_id=chat_id,
  1197. val=old_record['value']
  1198. )
  1199. )
  1200. db.query(
  1201. """UPDATE ciclopi_custom_order
  1202. SET value = {val}
  1203. WHERE chat_id = {chat_id}
  1204. AND value = 500
  1205. """.format(
  1206. chat_id=chat_id,
  1207. val=old_record['value']
  1208. )
  1209. )
  1210. elif action == 'up':
  1211. db.query(
  1212. """UPDATE ciclopi_custom_order
  1213. SET value = 500
  1214. WHERE chat_id = {chat_id}
  1215. AND value = {val} - 1
  1216. """.format(
  1217. chat_id=chat_id,
  1218. val=old_record['value']
  1219. )
  1220. )
  1221. db.query(
  1222. """UPDATE ciclopi_custom_order
  1223. SET value = value - 1
  1224. WHERE chat_id = {chat_id}
  1225. AND value = {val}
  1226. """.format(
  1227. chat_id=chat_id,
  1228. val=old_record['value']
  1229. )
  1230. )
  1231. db.query(
  1232. """UPDATE ciclopi_custom_order
  1233. SET value = {val}
  1234. WHERE chat_id = {chat_id}
  1235. AND value = 500
  1236. """.format(
  1237. chat_id=chat_id,
  1238. val=old_record['value']
  1239. )
  1240. )
  1241. order_record = list(
  1242. db['ciclopi_custom_order'].find(
  1243. chat_id=chat_id,
  1244. order_by=['value']
  1245. )
  1246. )
  1247. ordered_stations = [
  1248. Station(record['station'])
  1249. for record in order_record
  1250. ]
  1251. return order_record, ordered_stations
  1252. async def _ciclopi_button_favourites(bot, update, user_record, arguments):
  1253. result, text, reply_markup = '', '', None
  1254. action = (
  1255. arguments[0] if len(arguments) > 0
  1256. else 'up'
  1257. )
  1258. chat_id = (
  1259. update['message']['chat']['id'] if 'message' in update
  1260. else update['chat']['id'] if 'chat' in update
  1261. else 0
  1262. )
  1263. with bot.db as db:
  1264. order_record = list(
  1265. db['ciclopi_custom_order'].find(
  1266. chat_id=chat_id,
  1267. order_by=['value']
  1268. )
  1269. )
  1270. ordered_stations = [
  1271. Station(record['station'])
  1272. for record in order_record
  1273. ]
  1274. if action == 'add':
  1275. return await _ciclopi_button_favourites_add(
  1276. bot, update, user_record, arguments,
  1277. order_record, ordered_stations
  1278. )
  1279. elif action == 'dummy':
  1280. return bot.get_message(
  1281. 'ciclopi', 'button', 'favourites', 'sort', 'end',
  1282. update=update, user_record=user_record
  1283. ), '', None
  1284. elif action == 'set' and len(arguments) > 1:
  1285. action = arguments[1]
  1286. elif (
  1287. action in ['up', 'down']
  1288. and len(arguments) > 1
  1289. and arguments[1].isnumeric()
  1290. ):
  1291. station_id = int(arguments[1])
  1292. order_record, ordered_stations = move_favorite_station(
  1293. bot, chat_id, action, station_id,
  1294. order_record
  1295. )
  1296. text = bot.get_message(
  1297. 'ciclopi', 'button', 'favourites', 'sort', 'header',
  1298. update=update, user_record=user_record,
  1299. options=line_drawing_unordered_list(
  1300. [
  1301. station.name
  1302. for station in ordered_stations
  1303. ]
  1304. )
  1305. )
  1306. reply_markup = dict(
  1307. inline_keyboard=[
  1308. [
  1309. make_button(
  1310. text="{s.name} {sy}".format(
  1311. sy=(
  1312. '⬆️' if (
  1313. action == 'up'
  1314. and n != 1
  1315. ) else '⬇️' if (
  1316. action == 'down'
  1317. and n != len(ordered_stations)
  1318. ) else '⏹'
  1319. ),
  1320. s=station
  1321. ),
  1322. prefix='ciclopi:///',
  1323. data=[
  1324. 'fav',
  1325. (
  1326. action if (
  1327. action == 'up'
  1328. and n != 1
  1329. ) or (
  1330. action == 'down'
  1331. and n != len(ordered_stations)
  1332. )
  1333. else 'dummy'
  1334. ),
  1335. station.id
  1336. ]
  1337. )
  1338. ]
  1339. for n, station in enumerate(ordered_stations, 1)
  1340. ] + [
  1341. [
  1342. make_button(
  1343. text=bot.get_message(
  1344. 'ciclopi', 'button', 'favourites', 'sort', 'buttons',
  1345. 'edit',
  1346. update=update, user_record=user_record
  1347. ),
  1348. prefix='ciclopi:///',
  1349. data=['fav', 'add']
  1350. )
  1351. ]
  1352. ] + [
  1353. [
  1354. (
  1355. make_button(
  1356. text=bot.get_message(
  1357. 'ciclopi', 'button', 'favourites', 'sort',
  1358. 'buttons', 'move_down',
  1359. update=update, user_record=user_record
  1360. ),
  1361. prefix='ciclopi:///',
  1362. data=['fav', 'set', 'down']
  1363. ) if action == 'up'
  1364. else make_button(
  1365. text=bot.get_message(
  1366. 'ciclopi', 'button', 'favourites', 'sort',
  1367. 'buttons', 'move_up',
  1368. update=update, user_record=user_record
  1369. ),
  1370. prefix='ciclopi:///',
  1371. data=['fav', 'set', 'up']
  1372. )
  1373. )
  1374. ]
  1375. ] + [
  1376. get_menu_back_buttons(
  1377. bot=bot, update=update, user_record=user_record,
  1378. include_back_to_settings=True
  1379. )
  1380. ]
  1381. )
  1382. return result, text, reply_markup
  1383. async def _ciclopi_button_setpos(bot, update, user_record, arguments):
  1384. result, text, reply_markup = '', '', None
  1385. chat_id = (
  1386. update['message']['chat']['id'] if 'message' in update
  1387. else update['chat']['id'] if 'chat' in update
  1388. else 0
  1389. )
  1390. result = bot.get_message(
  1391. 'ciclopi', 'button', 'location', 'popup',
  1392. update=update, user_record=user_record
  1393. )
  1394. bot.set_individual_location_handler(
  1395. await async_wrapper(
  1396. set_ciclopi_location
  1397. ),
  1398. update
  1399. )
  1400. bot.set_individual_text_message_handler(
  1401. cancel_ciclopi_location,
  1402. update
  1403. )
  1404. asyncio.ensure_future(
  1405. bot.send_message(
  1406. chat_id=chat_id,
  1407. text=bot.get_message(
  1408. 'ciclopi', 'button', 'location', 'instructions',
  1409. update=update, user_record=user_record
  1410. ),
  1411. reply_markup=dict(
  1412. keyboard=[
  1413. [
  1414. dict(
  1415. text=bot.get_message(
  1416. 'ciclopi', 'button', 'location',
  1417. 'send_current_location',
  1418. update=update, user_record=user_record
  1419. ),
  1420. request_location=True
  1421. )
  1422. ],
  1423. [
  1424. dict(
  1425. text=bot.get_message(
  1426. 'ciclopi', 'button', 'location', 'cancel',
  1427. update=update, user_record=user_record
  1428. ),
  1429. )
  1430. ]
  1431. ],
  1432. resize_keyboard=True
  1433. )
  1434. )
  1435. )
  1436. return result, text, reply_markup
  1437. _ciclopi_button_routing_table = {
  1438. 'main': _ciclopi_button_main,
  1439. 'sort': _ciclopi_button_sort,
  1440. 'limit': _ciclopi_button_limit,
  1441. 'show': _ciclopi_button_show,
  1442. 'setpos': _ciclopi_button_setpos,
  1443. 'legend': _ciclopi_button_legend,
  1444. 'fav': _ciclopi_button_favourites
  1445. }
  1446. async def _ciclopi_button(bot, update, user_record):
  1447. data = update['data']
  1448. command, *arguments = extract(data, ':///').split('|')
  1449. if command in _ciclopi_button_routing_table:
  1450. result, text, reply_markup = await _ciclopi_button_routing_table[
  1451. command
  1452. ](
  1453. bot, update, user_record, arguments
  1454. )
  1455. else:
  1456. return
  1457. if text:
  1458. return dict(
  1459. text=result,
  1460. edit=dict(
  1461. text=text,
  1462. parse_mode='HTML',
  1463. reply_markup=reply_markup
  1464. )
  1465. )
  1466. return result
  1467. def init(bot, ciclopi_messages=None, ciclopi_messages_json=None,
  1468. _default_location=(43.718518, 10.402165)):
  1469. """Take a bot and assign CicloPi-related commands to it.
  1470. `ciclopi_messages` : dict
  1471. Multilanguage dictionary with all CicloPi-related messages.
  1472. `default_location` : tuple (float, float)
  1473. Tuple of coordinates (latitude, longitude) of default location.
  1474. Defaults to Borgo Stretto CicloPi station.
  1475. """
  1476. # Define a global `default_location` variable holding default location
  1477. global default_location
  1478. default_location = Location(_default_location)
  1479. bot.ciclopi_default_location = default_location
  1480. with bot.db as db:
  1481. if 'ciclopi_stations' not in db.tables:
  1482. db['ciclopi_stations'].insert_many(
  1483. sorted(
  1484. [
  1485. dict(
  1486. station_id=station_id,
  1487. name=station['name'],
  1488. latitude=station['coordinates'][0],
  1489. longitude=station['coordinates'][1]
  1490. )
  1491. for station_id, station in Station.stations.items()
  1492. ],
  1493. key=(lambda station: station['station_id'])
  1494. )
  1495. )
  1496. if 'ciclopi' not in db.tables:
  1497. db['ciclopi'].insert(
  1498. dict(
  1499. chat_id=0,
  1500. sorting=0,
  1501. latitude=0.0,
  1502. longitude=0.0,
  1503. stations_to_show=-1
  1504. )
  1505. )
  1506. if ciclopi_messages is None:
  1507. try:
  1508. from .messages import default_ciclopi_messages as ciclopi_messages
  1509. except ImportError:
  1510. ciclopi_messages = {}
  1511. bot.messages['ciclopi'] = ciclopi_messages
  1512. @bot.command(command='/ciclopi', aliases=["CicloPi 🚲", "🚲 CicloPi 🔴"],
  1513. reply_keyboard_button=(
  1514. bot.messages['ciclopi']['command']['reply_keyboard_button']
  1515. ),
  1516. show_in_keyboard=True,
  1517. description=(
  1518. bot.messages['ciclopi']['command']['description']
  1519. ),
  1520. help_section=bot.messages['ciclopi']['help'],
  1521. authorization_level='everybody')
  1522. async def ciclopi_command(bot, update, user_record):
  1523. return await _ciclopi_command(bot, update, user_record)
  1524. @bot.button(prefix='ciclopi:///', authorization_level='everybody')
  1525. async def ciclopi_button(bot, update, user_record):
  1526. return await _ciclopi_button(bot, update, user_record)