Queer European MD passionate about IT

ciclopi.py 51 KB

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