python-trueconf-bot 1.1.9__tar.gz → 1.1.10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/PKG-INFO +1 -1
  2. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/en/index.md +5 -0
  3. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/en/learn/filters.md +82 -0
  4. python_trueconf_bot-1.1.10/docs/hooks/llms_txt.py +537 -0
  5. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/ru/index.md +4 -0
  6. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/ru/learn/filters.md +86 -0
  7. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/mkdocs.yml +9 -1
  8. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/python_trueconf_bot.egg-info/PKG-INFO +1 -1
  9. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/python_trueconf_bot.egg-info/SOURCES.txt +1 -0
  10. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/_version.py +3 -3
  11. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/send_file_response.py +1 -0
  12. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/.github/workflows/docs.yml +0 -0
  13. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/.github/workflows/release-test.yml +0 -0
  14. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/.github/workflows/release.yml +0 -0
  15. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/.gitignore +0 -0
  16. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/LICENSE +0 -0
  17. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/README-PyPI.md +0 -0
  18. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/README-ru.md +0 -0
  19. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/README.md +0 -0
  20. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/assets/head_en.png +0 -0
  21. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/assets/head_ru.png +0 -0
  22. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/assets/logo.svg +0 -0
  23. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/en/examples/echo_bot.md +0 -0
  24. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/en/examples/multibot.md +0 -0
  25. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/en/features.md +0 -0
  26. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/en/img/head.png +0 -0
  27. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/en/img/router_scheme_en.svg +0 -0
  28. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/en/learn/enums.md +0 -0
  29. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/en/learn/files.md +0 -0
  30. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/en/learn/getting-started.md +0 -0
  31. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/en/learn/parse-messages.md +0 -0
  32. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/en/learn/types.md +0 -0
  33. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/en/reference/Bot.md +0 -0
  34. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/en/reference/Dispatcher.md +0 -0
  35. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/en/reference/Enums.md +0 -0
  36. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/en/reference/Responses.md +0 -0
  37. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/en/reference/Router.md +0 -0
  38. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/en/reference/Types.md +0 -0
  39. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/en/reference/index.md +0 -0
  40. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/en/release_notes.md +0 -0
  41. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/img/chatbot.svg +0 -0
  42. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/img/favicon.ico +0 -0
  43. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/img/logo.svg +0 -0
  44. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/overrides/main.html +0 -0
  45. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/ru/examples/echo_bot.md +0 -0
  46. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/ru/examples/multibot.md +0 -0
  47. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/ru/features.md +0 -0
  48. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/ru/img/head.png +0 -0
  49. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/ru/img/router_scheme_ru.svg +0 -0
  50. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/ru/learn/enums.md +0 -0
  51. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/ru/learn/files.md +0 -0
  52. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/ru/learn/getting-started.md +0 -0
  53. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/ru/learn/parse-messages.md +0 -0
  54. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/ru/learn/types.md +0 -0
  55. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/ru/release_notes.md +0 -0
  56. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/docs/stylesheets/extra.css +0 -0
  57. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/examples/README-ru.md +0 -0
  58. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/examples/README.md +0 -0
  59. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/examples/add_big_list_users_to_channel.py +0 -0
  60. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/examples/filters.py +0 -0
  61. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/examples/report_bot.py +0 -0
  62. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/examples/update_token.py +0 -0
  63. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/examples/webhook.py +0 -0
  64. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/pyproject.toml +0 -0
  65. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/python_trueconf_bot.egg-info/dependency_links.txt +0 -0
  66. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/python_trueconf_bot.egg-info/requires.txt +0 -0
  67. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/python_trueconf_bot.egg-info/top_level.txt +0 -0
  68. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/requirements-docs.txt +0 -0
  69. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/requirements.txt +0 -0
  70. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/setup.cfg +0 -0
  71. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/__init__.py +0 -0
  72. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/client/__init__.py +0 -0
  73. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/client/bot.py +0 -0
  74. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/client/context_controller.py +0 -0
  75. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/client/session.py +0 -0
  76. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/dispatcher/__init__.py +0 -0
  77. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/dispatcher/dispatcher.py +0 -0
  78. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/dispatcher/router.py +0 -0
  79. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/enums/__init__.py +0 -0
  80. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/enums/aouth_error.py +0 -0
  81. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/enums/chat_participant_role.py +0 -0
  82. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/enums/chat_type.py +0 -0
  83. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/enums/envelope_author_type.py +0 -0
  84. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/enums/file_ready_state.py +0 -0
  85. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/enums/incoming_update_method.py +0 -0
  86. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/enums/message_type.py +0 -0
  87. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/enums/parse_mode.py +0 -0
  88. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/enums/survey_type.py +0 -0
  89. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/enums/update_type.py +0 -0
  90. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/exceptions.py +0 -0
  91. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/filters/__init__.py +0 -0
  92. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/filters/base.py +0 -0
  93. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/filters/command.py +0 -0
  94. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/filters/instance_of.py +0 -0
  95. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/filters/message.py +0 -0
  96. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/filters/method.py +0 -0
  97. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/loggers.py +0 -0
  98. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/__init__.py +0 -0
  99. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/add_participant_to_chat.py +0 -0
  100. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/auth.py +0 -0
  101. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/base.py +0 -0
  102. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/change_participant_role.py +0 -0
  103. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/create_channel.py +0 -0
  104. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/create_favorites_chat.py +0 -0
  105. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/create_group_chat.py +0 -0
  106. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/create_p2p_chat.py +0 -0
  107. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/edit_message.py +0 -0
  108. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/edit_survey.py +0 -0
  109. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/forward_message.py +0 -0
  110. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/get_chat_by_id.py +0 -0
  111. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/get_chat_history.py +0 -0
  112. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/get_chat_participants.py +0 -0
  113. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/get_chats.py +0 -0
  114. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/get_file_info.py +0 -0
  115. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/get_message_by_id.py +0 -0
  116. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/get_user_display_name.py +0 -0
  117. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/has_chat_participant.py +0 -0
  118. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/remove_chat.py +0 -0
  119. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/remove_message.py +0 -0
  120. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/remove_participant_from_chat.py +0 -0
  121. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/send_file.py +0 -0
  122. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/send_message.py +0 -0
  123. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/send_survey.py +0 -0
  124. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/subscribe_file_progress.py +0 -0
  125. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/unsubscribe_file_progress.py +0 -0
  126. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/methods/upload_file.py +0 -0
  127. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/py.typed +0 -0
  128. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/__init__.py +0 -0
  129. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/author_box.py +0 -0
  130. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/chat_participant.py +0 -0
  131. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/content/__init__.py +0 -0
  132. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/content/attachment.py +0 -0
  133. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/content/base.py +0 -0
  134. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/content/chat_created.py +0 -0
  135. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/content/document.py +0 -0
  136. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/content/forward_message.py +0 -0
  137. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/content/photo.py +0 -0
  138. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/content/remove_participant.py +0 -0
  139. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/content/sticker.py +0 -0
  140. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/content/survey.py +0 -0
  141. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/content/text.py +0 -0
  142. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/content/video.py +0 -0
  143. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/input_file.py +0 -0
  144. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/last_message.py +0 -0
  145. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/message.py +0 -0
  146. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/parser.py +0 -0
  147. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/requests/__init__.py +0 -0
  148. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/requests/added_chat_participant.py +0 -0
  149. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/requests/changed_participant_role.py +0 -0
  150. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/requests/created_channel.py +0 -0
  151. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/requests/created_favorites_chat.py +0 -0
  152. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/requests/created_group_chat.py +0 -0
  153. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/requests/created_personal_chat.py +0 -0
  154. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/requests/edited_message.py +0 -0
  155. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/requests/removed_chat.py +0 -0
  156. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/requests/removed_chat_participant.py +0 -0
  157. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/requests/removed_message.py +0 -0
  158. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/requests/uploading_progress.py +0 -0
  159. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/__init__.py +0 -0
  160. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/add_chat_participant_response.py +0 -0
  161. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/api_error.py +0 -0
  162. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/auth_response_payload.py +0 -0
  163. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/change_participant_role_response.py +0 -0
  164. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/create_channel_response.py +0 -0
  165. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/create_favorites_chat_response.py +0 -0
  166. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/create_group_chat_response.py +0 -0
  167. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/create_p2p_chat_response.py +0 -0
  168. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/edit_message_response.py +0 -0
  169. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/edit_survey_response.py +0 -0
  170. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/forward_message_response.py +0 -0
  171. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/get_chat_by_id_response.py +0 -0
  172. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/get_chat_history_response.py +0 -0
  173. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/get_chat_participants_response.py +0 -0
  174. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/get_chats_response.py +0 -0
  175. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/get_file_info_response.py +0 -0
  176. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/get_message_by_id_response.py +0 -0
  177. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/get_user_display_name_response.py +0 -0
  178. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/has_chat_participant_response.py +0 -0
  179. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/remove_chat_participant_response.py +0 -0
  180. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/remove_chat_response.py +0 -0
  181. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/remove_message_response.py +0 -0
  182. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/send_message_response.py +0 -0
  183. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/send_survey_response.py +0 -0
  184. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/subscribe_file_progress_response.py +0 -0
  185. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/unsubscribe_file_progress_response.py +0 -0
  186. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/responses/upload_file_response.py +0 -0
  187. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/types/update.py +0 -0
  188. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/utils/__init__.py +0 -0
  189. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/utils/generate_secret_for_survey.py +0 -0
  190. {python_trueconf_bot-1.1.9 → python_trueconf_bot-1.1.10}/trueconf/utils/token.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-trueconf-bot
