chgksuite 0.27.2__tar.gz → 0.28.0__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 (179) hide show
  1. {chgksuite-0.27.2 → chgksuite-0.28.0}/PKG-INFO +1 -1
  2. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/cli.py +14 -1
  3. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/composer/telegram.py +130 -84
  4. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/parser.py +0 -1
  5. chgksuite-0.28.0/chgksuite/version.py +1 -0
  6. {chgksuite-0.27.2 → chgksuite-0.28.0}/pyproject.toml +5 -1
  7. chgksuite-0.27.2/.claude/settings.local.json +0 -10
  8. chgksuite-0.27.2/.github/workflows/build.yml +0 -283
  9. chgksuite-0.27.2/.gitlab-ci.yml +0 -17
  10. chgksuite-0.27.2/MANIFEST.in +0 -7
  11. chgksuite-0.27.2/adhoc/regexes_fixer.py +0 -66
  12. chgksuite-0.27.2/author_counter.py +0 -24
  13. chgksuite-0.27.2/chgksuite/version.py +0 -1
  14. chgksuite-0.27.2/dev_readme.md +0 -25
  15. chgksuite-0.27.2/docs/.cache/.gitignore +0 -1
  16. chgksuite-0.27.2/docs/.cache/12897346880794287463 +0 -43
  17. chgksuite-0.27.2/docs/.cache/13071136909783630831 +0 -27
  18. chgksuite-0.27.2/docs/.cache/13307108790744403429 +0 -58
  19. chgksuite-0.27.2/docs/.cache/13333983167045209281 +0 -27
  20. chgksuite-0.27.2/docs/.cache/14085495286103177657 +0 -4
  21. chgksuite-0.27.2/docs/.cache/15213489598760314030 +0 -149
  22. chgksuite-0.27.2/docs/.cache/15319492834190874243 +0 -4
  23. chgksuite-0.27.2/docs/.cache/17387058097034297385 +0 -4
  24. chgksuite-0.27.2/docs/.cache/17722115158263369208 +0 -4
  25. chgksuite-0.27.2/docs/.cache/18247088324849079572 +0 -4
  26. chgksuite-0.27.2/docs/.cache/1840587404175770520 +0 -43
  27. chgksuite-0.27.2/docs/.cache/2517734129797249711 +0 -4
  28. chgksuite-0.27.2/docs/.cache/273431981325847810 +0 -164
  29. chgksuite-0.27.2/docs/.cache/2872065536916780406 +0 -27
  30. chgksuite-0.27.2/docs/.cache/3476900567878811119 +0 -4
  31. chgksuite-0.27.2/docs/.cache/4757258273854424423 +0 -58
  32. chgksuite-0.27.2/docs/.cache/5728722062471071239 +0 -58
  33. chgksuite-0.27.2/docs/.cache/6061198691539556612 +0 -4
  34. chgksuite-0.27.2/docs/.cache/6079179394456200124 +0 -4
  35. chgksuite-0.27.2/docs/.cache/735877668610100612 +0 -73
  36. chgksuite-0.27.2/docs/.cache/9345880734637750342 +0 -4
  37. chgksuite-0.27.2/docs/.cache/9490951945832949972 +0 -4
  38. chgksuite-0.27.2/docs/docs/4s.md +0 -229
  39. chgksuite-0.27.2/docs/docs/add_stats.md +0 -9
  40. chgksuite-0.27.2/docs/docs/base.md +0 -43
  41. chgksuite-0.27.2/docs/docs/i14n.md +0 -31
  42. chgksuite-0.27.2/docs/docs/images/base.png +0 -0
  43. chgksuite-0.27.2/docs/docs/images/douplet_problem.png +0 -0
  44. chgksuite-0.27.2/docs/docs/images/i14n.png +0 -0
  45. chgksuite-0.27.2/docs/docs/images/i14n_parse.png +0 -0
  46. chgksuite-0.27.2/docs/docs/images/lj.png +0 -0
  47. chgksuite-0.27.2/docs/docs/images/main.png +0 -0
  48. chgksuite-0.27.2/docs/docs/images/openquiz.png +0 -0
  49. chgksuite-0.27.2/docs/docs/images/openquiz2.png +0 -0
  50. chgksuite-0.27.2/docs/docs/images/parse.png +0 -0
  51. chgksuite-0.27.2/docs/docs/images/pptx.png +0 -0
  52. chgksuite-0.27.2/docs/docs/images/pptx_additional_conf.png +0 -0
  53. chgksuite-0.27.2/docs/docs/images/pptx_slide_a.png +0 -0
  54. chgksuite-0.27.2/docs/docs/images/pptx_slide_q.png +0 -0
  55. chgksuite-0.27.2/docs/docs/images/roenko.png +0 -0
  56. chgksuite-0.27.2/docs/docs/images/rozhdsushkov.png +0 -0
  57. chgksuite-0.27.2/docs/docs/images/stats.png +0 -0
  58. chgksuite-0.27.2/docs/docs/images/telegram.png +0 -0
  59. chgksuite-0.27.2/docs/docs/images/trello_download.png +0 -0
  60. chgksuite-0.27.2/docs/docs/images/trello_token.png +0 -0
  61. chgksuite-0.27.2/docs/docs/images/trello_upload.png +0 -0
  62. chgksuite-0.27.2/docs/docs/images/word.png +0 -0
  63. chgksuite-0.27.2/docs/docs/index.md +0 -59
  64. chgksuite-0.27.2/docs/docs/lj.md +0 -9
  65. chgksuite-0.27.2/docs/docs/openquiz.md +0 -7
  66. chgksuite-0.27.2/docs/docs/pptx.md +0 -66
  67. chgksuite-0.27.2/docs/docs/stylesheets/extra.css +0 -29
  68. chgksuite-0.27.2/docs/docs/telegram.md +0 -46
  69. chgksuite-0.27.2/docs/docs/trello.md +0 -31
  70. chgksuite-0.27.2/docs/docs/word.md +0 -24
  71. chgksuite-0.27.2/docs/mkdocs.yml +0 -35
  72. chgksuite-0.27.2/history.md +0 -407
  73. chgksuite-0.27.2/hook-dateparser.py +0 -12
  74. chgksuite-0.27.2/packer.py +0 -172
  75. chgksuite-0.27.2/pytest.ini +0 -5
  76. chgksuite-0.27.2/pytest.sh +0 -7
  77. chgksuite-0.27.2/re_helper.py +0 -24
  78. chgksuite-0.27.2/requirements_dev.txt +0 -2
  79. chgksuite-0.27.2/ruff.toml +0 -2
  80. chgksuite-0.27.2/tests/2019-07-23_beln19_u.docx +0 -0
  81. chgksuite-0.27.2/tests/2019-07-23_beln19_u.docx.canon +0 -690
  82. chgksuite-0.27.2/tests/Kubok_knyagini_Olgi-2015.docx +0 -0
  83. chgksuite-0.27.2/tests/Kubok_knyagini_Olgi-2015.docx.canon +0 -644
  84. chgksuite-0.27.2/tests/Shkolny_Chemp_Estonii-2014_(48v).docx +0 -0
  85. chgksuite-0.27.2/tests/Shkolny_Chemp_Estonii-2014_(48v).docx.canon +0 -361
  86. chgksuite-0.27.2/tests/__init__.py +0 -0
  87. chgksuite-0.27.2/tests/balt09-1.txt +0 -783
  88. chgksuite-0.27.2/tests/balt09-1.txt.canon +0 -286
  89. chgksuite-0.27.2/tests/borromeo.txt +0 -11
  90. chgksuite-0.27.2/tests/borromeo.txt.canon +0 -16
  91. chgksuite-0.27.2/tests/canonize.py +0 -69
  92. chgksuite-0.27.2/tests/chgksuite_test.py +0 -364
  93. chgksuite-0.27.2/tests/conftest.py +0 -21
  94. chgksuite-0.27.2/tests/encrypt_test_files.py +0 -147
  95. chgksuite-0.27.2/tests/haifa2025.docx.encrypted +0 -0
  96. chgksuite-0.27.2/tests/haifa2025.docx.encrypted.canon +0 -0
  97. chgksuite-0.27.2/tests/handouter_layout_test.py +0 -310
  98. chgksuite-0.27.2/tests/link_unwrap.docx +0 -0
  99. chgksuite-0.27.2/tests/link_unwrap.docx.canon +0 -10
  100. chgksuite-0.27.2/tests/ljcredentials +0 -1
  101. chgksuite-0.27.2/tests/long_handout.png +0 -0
  102. chgksuite-0.27.2/tests/malkin_papkov_synchr.docx +0 -0
  103. chgksuite-0.27.2/tests/malkin_papkov_synchr.docx.canon +0 -99
  104. chgksuite-0.27.2/tests/octo2021_khmelkov.docx +0 -0
  105. chgksuite-0.27.2/tests/octo2021_khmelkov.docx.canon +0 -102
  106. chgksuite-0.27.2/tests/ovsch_boronenko_3.docx +0 -0
  107. chgksuite-0.27.2/tests/ovsch_boronenko_3.docx.canon +0 -334
  108. chgksuite-0.27.2/tests/pass1.docx +0 -0
  109. chgksuite-0.27.2/tests/pass1.docx.canon +0 -183
  110. chgksuite-0.27.2/tests/settings.json +0 -3
  111. chgksuite-0.27.2/tests/single_number_line_test.docx +0 -0
  112. chgksuite-0.27.2/tests/single_number_line_test.docx.canon +0 -46
  113. chgksuite-0.27.2/tests/smalltest.4s +0 -6
  114. chgksuite-0.27.2/tests/test.jpg +0 -0
  115. chgksuite-0.27.2/tests/test_blitz.txt +0 -8
  116. chgksuite-0.27.2/tests/test_blitz.txt.canon +0 -9
  117. chgksuite-0.27.2/tests/tourrev_with_razmin.docx +0 -0
  118. chgksuite-0.27.2/tests/tourrev_with_razmin.docx.canon +0 -34
  119. chgksuite-0.27.2/tests//320/241/320/247/320/240_/320/250/320/265/321/200/320/265/320/264/320/265/320/263/320/260_/320/225/321/200/320/274/320/270/321/210/320/272/320/270/320/275.docx +0 -0
  120. chgksuite-0.27.2/tests//320/241/320/247/320/240_/320/250/320/265/321/200/320/265/320/264/320/265/320/263/320/260_/320/225/321/200/320/274/320/270/321/210/320/272/320/270/320/275.docx.canon +0 -13
  121. chgksuite-0.27.2/uv.lock +0 -1454
  122. {chgksuite-0.27.2 → chgksuite-0.28.0}/.gitignore +0 -0
  123. {chgksuite-0.27.2 → chgksuite-0.28.0}/.hgignore +0 -0
  124. {chgksuite-0.27.2 → chgksuite-0.28.0}/LICENSE +0 -0
  125. {chgksuite-0.27.2 → chgksuite-0.28.0}/README.md +0 -0
  126. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/__init__.py +0 -0
  127. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/__main__.py +0 -0
  128. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/_html2md.py +0 -0
  129. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/common.py +0 -0
  130. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/composer/__init__.py +0 -0
  131. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/composer/chgksuite_parser.py +0 -0
  132. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/composer/composer_common.py +0 -0
  133. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/composer/db.py +0 -0
  134. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/composer/docx.py +0 -0
  135. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/composer/latex.py +0 -0
  136. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/composer/lj.py +0 -0
  137. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/composer/markdown.py +0 -0
  138. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/composer/openquiz.py +0 -0
  139. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/composer/pptx.py +0 -0
  140. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/composer/stats.py +0 -0
  141. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/composer/telegram_bot.py +0 -0
  142. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/handouter/__init__.py +0 -0
  143. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/handouter/gen.py +0 -0
  144. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/handouter/installer.py +0 -0
  145. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/handouter/pack.py +0 -0
  146. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/handouter/runner.py +0 -0
  147. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/handouter/tex_internals.py +0 -0
  148. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/handouter/utils.py +0 -0
  149. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/lastdir +0 -0
  150. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/parser_db.py +0 -0
  151. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/cheader.tex +0 -0
  152. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/fix-unnumbered-sections.sty +0 -0
  153. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/labels_az.toml +0 -0
  154. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/labels_by.toml +0 -0
  155. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/labels_by_tar.toml +0 -0
  156. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/labels_en.toml +0 -0
  157. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/labels_kz_cyr.toml +0 -0
  158. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/labels_ru.toml +0 -0
  159. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/labels_sr.toml +0 -0
  160. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/labels_ua.toml +0 -0
  161. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/labels_uz.toml +0 -0
  162. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/labels_uz_cyr.toml +0 -0
  163. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/pptx_config.toml +0 -0
  164. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/regexes_az.json +0 -0
  165. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/regexes_by.json +0 -0
  166. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/regexes_by_tar.json +0 -0
  167. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/regexes_en.json +0 -0
  168. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/regexes_kz_cyr.json +0 -0
  169. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/regexes_ru.json +0 -0
  170. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/regexes_sr.json +0 -0
  171. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/regexes_ua.json +0 -0
  172. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/regexes_uz.json +0 -0
  173. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/regexes_uz_cyr.json +0 -0
  174. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/template.docx +0 -0
  175. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/template.pptx +0 -0
  176. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/resources/trello.json +0 -0
  177. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/trello.py +0 -0
  178. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/typotools.py +0 -0
  179. {chgksuite-0.27.2 → chgksuite-0.28.0}/chgksuite/vulture_whitelist.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chgksuite
