tgdata 0.0.7__tar.gz → 0.0.8__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.
- {tgdata-0.0.7 → tgdata-0.0.8}/PKG-INFO +83 -2
- {tgdata-0.0.7 → tgdata-0.0.8}/README.md +81 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/setup.py +3 -1
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/__init__.py +10 -5
- tgdata-0.0.8/tgdata/discovery_engine.py +579 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/models.py +29 -0
- tgdata-0.0.8/tgdata/smoke_tests/test_11_discover_groups.py +224 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/tgdata.py +189 -1
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/utils.py +53 -2
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata.egg-info/PKG-INFO +83 -2
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata.egg-info/SOURCES.txt +2 -0
- tgdata-0.0.8/tgdata.egg-info/requires.txt +2 -0
- tgdata-0.0.7/tgdata.egg-info/requires.txt +0 -2
- {tgdata-0.0.7 → tgdata-0.0.8}/setup.cfg +0 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/connection_engine.py +0 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/message_engine.py +0 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/progress.py +0 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/smoke_tests/__init__.py +0 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/smoke_tests/my_test.py +0 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/smoke_tests/test_00_connection.py +0 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/smoke_tests/test_01_list_group_chats.py +0 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/smoke_tests/test_02_get_message_count.py +0 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/smoke_tests/test_03_get_all_messages.py +0 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/smoke_tests/test_04_get_all_messages_in_batches.py +0 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/smoke_tests/test_05_with_progress_feature.py +0 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/smoke_tests/test_06_polling_simple.py +0 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/smoke_tests/test_07_on_new_message_simple.py +0 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/smoke_tests/test_07_real_time_event_handler.py +0 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/smoke_tests/test_08_batch_callback.py +0 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/smoke_tests/test_08_polling.py +0 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/smoke_tests/test_10_advanced_features.py +0 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/smoke_tests/test_debug_min_id.py +0 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/smoke_tests/test_polling_debug.py +0 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata/smoke_tests/test_username_support.py +0 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata.egg-info/dependency_links.txt +0 -0
- {tgdata-0.0.7 → tgdata-0.0.8}/tgdata.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tgdata
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.8
|
|
4
4
|
Summary: A production-grade Python library for extracting and processing Telegram group and channel messages
|
|
5
5
|
Author: enes kuzucu
|
|
6
6
|
Classifier: Development Status :: 3 - Alpha
|
|
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
14
14
|
Classifier: Operating System :: OS Independent
|
|
15
15
|
Requires-Python: >=3.7
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
|
-
Requires-Dist: Telethon<2.0,>=1.
|
|
17
|
+
Requires-Dist: Telethon<2.0,>=1.33
|
|
18
18
|
Requires-Dist: pandas>=1.0
|
|
19
19
|
Dynamic: author
|
|
20
20
|
Dynamic: classifier
|
|
@@ -28,6 +28,14 @@ Dynamic: summary
|
|
|
28
28
|
|
|
29
29
|
A production-grade Python library for extracting and processing Telegram group and channel messages. Designed for ETL pipelines, data analysis, and archival purposes.
|
|
30
30
|
|
|
31
|
+
> **New in 0.0.8 — group discovery.** tgdata can now *find* groups and channels, not only read
|
|
32
|
+
> them: Telegram's name search, its "similar channels" recommendations, and links mined from
|
|
33
|
+
> posts, on the same persistent session, paced, flood-safe and budgeted. Four calls —
|
|
34
|
+
> `search_groups`, `similar_groups`, `linked_groups`, `discover_groups` — return candidate rooms
|
|
35
|
+
> in the `list_groups` row shape, already usable by `get_messages`. Quick start below under
|
|
36
|
+
> [Group discovery](#group-discovery--finding-groups-you-are-not-in); the full guide to using it
|
|
37
|
+
> properly is [`devdocs/guides/group_discovery.md`](devdocs/guides/group_discovery.md).
|
|
38
|
+
|
|
31
39
|
## Features
|
|
32
40
|
|
|
33
41
|
- 🚀 **Production-Ready**: Built for reliability and scale in ETL pipelines
|
|
@@ -42,6 +50,7 @@ A production-grade Python library for extracting and processing Telegram group a
|
|
|
42
50
|
- ⏱️ **Polling Support**: Poll for new messages at configurable intervals
|
|
43
51
|
- 🎯 **Batch Processing**: Handle large groups with configurable batch sizes and delays
|
|
44
52
|
- 🖼️ **Media Detection & Download**: Flag photos/videos per message (no download needed) and pull the files on demand or during the fetch
|
|
53
|
+
- 🔎 **Group Discovery**: Find rooms you are not in — Telegram's name search, its "similar channels", and links mined from posts — paced, flood-safe, budgeted
|
|
45
54
|
|
|
46
55
|
## Installation
|
|
47
56
|
|
|
@@ -110,6 +119,78 @@ Outputs:
|
|
|
110
119
|
Python Devs 10012312313
|
|
111
120
|
```
|
|
112
121
|
|
|
122
|
+
### Group discovery — finding groups you are not in
|
|
123
|
+
|
|
124
|
+
`list_groups` shows what the account already follows. To find *new* rooms there are three
|
|
125
|
+
routes, all returning the same columns as `list_groups` plus `FoundVia` and `FoundBy`.
|
|
126
|
+
This is the quick start; the rules for running discovery on a real account without earning
|
|
127
|
+
a block — pacing, budgets, resumable runs, reading the output — are in
|
|
128
|
+
[`devdocs/guides/group_discovery.md`](devdocs/guides/group_discovery.md).
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from tgdata import TgData, build_search_queries
|
|
132
|
+
|
|
133
|
+
tg = TgData("config.ini")
|
|
134
|
+
|
|
135
|
+
# 1. Telegram's global search — matches room NAMES and @usernames only, never post text
|
|
136
|
+
df = await tg.search_groups("Анталия аренда")
|
|
137
|
+
|
|
138
|
+
# 2. Telegram's "similar channels" — rooms whose subscribers overlap the seeds', no keyword needed
|
|
139
|
+
df = await tg.similar_groups(["@antalyadaa", "@sprout_antalya"], rounds=2)
|
|
140
|
+
|
|
141
|
+
# 3. t.me links found in a room's recent posts — usernames only (free), or resolved
|
|
142
|
+
df = await tg.linked_groups(group_id, posts=100, resolve=False)
|
|
143
|
+
|
|
144
|
+
# All three on one session, deduplicated by id, in cost order
|
|
145
|
+
df = await tg.discover_groups(
|
|
146
|
+
seeds=["@antalyadaa"],
|
|
147
|
+
queries=build_search_queries(["Анталия", "Antalya"], topics=["чат", "аренда", "новости"],
|
|
148
|
+
prefixes=["Турция"]),
|
|
149
|
+
mine_links=True,
|
|
150
|
+
heartbeat=lambda phase: print(phase), # liveness ticks: search / similar / links / pause / flood-wait Ns
|
|
151
|
+
found_callback=lambda row: rows.append(row), # each room as it is found — persist as you go
|
|
152
|
+
)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**The name-only rule.** Telegram's search matches names and @usernames, never what a room
|
|
156
|
+
posts. A perfect room under an unrelated name is invisible to every query — that is what
|
|
157
|
+
`similar_groups` and `linked_groups` are for. Rooms name themselves "place + topic", so
|
|
158
|
+
`build_search_queries` combines your place spellings with topic words, in both word orders.
|
|
159
|
+
|
|
160
|
+
| Column | Meaning |
|
|
161
|
+
|---|---|
|
|
162
|
+
| `GroupID` … `ParticipantsCount` | the `list_groups` columns; `ParticipantsCount` is present on search and similar-channel results, absent (`NA`) on rooms resolved from links — Telegram's lookup reply does not carry it |
|
|
163
|
+
| `FoundVia` | `search` · `similar` · `link` — the cheapest route that found the room |
|
|
164
|
+
| `FoundBy` | the query, the seed's `@name`, or the source room |
|
|
165
|
+
|
|
166
|
+
Unresolved link rows carry only `Username`/`Identifier`; `GroupID` and the flags are `NA`
|
|
167
|
+
(the columns are nullable, so dtypes never drift). Every row with an id is already usable
|
|
168
|
+
by `get_messages(row['GroupID'])` — Telegram results land in the session cache with their
|
|
169
|
+
access hash, no dialog sync needed.
|
|
170
|
+
|
|
171
|
+
**Pacing and waits.** Every request is followed by `pace` seconds (default 2, the proven
|
|
172
|
+
pace for a personal account). A rate-limit wait up to `max_flood_wait` is obeyed exactly,
|
|
173
|
+
with heartbeat ticks so a watchdog sees life; a longer one raises `DiscoveryInterrupted`,
|
|
174
|
+
which carries `.found` (everything collected so far) and `.retry_after`. A dropped connection
|
|
175
|
+
is waited out up to `max_offline` and the same request retried. Nothing found is ever lost.
|
|
176
|
+
|
|
177
|
+
**The budget.** Resolving a username to a room is the request Telegram punishes hardest —
|
|
178
|
+
a production account lost 8 and 21 hours of lookups to it. So every call has `max_resolve`
|
|
179
|
+
(default 100), `discover_groups` mines links from the first `link_sources` rooms only
|
|
180
|
+
(default 50), `resolve_links` is off by default (mined names come back as text, which costs
|
|
181
|
+
nothing), and a wait on a lookup is skipped or stopped, never waited out. A truncating cap
|
|
182
|
+
logs a warning telling you which parameter lifts it.
|
|
183
|
+
|
|
184
|
+
**Similar channels** work on channels and megagroups, not basic groups; two rounds by
|
|
185
|
+
default (a third drifts off topic); the seeds themselves are never returned. Yield depends
|
|
186
|
+
on the account — a Premium account got 64 recommendations from one seed, a normal account
|
|
187
|
+
reportedly about 10.
|
|
188
|
+
|
|
189
|
+
**Links** are read from the visible text, from hyperlinked text (where channels put nearly
|
|
190
|
+
all of them) and from URL buttons. Accepted: `t.me/name`, `t.me/name/123`, `t.me/s/name`,
|
|
191
|
+
`t.me/boost/name`, `telegram.me/name`. Skipped: invite links (`t.me/+…`), `t.me/c/…`,
|
|
192
|
+
reserved paths, and bots (`…bot`). Discovery reads; it never joins.
|
|
193
|
+
|
|
113
194
|
|
|
114
195
|
### Get all messages of a chat
|
|
115
196
|
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
A production-grade Python library for extracting and processing Telegram group and channel messages. Designed for ETL pipelines, data analysis, and archival purposes.
|
|
4
4
|
|
|
5
|
+
> **New in 0.0.8 — group discovery.** tgdata can now *find* groups and channels, not only read
|
|
6
|
+
> them: Telegram's name search, its "similar channels" recommendations, and links mined from
|
|
7
|
+
> posts, on the same persistent session, paced, flood-safe and budgeted. Four calls —
|
|
8
|
+
> `search_groups`, `similar_groups`, `linked_groups`, `discover_groups` — return candidate rooms
|
|
9
|
+
> in the `list_groups` row shape, already usable by `get_messages`. Quick start below under
|
|
10
|
+
> [Group discovery](#group-discovery--finding-groups-you-are-not-in); the full guide to using it
|
|
11
|
+
> properly is [`devdocs/guides/group_discovery.md`](devdocs/guides/group_discovery.md).
|
|
12
|
+
|
|
5
13
|
## Features
|
|
6
14
|
|
|
7
15
|
- 🚀 **Production-Ready**: Built for reliability and scale in ETL pipelines
|
|
@@ -16,6 +24,7 @@ A production-grade Python library for extracting and processing Telegram group a
|
|
|
16
24
|
- ⏱️ **Polling Support**: Poll for new messages at configurable intervals
|
|
17
25
|
- 🎯 **Batch Processing**: Handle large groups with configurable batch sizes and delays
|
|
18
26
|
- 🖼️ **Media Detection & Download**: Flag photos/videos per message (no download needed) and pull the files on demand or during the fetch
|
|
27
|
+
- 🔎 **Group Discovery**: Find rooms you are not in — Telegram's name search, its "similar channels", and links mined from posts — paced, flood-safe, budgeted
|
|
19
28
|
|
|
20
29
|
## Installation
|
|
21
30
|
|
|
@@ -84,6 +93,78 @@ Outputs:
|
|
|
84
93
|
Python Devs 10012312313
|
|
85
94
|
```
|
|
86
95
|
|
|
96
|
+
### Group discovery — finding groups you are not in
|
|
97
|
+
|
|
98
|
+
`list_groups` shows what the account already follows. To find *new* rooms there are three
|
|
99
|
+
routes, all returning the same columns as `list_groups` plus `FoundVia` and `FoundBy`.
|
|
100
|
+
This is the quick start; the rules for running discovery on a real account without earning
|
|
101
|
+
a block — pacing, budgets, resumable runs, reading the output — are in
|
|
102
|
+
[`devdocs/guides/group_discovery.md`](devdocs/guides/group_discovery.md).
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from tgdata import TgData, build_search_queries
|
|
106
|
+
|
|
107
|
+
tg = TgData("config.ini")
|
|
108
|
+
|
|
109
|
+
# 1. Telegram's global search — matches room NAMES and @usernames only, never post text
|
|
110
|
+
df = await tg.search_groups("Анталия аренда")
|
|
111
|
+
|
|
112
|
+
# 2. Telegram's "similar channels" — rooms whose subscribers overlap the seeds', no keyword needed
|
|
113
|
+
df = await tg.similar_groups(["@antalyadaa", "@sprout_antalya"], rounds=2)
|
|
114
|
+
|
|
115
|
+
# 3. t.me links found in a room's recent posts — usernames only (free), or resolved
|
|
116
|
+
df = await tg.linked_groups(group_id, posts=100, resolve=False)
|
|
117
|
+
|
|
118
|
+
# All three on one session, deduplicated by id, in cost order
|
|
119
|
+
df = await tg.discover_groups(
|
|
120
|
+
seeds=["@antalyadaa"],
|
|
121
|
+
queries=build_search_queries(["Анталия", "Antalya"], topics=["чат", "аренда", "новости"],
|
|
122
|
+
prefixes=["Турция"]),
|
|
123
|
+
mine_links=True,
|
|
124
|
+
heartbeat=lambda phase: print(phase), # liveness ticks: search / similar / links / pause / flood-wait Ns
|
|
125
|
+
found_callback=lambda row: rows.append(row), # each room as it is found — persist as you go
|
|
126
|
+
)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**The name-only rule.** Telegram's search matches names and @usernames, never what a room
|
|
130
|
+
posts. A perfect room under an unrelated name is invisible to every query — that is what
|
|
131
|
+
`similar_groups` and `linked_groups` are for. Rooms name themselves "place + topic", so
|
|
132
|
+
`build_search_queries` combines your place spellings with topic words, in both word orders.
|
|
133
|
+
|
|
134
|
+
| Column | Meaning |
|
|
135
|
+
|---|---|
|
|
136
|
+
| `GroupID` … `ParticipantsCount` | the `list_groups` columns; `ParticipantsCount` is present on search and similar-channel results, absent (`NA`) on rooms resolved from links — Telegram's lookup reply does not carry it |
|
|
137
|
+
| `FoundVia` | `search` · `similar` · `link` — the cheapest route that found the room |
|
|
138
|
+
| `FoundBy` | the query, the seed's `@name`, or the source room |
|
|
139
|
+
|
|
140
|
+
Unresolved link rows carry only `Username`/`Identifier`; `GroupID` and the flags are `NA`
|
|
141
|
+
(the columns are nullable, so dtypes never drift). Every row with an id is already usable
|
|
142
|
+
by `get_messages(row['GroupID'])` — Telegram results land in the session cache with their
|
|
143
|
+
access hash, no dialog sync needed.
|
|
144
|
+
|
|
145
|
+
**Pacing and waits.** Every request is followed by `pace` seconds (default 2, the proven
|
|
146
|
+
pace for a personal account). A rate-limit wait up to `max_flood_wait` is obeyed exactly,
|
|
147
|
+
with heartbeat ticks so a watchdog sees life; a longer one raises `DiscoveryInterrupted`,
|
|
148
|
+
which carries `.found` (everything collected so far) and `.retry_after`. A dropped connection
|
|
149
|
+
is waited out up to `max_offline` and the same request retried. Nothing found is ever lost.
|
|
150
|
+
|
|
151
|
+
**The budget.** Resolving a username to a room is the request Telegram punishes hardest —
|
|
152
|
+
a production account lost 8 and 21 hours of lookups to it. So every call has `max_resolve`
|
|
153
|
+
(default 100), `discover_groups` mines links from the first `link_sources` rooms only
|
|
154
|
+
(default 50), `resolve_links` is off by default (mined names come back as text, which costs
|
|
155
|
+
nothing), and a wait on a lookup is skipped or stopped, never waited out. A truncating cap
|
|
156
|
+
logs a warning telling you which parameter lifts it.
|
|
157
|
+
|
|
158
|
+
**Similar channels** work on channels and megagroups, not basic groups; two rounds by
|
|
159
|
+
default (a third drifts off topic); the seeds themselves are never returned. Yield depends
|
|
160
|
+
on the account — a Premium account got 64 recommendations from one seed, a normal account
|
|
161
|
+
reportedly about 10.
|
|
162
|
+
|
|
163
|
+
**Links** are read from the visible text, from hyperlinked text (where channels put nearly
|
|
164
|
+
all of them) and from URL buttons. Accepted: `t.me/name`, `t.me/name/123`, `t.me/s/name`,
|
|
165
|
+
`t.me/boost/name`, `telegram.me/name`. Skipped: invite links (`t.me/+…`), `t.me/c/…`,
|
|
166
|
+
reserved paths, and bots (`…bot`). Discovery reads; it never joins.
|
|
167
|
+
|
|
87
168
|
|
|
88
169
|
### Get all messages of a chat
|
|
89
170
|
|
|
@@ -33,7 +33,9 @@ setup(
|
|
|
33
33
|
install_requires=[
|
|
34
34
|
# min_id is exclusive in Telethon 1.x (the off-by-one fix relies on it);
|
|
35
35
|
# 2.0 is a breaking API rewrite, so cap below it. Verified on 1.40.
|
|
36
|
-
|
|
36
|
+
# 1.33 is the first release carrying channels.GetChannelRecommendationsRequest
|
|
37
|
+
# (group discovery, scoped/10) — verified against the 1.33.1 sdist.
|
|
38
|
+
'Telethon>=1.33,<2.0',
|
|
37
39
|
# Required, not optional: everything returns DataFrames. Nullable Int64
|
|
38
40
|
# (used for GroupedId album ids > 2^53) needs pandas >= 1.0.
|
|
39
41
|
'pandas>=1.0',
|
|
@@ -9,6 +9,8 @@ from .tgdata import TgData
|
|
|
9
9
|
|
|
10
10
|
# Errors
|
|
11
11
|
from .message_engine import GroupAccessError
|
|
12
|
+
from .connection_engine import AuthRequiredError
|
|
13
|
+
from .discovery_engine import DiscoveryInterrupted
|
|
12
14
|
|
|
13
15
|
# Models
|
|
14
16
|
from .models import (
|
|
@@ -27,13 +29,14 @@ from .utils import (
|
|
|
27
29
|
filter_messages_by_content,
|
|
28
30
|
get_message_statistics,
|
|
29
31
|
save_profile_photos,
|
|
30
|
-
create_metrics_report
|
|
32
|
+
create_metrics_report,
|
|
33
|
+
build_search_queries
|
|
31
34
|
)
|
|
32
35
|
|
|
33
36
|
# Progress tracking
|
|
34
37
|
from .progress import ProgressTracker
|
|
35
38
|
|
|
36
|
-
__version__ = "0.0.
|
|
39
|
+
__version__ = "0.0.8"
|
|
37
40
|
|
|
38
41
|
__all__ = [
|
|
39
42
|
# Main class
|
|
@@ -41,13 +44,15 @@ __all__ = [
|
|
|
41
44
|
|
|
42
45
|
# Errors
|
|
43
46
|
"GroupAccessError",
|
|
47
|
+
"AuthRequiredError",
|
|
48
|
+
"DiscoveryInterrupted",
|
|
44
49
|
|
|
45
50
|
# Models
|
|
46
51
|
"MessageData",
|
|
47
52
|
"GroupInfo",
|
|
48
53
|
"ConnectionConfig",
|
|
49
54
|
"RateLimitInfo",
|
|
50
|
-
|
|
55
|
+
|
|
51
56
|
# Utilities
|
|
52
57
|
"format_message_for_display",
|
|
53
58
|
"export_to_json",
|
|
@@ -57,8 +62,8 @@ __all__ = [
|
|
|
57
62
|
"get_message_statistics",
|
|
58
63
|
"save_profile_photos",
|
|
59
64
|
"create_metrics_report",
|
|
60
|
-
|
|
65
|
+
"build_search_queries",
|
|
66
|
+
|
|
61
67
|
# Progress
|
|
62
68
|
"ProgressTracker"
|
|
63
69
|
]
|
|
64
|
-
from tgdata.connection_engine import AuthRequiredError # noqa: E402,F401
|