3
- Version: 1.1.9
3
+ Version: 1.1.10
4
4
  Summary: Lightweight and powerful framework for the TrueConf Server Chatbot API
5
5
  Author-email: TrueConf LLC <info@trueconf.com>, Anton Baadzhi <baadzhianton@gmail.com>
6
6
  License-Expression: BSD-3-Clause-Clear
@@ -41,3 +41,8 @@ pip install {{product_name}}
41
41
  | JSON → Python | Pydantic models | Mashumaro dataclasses |
42
42
  | Transport | HTTPS + long polling / webhook | Asynchronous WebSocket |
43
43
  | Working with files | `bot.get_file(...)` + `bot.download_file(...)` | `message.photo.download()`, `message.document.download()`, `bot.download_file_by_id(...)` |
44
+
45
+ ## LLM Files
46
+
47
+ - [llms.txt](https://trueconf.github.io/python-trueconf-bot/master/llms.txt)
48
+ - [llms-full.txt](https://trueconf.github.io/python-trueconf-bot/master/llms-full.txt)
@@ -215,8 +215,90 @@ F.text.upper().in_({"FOO", "BAR"}) # message.text.upper() in {"FOO", "BAR"}
215
215
  F.text.len() == 5 # len(message.text) == 5
216
216
  ```
217
217
 
218
+ ## Building Custom Filters
219
+
220
+ You can build custom filters to fine-tune your event-handling logic. This allows you to move condition checks—such as verifying whether a user exists in the database or checking access permissions—into a separate, reusable component.
221
+
222
+ **Implementation requirements:**
223
+
224
+ * **Class-based structure:** A custom filter must be implemented as a class. This makes it possible to preserve state, such as filter settings, when the filter is initialized.
225
+ * **`__call__` method:** The class must implement a `__call__` method. Without it, the class instance will not be considered callable, and the filter will not work.
226
+ * **Return value:** The `__call__` method must return `True` if the event matches the filter conditions, or `False` if the event should be ignored by the handler.
227
+
228
+ ```python
229
+ from typing import Any
230
+
231
+ class MyFilter:
232
+ """
233
+ This filter checks whether a user belongs
234
+ to the specified group via the server API.
235
+ """
236
+
237
+ def __init__(self, target_group: str):
238
+ # Store the required group when creating the instance
239
+ self.target_group = target_group
240
+
241
+ async def __call__(self, event: Any) -> bool:
242
+ user = getattr(event, "from_user", None)
243
+ if not user:
244
+ return False
245
+
246
+ user_id = user.id
247
+ # Perform the check, for example via an API call
248
+ user_info: dict = await server_api.get_user(user_id)
249
+ groups = user_info.get("groups", [])
250
+
251
+ # Make sure to return True or False
252
+ return any(group.get("id") == self.target_group for group in groups)
253
+ ```
254
+
255
+ Once the filter class is defined, you can use it in handler decorators exactly the same way as the built-in **Command** or **F** filters. When registering a handler, simply pass an instance of your class as an argument.
256
+
257
+ ```python hl_lines="8"
258
+ from aiogram import Router, Message
259
+ # Import your filter class
260
+ from my_filters import MyFilter
261
+
262
+ router = Router()
263
+
264
+ # Attach the filter in the same way as Command or F
265
+ @router.message(MyFilter(target_group="0032"))
266
+ async def my_handler(message: Message):
267
+ await message.answer("Access granted: you belong to the required group!")
268
+ ```
269
+
270
+ When a message is received, the bot will use the class instance, call its `__call__` method, and, if it returns `True`, execute the handler code.
271
+
218
272
  ## Combining Filters
219
273
 
274
+ In **python-trueconf-bot**, you can combine multiple filters within a single handler. By default, when filters are separated by commas, they use **AND** logic—the handler will run only if **all** filters return `True`.
275
+
276
+ **Command and a custom filter**
277
+
278
+ You can restrict access to a specific command by using your own filter. The handler will run only if the message is the specified command **and** your filter returns `True`.
279
+
280
+ ```python
281
+ # Triggers if the /admin command is received
282
+ # AND the user belongs to group "0032"
283
+ @r.message(Command("admin"), InGroupFilter(target_group="0032"))
284
+ async def admin_panel(message: Message):
285
+ await message.answer("Welcome to the admin panel!")
286
+ ```
287
+
288
+ **MagicFilter (F) and a custom filter**
289
+
290
+ Combine message-content checks with your own access rules. For example, you can react to a specific word in the text only for users from certain groups:
291
+
292
+ ```python
293
+ # Triggers if the text contains "help"
294
+ # AND the user belongs to the "support" group
295
+ @r.message(F.text.contains("help"), InGroupFilter(target_group="0003"))
296
+ async def support_handler(message: Message):
297
+ await message.answer("The support team has been notified of your request.")
298
+ ```
299
+
300
+ ### Combining Command Arguments and MagicFilter
301
+
220
302
  Combining **Command** and **MagicFilter** is a common and recommended practice:
221
303
  **Command** parses the command and creates a **CommandObject**, while `magic`
222
304
  allows you to impose additional conditions on `args` or other parts of the
@@ -0,0 +1,537 @@
1
+
2
+ import os
3
+ import re
4
+ import subprocess
5
+ from datetime import datetime, timezone
6
+ from html import unescape
7
+ from importlib.metadata import PackageNotFoundError, version as package_version
8
+ from urllib.parse import urljoin
9
+
10
+ from mkdocs.utils import log
11
+
12
+
13
+ ALLOWED_PATTERNS = [
14
+ r"^index\.html$",
15
+ r"^(features|release_notes)/index\.html$",
16
+ r"^learn/.*/index\.html$",
17
+ r"^reference/.*/index\.html$",
18
+ r"^examples/.*/index\.html$",
19
+ ]
20
+
21
+ EXCLUDED = {
22
+ "404.html",
23
+ "sitemap.xml",
24
+ "objects.inv",
25
+ }
26
+
27
+ SECTION_LABELS = {
28
+ "learn": "Learn",
29
+ "reference": "Reference",
30
+ "examples": "Examples",
31
+ }
32
+
33
+ SECTION_ORDER = {
34
+ "root": 0,
35
+ "learn": 1,
36
+ "reference": 2,
37
+ "examples": 3,
38
+ "other": 4,
39
+ }
40
+
41
+ BLOCK_TAGS = ("p", "div", "section", "article", "blockquote")
42
+ LIST_TAGS = ("ul", "ol")
43
+
44
+
45
+ def should_include_page(path: str) -> bool:
46
+ """Return True when the rendered file should be included in llms outputs."""
47
+ normalized = path.replace("\\", "/").lstrip("/")
48
+
49
+ if normalized in EXCLUDED:
50
+ return False
51
+
52
+ if not normalized.endswith(".html"):
53
+ return False
54
+
55
+ if normalized.startswith("ru/") or "/ru/" in normalized:
56
+ return False
57
+
58
+ return any(re.match(pattern, normalized) for pattern in ALLOWED_PATTERNS)
59
+
60
+
61
+ def extract_main_content(html: str) -> str:
62
+ """Extract the main Material content area from rendered HTML."""
63
+ patterns = [
64
+ r'<article class="md-content__inner md-typeset">(.*?)</article>',
65
+ r"<article[^>]*class=\"[^\"]*md-content__inner[^\"]*md-typeset[^\"]*\"[^>]*>(.*?)</article>",
66
+ r"<main[^>]*>(.*?)</main>",
67
+ ]
68
+
69
+ for pattern in patterns:
70
+ match = re.search(pattern, html, re.DOTALL | re.IGNORECASE)
71
+ if match:
72
+ return match.group(1)
73
+
74
+ return html
75
+
76
+
77
+ def strip_tags(value: str) -> str:
78
+ """Remove HTML tags and normalize whitespace in a short fragment."""
79
+ value = re.sub(r"<[^>]+>", " ", value)
80
+ value = unescape(value)
81
+ value = re.sub(r"\s+", " ", value)
82
+ return value.strip()
83
+
84
+
85
+ def normalize_text(text: str) -> str:
86
+ """Normalize whitespace while preserving paragraph-like spacing."""
87
+ text = text.replace("\r", "")
88
+ text = re.sub(r"\n[ \t]+", "\n", text)
89
+ text = re.sub(r"[ \t]+\n", "\n", text)
90
+ text = re.sub(r"[ \t]{2,}", " ", text)
91
+ text = re.sub(r"\n{3,}", "\n\n", text)
92
+ return text.strip()
93
+
94
+
95
+ def convert_table_to_markdown(table_html: str) -> str:
96
+ """Convert a basic HTML table to Markdown."""
97
+ rows = re.findall(r"<tr[^>]*>(.*?)</tr>", table_html, re.DOTALL | re.IGNORECASE)
98
+ parsed_rows = []
99
+
100
+ for row in rows:
101
+ cells = re.findall(r"<t[hd][^>]*>(.*?)</t[hd]>", row, re.DOTALL | re.IGNORECASE)
102
+ cleaned = [normalize_text(html_to_markdown(cell)) for cell in cells]
103
+ if cleaned:
104
+ parsed_rows.append(cleaned)
105
+
106
+ if not parsed_rows:
107
+ return ""
108
+
109
+ column_count = max(len(row) for row in parsed_rows)
110
+ padded_rows = [row + [""] * (column_count - len(row)) for row in parsed_rows]
111
+
112
+ header = padded_rows[0]
113
+ separator = ["---"] * column_count
114
+ body = padded_rows[1:]
115
+
116
+ lines = [
117
+ "| " + " | ".join(header) + " |",
118
+ "| " + " | ".join(separator) + " |",
119
+ ]
120
+
121
+ for row in body:
122
+ lines.append("| " + " | ".join(row) + " |")
123
+
124
+ return "\n".join(lines)
125
+
126
+
127
+ def convert_details_to_markdown(details_html: str) -> str:
128
+ """Convert details/summary blocks into visible Markdown sections."""
129
+ summary_match = re.search(
130
+ r"<summary[^>]*>(.*?)</summary>",
131
+ details_html,
132
+ re.DOTALL | re.IGNORECASE,
133
+ )
134
+ summary = strip_tags(summary_match.group(1)) if summary_match else "Details"
135
+
136
+ content = re.sub(
137
+ r"<summary[^>]*>.*?</summary>",
138
+ "",
139
+ details_html,
140
+ flags=re.DOTALL | re.IGNORECASE,
141
+ )
142
+
143
+ # Avoid recursive processing when details blocks are nested.
144
+ content = re.sub(
145
+ r"<summary[^>]*>(.*?)</summary>",
146
+ lambda match: f"<h4>{strip_tags(match.group(1))}</h4>",
147
+ content,
148
+ flags=re.DOTALL | re.IGNORECASE,
149
+ )
150
+ content = re.sub(r"</?details[^>]*>", "", content, flags=re.IGNORECASE)
151
+
152
+ content_md = normalize_text(html_to_markdown(content))
153
+
154
+ if content_md:
155
+ return f"\n\n### {summary}\n\n{content_md}\n\n"
156
+
157
+ return f"\n\n### {summary}\n\n"
158
+
159
+
160
+ def html_to_markdown(html: str) -> str:
161
+ """Convert rendered HTML into readable Markdown for llms outputs."""
162
+ if not html:
163
+ return ""
164
+
165
+ text = html
166
+
167
+ text = re.sub(r"<script\b[^>]*>.*?</script>", "", text, flags=re.DOTALL | re.IGNORECASE)
168
+ text = re.sub(r"<style\b[^>]*>.*?</style>", "", text, flags=re.DOTALL | re.IGNORECASE)
169
+ text = re.sub(
170
+ r"<a[^>]*class=\"[^\"]*headerlink[^\"]*\"[^>]*>.*?</a>",
171
+ "",
172
+ text,
173
+ flags=re.DOTALL | re.IGNORECASE,
174
+ )
175
+ text = re.sub(
176
+ r"<span[^>]*class=\"[^\"]*gp[^\"]*\"[^>]*>.*?</span>",
177
+ "",
178
+ text,
179
+ flags=re.DOTALL | re.IGNORECASE,
180
+ )
181
+ text = re.sub(r"</?span[^>]*>", "", text, flags=re.IGNORECASE)
182
+
183
+ text = re.sub(
184
+ r"<details\b[^>]*>.*?</details>",
185
+ lambda match: convert_details_to_markdown(match.group(0)),
186
+ text,
187
+ flags=re.DOTALL | re.IGNORECASE,
188
+ )
189
+
190
+ text = re.sub(
191
+ r"<table\b[^>]*>.*?</table>",
192
+ lambda match: "\n\n" + convert_table_to_markdown(match.group(0)) + "\n\n",
193
+ text,
194
+ flags=re.DOTALL | re.IGNORECASE,
195
+ )
196
+
197
+ def replace_pre(match: re.Match) -> str:
198
+ attrs = match.group(1) or ""
199
+ code = match.group(2)
200
+ lang_match = re.search(r"language-([a-zA-Z0-9_+\-.]+)", attrs)
201
+ language = lang_match.group(1) if lang_match else ""
202
+ code_text = unescape(re.sub(r"<[^>]+>", "", code)).strip("\n")
203
+ return f"\n\n```{language}\n{code_text}\n```\n\n"
204
+
205
+ text = re.sub(
206
+ r"<pre[^>]*><code([^>]*)>(.*?)</code></pre>",
207
+ replace_pre,
208
+ text,
209
+ flags=re.DOTALL | re.IGNORECASE,
210
+ )
211
+
212
+ text = re.sub(
213
+ r"<code[^>]*>(.*?)</code>",
214
+ lambda match: f"`{strip_tags(match.group(1))}`",
215
+ text,
216
+ flags=re.DOTALL | re.IGNORECASE,
217
+ )
218
+
219
+ def replace_anchor(match: re.Match) -> str:
220
+ attrs = match.group(1) or ""
221
+ content = strip_tags(match.group(2))
222
+ href_match = re.search(r'href=["\']([^"\']+)["\']', attrs, re.IGNORECASE)
223
+ href = href_match.group(1).strip() if href_match else ""
224
+
225
+ if not content:
226
+ return ""
227
+
228
+ if href and not href.startswith("#"):
229
+ return f"[{content}]({href})"
230
+
231
+ return content
232
+
233
+ text = re.sub(
234
+ r"<a([^>]*)>(.*?)</a>",
235
+ replace_anchor,
236
+ text,
237
+ flags=re.DOTALL | re.IGNORECASE,
238
+ )
239
+
240
+ for level in range(6, 0, -1):
241
+ pattern = rf"<h{level}[^>]*>(.*?)</h{level}>"
242
+ text = re.sub(
243
+ pattern,
244
+ lambda match, lvl=level: f"\n\n{'#' * lvl} {strip_tags(match.group(1))}\n\n",
245
+ text,
246
+ flags=re.DOTALL | re.IGNORECASE,
247
+ )
248
+
249
+ text = re.sub(r"<br\s*/?>", "\n", text, flags=re.IGNORECASE)
250
+ text = re.sub(r"</li>", "\n", text, flags=re.IGNORECASE)
251
+ text = re.sub(r"<li[^>]*>", "\n- ", text, flags=re.IGNORECASE)
252
+
253
+ for tag in BLOCK_TAGS:
254
+ text = re.sub(rf"<{tag}[^>]*>", "\n\n", text, flags=re.IGNORECASE)
255
+ text = re.sub(rf"</{tag}>", "\n\n", text, flags=re.IGNORECASE)
256
+
257
+ for tag in LIST_TAGS:
258
+ text = re.sub(rf"<{tag}[^>]*>", "\n", text, flags=re.IGNORECASE)
259
+ text = re.sub(rf"</{tag}>", "\n", text, flags=re.IGNORECASE)
260
+
261
+ text = re.sub(r"<[^>]+>", "", text)
262
+ text = unescape(text)
263
+ text = normalize_text(text)
264
+
265
+ lines = []
266
+ for line in text.split("\n"):
267
+ stripped = line.strip()
268
+ if stripped.startswith("-"):
269
+ stripped = re.sub(r"^-\s*", "- ", stripped)
270
+ lines.append(stripped if stripped else "")
271
+
272
+ text = "\n".join(lines)
273
+ text = re.sub(r"\n{3,}", "\n\n", text)
274
+ return text.strip()
275
+
276
+
277
+ def get_version() -> str:
278
+ """Resolve the project version from package metadata or git tags."""
279
+ try:
280
+ return package_version("python-trueconf-bot")
281
+ except PackageNotFoundError:
282
+ pass
283
+ except Exception as exc: # pragma: no cover
284
+ log.debug(f"llms-txt: Failed to read package metadata version: {exc}")
285
+
286
+ try:
287
+ return (
288
+ subprocess.check_output(
289
+ ["git", "describe", "--tags", "--abbrev=0"],
290
+ stderr=subprocess.DEVNULL,
291
+ text=True,
292
+ )
293
+ .strip()
294
+ )
295
+ except Exception as exc: # pragma: no cover
296
+ log.debug(f"llms-txt: Failed to read git version: {exc}")
297
+
298
+ return "dev"
299
+
300
+
301
+ def get_page_title(html: str, fallback: str) -> str:
302
+ """Extract a human-readable page title from rendered HTML."""
303
+ h1_match = re.search(r"<h1[^>]*>(.*?)</h1>", html, re.DOTALL | re.IGNORECASE)
304
+ if h1_match:
305
+ title = strip_tags(h1_match.group(1))
306
+ if title:
307
+ return title
308
+
309
+ title_match = re.search(r"<title[^>]*>(.*?)</title>", html, re.DOTALL | re.IGNORECASE)
310
+ if title_match:
311
+ raw_title = strip_tags(title_match.group(1))
312
+ title = re.split(r"\s[-|–—]\s", raw_title)[0].strip()
313
+ if title:
314
+ return title
315
+
316
+ slug = fallback.replace("index.html", "").strip("/")
317
+ if not slug:
318
+ return "Home"
319
+
320
+ return slug.split("/")[-1].replace("-", " ").replace("_", " ").title()
321
+
322
+
323
+ def normalize_doc_path(doc_path: str) -> str:
324
+ """Normalize a nav doc path into the corresponding rendered html path."""
325
+ path = doc_path.replace("\\", "/").strip("/")
326
+
327
+ if path.endswith(".md"):
328
+ path = path[:-3]
329
+
330
+ if not path or path == ".":
331
+ return "index.html"
332
+
333
+ if path.endswith("/index"):
334
+ return f"{path}.html"
335
+
336
+ return f"{path}/index.html"
337
+
338
+
339
+ def flatten_nav(nav_items) -> list[str]:
340
+ """Flatten MkDocs nav config into ordered rendered html paths."""
341
+ ordered_paths: list[str] = []
342
+
343
+ def traverse(item) -> None:
344
+ if isinstance(item, list):
345
+ for nested in item:
346
+ traverse(nested)
347
+ return
348
+
349
+ if isinstance(item, dict):
350
+ for value in item.values():
351
+ traverse(value)
352
+ return
353
+
354
+ if isinstance(item, str):
355
+ ordered_paths.append(normalize_doc_path(item))
356
+
357
+ traverse(nav_items or [])
358
+ return ordered_paths
359
+
360
+
361
+ def get_section_key(path: str) -> str:
362
+ normalized = path.replace("\\", "/")
363
+ if normalized == "index.html" or normalized.startswith(("features/", "release_notes/")):
364
+ return "root"
365
+ if normalized.startswith("learn/"):
366
+ return "learn"
367
+ if normalized.startswith("reference/"):
368
+ return "reference"
369
+ if normalized.startswith("examples/"):
370
+ return "examples"
371
+ return "other"
372
+
373
+
374
+ def path_sort_key(path: str, nav_positions: dict[str, int]):
375
+ section = get_section_key(path)
376
+ nav_position = nav_positions.get(path, 10**6)
377
+ return (
378
+ SECTION_ORDER.get(section, 99),
379
+ nav_position,
380
+ path.count("/"),
381
+ path.lower(),
382
+ )
383
+
384
+
385
+ def build_url(base_url: str, rel_path: str) -> str:
386
+ """Build a public URL for a rendered html file."""
387
+ normalized = rel_path.replace("\\", "/").lstrip("/")
388
+ if normalized == "index.html":
389
+ return base_url
390
+
391
+ if normalized.endswith("/index.html"):
392
+ normalized = normalized[: -len("index.html")]
393
+
394
+ return urljoin(base_url + "/", normalized)
395
+
396
+
397
+ def make_indent(level: int) -> str:
398
+ return " " * max(level, 0)
399
+
400
+
401
+ def add_index_entry(index_lines: list[str], title: str, url: str, level: int = 0) -> None:
402
+ index_lines.append(f"{make_indent(level)}- [{title}]({url})")
403
+
404
+
405
+ def build_index_lines(page_entries: list[dict]) -> list[str]:
406
+ """Build the hierarchical INDEX section."""
407
+ index_lines = ["## INDEX"]
408
+
409
+ home_entry = next((entry for entry in page_entries if entry["path"] == "index.html"), None)
410
+ features_entry = next(
411
+ (entry for entry in page_entries if entry["path"] == "features/index.html"),
412
+ None,
413
+ )
414
+ release_notes_entry = next(
415
+ (entry for entry in page_entries if entry["path"] == "release_notes/index.html"),
416
+ None,
417
+ )
418
+
419
+ if home_entry:
420
+ add_index_entry(index_lines, home_entry["title"], home_entry["url"], level=0)
421
+ if features_entry:
422
+ add_index_entry(index_lines, features_entry["title"], features_entry["url"], level=0)
423
+
424
+ grouped_sections = {"learn": [], "reference": [], "examples": []}
425
+ for entry in page_entries:
426
+ section = get_section_key(entry["path"])
427
+ if section in grouped_sections:
428
+ grouped_sections[section].append(entry)
429
+
430
+ for section_key in ("learn", "reference", "examples"):
431
+ entries = grouped_sections[section_key]
432
+ if not entries:
433
+ continue
434
+
435
+ index_lines.append(f"- [{SECTION_LABELS[section_key]}](#{section_key})")
436
+ for entry in entries:
437
+ add_index_entry(index_lines, entry["title"], entry["url"], level=1)
438
+
439
+ if release_notes_entry:
440
+ add_index_entry(index_lines, release_notes_entry["title"], release_notes_entry["url"], level=0)
441
+
442
+ return index_lines
443
+
444
+
445
+ def write_lines(path: str, lines: list[str]) -> None:
446
+ """Write text lines using UTF-8 encoding."""
447
+ with open(path, "w", encoding="utf-8") as file_obj:
448
+ file_obj.write("\n".join(lines).strip() + "\n")
449
+
450
+
451
+ def on_post_build(config):
452
+ """Generate llms.txt and llms-full.txt from rendered HTML files."""
453
+ site_dir = config["site_dir"]
454
+ base_url = (config.get("site_url") or "http://127.0.0.1:8000").rstrip("/")
455
+ nav_paths = flatten_nav(config.get("nav", []))
456
+ nav_positions = {path: idx for idx, path in enumerate(nav_paths)}
457
+
458
+ discovered_paths = []
459
+ for root, _, filenames in os.walk(site_dir):
460
+ for filename in filenames:
461
+ rel_path = os.path.relpath(os.path.join(root, filename), site_dir)
462
+ rel_path = rel_path.replace("\\", "/")
463
+ if should_include_page(rel_path):
464
+ discovered_paths.append(rel_path)
465
+
466
+ sorted_paths = sorted(set(discovered_paths), key=lambda path: path_sort_key(path, nav_positions))
467
+
468
+ page_entries = []
469
+ for rel_path in sorted_paths:
470
+ abs_path = os.path.join(site_dir, rel_path)
471
+
472
+ try:
473
+ with open(abs_path, "r", encoding="utf-8") as file_obj:
474
+ html = file_obj.read()
475
+ except Exception as exc:
476
+ log.warning(f"llms-txt: Failed to read rendered HTML {rel_path}: {exc}")
477
+ continue
478
+
479
+ main_html = extract_main_content(html)
480
+ markdown = html_to_markdown(main_html)
481
+ title = get_page_title(html, rel_path)
482
+ url = build_url(base_url, rel_path)
483
+
484
+ page_entries.append(
485
+ {
486
+ "path": rel_path,
487
+ "title": title,
488
+ "url": url,
489
+ "content": markdown,
490
+ }
491
+ )
492
+
493
+ index_lines = build_index_lines(page_entries)
494
+ generated_at = datetime.now(timezone.utc).replace(microsecond=0).isoformat()
495
+ version = get_version()
496
+
497
+ llms_lines = [
498
+ "# Python TrueConf Bot SDK",
499
+ *index_lines,
500
+ "",
501
+ "## CONTENT",
502
+ "See `llms-full.txt` for the complete rendered documentation.",
503
+ ]
504
+
505
+ full_lines = [
506
+ "# Python TrueConf Bot SDK",
507
+ "## METADATA",
508
+ f"- **Version:** {version}",
509
+ f"- **Generated:** {generated_at}",
510
+ f"- **URL:** {base_url}/",
511
+ "",
512
+ "---",
513
+ "",
514
+ *index_lines,
515
+ "",
516
+ "---",
517
+ "",
518
+ "## CONTENT",
519
+ ]
520
+
521
+ for entry in page_entries:
522
+ full_lines.extend(
523
+ [
524
+ "",
525
+ f"# DOCUMENT: {entry['title']}",
526
+ f"**Source:** {entry['url']}",
527
+ "",
528
+ entry["content"] or "_No content extracted._",
529
+ "",
530
+ "---",
531
+ ]
532
+ )
533
+
534
+ write_lines(os.path.join(site_dir, "llms.txt"), llms_lines)
535
+ write_lines(os.path.join(site_dir, "llms-full.txt"), full_lines)
536
+
537
+ log.info("llms-txt: Generated llms.txt and llms-full.txt from rendered HTML")
@@ -35,4 +35,8 @@ pip install {{product_name}}
35
35
  - TrueConf API становится «человеческим»: вместо JSON-словарей вы работаете с Python-классами.
36
36
  - Вы получаете удобный инструмент для интеграции ботов в корпоративные чаты TrueConf.
37
37
 
38
+ ## Документация для ИИ (LLM)
39
+
40
+ - [llms.txt](https://trueconf.github.io/python-trueconf-bot/master/llms.txt)
41
+ - [llms-full.txt](https://trueconf.github.io/python-trueconf-bot/master/llms-full.txt)
38
42