3
- Version: 0.27.2
3
+ Version: 0.28.0
4
4
  Summary: A package for chgk automation
5
5
  Project-URL: Homepage, https://gitlab.com/peczony/chgksuite
6
6
  Author-email: Alexander Pecheny <ap@pecheny.me>
@@ -12,6 +12,7 @@ from chgksuite.common import (
12
12
  load_settings,
13
13
  )
14
14
  from chgksuite.composer import gui_compose
15
+ from chgksuite.composer.telegram import get_saved_telegram_targets
15
16
  from chgksuite.handouter.runner import gui_handouter
16
17
  from chgksuite.parser import gui_parse
17
18
  from chgksuite.trello import gui_trello
@@ -34,7 +35,14 @@ class ArgparseBuilder:
34
35
  if self.use_wrapper:
35
36
  return getattr(parser, func)(*args, **kwargs)
36
37
  else:
37
- for k in ("caption", "advanced", "argtype", "hide", "filetypes"):
38
+ for k in (
39
+ "caption",
40
+ "advanced",
41
+ "argtype",
42
+ "hide",
43
+ "filetypes",
44
+ "combobox_values",
45
+ ):
38
46
  try:
39
47
  kwargs.pop(k)
40
48
  except KeyError:
@@ -654,12 +662,15 @@ class ArgparseBuilder:
654
662
  help="a made-up string designating account to use.",
