telegram-signer 1.0.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.
- telegram-signer-1.0.0/LICENSE +29 -0
- telegram-signer-1.0.0/PKG-INFO +182 -0
- telegram-signer-1.0.0/README.md +168 -0
- telegram-signer-1.0.0/pyproject.toml +79 -0
- telegram-signer-1.0.0/setup.cfg +4 -0
- telegram-signer-1.0.0/telegram_signer/__init__.py +2 -0
- telegram-signer-1.0.0/telegram_signer/__main__.py +10 -0
- telegram-signer-1.0.0/telegram_signer/_kurigram/__init__.py +4 -0
- telegram-signer-1.0.0/telegram_signer/_kurigram/methods.py +77 -0
- telegram-signer-1.0.0/telegram_signer/ai_tools.py +206 -0
- telegram-signer-1.0.0/telegram_signer/cli/__init__.py +3 -0
- telegram-signer-1.0.0/telegram_signer/cli/signer.py +279 -0
- telegram-signer-1.0.0/telegram_signer/config.py +375 -0
- telegram-signer-1.0.0/telegram_signer/core.py +1417 -0
- telegram-signer-1.0.0/telegram_signer/logger.py +90 -0
- telegram-signer-1.0.0/telegram_signer/sign_record_store.py +350 -0
- telegram-signer-1.0.0/telegram_signer/utils.py +88 -0
- telegram-signer-1.0.0/telegram_signer.egg-info/PKG-INFO +182 -0
- telegram-signer-1.0.0/telegram_signer.egg-info/SOURCES.txt +26 -0
- telegram-signer-1.0.0/telegram_signer.egg-info/dependency_links.txt +1 -0
- telegram-signer-1.0.0/telegram_signer.egg-info/entry_points.txt +2 -0
- telegram-signer-1.0.0/telegram_signer.egg-info/requires.txt +7 -0
- telegram-signer-1.0.0/telegram_signer.egg-info/top_level.txt +4 -0
- telegram-signer-1.0.0/tests/test_cli_migrate_sign_records.py +68 -0
- telegram-signer-1.0.0/tests/test_core.py +1014 -0
- telegram-signer-1.0.0/tests/test_sign_config_v2_to_v3.py +179 -0
- telegram-signer-1.0.0/tests/test_sign_record_store.py +141 -0
- telegram-signer-1.0.0/tests/test_utils.py +116 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023, Amchii
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
29
|
+
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: telegram-signer
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Telegram check-in scheduler with keyboard clicks and optional AI-assisted captcha handling.
|
|
5
|
+
Author: Telegram Signer Contributors
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/git-buster/telegram-signer
|
|
8
|
+
Project-URL: Repository, https://github.com/git-buster/telegram-signer
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
|
|
15
|
+
# Telegram Signer
|
|
16
|
+
|
|
17
|
+
Telegram Signer is a Python CLI tool for scheduled Telegram check-ins. It can send
|
|
18
|
+
configured check-in messages, click inline keyboard buttons, handle topic IDs, keep
|
|
19
|
+
SQLite check-in records, and optionally use an OpenAI-compatible API for image or
|
|
20
|
+
math-style verification steps.
|
|
21
|
+
|
|
22
|
+
This fork is intentionally focused on check-ins only. It does not include extra
|
|
23
|
+
dashboard or message-watching features.
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- Required account name in every command: `telegram-signer <account> ...`
|
|
28
|
+
- Scheduled check-in tasks with cron-style or time-style schedules
|
|
29
|
+
- Legacy config compatibility for existing signer `config.json` files
|
|
30
|
+
- SQLite check-in record storage with lazy migration from `sign_record.json`
|
|
31
|
+
- Telegram forum topic support through `message_thread_id`
|
|
32
|
+
- Action flow support:
|
|
33
|
+
- send text
|
|
34
|
+
- send dice
|
|
35
|
+
- click a button by text
|
|
36
|
+
- choose an option by image through an LLM
|
|
37
|
+
- reply to a calculation prompt through an LLM
|
|
38
|
+
- click captcha buttons according to a slot-machine dice result
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
Requires Python 3.10 or newer.
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
pip install -U telegram-signer
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Command Format
|
|
49
|
+
|
|
50
|
+
The account name is required and comes before the command:
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
telegram-signer account_a login
|
|
54
|
+
telegram-signer account_a logout
|
|
55
|
+
telegram-signer account_a run task_name
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Run multiple accounts with your shell by starting separate processes. Keep this to
|
|
59
|
+
five accounts or fewer:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
telegram-signer account_a run task_a &
|
|
63
|
+
telegram-signer account_b run task_b &
|
|
64
|
+
telegram-signer account_c run task_c &
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Common Commands
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
telegram-signer account_a login
|
|
71
|
+
telegram-signer account_a logout
|
|
72
|
+
telegram-signer account_a list
|
|
73
|
+
telegram-signer account_a reconfig task_name
|
|
74
|
+
telegram-signer account_a run task_name
|
|
75
|
+
telegram-signer account_a run-force task_name
|
|
76
|
+
telegram-signer account_a list-records task_name
|
|
77
|
+
telegram-signer account_a migrate-records
|
|
78
|
+
telegram-signer account_a llm-config
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Global options must be placed before the account name:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
telegram-signer --workdir .signer account_a run task_name
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Config Compatibility
|
|
88
|
+
|
|
89
|
+
Existing signer configs remain supported. The default path is:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
<workdir>/telegram-signer/config/<task_name>/config.json
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Older signer config versions are still migrated through the existing compatibility
|
|
96
|
+
chain. Existing configs from the older path are still read and copied to the new
|
|
97
|
+
path when loaded:
|
|
98
|
+
|
|
99
|
+
```text
|
|
100
|
+
<workdir>/signs/<task_name>/config.json
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Check-in records use SQLite at:
|
|
104
|
+
|
|
105
|
+
```text
|
|
106
|
+
<workdir>/data.sqlite3
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Legacy record files are still readable and can be migrated:
|
|
110
|
+
|
|
111
|
+
```text
|
|
112
|
+
<workdir>/signs/<task_name>/<user_id>/sign_record.json
|
|
113
|
+
<workdir>/signs/<task_name>/sign_record.json
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Example Config
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"chats": [
|
|
121
|
+
{
|
|
122
|
+
"chat_id": "@target_bot_or_group",
|
|
123
|
+
"message_thread_id": null,
|
|
124
|
+
"name": "daily check-in",
|
|
125
|
+
"delete_after": null,
|
|
126
|
+
"actions": [
|
|
127
|
+
{"action": 1, "text": "/checkin"},
|
|
128
|
+
{"action": 3, "text": "Check in"},
|
|
129
|
+
{"action": 6, "if_dice_emoji": "🎰"}
|
|
130
|
+
],
|
|
131
|
+
"action_interval": 1
|
|
132
|
+
}
|
|
133
|
+
],
|
|
134
|
+
"sign_at": "0 6 * * *",
|
|
135
|
+
"random_seconds": 0,
|
|
136
|
+
"sign_interval": 1
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Action IDs:
|
|
141
|
+
|
|
142
|
+
| ID | Action |
|
|
143
|
+
| --- | --- |
|
|
144
|
+
| 1 | send text |
|
|
145
|
+
| 2 | send dice |
|
|
146
|
+
| 3 | click keyboard button by text |
|
|
147
|
+
| 4 | choose option by image |
|
|
148
|
+
| 5 | reply to calculation prompt |
|
|
149
|
+
| 6 | solve slot-machine captcha buttons when an `if_*` condition matches |
|
|
150
|
+
|
|
151
|
+
Slot-machine captcha solving is intentionally conditional. Add at least one of
|
|
152
|
+
these fields to an `action: 6` step:
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{"action": 6, "if_dice_emoji": "🎰"}
|
|
156
|
+
{"action": 6, "if_text": "choose the symbols"}
|
|
157
|
+
{"action": 6, "if_regex": "slot|captcha|verification"}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
If no `if_*` condition is present, the action is skipped.
|
|
161
|
+
|
|
162
|
+
## LLM Configuration
|
|
163
|
+
|
|
164
|
+
LLM-backed actions read configuration from either environment variables or the
|
|
165
|
+
workdir config file created by `llm-config`.
|
|
166
|
+
|
|
167
|
+
Environment variables:
|
|
168
|
+
|
|
169
|
+
```text
|
|
170
|
+
OPENAI_API_KEY
|
|
171
|
+
OPENAI_BASE_URL
|
|
172
|
+
OPENAI_MODEL
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Persistent workdir file:
|
|
176
|
+
|
|
177
|
+
```text
|
|
178
|
+
<workdir>/.openai_config.json
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Do not commit sessions, session strings, `.env` files, `.openai_config.json`, logs,
|
|
182
|
+
or local SQLite databases.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Telegram Signer
|
|
2
|
+
|
|
3
|
+
Telegram Signer is a Python CLI tool for scheduled Telegram check-ins. It can send
|
|
4
|
+
configured check-in messages, click inline keyboard buttons, handle topic IDs, keep
|
|
5
|
+
SQLite check-in records, and optionally use an OpenAI-compatible API for image or
|
|
6
|
+
math-style verification steps.
|
|
7
|
+
|
|
8
|
+
This fork is intentionally focused on check-ins only. It does not include extra
|
|
9
|
+
dashboard or message-watching features.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- Required account name in every command: `telegram-signer <account> ...`
|
|
14
|
+
- Scheduled check-in tasks with cron-style or time-style schedules
|
|
15
|
+
- Legacy config compatibility for existing signer `config.json` files
|
|
16
|
+
- SQLite check-in record storage with lazy migration from `sign_record.json`
|
|
17
|
+
- Telegram forum topic support through `message_thread_id`
|
|
18
|
+
- Action flow support:
|
|
19
|
+
- send text
|
|
20
|
+
- send dice
|
|
21
|
+
- click a button by text
|
|
22
|
+
- choose an option by image through an LLM
|
|
23
|
+
- reply to a calculation prompt through an LLM
|
|
24
|
+
- click captcha buttons according to a slot-machine dice result
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
Requires Python 3.10 or newer.
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
pip install -U telegram-signer
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Command Format
|
|
35
|
+
|
|
36
|
+
The account name is required and comes before the command:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
telegram-signer account_a login
|
|
40
|
+
telegram-signer account_a logout
|
|
41
|
+
telegram-signer account_a run task_name
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Run multiple accounts with your shell by starting separate processes. Keep this to
|
|
45
|
+
five accounts or fewer:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
telegram-signer account_a run task_a &
|
|
49
|
+
telegram-signer account_b run task_b &
|
|
50
|
+
telegram-signer account_c run task_c &
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Common Commands
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
telegram-signer account_a login
|
|
57
|
+
telegram-signer account_a logout
|
|
58
|
+
telegram-signer account_a list
|
|
59
|
+
telegram-signer account_a reconfig task_name
|
|
60
|
+
telegram-signer account_a run task_name
|
|
61
|
+
telegram-signer account_a run-force task_name
|
|
62
|
+
telegram-signer account_a list-records task_name
|
|
63
|
+
telegram-signer account_a migrate-records
|
|
64
|
+
telegram-signer account_a llm-config
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Global options must be placed before the account name:
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
telegram-signer --workdir .signer account_a run task_name
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Config Compatibility
|
|
74
|
+
|
|
75
|
+
Existing signer configs remain supported. The default path is:
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
<workdir>/telegram-signer/config/<task_name>/config.json
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Older signer config versions are still migrated through the existing compatibility
|
|
82
|
+
chain. Existing configs from the older path are still read and copied to the new
|
|
83
|
+
path when loaded:
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
<workdir>/signs/<task_name>/config.json
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Check-in records use SQLite at:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
<workdir>/data.sqlite3
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Legacy record files are still readable and can be migrated:
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
<workdir>/signs/<task_name>/<user_id>/sign_record.json
|
|
99
|
+
<workdir>/signs/<task_name>/sign_record.json
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Example Config
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"chats": [
|
|
107
|
+
{
|
|
108
|
+
"chat_id": "@target_bot_or_group",
|
|
109
|
+
"message_thread_id": null,
|
|
110
|
+
"name": "daily check-in",
|
|
111
|
+
"delete_after": null,
|
|
112
|
+
"actions": [
|
|
113
|
+
{"action": 1, "text": "/checkin"},
|
|
114
|
+
{"action": 3, "text": "Check in"},
|
|
115
|
+
{"action": 6, "if_dice_emoji": "🎰"}
|
|
116
|
+
],
|
|
117
|
+
"action_interval": 1
|
|
118
|
+
}
|
|
119
|
+
],
|
|
120
|
+
"sign_at": "0 6 * * *",
|
|
121
|
+
"random_seconds": 0,
|
|
122
|
+
"sign_interval": 1
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Action IDs:
|
|
127
|
+
|
|
128
|
+
| ID | Action |
|
|
129
|
+
| --- | --- |
|
|
130
|
+
| 1 | send text |
|
|
131
|
+
| 2 | send dice |
|
|
132
|
+
| 3 | click keyboard button by text |
|
|
133
|
+
| 4 | choose option by image |
|
|
134
|
+
| 5 | reply to calculation prompt |
|
|
135
|
+
| 6 | solve slot-machine captcha buttons when an `if_*` condition matches |
|
|
136
|
+
|
|
137
|
+
Slot-machine captcha solving is intentionally conditional. Add at least one of
|
|
138
|
+
these fields to an `action: 6` step:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{"action": 6, "if_dice_emoji": "🎰"}
|
|
142
|
+
{"action": 6, "if_text": "choose the symbols"}
|
|
143
|
+
{"action": 6, "if_regex": "slot|captcha|verification"}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
If no `if_*` condition is present, the action is skipped.
|
|
147
|
+
|
|
148
|
+
## LLM Configuration
|
|
149
|
+
|
|
150
|
+
LLM-backed actions read configuration from either environment variables or the
|
|
151
|
+
workdir config file created by `llm-config`.
|
|
152
|
+
|
|
153
|
+
Environment variables:
|
|
154
|
+
|
|
155
|
+
```text
|
|
156
|
+
OPENAI_API_KEY
|
|
157
|
+
OPENAI_BASE_URL
|
|
158
|
+
OPENAI_MODEL
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Persistent workdir file:
|
|
162
|
+
|
|
163
|
+
```text
|
|
164
|
+
<workdir>/.openai_config.json
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Do not commit sessions, session strings, `.env` files, `.openai_config.json`, logs,
|
|
168
|
+
or local SQLite databases.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "telegram-signer"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Telegram check-in scheduler with keyboard clicks and optional AI-assisted captcha handling."
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "Telegram Signer Contributors" }
|
|
11
|
+
]
|
|
12
|
+
license = { text = "BSD-3-Clause" }
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
requires-python = ">=3.10"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Programming Language :: Python :: 3 :: Only"
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"kurigram>=2.2.19,<2.3.0",
|
|
21
|
+
"click",
|
|
22
|
+
"pydantic",
|
|
23
|
+
"openai",
|
|
24
|
+
"croniter",
|
|
25
|
+
"json_repair",
|
|
26
|
+
"typing-extensions",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[dependency-groups]
|
|
30
|
+
dev = [
|
|
31
|
+
"ruff>=0.14.6",
|
|
32
|
+
"tox>=4.32.0",
|
|
33
|
+
"pytest>=9.0.1",
|
|
34
|
+
"pytest-asyncio>=1.3.0",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/git-buster/telegram-signer"
|
|
39
|
+
Repository = "https://github.com/git-buster/telegram-signer"
|
|
40
|
+
|
|
41
|
+
[project.scripts]
|
|
42
|
+
telegram-signer = "telegram_signer.__main__:signer"
|
|
43
|
+
|
|
44
|
+
[tool.setuptools]
|
|
45
|
+
include-package-data = true
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.dynamic]
|
|
48
|
+
version = { attr = "telegram_signer.__version__" }
|
|
49
|
+
|
|
50
|
+
[tool.setuptools.packages.find]
|
|
51
|
+
exclude = ["tests*"]
|
|
52
|
+
|
|
53
|
+
[tool.ruff]
|
|
54
|
+
line-length = 88
|
|
55
|
+
|
|
56
|
+
[tool.ruff.lint]
|
|
57
|
+
extend-select = [
|
|
58
|
+
"I", # isort
|
|
59
|
+
"B", # flake8-bugbear
|
|
60
|
+
"W", # pycodestyle
|
|
61
|
+
"C4", # flake8-comprehensions
|
|
62
|
+
]
|
|
63
|
+
ignore = [
|
|
64
|
+
"E501", # line too long, handled by black
|
|
65
|
+
"B008", # do not perform function calls in argument defaults
|
|
66
|
+
"C901", # too complex
|
|
67
|
+
"W191", # indentation contains tabs
|
|
68
|
+
"B019", # Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks
|
|
69
|
+
"B904" # raise without from inside except
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
[tool.pytest.ini_options]
|
|
73
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
74
|
+
addopts = [
|
|
75
|
+
"-vv",
|
|
76
|
+
"-x",
|
|
77
|
+
"-W=ignore::DeprecationWarning:pyrogram.utils"
|
|
78
|
+
]
|
|
79
|
+
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from typing import AsyncGenerator, Union
|
|
2
|
+
|
|
3
|
+
import pyrogram
|
|
4
|
+
from pyrogram import raw, types, utils
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SafeGetForumTopics:
|
|
8
|
+
"""Temporary workaround for Kurigram forum topic pagination.
|
|
9
|
+
|
|
10
|
+
Kurigram 2.2.19 may raise AttributeError when the last topic in a page has
|
|
11
|
+
no ``top_message``. Keep this patch isolated so it can be removed once the
|
|
12
|
+
upstream method is fixed.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
async def get_forum_topics(
|
|
16
|
+
self: "pyrogram.Client",
|
|
17
|
+
chat_id: Union[int, str],
|
|
18
|
+
limit: int = 0,
|
|
19
|
+
) -> AsyncGenerator["types.ForumTopic", None]:
|
|
20
|
+
current = 0
|
|
21
|
+
total = limit or (1 << 31) - 1
|
|
22
|
+
limit = min(100, total)
|
|
23
|
+
|
|
24
|
+
offset_date = 0
|
|
25
|
+
offset_id = 0
|
|
26
|
+
offset_topic = 0
|
|
27
|
+
seen_topic_ids = set()
|
|
28
|
+
|
|
29
|
+
while True:
|
|
30
|
+
result = await self.invoke(
|
|
31
|
+
raw.functions.messages.GetForumTopics(
|
|
32
|
+
peer=await self.resolve_peer(chat_id),
|
|
33
|
+
offset_date=offset_date,
|
|
34
|
+
offset_id=offset_id,
|
|
35
|
+
offset_topic=offset_topic,
|
|
36
|
+
limit=limit,
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
users = {item.id: item for item in result.users}
|
|
41
|
+
chats = {item.id: item for item in result.chats}
|
|
42
|
+
messages = {}
|
|
43
|
+
|
|
44
|
+
for message in result.messages:
|
|
45
|
+
if isinstance(message, raw.types.MessageEmpty):
|
|
46
|
+
continue
|
|
47
|
+
messages[message.id] = await types.Message._parse(
|
|
48
|
+
self, message, users, chats
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
page_topics = []
|
|
52
|
+
for topic in result.topics:
|
|
53
|
+
parsed_topic = types.ForumTopic._parse(
|
|
54
|
+
self, topic, messages, users, chats
|
|
55
|
+
)
|
|
56
|
+
if parsed_topic is None or parsed_topic.id in seen_topic_ids:
|
|
57
|
+
continue
|
|
58
|
+
seen_topic_ids.add(parsed_topic.id)
|
|
59
|
+
page_topics.append(parsed_topic)
|
|
60
|
+
|
|
61
|
+
if not page_topics:
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
for topic in page_topics:
|
|
65
|
+
yield topic
|
|
66
|
+
current += 1
|
|
67
|
+
if current >= total:
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
last_topic = page_topics[-1]
|
|
71
|
+
if last_topic.top_message is None or last_topic.top_message.date is None:
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
offset_id = last_topic.top_message.id
|
|
75
|
+
offset_date = utils.datetime_to_timestamp(last_topic.top_message.date)
|
|
76
|
+
offset_topic = last_topic.id
|
|
77
|
+
|