Queer European MD passionate about IT

ciclopi.py 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612
  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. placeholder_id = bot.set_placeholder(
  515. timeout=datetime.timedelta(seconds=1),
  516. sent_message=sent_message,
  517. chat_id=chat_id,
  518. text="<i>{message}...</i>".format(
  519. message=bot.get_message(
  520. 'ciclopi', 'command', 'updating',
  521. update=update, user_record=user_record
  522. )
  523. )
  524. )
  525. ciclopi_data = await ciclopi_webpage.get_page()
  526. if ciclopi_data is None or isinstance(ciclopi_data, Exception):
  527. text = bot.get_message(
  528. 'ciclopi', 'command', 'unavailable_website',
  529. update=update, user_record=user_record
  530. )
  531. else:
  532. with bot.db as db:
  533. ciclopi_record = db['ciclopi'].find_one(
  534. chat_id=chat_id
  535. )
  536. custom_order = list(
  537. db['ciclopi_custom_order'].find(
  538. chat_id=chat_id
  539. )
  540. )
  541. if (
  542. ciclopi_record is not None
  543. and isinstance(ciclopi_record, dict)
  544. and 'sorting' in ciclopi_record
  545. and ciclopi_record['sorting'] in CICLOPI_SORTING_CHOICES
  546. ):
  547. sorting_code = ciclopi_record['sorting']
  548. if (
  549. 'latitude' in ciclopi_record
  550. and ciclopi_record['latitude'] is not None
  551. and 'longitude' in ciclopi_record
  552. and ciclopi_record['longitude'] is not None
  553. ):
  554. saved_place = Location(
  555. (
  556. ciclopi_record['latitude'],
  557. ciclopi_record['longitude']
  558. )
  559. )
  560. else:
  561. saved_place = default_location
  562. else:
  563. sorting_code = 0
  564. if (
  565. ciclopi_record is not None
  566. and isinstance(ciclopi_record, dict)
  567. and 'stations_to_show' in ciclopi_record
  568. and ciclopi_record[
  569. 'stations_to_show'
  570. ] in CICLOPI_STATIONS_TO_SHOW
  571. ):
  572. stations_to_show = ciclopi_record[
  573. 'stations_to_show'
  574. ]
  575. else:
  576. stations_to_show = default_stations_to_show
  577. location = (
  578. saved_place if sorting_code != 0
  579. else default_location
  580. )
  581. sorting_method = (
  582. (lambda station: station.distance) if sorting_code in [0, 2]
  583. else (lambda station: station.name) if sorting_code == 1
  584. else ciclopi_custom_sorter(custom_order) if sorting_code == 3
  585. else (lambda station: 0)
  586. )
  587. stations = sorted(
  588. _get_stations(
  589. ciclopi_data,
  590. location
  591. ),
  592. key=sorting_method
  593. )
  594. if (
  595. stations_to_show == -1
  596. and not show_all
  597. ):
  598. stations = list(
  599. filter(
  600. lambda station: station.id in [
  601. record['station']
  602. for record in custom_order
  603. ],
  604. stations
  605. )
  606. )
  607. if (
  608. stations_to_show > 0
  609. and sorting_code != 1
  610. and not show_all
  611. ):
  612. stations = stations[:stations_to_show]
  613. filter_label = ""
  614. if stations_to_show == -1:
  615. filter_label = bot.get_message(
  616. 'ciclopi', 'filters', 'fav', 'all' if show_all else 'only',
  617. update=update, user_record=user_record
  618. )
  619. elif len(stations) < len(Station.stations):
  620. filter_label = bot.get_message(
  621. 'ciclopi', 'filters', 'num',
  622. update=update, user_record=user_record,
  623. n=stations_to_show
  624. )
  625. if filter_label:
  626. filter_label = ' ({label})'.format(
  627. label=filter_label
  628. )
  629. text = (
  630. "🚲 {title} {order}"
  631. "{filter} {sort[symbol]}\n"
  632. "\n"
  633. "{stations_list}"
  634. ).format(
  635. title=bot.get_message(
  636. 'ciclopi', 'command', 'title',
  637. update=update, user_record=user_record
  638. ),
  639. sort=CICLOPI_SORTING_CHOICES[sorting_code],
  640. order=bot.get_message(
  641. 'ciclopi', 'sorting',
  642. CICLOPI_SORTING_CHOICES[sorting_code]['id'],
  643. 'short_description',
  644. update=update, user_record=user_record
  645. ),
  646. filter=filter_label,
  647. stations_list=(
  648. '\n\n'.join(
  649. station.status.format(
  650. not_available=bot.get_message(
  651. 'ciclopi', 'status', 'not_available',
  652. update=update, user_record=user_record
  653. )
  654. )
  655. for station in stations
  656. ) if len(stations)
  657. else "<i>- {message} -</i>".format(
  658. message=bot.get_message(
  659. 'ciclopi', 'command', 'no_station_available',
  660. update=update, user_record=user_record
  661. )
  662. )
  663. ),
  664. )
  665. if not text:
  666. return
  667. reply_markup = make_inline_keyboard(
  668. (
  669. [
  670. make_button(
  671. text="💯 {message}".format(
  672. message=bot.get_message(
  673. 'ciclopi', 'command', 'buttons', 'all',
  674. update=update, user_record=user_record
  675. )
  676. ),
  677. prefix='ciclopi:///',
  678. data=['show', 'all']
  679. )
  680. ] if len(stations) < len(Station.stations)
  681. else [
  682. make_button(
  683. "{sy} {message}".format(
  684. message=(
  685. bot.get_message(
  686. 'ciclopi', 'command', 'buttons', 'only_fav',
  687. update=update, user_record=user_record
  688. ) if stations_to_show == -1
  689. else bot.get_message(
  690. 'ciclopi', 'command', 'buttons', 'first_n',
  691. update=update, user_record=user_record,
  692. n=stations_to_show
  693. )
  694. ),
  695. sy=CICLOPI_STATIONS_TO_SHOW[stations_to_show]['symbol']
  696. ),
  697. prefix='ciclopi:///',
  698. data=['show']
  699. )
  700. ] if show_all
  701. else []
  702. ) + [
  703. make_button(
  704. text=bot.get_message(
  705. 'ciclopi', 'command', 'buttons', 'update',
  706. update=update, user_record=user_record
  707. ),
  708. prefix='ciclopi:///',
  709. data=(
  710. ['show'] + (
  711. [] if len(stations) < len(Station.stations)
  712. else ['all']
  713. )
  714. )
  715. ),
  716. make_button(
  717. text=bot.get_message(
  718. 'ciclopi', 'command', 'buttons', 'legend',
  719. update=update, user_record=user_record
  720. ),
  721. prefix='ciclopi:///',
  722. data=['legend']
  723. ),
  724. make_button(
  725. text=bot.get_message(
  726. 'ciclopi', 'command', 'buttons', 'settings',
  727. update=update, user_record=user_record
  728. ),
  729. prefix='ciclopi:///',
  730. data=['main']
  731. )
  732. ],
  733. 2
  734. )
  735. parameters = dict(
  736. update=update,
  737. text=text,
  738. parse_mode='HTML',
  739. reply_markup=reply_markup
  740. )
  741. method = (
  742. bot.send_message
  743. if sent_message is None
  744. else bot.edit_message_text
  745. )
  746. await method(**parameters)
  747. # Mark request as done
  748. bot.placeholder_requests[placeholder_id] = 1
  749. return
  750. def get_menu_back_buttons(bot, update, user_record,
  751. include_back_to_settings=True):
  752. """Return a list of menu buttons to navigate back in the menu.
  753. `include_back_to_settings` : Bool
  754. Set it to True to include a 'back to settings' menu button.
  755. """
  756. if include_back_to_settings:
  757. buttons = [
  758. make_button(
  759. text="⚙️ {message}".format(
  760. message=bot.get_message(
  761. 'ciclopi', 'button', 'back_to_settings',
  762. update=update, user_record=user_record
  763. )
  764. ),
  765. prefix='ciclopi:///',
  766. data=['main']
  767. )
  768. ]
  769. else:
  770. buttons = []
  771. buttons += [
  772. make_button(
  773. text="🚲 {message}".format(
  774. message=bot.get_message(
  775. 'ciclopi', 'button', 'back_to_stations',
  776. update=update, user_record=user_record
  777. )
  778. ),
  779. prefix='ciclopi:///',
  780. data=['show']
  781. )
  782. ]
  783. return buttons
  784. async def _ciclopi_button_main(bot, update, user_record, arguments):
  785. result, text, reply_markup = '', '', None
  786. text = (
  787. "⚙️ {settings_title} 🚲\n"
  788. "\n"
  789. "{settings_list}"
  790. ).format(
  791. settings_title=bot.get_message(
  792. 'ciclopi', 'button', 'title',
  793. update=update, user_record=user_record
  794. ),
  795. settings_list='\n'.join(
  796. "- {symbol} {name}: {description}".format(
  797. symbol=bot.get_message(
  798. 'ciclopi', 'settings', setting, 'symbol',
  799. update=update, user_record=user_record
  800. ),
  801. name=bot.get_message(
  802. 'ciclopi', 'settings', setting, 'name',
  803. update=update, user_record=user_record
  804. ),
  805. description=bot.get_message(
  806. 'ciclopi', 'settings', setting, 'description',
  807. update=update, user_record=user_record
  808. )
  809. )
  810. for setting in bot.messages['ciclopi']['settings']
  811. )
  812. )
  813. reply_markup = make_inline_keyboard(
  814. [
  815. make_button(
  816. text="{symbol} {name}".format(
  817. symbol=bot.get_message(
  818. 'ciclopi', 'settings', setting, 'symbol',
  819. update=update, user_record=user_record
  820. ),
  821. name=bot.get_message(
  822. 'ciclopi', 'settings', setting, 'name',
  823. update=update, user_record=user_record
  824. )
  825. ),
  826. prefix='ciclopi:///',
  827. data=[setting]
  828. )
  829. for setting in bot.messages['ciclopi']['settings']
  830. ] + get_menu_back_buttons(
  831. bot=bot, update=update, user_record=user_record,
  832. include_back_to_settings=False
  833. )
  834. )
  835. return result, text, reply_markup
  836. async def _ciclopi_button_sort(bot, update, user_record, arguments):
  837. result, text, reply_markup = '', '', None
  838. chat_id = (
  839. update['message']['chat']['id'] if 'message' in update
  840. else update['chat']['id'] if 'chat' in update
  841. else 0
  842. )
  843. with bot.db as db:
  844. ciclopi_record = db['ciclopi'].find_one(
  845. chat_id=chat_id
  846. )
  847. if ciclopi_record is None:
  848. ciclopi_record = dict(
  849. chat_id=chat_id,
  850. sorting=0
  851. )
  852. if len(arguments) == 1:
  853. new_choice = (
  854. int(arguments[0])
  855. if arguments[0].isnumeric()
  856. else 0
  857. )
  858. if new_choice == ciclopi_record['sorting']:
  859. return bot.get_message(
  860. 'ciclopi', 'button', 'no_change',
  861. update=update, user_record=user_record
  862. ), '', None
  863. elif new_choice not in CICLOPI_SORTING_CHOICES:
  864. return bot.get_message(
  865. 'ciclopi', 'button', 'unknown_option',
  866. update=update, user_record=user_record
  867. ), '', None
  868. db['ciclopi'].upsert(
  869. dict(
  870. chat_id=chat_id,
  871. sorting=new_choice
  872. ),
  873. ['chat_id'],
  874. ensure=True
  875. )
  876. ciclopi_record['sorting'] = new_choice
  877. result = bot.get_message(
  878. 'ciclopi', 'button', 'done',
  879. update=update, user_record=user_record
  880. )
  881. text = bot.get_message(
  882. 'ciclopi', 'button', 'sorting_header',
  883. update=update, user_record=user_record
  884. ).format(
  885. options='\n'.join(
  886. "- {symbol} {name}: {description}".format(
  887. symbol=choice['symbol'],
  888. name=bot.get_message(
  889. 'ciclopi', 'sorting', choice['id'], 'name',
  890. update=update, user_record=user_record
  891. ),
  892. description=bot.get_message(
  893. 'ciclopi', 'sorting', choice['id'], 'description',
  894. update=update, user_record=user_record
  895. )
  896. )
  897. for choice in CICLOPI_SORTING_CHOICES.values()
  898. )
  899. )
  900. reply_markup = make_inline_keyboard(
  901. [
  902. make_button(
  903. text="{s} {name} {c[symbol]}".format(
  904. c=choice,
  905. s=(
  906. '✅'
  907. if code == ciclopi_record['sorting']
  908. else '☑️'
  909. ),
  910. name=bot.get_message(
  911. 'ciclopi', 'sorting', choice['id'], 'name',
  912. update=update, user_record=user_record
  913. )
  914. ),
  915. prefix='ciclopi:///',
  916. data=['sort', code]
  917. )
  918. for code, choice in CICLOPI_SORTING_CHOICES.items()
  919. ] + get_menu_back_buttons(
  920. bot=bot, update=update, user_record=user_record,
  921. include_back_to_settings=True
  922. )
  923. )
  924. return result, text, reply_markup
  925. async def _ciclopi_button_limit(bot, update, user_record, arguments):
  926. result, text, reply_markup = '', '', None
  927. chat_id = (
  928. update['message']['chat']['id'] if 'message' in update
  929. else update['chat']['id'] if 'chat' in update
  930. else 0
  931. )
  932. with bot.db as db:
  933. ciclopi_record = db['ciclopi'].find_one(
  934. chat_id=chat_id
  935. )
  936. if ciclopi_record is None or 'stations_to_show' not in ciclopi_record:
  937. ciclopi_record = dict(
  938. chat_id=chat_id,
  939. stations_to_show=5
  940. )
  941. if len(arguments) == 1:
  942. new_choice = (
  943. int(arguments[0])
  944. if arguments[0].lstrip('+-').isnumeric()
  945. else 0
  946. )
  947. if new_choice == ciclopi_record['stations_to_show']:
  948. return bot.get_message(
  949. 'ciclopi', 'button', 'no_change',
  950. update=update, user_record=user_record
  951. ), '', None
  952. elif new_choice not in CICLOPI_STATIONS_TO_SHOW:
  953. return bot.get_message(
  954. 'ciclopi', 'button', 'unknown_option',
  955. update=update, user_record=user_record
  956. ), '', None
  957. db['ciclopi'].upsert(
  958. dict(
  959. chat_id=chat_id,
  960. stations_to_show=new_choice
  961. ),
  962. ['chat_id'],
  963. ensure=True
  964. )
  965. ciclopi_record['stations_to_show'] = new_choice
  966. result = bot.get_message(
  967. 'ciclopi', 'button', 'done',
  968. update=update, user_record=user_record
  969. )
  970. text = bot.get_message(
  971. 'ciclopi', 'button', 'limit_header',
  972. update=update, user_record=user_record
  973. ).format(
  974. options='\n'.join(
  975. "- {symbol} {name}".format(
  976. symbol=choice['symbol'],
  977. name=bot.get_message(
  978. 'ciclopi', 'filters', choice['id'], 'name',
  979. update=update, user_record=user_record
  980. )
  981. )
  982. for choice in CICLOPI_STATIONS_TO_SHOW.values()
  983. )
  984. )
  985. reply_markup = make_inline_keyboard(
  986. [
  987. make_button(
  988. text="{s} {name} {symbol}".format(
  989. symbol=choice['symbol'],
  990. name=bot.get_message(
  991. 'ciclopi', 'filters', choice['id'], 'name',
  992. update=update, user_record=user_record
  993. ),
  994. s=(
  995. '✅'
  996. if code == ciclopi_record['stations_to_show']
  997. else '☑️'
  998. )
  999. ),
  1000. prefix='ciclopi:///',
  1001. data=['limit', code]
  1002. )
  1003. for code, choice in CICLOPI_STATIONS_TO_SHOW.items()
  1004. ] + get_menu_back_buttons(
  1005. bot=bot, update=update, user_record=user_record,
  1006. include_back_to_settings=True
  1007. )
  1008. )
  1009. return result, text, reply_markup
  1010. async def _ciclopi_button_show(bot, update, user_record, arguments):
  1011. result, text, reply_markup = '', '', None
  1012. fake_update = update['message']
  1013. fake_update['from'] = update['from']
  1014. asyncio.ensure_future(
  1015. _ciclopi_command(
  1016. bot=bot,
  1017. update=fake_update,
  1018. user_record=user_record,
  1019. sent_message=fake_update,
  1020. show_all=(
  1021. True if len(arguments) == 1 and arguments[0] == 'all'
  1022. else False
  1023. )
  1024. )
  1025. )
  1026. return result, text, reply_markup
  1027. async def _ciclopi_button_legend(bot, update, user_record, arguments):
  1028. result, text, reply_markup = '', '', None
  1029. text = (
  1030. "<b>{s[name]}</b> | <i>{s[description]}</i>\n"
  1031. "<code> </code>🚲 {s[bikes]} | 🅿️ {s[free]} | 📍 {s[distance]}"
  1032. ).format(
  1033. s={
  1034. key: bot.get_message(
  1035. 'ciclopi', 'button', 'legend', key,
  1036. update=update, user_record=user_record
  1037. )
  1038. for key in ('name', 'distance', 'description', 'bikes', 'free')
  1039. }
  1040. )
  1041. reply_markup = make_inline_keyboard(
  1042. get_menu_back_buttons(
  1043. bot=bot, update=update, user_record=user_record,
  1044. include_back_to_settings=True
  1045. )
  1046. )
  1047. return result, text, reply_markup
  1048. async def _ciclopi_button_favourites_add(bot, update, user_record, arguments,
  1049. order_record, ordered_stations):
  1050. result, text, reply_markup = '', '', None
  1051. result = bot.get_message(
  1052. 'ciclopi', 'button', 'favourites', 'popup',
  1053. update=update, user_record=user_record
  1054. )
  1055. if len(arguments) == 2 and arguments[1].isnumeric():
  1056. station_id = int(arguments[1])
  1057. chat_id = (
  1058. update['message']['chat']['id'] if 'message' in update
  1059. else update['chat']['id'] if 'chat' in update
  1060. else 0
  1061. )
  1062. with bot.db as db:
  1063. if station_id in (s.id for s in ordered_stations): # Remove
  1064. # Find `old_record` to be removed
  1065. for old_record in order_record:
  1066. if old_record['station'] == station_id:
  1067. break
  1068. db.query(
  1069. """UPDATE ciclopi_custom_order
  1070. SET value = value - 1
  1071. WHERE chat_id = {chat_id}
  1072. AND value > {val}
  1073. """.format(
  1074. chat_id=chat_id,
  1075. val=old_record['value']
  1076. )
  1077. )
  1078. db['ciclopi_custom_order'].delete(
  1079. id=old_record['id']
  1080. )
  1081. order_record = list(
  1082. filter(
  1083. (lambda r: r['station'] != station_id),
  1084. order_record
  1085. )
  1086. )
  1087. ordered_stations = list(
  1088. filter(
  1089. (lambda s: s.id != station_id),
  1090. ordered_stations
  1091. )
  1092. )
  1093. else: # Add
  1094. new_record = dict(
  1095. chat_id=chat_id,
  1096. station=station_id,
  1097. value=(len(order_record) + 1)
  1098. )
  1099. db['ciclopi_custom_order'].upsert(
  1100. new_record,
  1101. ['chat_id', 'station'],
  1102. ensure=True
  1103. )
  1104. order_record.append(new_record)
  1105. ordered_stations.append(
  1106. Station(station_id)
  1107. )
  1108. text = bot.get_message(
  1109. 'ciclopi', 'button', 'favourites', 'header',
  1110. update=update, user_record=user_record,
  1111. options=line_drawing_unordered_list(
  1112. [
  1113. station.name
  1114. for station in ordered_stations
  1115. ]
  1116. )
  1117. )
  1118. reply_markup = dict(
  1119. inline_keyboard=make_lines_of_buttons(
  1120. [
  1121. make_button(
  1122. text=(
  1123. "{sy} {n}"
  1124. ).format(
  1125. sy=(
  1126. '✅' if station_id in [
  1127. s.id for s in ordered_stations
  1128. ]
  1129. else '☑️'
  1130. ),
  1131. n=station['name']
  1132. ),
  1133. prefix='ciclopi:///',
  1134. data=['fav', 'add', station_id]
  1135. )
  1136. for station_id, station in sorted(
  1137. Station.stations.items(),
  1138. key=lambda t: t[1]['name'] # Sort by station_name
  1139. )
  1140. ],
  1141. 3
  1142. ) + make_lines_of_buttons(
  1143. [
  1144. make_button(
  1145. text=bot.get_message(
  1146. 'ciclopi', 'button', 'favourites', 'sort', 'buttons',
  1147. 'change_order',
  1148. update=update, user_record=user_record
  1149. ),
  1150. prefix="ciclopi:///",
  1151. data=["fav"]
  1152. )
  1153. ] + get_menu_back_buttons(
  1154. bot=bot, update=update, user_record=user_record,
  1155. include_back_to_settings=True
  1156. ),
  1157. 3
  1158. )
  1159. )
  1160. return result, text, reply_markup
  1161. def move_favorite_station(
  1162. bot, chat_id, action, station_id,
  1163. order_record
  1164. ):
  1165. """Move a station in `chat_id`-associated custom order.
  1166. `bot`: Bot object, having a `.db` property.
  1167. `action`: should be `up` or `down`
  1168. `order_record`: list of records about `chat_id`-associated custom order.
  1169. """
  1170. assert action in ('up', 'down'), "Invalid action!"
  1171. for old_record in order_record:
  1172. if old_record['station'] == station_id:
  1173. break
  1174. with bot.db as db:
  1175. if action == 'down':
  1176. db.query(
  1177. """UPDATE ciclopi_custom_order
  1178. SET value = 500
  1179. WHERE chat_id = {chat_id}
  1180. AND value = {val} + 1
  1181. """.format(
  1182. chat_id=chat_id,
  1183. val=old_record['value']
  1184. )
  1185. )
  1186. db.query(
  1187. """UPDATE ciclopi_custom_order
  1188. SET value = value + 1
  1189. WHERE chat_id = {chat_id}
  1190. AND value = {val}
  1191. """.format(
  1192. chat_id=chat_id,
  1193. val=old_record['value']
  1194. )
  1195. )
  1196. db.query(
  1197. """UPDATE ciclopi_custom_order
  1198. SET value = {val}
  1199. WHERE chat_id = {chat_id}
  1200. AND value = 500
  1201. """.format(
  1202. chat_id=chat_id,
  1203. val=old_record['value']
  1204. )
  1205. )
  1206. elif action == 'up':
  1207. db.query(
  1208. """UPDATE ciclopi_custom_order
  1209. SET value = 500
  1210. WHERE chat_id = {chat_id}
  1211. AND value = {val} - 1
  1212. """.format(
  1213. chat_id=chat_id,
  1214. val=old_record['value']
  1215. )
  1216. )
  1217. db.query(
  1218. """UPDATE ciclopi_custom_order
  1219. SET value = value - 1
  1220. WHERE chat_id = {chat_id}
  1221. AND value = {val}
  1222. """.format(
  1223. chat_id=chat_id,
  1224. val=old_record['value']
  1225. )
  1226. )
  1227. db.query(
  1228. """UPDATE ciclopi_custom_order
  1229. SET value = {val}
  1230. WHERE chat_id = {chat_id}
  1231. AND value = 500
  1232. """.format(
  1233. chat_id=chat_id,
  1234. val=old_record['value']
  1235. )
  1236. )
  1237. order_record = list(
  1238. db['ciclopi_custom_order'].find(
  1239. chat_id=chat_id,
  1240. order_by=['value']
  1241. )
  1242. )
  1243. ordered_stations = [
  1244. Station(record['station'])
  1245. for record in order_record
  1246. ]
  1247. return order_record, ordered_stations
  1248. async def _ciclopi_button_favourites(bot, update, user_record, arguments):
  1249. result, text, reply_markup = '', '', None
  1250. action = (
  1251. arguments[0] if len(arguments) > 0
  1252. else 'up'
  1253. )
  1254. chat_id = (
  1255. update['message']['chat']['id'] if 'message' in update
  1256. else update['chat']['id'] if 'chat' in update
  1257. else 0
  1258. )
  1259. with bot.db as db:
  1260. order_record = list(
  1261. db['ciclopi_custom_order'].find(
  1262. chat_id=chat_id,
  1263. order_by=['value']
  1264. )
  1265. )
  1266. ordered_stations = [
  1267. Station(record['station'])
  1268. for record in order_record
  1269. ]
  1270. if action == 'add':
  1271. return await _ciclopi_button_favourites_add(
  1272. bot, update, user_record, arguments,
  1273. order_record, ordered_stations
  1274. )
  1275. elif action == 'dummy':
  1276. return bot.get_message(
  1277. 'ciclopi', 'button', 'favourites', 'sort', 'end',
  1278. update=update, user_record=user_record
  1279. ), '', None
  1280. elif action == 'set' and len(arguments) > 1:
  1281. action = arguments[1]
  1282. elif (
  1283. action in ['up', 'down']
  1284. and len(arguments) > 1
  1285. and arguments[1].isnumeric()
  1286. ):
  1287. station_id = int(arguments[1])
  1288. order_record, ordered_stations = move_favorite_station(
  1289. bot, chat_id, action, station_id,
  1290. order_record
  1291. )
  1292. text = bot.get_message(
  1293. 'ciclopi', 'button', 'favourites', 'sort', 'header',
  1294. update=update, user_record=user_record,
  1295. options=line_drawing_unordered_list(
  1296. [
  1297. station.name
  1298. for station in ordered_stations
  1299. ]
  1300. )
  1301. )
  1302. reply_markup = dict(
  1303. inline_keyboard=[
  1304. [
  1305. make_button(
  1306. text="{s.name} {sy}".format(
  1307. sy=(
  1308. '⬆️' if (
  1309. action == 'up'
  1310. and n != 1
  1311. ) else '⬇️' if (
  1312. action == 'down'
  1313. and n != len(ordered_stations)
  1314. ) else '⏹'
  1315. ),
  1316. s=station
  1317. ),
  1318. prefix='ciclopi:///',
  1319. data=[
  1320. 'fav',
  1321. (
  1322. action if (
  1323. action == 'up'
  1324. and n != 1
  1325. ) or (
  1326. action == 'down'
  1327. and n != len(ordered_stations)
  1328. )
  1329. else 'dummy'
  1330. ),
  1331. station.id
  1332. ]
  1333. )
  1334. ]
  1335. for n, station in enumerate(ordered_stations, 1)
  1336. ] + [
  1337. [
  1338. make_button(
  1339. text=bot.get_message(
  1340. 'ciclopi', 'button', 'favourites', 'sort', 'buttons',
  1341. 'edit',
  1342. update=update, user_record=user_record
  1343. ),
  1344. prefix='ciclopi:///',
  1345. data=['fav', 'add']
  1346. )
  1347. ]
  1348. ] + [
  1349. [
  1350. (
  1351. make_button(
  1352. text=bot.get_message(
  1353. 'ciclopi', 'button', 'favourites', 'sort',
  1354. 'buttons', 'move_down',
  1355. update=update, user_record=user_record
  1356. ),
  1357. prefix='ciclopi:///',
  1358. data=['fav', 'set', 'down']
  1359. ) if action == 'up'
  1360. else make_button(
  1361. text=bot.get_message(
  1362. 'ciclopi', 'button', 'favourites', 'sort',
  1363. 'buttons', 'move_up',
  1364. update=update, user_record=user_record
  1365. ),
  1366. prefix='ciclopi:///',
  1367. data=['fav', 'set', 'up']
  1368. )
  1369. )
  1370. ]
  1371. ] + [
  1372. get_menu_back_buttons(
  1373. bot=bot, update=update, user_record=user_record,
  1374. include_back_to_settings=True
  1375. )
  1376. ]
  1377. )
  1378. return result, text, reply_markup
  1379. async def _ciclopi_button_setpos(bot, update, user_record, arguments):
  1380. result, text, reply_markup = '', '', None
  1381. chat_id = (
  1382. update['message']['chat']['id'] if 'message' in update
  1383. else update['chat']['id'] if 'chat' in update
  1384. else 0
  1385. )
  1386. result = bot.get_message(
  1387. 'ciclopi', 'button', 'location', 'popup',
  1388. update=update, user_record=user_record
  1389. )
  1390. bot.set_individual_location_handler(
  1391. await async_wrapper(
  1392. set_ciclopi_location
  1393. ),
  1394. update
  1395. )
  1396. bot.set_individual_text_message_handler(
  1397. cancel_ciclopi_location,
  1398. update
  1399. )
  1400. asyncio.ensure_future(
  1401. bot.send_message(
  1402. chat_id=chat_id,
  1403. text=bot.get_message(
  1404. 'ciclopi', 'button', 'location', 'instructions',
  1405. update=update, user_record=user_record
  1406. ),
  1407. reply_markup=dict(
  1408. keyboard=[
  1409. [
  1410. dict(
  1411. text=bot.get_message(
  1412. 'ciclopi', 'button', 'location',
  1413. 'send_current_location',
  1414. update=update, user_record=user_record
  1415. ),
  1416. request_location=True
  1417. )
  1418. ],
  1419. [
  1420. dict(
  1421. text=bot.get_message(
  1422. 'ciclopi', 'button', 'location', 'cancel',
  1423. update=update, user_record=user_record
  1424. ),
  1425. )
  1426. ]
  1427. ],
  1428. resize_keyboard=True
  1429. )
  1430. )
  1431. )
  1432. return result, text, reply_markup
  1433. _ciclopi_button_routing_table = {
  1434. 'main': _ciclopi_button_main,
  1435. 'sort': _ciclopi_button_sort,
  1436. 'limit': _ciclopi_button_limit,
  1437. 'show': _ciclopi_button_show,
  1438. 'setpos': _ciclopi_button_setpos,
  1439. 'legend': _ciclopi_button_legend,
  1440. 'fav': _ciclopi_button_favourites
  1441. }
  1442. async def _ciclopi_button(bot, update, user_record):
  1443. data = update['data']
  1444. command, *arguments = extract(data, ':///').split('|')
  1445. if command in _ciclopi_button_routing_table:
  1446. result, text, reply_markup = await _ciclopi_button_routing_table[
  1447. command
  1448. ](
  1449. bot, update, user_record, arguments
  1450. )
  1451. else:
  1452. return
  1453. if text:
  1454. return dict(
  1455. text=result,
  1456. edit=dict(
  1457. text=text,
  1458. parse_mode='HTML',
  1459. reply_markup=reply_markup
  1460. )
  1461. )
  1462. return result
  1463. def init(bot, ciclopi_messages=None, ciclopi_messages_json=None,
  1464. _default_location=(43.718518, 10.402165)):
  1465. """Take a bot and assign CicloPi-related commands to it.
  1466. `ciclopi_messages` : dict
  1467. Multilanguage dictionary with all CicloPi-related messages.
  1468. `default_location` : tuple (float, float)
  1469. Tuple of coordinates (latitude, longitude) of default location.
  1470. Defaults to Borgo Stretto CicloPi station.
  1471. """
  1472. # Define a global `default_location` variable holding default location
  1473. global default_location
  1474. default_location = Location(_default_location)
  1475. bot.ciclopi_default_location = default_location
  1476. with bot.db as db:
  1477. if 'ciclopi_stations' not in db.tables:
  1478. db['ciclopi_stations'].insert_many(
  1479. sorted(
  1480. [
  1481. dict(
  1482. station_id=station_id,
  1483. name=station['name'],
  1484. latitude=station['coordinates'][0],
  1485. longitude=station['coordinates'][1]
  1486. )
  1487. for station_id, station in Station.stations.items()
  1488. ],
  1489. key=(lambda station: station['station_id'])
  1490. )
  1491. )
  1492. if 'ciclopi' not in db.tables:
  1493. db['ciclopi'].insert(
  1494. dict(
  1495. chat_id=0,
  1496. sorting=0,
  1497. latitude=0.0,
  1498. longitude=0.0,
  1499. stations_to_show=-1
  1500. )
  1501. )
  1502. if ciclopi_messages is None:
  1503. try:
  1504. from .messages import default_ciclopi_messages as ciclopi_messages
  1505. except ImportError:
  1506. ciclopi_messages = {}
  1507. bot.messages['ciclopi'] = ciclopi_messages
  1508. @bot.command(command='/ciclopi', aliases=["CicloPi 🚲", "🚲 CicloPi 🔴"],
  1509. reply_keyboard_button=(
  1510. bot.messages['ciclopi']['command']['reply_keyboard_button']
  1511. ),
  1512. show_in_keyboard=True,
  1513. description=(
  1514. bot.messages['ciclopi']['command']['description']
  1515. ),
  1516. help_section=bot.messages['ciclopi']['help'],
  1517. authorization_level='everybody')
  1518. async def ciclopi_command(bot, update, user_record):
  1519. return await _ciclopi_command(bot, update, user_record)
  1520. @bot.button(prefix='ciclopi:///', authorization_level='everybody')
  1521. async def ciclopi_button(bot, update, user_record):
  1522. return await _ciclopi_button(bot, update, user_record)