655
663
  caption="Аккаунт для постинга",
656
664
  )
665
+ saved_targets = get_saved_telegram_targets()
657
666
  self.add_argument(
658
667
  cmdcompose_telegram,
659
668
  "--tgchannel",
660
669
  required=True,
661
670
  help="a channel to post questions to.",
662
671
  caption="Название канала, в который постим",
672
+ argtype="combobox",
673
+ combobox_values=saved_targets,
663
674
  )
664
675
  self.add_argument(
665
676
  cmdcompose_telegram,
@@ -667,6 +678,8 @@ class ArgparseBuilder:
667
678
  required=True,
668
679
  help="a chat connected to the channel.",
669
680
  caption="Название чата, привязанного к каналу",
681
+ argtype="combobox",
682
+ combobox_values=saved_targets,
670
683
  )
671
684
  self.add_argument(
672
685
  cmdcompose_telegram,
@@ -17,6 +17,28 @@ from chgksuite.composer.composer_common import BaseExporter, parseimg
17
17
  from chgksuite.composer.telegram_bot import run_bot_in_thread
18
18
 
19
19
 
20
+ def get_saved_telegram_targets():
21
+ """
22
+ Load all saved channel/chat usernames from resolve.db.
23
+ Returns a list of usernames that have been previously used.
24
+ """
25
+ chgksuite_dir = get_chgksuite_dir()
26
+ resolve_db_path = os.path.join(chgksuite_dir, "resolve.db")
27
+
28
+ if not os.path.exists(resolve_db_path):
29
+ return []
30
+
31
+ try:
32
+ conn = sqlite3.connect(resolve_db_path)
33
+ cursor = conn.cursor()
34
+ cursor.execute("SELECT username FROM resolve ORDER BY username")
35
+ results = cursor.fetchall()
36
+ conn.close()
37
+ return [row[0] for row in results]
38
+ except Exception:
39
+ return []
40
+
41
+
20
42
  def get_text(msg_data):
21
43
  if "message" in msg_data and "text" in msg_data["message"]:
22
44
  return msg_data["message"]["text"]
@@ -44,19 +66,14 @@ class TelegramExporter(BaseExporter):
44
66
  self.chat_id = None # Discussion group ID linked to the channel
45
67
  self.auth_uuid = uuid.uuid4().hex[:8]
46
68
  self.chat_auth_uuid = uuid.uuid4().hex[:8]
69
+ self.session = requests.Session()
47
70
  self.init_telegram()
48
71
 
49
72
  def check_connectivity(self):
50
- req_me = requests.get(f"https://api.telegram.org/bot{self.bot_token}/getMe")
51
- if req_me.status_code != 200:
52
- raise Exception(
53
- f"getMe request wasn't successful: {req_me.status_code} {req_me.text}"
54
- )
55
- obj = req_me.json()
56
- assert obj["ok"]
73
+ result = self.send_api_request("getMe")
57
74
  if self.args.debug:
58
- print(f"connection successful! {obj}")
59
- self.bot_id = obj["result"]["id"]
75
+ print(f"connection successful! {result}")
76
+ self.bot_id = result["id"]
60
77
 
61
78
  def init_temp_db(self):
62
79
  self.db_conn = sqlite3.connect(self.temp_db_path)
@@ -102,8 +119,6 @@ class TelegramExporter(BaseExporter):
102
119
  ).fetchall()
