Queer European MD passionate about IT

api_helper.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. """Get and parse Telegram API web page."""
  2. # Standard library modules
  3. import argparse
  4. import asyncio
  5. import logging
  6. # Third party modules
  7. import aiohttp
  8. from bs4 import BeautifulSoup
  9. # Project modules
  10. from .api import TelegramBot
  11. api_url = "https://core.telegram.org/bots/api"
  12. class TelegramApiMethod(object):
  13. types = {
  14. 'Array of String': "List[str]",
  15. 'Boolean': "bool",
  16. 'Integer': "int",
  17. 'Integer or String': "Union[int, str]",
  18. 'String': "str",
  19. }
  20. """Telegram bot API method."""
  21. def __init__(self, name, description, table):
  22. """Initialize object with name, description and table data."""
  23. self._name = name
  24. self._description = description
  25. self._table = table
  26. self._parameters = self.get_parameters_from_table()
  27. @property
  28. def name(self):
  29. """Return method name."""
  30. return self._name
  31. @property
  32. def description(self):
  33. """Return method description."""
  34. return self._description
  35. @property
  36. def table(self):
  37. """Return method parameters table."""
  38. return self._table
  39. @property
  40. def parameters(self):
  41. return self._parameters
  42. @property
  43. def parameters_with_types(self):
  44. return [
  45. f"{parameter['name']}: {parameter['type']}"
  46. for parameter in self._parameters
  47. ]
  48. def print_parameters_table(self):
  49. """Extract parameters from API table."""
  50. result = ''
  51. if self.table is None:
  52. return "No parameters"
  53. rows = self.table.tbody.find_all('tr')
  54. if rows is None:
  55. rows = []
  56. for row in rows:
  57. result += '------\n'
  58. columns = row.find_all('td')
  59. if columns is None:
  60. columns = []
  61. for column in columns:
  62. result += f'| {column.text.strip()} |'
  63. result += '\n'
  64. result += '\n'
  65. return result
  66. def get_parameters_from_table(self):
  67. if self.table is None:
  68. return []
  69. parameters = []
  70. rows = self.table.tbody.find_all('tr') or []
  71. for row in rows:
  72. columns = row.find_all('td') or []
  73. name, type_, *_ = map(lambda column: column.text.strip(), columns)
  74. if type_ in self.types:
  75. type_ = self.types[type_]
  76. else:
  77. type_ = f"'{type_}'"
  78. parameters.append(
  79. dict(
  80. name=name,
  81. type=type_
  82. )
  83. )
  84. return parameters
  85. async def print_api_methods(loop=None,
  86. filename=None,
  87. print_all=False,
  88. output_file=None):
  89. """Get information from Telegram bot API web page."""
  90. if loop is None:
  91. loop = asyncio.get_event_loop()
  92. implemented_methods = dir(TelegramBot)
  93. async with aiohttp.ClientSession(
  94. loop=loop,
  95. timeout=aiohttp.ClientTimeout(
  96. total=100
  97. )
  98. ) as session:
  99. async with session.get(
  100. api_url
  101. ) as response:
  102. web_page = BeautifulSoup(
  103. await response.text(),
  104. "html.parser"
  105. )
  106. if filename is not None:
  107. with open(filename, 'w') as _file:
  108. _file.write(web_page.decode())
  109. methods = []
  110. for method in web_page.find_all("h4"):
  111. method_name = method.text
  112. description = ''
  113. parameters_table = None
  114. for tag in method.next_siblings:
  115. if tag.name is None:
  116. continue
  117. if tag.name == 'h4':
  118. break # Stop searching in siblings if <h4> is found
  119. if tag.name == 'table':
  120. parameters_table = tag
  121. break # Stop searching in siblings if <table> is found
  122. description += tag.get_text()
  123. # Methods start with a lowercase letter
  124. if method_name and method_name[0] == method_name[0].lower():
  125. methods.append(
  126. TelegramApiMethod(
  127. method_name,
  128. description,
  129. parameters_table
  130. )
  131. )
  132. new_line = '\n'
  133. if output_file:
  134. with open(output_file, 'w') as file:
  135. file.write(
  136. "from typing import List, Union\n"
  137. "from davtelepot.api import TelegramBot\n"
  138. "self = TelegramBot('fake_token')\n\n\n"
  139. )
  140. file.writelines(
  141. f"async def {method.name}("
  142. f"{', '.join(method.parameters_with_types)}"
  143. "):\n"
  144. " \"\"\""
  145. f"{method.description.replace(new_line, new_line + ' ' * 4)}\n"
  146. " See https://core.telegram.org/bots/api#"
  147. f"{method.name.lower()} for details.\n"
  148. " \"\"\"\n"
  149. " return await self.api_request(\n"
  150. f" '{method.name}',\n"
  151. " parameters=locals()\n"
  152. " )\n\n\n"
  153. for method in methods
  154. if print_all or method.name not in implemented_methods
  155. )
  156. else:
  157. print(
  158. '\n'.join(
  159. f"NAME\n\t{method.name}\n"
  160. f"PARAMETERS\n\t{', '.join(method.parameters_with_types)}\n"
  161. f"DESCRIPTION\n\t{method.description}\n"
  162. f"TABLE\n\t{method.print_parameters_table()}\n\n"
  163. for method in methods
  164. if print_all or method.name not in implemented_methods
  165. )
  166. )
  167. def main():
  168. cli_parser = argparse.ArgumentParser(
  169. description='Get Telegram API methods information from telegram '
  170. 'website.\n'
  171. 'Implement missing (or --all) methods in --out file, '
  172. 'or print methods information.',
  173. allow_abbrev=False,
  174. )
  175. cli_parser.add_argument('--file', '-f', '--filename', type=str,
  176. default=None,
  177. required=False,
  178. help='File path to store Telegram API web page')
  179. cli_parser.add_argument('--all', '-a',
  180. action='store_true',
  181. help='Print all methods (default: print missing '
  182. 'methods only)')
  183. cli_parser.add_argument('--out', '--output', '-o', type=str,
  184. default=None,
  185. required=False,
  186. help='File path to store methods implementation')
  187. cli_arguments = vars(cli_parser.parse_args())
  188. filename = cli_arguments['file']
  189. print_all = cli_arguments['all']
  190. output_file = cli_arguments['out']
  191. loop = asyncio.get_event_loop()
  192. loop.run_until_complete(
  193. print_api_methods(loop=loop,
  194. filename=filename,
  195. print_all=print_all,
  196. output_file=output_file)
  197. )
  198. logging.info("Done!")
  199. if __name__ == '__main__':
  200. main()