103
120
  if messages and json.loads(messages[0][0])["status"] == "ok":
104
121
  break
105
- # Request user authentication
106
- self.authenticate_user()
107
122
 
108
123
  def authenticate_user(self):
109
124
  print("\n" + "=" * 50)
@@ -208,32 +223,46 @@ class TelegramExporter(BaseExporter):
208
223
  def send_api_request(self, method, data=None, files=None):
209
224
  """Send a request to the Telegram Bot API."""
210
225
  url = f"https://api.telegram.org/bot{self.bot_token}/{method}"
226
+ retry_delay = 10 # Start with 10 seconds
227
+ max_retry_delay = 120 # Cap at 2 minutes
211
228
 
212
- try:
213
- if files:
214
- response = requests.post(url, data=data, files=files, timeout=60)
215
- else:
216
- response = requests.post(url, json=data, timeout=30)
229
+ while True:
230
+ try:
231
+ if files:
232
+ response = self.session.post(
233
+ url, data=data, files=files, timeout=60
234
+ )
235
+ else:
236
+ response = self.session.post(url, json=data, timeout=30)
217
237
 
218
- response_data = response.json()
238
+ response_data = response.json()
219
239
 
220
- if not response_data.get("ok"):
221
- error_message = response_data.get("description", "Unknown error")
222
- self.logger.error(f"Telegram API error: {error_message}")
240
+ if not response_data.get("ok"):
241
+ error_message = response_data.get("description", "Unknown error")
242
+ self.logger.error(f"Telegram API error: {error_message}")
223
243
 
224
- # Handle rate limiting
225
- if "retry_after" in response_data:
226
- retry_after = response_data["retry_after"]
227
- self.logger.info(f"Rate limited. Waiting for {retry_after} seconds")
228
- time.sleep(retry_after + 1)
229
- return self.send_api_request(method, data, files)
244
+ # Handle rate limiting
245
+ if "retry_after" in response_data:
246
+ retry_after = response_data["retry_after"]
247
+ self.logger.info(
248
+ f"Rate limited. Waiting for {retry_after} seconds"
249
+ )
250
+ time.sleep(retry_after + 1)
251
+ return self.send_api_request(method, data, files)
230
252
 
231
- raise Exception(f"Telegram API error: {error_message}")
253
+ raise Exception(f"Telegram API error: {error_message}")
232
254
 
233
- return response_data["result"]
234
- except requests.exceptions.RequestException as e:
235
- self.logger.error(f"Request error: {e}")
236
- raise
255
+ return response_data["result"]
256
+ except requests.exceptions.RequestException as e:
257
+ if "Connection reset by peer" in str(e):
258
+ self.logger.warning(
259
+ f"Connection reset by peer. Retrying in {retry_delay} seconds..."
260
+ )
261
+ time.sleep(retry_delay)
262
+ retry_delay = min(retry_delay * 2, max_retry_delay)
263
+ continue
264
+ self.logger.error(f"Request error: {e}")
265
+ raise
237
266
 
238
267
  def get_message_link(self, chat_id, message_id, username=None):
239
268
  """Generate a link to a Telegram message."""
@@ -851,66 +880,82 @@ class TelegramExporter(BaseExporter):
851
880
  channel_result = self.extract_id_from_link(self.args.tgchannel)
852
881
  chat_result = self.extract_id_from_link(self.args.tgchat)
853
882
 
854
- # Handle channel resolution
883
+ # First, try to resolve both IDs without user interaction
884
+ channel_id = None
885
+ chat_id = None
886
+ needs_channel_interaction = False
887
+ needs_chat_interaction = False
888
+
855
889
  if isinstance(channel_result, int):
856
890
  channel_id = channel_result
857
891
  elif isinstance(channel_result, str):
858
892
  channel_id = self.resolve_username_to_id(channel_result)
859
893
  if not channel_id:
860
- print("\n" + "=" * 50)
861
- print("Please forward any message from the target channel to the bot.")
862
- print("This will allow me to extract the channel ID automatically.")
863
- print("=" * 50 + "\n")
864
-
865
- # Wait for a forwarded message with channel information
866
- channel_id = self.wait_for_forwarded_message(
867
- entity_type="channel", check_type=True
868
- )
869
- if channel_id:
870
- self.save_username(channel_result, channel_id)
871
- else:
872
- raise Exception("Failed to get channel ID from forwarded message")
894
+ needs_channel_interaction = True
873
895
  else:
874
896
  raise Exception("Channel ID is undefined")
875
897
 
876
- # Handle chat resolution
877
898
  if isinstance(chat_result, int):
878
899
  chat_id = chat_result
879
900
  elif isinstance(chat_result, str):
880
901
  chat_id = self.resolve_username_to_id(chat_result)
881
902
  if not chat_id:
882
- print("\n" + "=" * 50)
883
- print(
884
- f"Please write a message in the discussion group with text: {self.chat_auth_uuid}"
885
- )
886
- print("This will allow me to extract the group ID automatically.")
887
- print(
888
- "The bot MUST be added do the group and made admin, else it won't work!"
889
- )
890
- print("=" * 50 + "\n")
903
+ needs_chat_interaction = True
904
+ else:
905
+ raise Exception("Chat ID is undefined")
891
906
 
892
- # Wait for a forwarded message with chat information
907
+ # Only authenticate if we need user interaction
908
+ if needs_channel_interaction or needs_chat_interaction:
909
+ self.authenticate_user()
910
+
911
+ # Handle channel resolution with user interaction if needed
912
+ if needs_channel_interaction:
913
+ print("\n" + "=" * 50)
914
+ print("Please forward any message from the target channel to the bot.")
915
+ print("This will allow me to extract the channel ID automatically.")
916
+ print("=" * 50 + "\n")
917
+
918
+ # Wait for a forwarded message with channel information
919
+ channel_id = self.wait_for_forwarded_message(
920
+ entity_type="channel", check_type=True
921
+ )
922
+ if channel_id:
923
+ self.save_username(channel_result, channel_id)
924
+ else:
925
+ raise Exception("Failed to get channel ID from forwarded message")
926
+
927
+ # Handle chat resolution with user interaction if needed
928
+ if needs_chat_interaction:
929
+ print("\n" + "=" * 50)
930
+ print(
931
+ f"Please write a message in the discussion group with text: {self.chat_auth_uuid}"
932
+ )
933
+ print("This will allow me to extract the group ID automatically.")
934
+ print(
935
+ "The bot MUST be added do the group and made admin, else it won't work!"
936
+ )
937
+ print("=" * 50 + "\n")
938
+
939
+ # Wait for a forwarded message with chat information
940
+ chat_id = self.wait_for_forwarded_message(
941
+ entity_type="chat", check_type=False
942
+ )
943
+ if not chat_id:
944
+ self.logger.error("Failed to get chat ID from forwarded message")
945
+ return False
946
+ while chat_id == channel_id:
947
+ error_msg = (
948
+ "Chat ID and channel ID are the same. The problem may be that "
949
+ "you posted a message in the channel, not in the discussion group."
950
+ )
951
+ self.logger.error(error_msg)
893
952
  chat_id = self.wait_for_forwarded_message(
894
- entity_type="chat", check_type=False
953
+ entity_type="chat",
954
+ check_type=False,
955
+ add_msg=error_msg,
895
956
  )
896
- if not chat_id:
897
- self.logger.error("Failed to get chat ID from forwarded message")
898
- return False
899
- while chat_id == channel_id:
900
- error_msg = (
901
- "Chat ID and channel ID are the same. The problem may be that "
902
- "you posted a message in the channel, not in the discussion group."
903
- )
904
- self.logger.error(error_msg)
905
- chat_id = self.wait_for_forwarded_message(
906
- entity_type="chat",
907
- check_type=False,
908
- add_msg=error_msg,
909
- )
910
- if chat_id:
911
- self.save_username(chat_result, chat_id)
912
- else:
913
- raise Exception("Chat ID is undefined")
957
+ if chat_id:
958
+ self.save_username(chat_result, chat_id)
914
959
 
915
960
  if not channel_id:
916
961
  raise Exception("Channel ID is undefined")
@@ -1234,14 +1279,15 @@ class TelegramExporter(BaseExporter):
1234
1279
  return None
1235
1280
 
1236
1281
  def verify_access(self, telegram_id, hr_type=None):
1237
- url = f"https://api.telegram.org/bot{self.bot_token}/getChatAdministrators"
1238
1282
  if not str(telegram_id).startswith("-100"):
1239
1283
  telegram_id = f"-100{telegram_id}"
1240
- req = requests.post(url, data={"chat_id": telegram_id})
1241
- if self.args.debug:
1242
- print(req.status_code, req.text)
1243
- if req.status_code != 200:
1244
- raise Exception(f"Bot isn't added to {hr_type}")
1245
- obj = req.json()
1246
- admin_ids = {x["user"]["id"] for x in obj["result"]}
1247
- return self.bot_id in admin_ids
1284
+ try:
1285
+ result = self.send_api_request(
1286
+ "getChatAdministrators", {"chat_id": telegram_id}
1287
+ )
1288
+ if self.args.debug:
1289
+ print(f"getChatAdministrators result: {result}")
1290
+ admin_ids = {x["user"]["id"] for x in result}
1291
+ return self.bot_id in admin_ids
1292
+ except Exception as e:
1293
+ raise Exception(f"Bot isn't added to {hr_type}: {e}")
@@ -38,7 +38,6 @@ from chgksuite.common import (
38
38
  init_logger,
39
39
  load_settings,
40
40
  log_wrap,
41
- read_text_file,
42
41
  set_lastdir,
43
42
  )
44
43
  from chgksuite.composer import gui_compose
@@ -0,0 +1 @@
1
+ __version__ = "0.28.0"
@@ -54,7 +54,11 @@ chgksuite = "chgksuite.__main__:main"
54
54
  path = "chgksuite/version.py"
55
55
 
56
56
  [tool.hatch.build.targets.sdist]
57
- exclude = [".venv*"]
57
+ include = [
58
+ "chgksuite/**/*",
59
+ "/LICENSE",
60
+ "/README.md",
61
+ ]
58
62
 
59
63
  [tool.hatch.build.targets.wheel]
60
64
  packages = ["chgksuite"]
@@ -1,10 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(uv run pytest:*)",
5
- "Bash(ls:*)",
6
- "Bash(python3:*)",
7
- "Bash(mv:*)"
8
- ]
9
- }
10
- }
@@ -1,283 +0,0 @@
1
- name: Build Standalone Executables
2
-
3
- on:
4
- push:
5
- tags:
6
- - 'v*'
7
- workflow_dispatch:
8
- inputs:
9
- tag:
10
- description: 'Tag to build and release (e.g., v0.26.1). Leave empty for build-only.'
11
- required: false
12
- default: ''
13
-
14
- jobs:
15
- build:
16
- strategy:
17
- fail-fast: false
18
- matrix:
19
- include:
20
- - os: ubuntu-24.04
21
- platform: linux-x64
22
- rust_target: x86_64-unknown-linux-gnu
23
- - os: ubuntu-24.04-arm
24
- platform: linux-arm64
25
- rust_target: aarch64-unknown-linux-gnu
26
- - os: macos-15-intel
27
- platform: macos-x64
28
- rust_target: x86_64-apple-darwin
29
- - os: macos-latest
30
- platform: macos-arm64
31
- rust_target: aarch64-apple-darwin
32
- - os: windows-latest
33
- platform: windows-x64
34
- rust_target: x86_64-pc-windows-msvc
35
-
36
- runs-on: ${{ matrix.os }}
37
-
38
- steps:
39
- - name: Checkout chgksuite
40
- uses: actions/checkout@v4
41
-
42
- - name: Checkout pyapp
43
- uses: actions/checkout@v4
44
- with:
45
- repository: ofek/pyapp
46
- path: pyapp
47
- ref: v0.29.0
48
-
49
- - name: Install Rust
50
- uses: dtolnay/rust-toolchain@stable
51
- with:
52
- targets: ${{ matrix.rust_target }}
53
-
54
- - name: Create build directory
55
- shell: bash
56
- run: mkdir -p artifacts
57
-
58
- # Determine versions and create requirements files
59
- - name: Setup build configuration
60
- id: config
61
- shell: bash
62
- run: |
63
- TAG="${{ inputs.tag || github.ref_name }}"
64
-
65
- # Check if this is a release build (tag) or dev build (branch)
66
- IS_RELEASE="false"
67
- if [[ "${{ github.ref }}" == refs/tags/* ]] || [[ -n "${{ inputs.tag }}" ]]; then
68
- IS_RELEASE="true"
69
- fi
70
-
71
- # Extract version from tag (remove 'v' prefix if present)
72
- if [[ "$TAG" =~ ^v ]]; then
73
- VERSION="${TAG:1}"
74
- else
75
- VERSION="$TAG"
76
- fi
77
-
78
- # Fallback to reading from version.py if no tag
79
- if [ -z "$VERSION" ] || [[ ! "$VERSION" =~ ^[0-9] ]]; then
80
- VERSION=$(grep -oP '__version__\s*=\s*"\K[^"]+' chgksuite/version.py 2>/dev/null || \
81
- grep -o '__version__\s*=\s*"[^"]*"' chgksuite/version.py | cut -d'"' -f2 || \
82
- echo "0.0.0")
83
- fi
84
- echo "version=$VERSION" >> $GITHUB_OUTPUT
85
- echo "is_release=$IS_RELEASE" >> $GITHUB_OUTPUT
86
-
87
- # For non-release builds, always use --pre to get latest beta versions
88
- if [ "$IS_RELEASE" = "false" ]; then
89
- PIP_PRE="--pre"
90
- elif [[ "$VERSION" =~ (a|b|rc)[0-9]+ ]]; then
91
- PIP_PRE="--pre"
92
- else
93
- PIP_PRE=""
94
- fi
95
- echo "pip_pre=$PIP_PRE" >> $GITHUB_OUTPUT
96
-
97
- echo "Build configuration:"
98
- echo " Version: $VERSION"
99
- echo " Is release: $IS_RELEASE"
100
- echo " Pip pre flag: $PIP_PRE"
101
-
102
- # Create requirements files in pyapp directory to avoid Windows path issues
103
- - name: Create requirements files
104
- shell: bash
105
- run: |
106
- # Qt requirements
107
- echo "chgksuite" > pyapp/requirements-qt.txt
108
- echo "chgksuite-qt" >> pyapp/requirements-qt.txt
109
-
110
- # Tk requirements
111
- echo "chgksuite" > pyapp/requirements-tk.txt
112
- echo "chgksuite-tk" >> pyapp/requirements-tk.txt
113
-
114
- echo "=== Qt requirements ==="
115
- cat pyapp/requirements-qt.txt
116
- echo "=== Tk requirements ==="
117
- cat pyapp/requirements-tk.txt
118
-
119
- # Build Qt GUI (skip on linux-arm64 - no PyQt6 wheel)
120
- - name: Build Qt GUI
121
- if: matrix.platform != 'linux-arm64'
122
- shell: bash
123
- working-directory: pyapp
124
- run: |
125
- export PYAPP_PROJECT_NAME="chgksuite-qt"
126
- export PYAPP_PROJECT_VERSION="${{ steps.config.outputs.version }}"
127
- export PYAPP_PROJECT_DEPENDENCY_FILE="$(pwd)/requirements-qt.txt"
128
- export PYAPP_EXEC_SPEC="chgksuite_qt.__main__:main"
129
- export PYAPP_PYTHON_VERSION="3.12"
130
- export PYAPP_IS_GUI="1"
131
- export PYAPP_UV_ENABLED="1"
132
- export PYAPP_PIP_EXTRA_ARGS="${{ steps.config.outputs.pip_pre }}"
133
- export PYAPP_PASS_LOCATION="1"
134
-
135
- echo "Building Qt GUI with:"
136
- echo " PYAPP_PROJECT_DEPENDENCY_FILE=$PYAPP_PROJECT_DEPENDENCY_FILE"
137
- echo " PYAPP_PIP_EXTRA_ARGS=$PYAPP_PIP_EXTRA_ARGS"
138
-
139
- cargo build --release --target ${{ matrix.rust_target }}
140
-
141
- # Copy binary with platform-specific naming
142
- if [ "${{ runner.os }}" = "Windows" ]; then
143
- cp target/${{ matrix.rust_target }}/release/pyapp.exe ../artifacts/chgkq-${{ matrix.platform }}.exe
144
- else
145
- cp target/${{ matrix.rust_target }}/release/pyapp ../artifacts/chgkq-${{ matrix.platform }}
146
- fi
147
-
148
- # Build Tk GUI
149
- - name: Build Tk GUI
150
- shell: bash
151
- working-directory: pyapp
152
- run: |
153
- export PYAPP_PROJECT_NAME="chgksuite-tk"
154
- export PYAPP_PROJECT_VERSION="${{ steps.config.outputs.version }}"
155
- export PYAPP_PROJECT_DEPENDENCY_FILE="$(pwd)/requirements-tk.txt"
156
- export PYAPP_EXEC_SPEC="chgksuite_tk.__main__:main"
157
- export PYAPP_PYTHON_VERSION="3.12"
158
- export PYAPP_IS_GUI="1"
159
- export PYAPP_UV_ENABLED="1"
160
- export PYAPP_PIP_EXTRA_ARGS="${{ steps.config.outputs.pip_pre }}"
161
- export PYAPP_PASS_LOCATION="1"
162
-
163
- echo "Building Tk GUI with:"
164
- echo " PYAPP_PROJECT_DEPENDENCY_FILE=$PYAPP_PROJECT_DEPENDENCY_FILE"
165
- echo " PYAPP_PIP_EXTRA_ARGS=$PYAPP_PIP_EXTRA_ARGS"
166
-
167
- cargo build --release --target ${{ matrix.rust_target }}
168
-
169
- # Copy binary with platform-specific naming
170
- if [ "${{ runner.os }}" = "Windows" ]; then
171
- cp target/${{ matrix.rust_target }}/release/pyapp.exe ../artifacts/chgkt-${{ matrix.platform }}.exe
172
- else
173
- cp target/${{ matrix.rust_target }}/release/pyapp ../artifacts/chgkt-${{ matrix.platform }}
174
- fi
175
-
176
- # Create macOS .app bundles (required for Finder launch)
177
- - name: Create macOS app bundles
178
- if: runner.os == 'macOS'
179
- shell: bash
180
- run: |
181
- # Create Qt .app bundle
182
- if [ -f "artifacts/chgkq-${{ matrix.platform }}" ]; then
183
- mkdir -p "artifacts/chgkq.app/Contents/MacOS"
184
- mv "artifacts/chgkq-${{ matrix.platform }}" "artifacts/chgkq.app/Contents/MacOS/chgkq"
185
- cat > "artifacts/chgkq.app/Contents/Info.plist" << 'EOF'
186
- <?xml version="1.0" encoding="UTF-8"?>
187
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
188
- <plist version="1.0">
189
- <dict>
190
- <key>CFBundleExecutable</key>
191
- <string>chgkq</string>
192
- <key>CFBundleIdentifier</key>
193
- <string>me.pecheny.chgkq</string>
194
- <key>CFBundleName</key>
195
- <string>chgksuite-qt</string>
196
- <key>CFBundlePackageType</key>
197
- <string>APPL</string>
198
- <key>CFBundleVersion</key>
199
- <string>${{ steps.config.outputs.version }}</string>
200
- <key>CFBundleShortVersionString</key>
201
- <string>${{ steps.config.outputs.version }}</string>
202
- <key>LSMinimumSystemVersion</key>
203
- <string>10.15</string>
204
- <key>NSHighResolutionCapable</key>
205
- <true/>
206
- </dict>
207
- </plist>
208
- EOF
209
- # Ad-hoc sign the app bundle to prevent "damaged" error
210
- codesign --force --deep --sign - "artifacts/chgkq.app"
211
- # Zip the .app bundle
212
- cd artifacts && zip -r "chgkq-${{ matrix.platform }}.zip" chgkq.app && rm -rf chgkq.app && cd ..
213
- fi
214
-
215
- # Create Tk .app bundle
216
- if [ -f "artifacts/chgkt-${{ matrix.platform }}" ]; then
217
- mkdir -p "artifacts/chgkt.app/Contents/MacOS"
218
- mv "artifacts/chgkt-${{ matrix.platform }}" "artifacts/chgkt.app/Contents/MacOS/chgkt"
219
- cat > "artifacts/chgkt.app/Contents/Info.plist" << 'EOF'
220
- <?xml version="1.0" encoding="UTF-8"?>
221
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
222
- <plist version="1.0">
223
- <dict>
224
- <key>CFBundleExecutable</key>
225
- <string>chgkt</string>
226
- <key>CFBundleIdentifier</key>
227
- <string>me.pecheny.chgkt</string>
228
- <key>CFBundleName</key>
229
- <string>chgksuite-tk</string>
230
- <key>CFBundlePackageType</key>
231
- <string>APPL</string>
232
- <key>CFBundleVersion</key>
233
- <string>${{ steps.config.outputs.version }}</string>
234
- <key>CFBundleShortVersionString</key>
235
- <string>${{ steps.config.outputs.version }}</string>
236
- <key>LSMinimumSystemVersion</key>
237
- <string>10.15</string>
238
- <key>NSHighResolutionCapable</key>
239
- <true/>
240
- </dict>
241
- </plist>
242
- EOF
243
- # Ad-hoc sign the app bundle to prevent "damaged" error
244
- codesign --force --deep --sign - "artifacts/chgkt.app"
245
- # Zip the .app bundle
246
- cd artifacts && zip -r "chgkt-${{ matrix.platform }}.zip" chgkt.app && rm -rf chgkt.app && cd ..
247
- fi
248
-
249
- - name: List artifacts
250
- shell: bash
251
- run: ls -lh artifacts/
252
-
253
- - name: Upload artifacts
254
- uses: actions/upload-artifact@v4
255
- with:
256
- name: builds-${{ matrix.platform }}
257
- path: artifacts/*
258
-
259
- release:
260
- needs: build
261
- runs-on: ubuntu-latest
262
- if: startsWith(github.ref, 'refs/tags/') || inputs.tag != ''
263
- permissions:
264
- contents: write
265
-
266
- steps:
267
- - name: Download all artifacts
268
- uses: actions/download-artifact@v4
269
- with:
270
- path: artifacts
271
- merge-multiple: true
272
-
273
- - name: List artifacts
274
- run: ls -la artifacts/
275
-
276
- - name: Create Release
277
- uses: softprops/action-gh-release@v1
278
- with:
279
- tag_name: ${{ inputs.tag || github.ref_name }}
280
- files: artifacts/*
281
- generate_release_notes: true
282
- env:
283
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -1,17 +0,0 @@
1
- image: python:3.14-trixie
2
-
3
- before_script:
4
- - pip install zensical
5
-
6
- pages:
7
- stage: deploy
8
- script:
9
- - cd docs
10
- - zensical build
11
- - mv public ../public
12
- - cd ..
13
- artifacts:
14
- paths:
15
- - public
16
- rules:
17
- - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH