funstat-api 0.1.0__tar.gz → 0.1.2__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.
@@ -0,0 +1,25 @@
1
+ name: Deploy Docs
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ permissions:
9
+ contents: write
10
+
11
+ jobs:
12
+ deploy:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.12"
20
+
21
+ - name: Install dependencies
22
+ run: pip install mkdocs-material
23
+
24
+ - name: Deploy to GitHub Pages
25
+ run: mkdocs gh-deploy --force
@@ -1,8 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: funstat-api
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Sync and async Python client for the Funstat API (Telegram user/group statistics)
5
5
  Project-URL: Homepage, https://github.com/chizumeiji/funstat-api
6
+ Project-URL: Documentation, https://chizumeiji.github.io/funstat-api/
6
7
  Project-URL: Bug Tracker, https://github.com/chizumeiji/funstat-api/issues
7
8
  Author-email: meiji <chizumeiji@gmail.com>
8
9
  License: MIT License
@@ -50,6 +51,8 @@ Python client for the [Funstat](http://funstat.in/?start=0108FC1E9BEF75617466) A
50
51
 
51
52
  Supports both **sync** and **async** usage.
52
53
 
54
+ Documentation: https://chizumeiji.github.io/funstat-api/
55
+
53
56
  ## Installation
54
57
 
55
58
  ```bash
@@ -4,6 +4,8 @@ Python client for the [Funstat](http://funstat.in/?start=0108FC1E9BEF75617466) A
4
4
 
5
5
  Supports both **sync** and **async** usage.
6
6
 
7
+ Documentation: https://chizumeiji.github.io/funstat-api/
8
+
7
9
  ## Installation
8
10
 
9
11
  ```bash
@@ -0,0 +1,82 @@
1
+ # funstat-api
2
+
3
+ Python-клиент для [Funstat](http://funstat.in/?start=0108FC1E9BEF75617466) API — статистика пользователей и групп Telegram.
4
+
5
+ Поддерживает **синхронный** и **асинхронный** режимы работы.
6
+
7
+ ## Установка
8
+
9
+ ```bash
10
+ pip install funstat-api
11
+ ```
12
+
13
+ ## Быстрый старт
14
+
15
+ ### Синхронно
16
+
17
+ ```python
18
+ from funstat_api import FunstatClient
19
+
20
+ fs = FunstatClient("your_token")
21
+
22
+ # Статистика пользователя
23
+ stats = fs.stats("durov")
24
+ print(stats.data.total_msg_count)
25
+
26
+ # Участники группы
27
+ members = fs.get_group_members("https://t.me/mychat")
28
+
29
+ # Использование как контекстный менеджер
30
+ with FunstatClient("your_token") as fs:
31
+ print(fs.ping())
32
+ ```
33
+
34
+ ### Асинхронно
35
+
36
+ ```python
37
+ import asyncio
38
+ from funstat_api import AsyncFunstatClient
39
+
40
+ async def main():
41
+ async with AsyncFunstatClient("your_token") as fs:
42
+ stats = await fs.stats("durov")
43
+ print(stats.data.total_msg_count)
44
+
45
+ asyncio.run(main())
46
+ ```
47
+
48
+ ## Доступные методы
49
+
50
+ | Метод | Описание |
51
+ |-------|----------|
52
+ | `ping()` | Проверить доступность API и задержку |
53
+ | `get_balance()` | Получить баланс токена |
54
+ | `resolve_username(username)` | Получить информацию по username |
55
+ | `basic_info_by_id(ids)` | Базовая информация по ID пользователя(ей) |
56
+ | `stats_min(user)` | Минимальная статистика пользователя |
57
+ | `stats(user)` | Полная статистика пользователя |
58
+ | `messages_count(user)` | Общее количество сообщений |
59
+ | `groups_count(user)` | Количество групп |
60
+ | `get_messages(user, ...)` | Список сообщений с пагинацией |
61
+ | `get_chats(user)` | Список чатов пользователя |
62
+ | `get_names(user)` | История имён |
63
+ | `get_usernames(user)` | История username'ов |
64
+ | `get_stickers(user)` | Использованные стикерпаки |
65
+ | `get_gifts(user)` | Подарки и их отправители |
66
+ | `common_groups(user)` | Статистика общих групп |
67
+ | `username_usage(username)` | Кто использует или использовал username |
68
+ | `get_group_info(group)` | Информация о группе/канале |
69
+ | `get_group_members(group)` | Участники группы |
70
+ | `search_text(query)` | Поиск сообщений по тексту |
71
+
72
+ Аргументы `user` и `group` принимают: числовой ID, `@username` или ссылку `https://t.me/...`.
73
+
74
+ ## Зависимости
75
+
76
+ - `pydantic >= 2.0`
77
+ - `requests >= 2.28` (синхронный клиент)
78
+ - `httpx >= 0.24` (асинхронный клиент)
79
+
80
+ ## Лицензия
81
+
82
+ MIT
@@ -0,0 +1,57 @@
1
+ # Async Client
2
+
3
+ ```python
4
+ from funstat_api import AsyncFunstatClient
5
+ ```
6
+
7
+ ## `AsyncFunstatClient(token)`
8
+
9
+ Creates an asynchronous client. Uses `httpx` under the hood. All methods must be `await`-ed.
10
+
11
+ | Parameter | Type | Description |
12
+ |-----------|------|-------------|
13
+ | `token` | `str` | Your Funstat API token |
14
+
15
+ ---
16
+
17
+ ## Methods
18
+
19
+ All methods are identical to the [Sync Client](sync.md) but are coroutines — prefix every call with `await`.
20
+ Return types and parameters are the same — refer to [Sync Client](sync.md) for the full reference and [Models](models.md) for response type details.
21
+
22
+ ```python
23
+ async with AsyncFunstatClient("your_token") as fs:
24
+ stats = await fs.stats("durov")
25
+ messages = await fs.get_messages("durov", limit=50)
26
+ members = await fs.get_group_members("https://t.me/mychat")
27
+ usage = await fs.username_usage("durov")
28
+ ```
29
+
30
+ ---
31
+
32
+ ### Parallel requests
33
+
34
+ Because the client is async, you can fire multiple requests concurrently with `asyncio.gather`:
35
+
36
+ ```python
37
+ import asyncio
38
+ from funstat_api import AsyncFunstatClient
39
+
40
+ async def main():
41
+ async with AsyncFunstatClient("your_token") as fs:
42
+ stats, chats, names = await asyncio.gather(
43
+ fs.stats("durov"), # → UserStatsResponse
44
+ fs.get_chats("durov"), # → UsrChatInfoResponse
45
+ fs.get_names("durov"), # → UserNameInfoResponse
46
+ )
47
+
48
+ asyncio.run(main())
49
+ ```
50
+
51
+ ---
52
+
53
+ ### close
54
+
55
+ `async close()`
56
+
57
+ Closes the underlying `httpx.AsyncClient`. Called automatically when using `async with`.
@@ -0,0 +1,66 @@
1
+ # Exceptions
2
+
3
+ ```python
4
+ from funstat_api import FunstatError, ResolveError, ApiError
5
+ ```
6
+
7
+ ---
8
+
9
+ ## `FunstatError`
10
+
11
+ Base exception for all funstat errors. Catch this to handle any library error:
12
+
13
+ ```python
14
+ from funstat_api import FunstatError
15
+
16
+ try:
17
+ result = fs.stats("someone")
18
+ except FunstatError as e:
19
+ print(f"Something went wrong: {e}")
20
+ ```
21
+
22
+ ---
23
+
24
+ ## `ResolveError`
25
+
26
+ Raised when a username or group cannot be resolved to a numeric ID.
27
+ See [`resolve_username`](sync.md#resolve_usernameusername--resolveduserresponse--none) and [`username_usage`](sync.md#username_usageusername--usernameusageresponse--none) for context.
28
+
29
+ ```python
30
+ from funstat_api import ResolveError
31
+
32
+ try:
33
+ result = fs.stats("nonexistent_xyz_404")
34
+ except ResolveError as e:
35
+ print(f"Not found: {e}")
36
+ ```
37
+
38
+ ---
39
+
40
+ ## `ApiError`
41
+
42
+ Raised when the API returns a non-200 HTTP status code.
43
+
44
+ | Attribute | Type | Description |
45
+ |-----------|------|-------------|
46
+ | `status_code` | `int` | HTTP status code returned by the API |
47
+ | `path` | `str` | API path that was requested |
48
+
49
+ ```python
50
+ from funstat_api import ApiError
51
+
52
+ try:
53
+ result = fs.stats("durov")
54
+ except ApiError as e:
55
+ print(f"HTTP {e.status_code} on {e.path}")
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Exception hierarchy
61
+
62
+ ```
63
+ FunstatError
64
+ ├── ResolveError # username/group not found
65
+ └── ApiError # non-200 HTTP response
66
+ ```
@@ -0,0 +1,360 @@
1
+ # Models
2
+
3
+ All API responses are typed Pydantic v2 models. Import them directly:
4
+
5
+ ```python
6
+ from funstat_api import UserStats, GroupMember, ChatInfo # etc.
7
+ ```
8
+
9
+ ---
10
+
11
+ ## Response wrappers
12
+
13
+ Every method returns a response wrapper with a consistent shape:
14
+
15
+ | Field | Type | Description |
16
+ |-------|------|-------------|
17
+ | `success` | `bool` | Whether the request succeeded |
18
+ | `tech` | [`TechInfo`](#techinfo) | Request cost and balance info |
19
+ | `data` | `... \| None` | The actual payload |
20
+ | `paging` | [`Paging`](#paging)` \| None` | Pagination info (paginated endpoints only) |
21
+
22
+ ---
23
+
24
+ ## TechInfo
25
+
26
+ ```python
27
+ class TechInfo:
28
+ request_cost: float
29
+ current_ballance: float
30
+ request_duration: str
31
+ ```
32
+
33
+ ---
34
+
35
+ ## Paging
36
+
37
+ ```python
38
+ class Paging:
39
+ total: int
40
+ current_page: int
41
+ page_size: int
42
+ total_pages: int
43
+ ```
44
+
45
+ ---
46
+
47
+ ## ResolvedUser
48
+
49
+ ```python
50
+ class ResolvedUser:
51
+ id: int
52
+ username: str | None
53
+ first_name: str | None
54
+ last_name: str | None
55
+ is_active: bool
56
+ is_bot: bool
57
+ has_premium: bool | None
58
+ ```
59
+
60
+ ---
61
+
62
+ ## UserStatsMin
63
+
64
+ ```python
65
+ class UserStatsMin:
66
+ id: int
67
+ first_name: str | None
68
+ last_name: str | None
69
+ is_bot: bool
70
+ is_active: bool
71
+ first_msg_date: str | None
72
+ last_msg_date: str | None
73
+ total_msg_count: int
74
+ msg_in_groups_count: int
75
+ adm_in_groups: int
76
+ usernames_count: int
77
+ names_count: int
78
+ total_groups: int
79
+ ```
80
+
81
+ ---
82
+
83
+ ## UserStats
84
+
85
+ Extends [`UserStatsMin`](#userstatsmin) with additional fields:
86
+
87
+ ```python
88
+ class UserStats(UserStatsMin):
89
+ is_cyrillic_primary: bool | None
90
+ lang_code: str | None
91
+ unique_percent: float | None
92
+ circle_count: int
93
+ voice_count: int
94
+ reply_percent: float
95
+ media_percent: float
96
+ link_percent: float
97
+ favorite_chat: ChatInfo | None
98
+ media_usage: str | None
99
+ stars_val: int | None
100
+ gift_count: int | None
101
+ about: str | None
102
+ # ... and more
103
+ ```
104
+
105
+ ---
106
+
107
+ ## ChatInfo
108
+
109
+ ```python
110
+ class ChatInfo:
111
+ id: int
112
+ title: str
113
+ is_private: bool
114
+ username: str | None
115
+ ```
116
+
117
+ ---
118
+
119
+ ## ChatInfoExt
120
+
121
+ Extends [`ChatInfo`](#chatinfo):
122
+
123
+ ```python
124
+ class ChatInfoExt(ChatInfo):
125
+ is_channel: bool
126
+ link: str | None
127
+ ```
128
+
129
+ ---
130
+
131
+ ## GroupMember
132
+
133
+ ```python
134
+ class GroupMember:
135
+ id: int
136
+ username: str | None
137
+ name: str | None
138
+ first_name: str | None
139
+ last_name: str | None
140
+ is_admin: bool | None
141
+ is_active: bool
142
+ today_msg: int
143
+ has_prem: bool | None
144
+ has_photo: bool
145
+ dc_id: int | None
146
+ ```
147
+
148
+ ---
149
+
150
+ ## UserMsg
151
+
152
+ ```python
153
+ class UserMsg:
154
+ date: str
155
+ message_id: int
156
+ reply_to_message_id: int | None
157
+ media_code: int | None
158
+ media_name: str | None
159
+ text: str | None
160
+ group: ChatInfo
161
+ ```
162
+
163
+ ---
164
+
165
+ ## UserNameInfo
166
+
167
+ ```python
168
+ class UserNameInfo:
169
+ name: str
170
+ date_time: str
171
+ ```
172
+
173
+ ---
174
+
175
+ ## UsrChatInfo
176
+
177
+ ```python
178
+ class UsrChatInfo:
179
+ chat: ChatInfo
180
+ last_message_id: int
181
+ messages_count: int
182
+ last_message: str | None
183
+ first_message: str | None
184
+ is_admin: bool
185
+ is_left: bool
186
+ ```
187
+
188
+ ---
189
+
190
+ ## GiftRelationInfo
191
+
192
+ ```python
193
+ class GiftRelationInfo:
194
+ last_gift_date: str | None
195
+ from_user_id: int
196
+ from_first_name: str | None
197
+ from_last_name: str | None
198
+ from_main_username: str | None
199
+ from_is_active: bool
200
+ to_user_id: int
201
+ to_first_name: str | None
202
+ to_last_name: str | None
203
+ to_main_username: str | None
204
+ to_is_active: bool
205
+ ```
206
+
207
+ ---
208
+
209
+ ## StickerInfo
210
+
211
+ ```python
212
+ class StickerInfo:
213
+ sticker_set_id: int
214
+ last_seen: str
215
+ min_seen: str
216
+ resolved: str | None
217
+ title: str | None
218
+ short_name: str | None
219
+ stickers_count: int | None
220
+ ```
221
+
222
+ ---
223
+
224
+ ## UCommonGroupInfo
225
+
226
+ ```python
227
+ class UCommonGroupInfo:
228
+ user_id: int
229
+ common_groups: int
230
+ first_name: str | None
231
+ last_name: str | None
232
+ username: str | None
233
+ is_user_active: bool
234
+ ```
235
+
236
+ ---
237
+
238
+ ## UsernameUsageModel
239
+
240
+ ```python
241
+ class UsernameUsageModel:
242
+ actual_users: list[ResolvedUser] | None
243
+ usage_by_users_in_the_past: list[ResolvedUser] | None
244
+ actual_groups_or_channels: list[ChatInfoExt] | None
245
+ mention_by_channel_or_group_desc: list[ChatInfoExt] | None
246
+ ```
247
+
248
+ ---
249
+
250
+ ## WhoWroteText
251
+
252
+ ```python
253
+ class WhoWroteText:
254
+ message_id: int
255
+ user_id: int
256
+ date: str
257
+ name: str | None
258
+ username: str | None
259
+ is_active: bool
260
+ group: ChatInfoExt
261
+ text: str | None
262
+ ```
263
+
264
+ ---
265
+
266
+ ## WhoWroteTextPaged
267
+
268
+ ```python
269
+ class WhoWroteTextPaged:
270
+ total: int
271
+ data: list[WhoWroteText]
272
+ is_last_page: bool | None
273
+ page_size: int | None
274
+ current_page: int | None
275
+ total_pages: int | None
276
+ ```
277
+
278
+ ---
279
+
280
+ ## PingResult
281
+
282
+ ```python
283
+ class PingResult:
284
+ request_ping: str # duration reported by the API
285
+ responce_ping: float # actual round-trip in seconds
286
+ ```
287
+
288
+ ---
289
+
290
+ ## Response wrappers reference
291
+
292
+ | Class | `.data` type |
293
+ |-------|-------------|
294
+ | `ResolvedUserResponse` | `list[`[`ResolvedUser`](#resolveduser)`]` |
295
+ | `UserStatsMinResponse` | [`UserStatsMin`](#userstatsmin) |
296
+ | `UserStatsResponse` | [`UserStats`](#userstats) |
297
+ | `UserMsgPagedResponse` | `list[`[`UserMsg`](#usermsg)`]` |
298
+ | `UserNameInfoResponse` | `list[`[`UserNameInfo`](#usernameinfo)`]` |
299
+ | `UsrChatInfoResponse` | `list[`[`UsrChatInfo`](#usrchatinfo)`]` |
300
+ | `GroupMemberResponse` | `list[`[`GroupMember`](#groupmember)`]` |
301
+ | `GiftRelationResponse` | `list[`[`GiftRelationInfo`](#giftrelationinfo)`]` |
302
+ | `StickerInfoResponse` | `list[`[`StickerInfo`](#stickerinfo)`]` |
303
+ | `UCommonGroupInfoResponse` | `list[`[`UCommonGroupInfo`](#ucommongroupinfo)`]` |
304
+ | `UsernameUsageResponse` | [`UsernameUsageModel`](#usernameusagemodel) |
305
+ | `ChatInfoExtResponse` | `list[`[`ChatInfoExt`](#chatinfoext)`]` |
306
+ | `WhoWroteTextResponse` | [`WhoWroteTextPaged`](#whowrotetextpaged) |
307
+
308
+ ---
309
+
310
+ ## ResolvedUserResponse
311
+
312
+ `success` · [`TechInfo`](#techinfo) · `data: list[`[`ResolvedUser`](#resolveduser)`]`
313
+
314
+ ## UserStatsMinResponse
315
+
316
+ `success` · [`TechInfo`](#techinfo) · `data:` [`UserStatsMin`](#userstatsmin)
317
+
318
+ ## UserStatsResponse
319
+
320
+ `success` · [`TechInfo`](#techinfo) · `data:` [`UserStats`](#userstats)
321
+
322
+ ## UserMsgPagedResponse
323
+
324
+ `success` · [`TechInfo`](#techinfo) · [`Paging`](#paging) · `data: list[`[`UserMsg`](#usermsg)`]`
325
+
326
+ ## UserNameInfoResponse
327
+
328
+ `success` · [`TechInfo`](#techinfo) · `data: list[`[`UserNameInfo`](#usernameinfo)`]`
329
+
330
+ ## UsrChatInfoResponse
331
+
332
+ `success` · [`TechInfo`](#techinfo) · `data: list[`[`UsrChatInfo`](#usrchatinfo)`]`
333
+
334
+ ## GroupMemberResponse
335
+
336
+ `success` · [`TechInfo`](#techinfo) · `data: list[`[`GroupMember`](#groupmember)`]`
337
+
338
+ ## GiftRelationResponse
339
+
340
+ `success` · [`TechInfo`](#techinfo) · `data: list[`[`GiftRelationInfo`](#giftrelationinfo)`]`
341
+
342
+ ## StickerInfoResponse
343
+
344
+ `success` · [`TechInfo`](#techinfo) · `data: list[`[`StickerInfo`](#stickerinfo)`]`
345
+
346
+ ## UCommonGroupInfoResponse
347
+
348
+ `success` · [`TechInfo`](#techinfo) · `data: list[`[`UCommonGroupInfo`](#ucommongroupinfo)`]`
349
+
350
+ ## UsernameUsageResponse
351
+
352
+ `success` · [`TechInfo`](#techinfo) · `data:` [`UsernameUsageModel`](#usernameusagemodel)
353
+
354
+ ## ChatInfoExtResponse
355
+
356
+ `success` · [`TechInfo`](#techinfo) · `data: list[`[`ChatInfoExt`](#chatinfoext)`]`
357
+
358
+ ## WhoWroteTextResponse
359
+
360
+ `success` · [`TechInfo`](#techinfo) · `data:` [`WhoWroteTextPaged`](#whowrotetextpaged)
@@ -0,0 +1,257 @@
1
+ # Sync Client
2
+
3
+ ```python
4
+ from funstat_api import FunstatClient
5
+ ```
6
+
7
+ ## `FunstatClient(token)`
8
+
9
+ Creates a synchronous client. Uses `requests` under the hood.
10
+
11
+ | Parameter | Type | Description |
12
+ |-----------|------|-------------|
13
+ | `token` | `str` | Your Funstat API token |
14
+
15
+ ---
16
+
17
+ ## Methods
18
+
19
+ ### ping
20
+
21
+ `ping() →` [`PingResult`](models.md#pingresult) `| None`
22
+
23
+ Check API availability and measure round-trip latency.
24
+
25
+ ```python
26
+ result = fs.ping()
27
+ print(result.responce_ping) # seconds
28
+ ```
29
+
30
+ ---
31
+
32
+ ### get_balance
33
+
34
+ `get_balance() →` [`TechInfo`](models.md#techinfo) `| None`
35
+
36
+ Get current token balance and cost info.
37
+
38
+ ```python
39
+ balance = fs.get_balance()
40
+ print(balance.current_ballance)
41
+ ```
42
+
43
+ ---
44
+
45
+ ### resolve_username
46
+
47
+ `resolve_username(username) →` [`ResolvedUserResponse`](models.md#resolveduserresponse) `| None`
48
+
49
+ Resolve a username to full user info.
50
+
51
+ ```python
52
+ result = fs.resolve_username("durov")
53
+ user = result.data[0]
54
+ print(user.id, user.first_name)
55
+ ```
56
+
57
+ ---
58
+
59
+ ### basic_info_by_id
60
+
61
+ `basic_info_by_id(ids) →` [`ResolvedUserResponse`](models.md#resolveduserresponse) `| None`
62
+
63
+ Get basic info for one or multiple users by numeric ID.
64
+
65
+ ```python
66
+ result = fs.basic_info_by_id([123, 456])
67
+ ```
68
+
69
+ ---
70
+
71
+ ### stats_min
72
+
73
+ `stats_min(user) →` [`UserStatsMinResponse`](models.md#userstatsminresponse) `| None`
74
+
75
+ Get minimal statistics for a user.
76
+
77
+ ```python
78
+ result = fs.stats_min("durov")
79
+ print(result.data.total_msg_count)
80
+ ```
81
+
82
+ ---
83
+
84
+ ### stats
85
+
86
+ `stats(user) →` [`UserStatsResponse`](models.md#userstatsresponse) `| None`
87
+
88
+ Get full statistics for a user.
89
+
90
+ ```python
91
+ result = fs.stats("durov")
92
+ print(result.data.reply_percent)
93
+ ```
94
+
95
+ ---
96
+
97
+ ### messages_count
98
+
99
+ `messages_count(user) → int`
100
+
101
+ Get total message count for a user.
102
+
103
+ ---
104
+
105
+ ### groups_count
106
+
107
+ `groups_count(user, only_msg=False) → int`
108
+
109
+ Get number of groups a user belongs to.
110
+ Set `only_msg=True` to count only groups where the user has sent messages.
111
+
112
+ ---
113
+
114
+ ### get_messages
115
+
116
+ `get_messages(user, filter=None, group=None, limit=20, page=1) →` [`UserMsgPagedResponse`](models.md#usermsgpagedresponse) `| None`
117
+
118
+ Get paginated message history for a user.
119
+
120
+ | Parameter | Type | Description |
121
+ |-----------|------|-------------|
122
+ | `user` | `int \| str` | User identifier |
123
+ | `filter` | `str \| None` | Filter by text content |
124
+ | `group` | `int \| str \| None` | Filter by group |
125
+ | `limit` | `int` | Page size (default 20) |
126
+ | `page` | `int` | Page number (default 1) |
127
+
128
+ ```python
129
+ result = fs.get_messages("durov", filter="hello", limit=50)
130
+ for msg in result.data:
131
+ print(msg.date, msg.text)
132
+ ```
133
+
134
+ ---
135
+
136
+ ### get_chats
137
+
138
+ `get_chats(user) →` [`UsrChatInfoResponse`](models.md#usrchatinforesponse) `| None`
139
+
140
+ Get list of chats the user participates in.
141
+
142
+ ---
143
+
144
+ ### get_names
145
+
146
+ `get_names(user) →` [`UserNameInfoResponse`](models.md#usernameinforesponse) `| None`
147
+
148
+ Get name change history for a user.
149
+
150
+ ---
151
+
152
+ ### get_usernames
153
+
154
+ `get_usernames(user) →` [`UserNameInfoResponse`](models.md#usernameinforesponse) `| None`
155
+
156
+ Get username change history for a user.
157
+
158
+ ---
159
+
160
+ ### get_stickers
161
+
162
+ `get_stickers(user) →` [`StickerInfoResponse`](models.md#stickerinforesponse) `| None`
163
+
164
+ Get sticker packs used by a user.
165
+
166
+ ---
167
+
168
+ ### get_gifts
169
+
170
+ `get_gifts(user, limit=20, page=1) →` [`GiftRelationResponse`](models.md#giftrelationresponse) `| None`
171
+
172
+ Get gift relations (sent/received) for a user.
173
+
174
+ ---
175
+
176
+ ### common_groups
177
+
178
+ `common_groups(user) →` [`UCommonGroupInfoResponse`](models.md#ucommongroupinforesponse) `| None`
179
+
180
+ Get statistics on common groups with other users.
181
+
182
+ ---
183
+
184
+ ### rep
185
+
186
+ `rep(user) → dict | None`
187
+
188
+ Get reputation data for a user.
189
+
190
+ ---
191
+
192
+ ### username_usage
193
+
194
+ `username_usage(username) →` [`UsernameUsageResponse`](models.md#usernameusageresponse) `| None`
195
+
196
+ Find who currently uses or has previously used a username.
197
+
198
+ ```python
199
+ result = fs.username_usage("durov")
200
+ print(result.data.actual_users)
201
+ ```
202
+
203
+ ---
204
+
205
+ ### common_groups_for_users
206
+
207
+ `common_groups_for_users(ids) →` [`ChatInfoExtResponse`](models.md#chatinfoextresponse) `| None`
208
+
209
+ Find groups that a list of users all share.
210
+
211
+ ```python
212
+ result = fs.common_groups_for_users([123, 456, 789])
213
+ ```
214
+
215
+ ---
216
+
217
+ ### get_group_info
218
+
219
+ `get_group_info(group) → dict | None`
220
+
221
+ Get raw info about a group or channel.
222
+
223
+ ---
224
+
225
+ ### get_group_members
226
+
227
+ `get_group_members(group) →` [`GroupMemberResponse`](models.md#groupmemberresponse) `| None`
228
+
229
+ Get member list of a group.
230
+
231
+ ```python
232
+ result = fs.get_group_members("https://t.me/mychat")
233
+ for member in result.data:
234
+ print(member.id, member.username)
235
+ ```
236
+
237
+ ---
238
+
239
+ ### search_text
240
+
241
+ `search_text(query, page=1, page_size=20) →` [`WhoWroteTextResponse`](models.md#whowrotetextresponse) `| None`
242
+
243
+ Search all indexed messages by text content.
244
+
245
+ ```python
246
+ result = fs.search_text("hello world")
247
+ for item in result.data.data:
248
+ print(item.user_id, item.text)
249
+ ```
250
+
251
+ ---
252
+
253
+ ### close
254
+
255
+ `close()`
256
+
257
+ Close the underlying HTTP session. Called automatically when using `with`.
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 — Initial release
4
+
5
+ - Sync client `FunstatClient` powered by `requests`
6
+ - Async client `AsyncFunstatClient` powered by `httpx`
7
+ - Full Pydantic v2 models for all API responses
8
+ - Auto-resolution of usernames, `@username`, and `t.me/` links
9
+ - Context manager support for both clients
@@ -0,0 +1,78 @@
1
+ # Getting Started
2
+
3
+ ## Requirements
4
+
5
+ - Python 3.10+
6
+ - A Funstat API token — get one at [funstat.info](http://funstat.in/?start=0108FC1E9BEF75617466)
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ pip install funstat-api
12
+ ```
13
+
14
+ ## Authentication
15
+
16
+ Pass your token when creating a client:
17
+
18
+ ```python
19
+ from funstat_api import FunstatClient
20
+
21
+ fs = FunstatClient("your_token_here")
22
+ ```
23
+
24
+ ## Checking the connection
25
+
26
+ ```python
27
+ ping = fs.ping()
28
+ print(ping.responce_ping) # round-trip time in seconds
29
+
30
+ balance = fs.get_balance()
31
+ print(balance.current_ballance)
32
+ ```
33
+
34
+ ## Identifying users
35
+
36
+ All methods that accept a `user` argument are flexible — you can pass:
37
+
38
+ ```python
39
+ fs.stats(12345678) # numeric ID
40
+ fs.stats("durov") # username without @
41
+ fs.stats("@durov") # username with @
42
+ fs.stats("https://t.me/durov") # full t.me link
43
+ ```
44
+
45
+ The same applies to `group` arguments.
46
+
47
+ ## Context managers
48
+
49
+ Both clients support context managers to ensure connections are closed properly:
50
+
51
+ === "Sync"
52
+
53
+ ```python
54
+ with FunstatClient("your_token") as fs:
55
+ print(fs.stats("durov"))
56
+ ```
57
+
58
+ === "Async"
59
+
60
+ ```python
61
+ async with AsyncFunstatClient("your_token") as fs:
62
+ print(await fs.stats("durov"))
63
+ ```
64
+
65
+ ## Error handling
66
+
67
+ ```python
68
+ from funstat_api import FunstatClient, ResolveError, ApiError
69
+
70
+ fs = FunstatClient("your_token")
71
+
72
+ try:
73
+ stats = fs.stats("nonexistent_user_xyz")
74
+ except ResolveError as e:
75
+ print(f"User not found: {e}")
76
+ except ApiError as e:
77
+ print(f"API error {e.status_code} on {e.path}")
78
+ ```
@@ -0,0 +1,61 @@
1
+ # funstat-api
2
+
3
+ **Python client for the [Funstat](http://funstat.in/?start=0108FC1E9BEF75617466) API** — Telegram user and group statistics.
4
+
5
+ Supports both **sync** and **async** modes out of the box.
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pip install funstat-api
13
+ ```
14
+
15
+ ## Quick Example
16
+
17
+ === "Sync"
18
+
19
+ ```python
20
+ from funstat_api import FunstatClient
21
+
22
+ with FunstatClient("your_token") as fs:
23
+ stats = fs.stats("durov")
24
+ print(stats.data.total_msg_count)
25
+ ```
26
+
27
+ === "Async"
28
+
29
+ ```python
30
+ import asyncio
31
+ from funstat_api import AsyncFunstatClient
32
+
33
+ async def main():
34
+ async with AsyncFunstatClient("your_token") as fs:
35
+ stats = await fs.stats("durov")
36
+ print(stats.data.total_msg_count)
37
+
38
+ asyncio.run(main())
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Features
44
+
45
+ - ✅ Sync client powered by `requests`
46
+ - ✅ Async client powered by `httpx`
47
+ - ✅ Fully typed — all responses are Pydantic models
48
+ - ✅ Accepts username, `@username`, numeric ID, or `t.me/` link
49
+ - ✅ Auto-resolves usernames to IDs internally
50
+
51
+ ---
52
+
53
+ [Get Started :material-arrow-right:](getting-started.md){ .md-button .md-button--primary }
54
+
55
+ ---
56
+
57
+ ## Links
58
+
59
+ - [PyPI](https://pypi.org/project/funstat-api/)
60
+ - [GitHub](https://github.com/chizumeiji/funstat-api)
61
+ - [Funstat](http://funstat.in/?start=0108FC1E9BEF75617466)
@@ -0,0 +1,42 @@
1
+
2
+ h2, h3 {
3
+ transition: opacity 0.25s ease;
4
+ }
5
+
6
+ :is(h3:target) ~ h3,
7
+ h3:has(~ h3:target) {
8
+ opacity: 0.45;
9
+ }
10
+
11
+ h3:target {
12
+ opacity: 1 !important;
13
+ border-left: 3px solid var(--md-accent-fg-color);
14
+ padding-left: 0.55rem;
15
+ margin-left: -0.7rem;
16
+ border-radius: 2px;
17
+ color: lavender;
18
+ transition: border-left 0.2s ease, padding-left 0.2s ease, opacity 0.25s ease;
19
+ }
20
+
21
+ h3:target ~ h3 {
22
+ opacity: 0.45;
23
+ }
24
+
25
+ :is(h2:target) ~ h2,
26
+ h2:has(~ h2:target) {
27
+ opacity: 0.45;
28
+ }
29
+
30
+ h2:target {
31
+ opacity: 1 !important;
32
+ border-left: 3px solid var(--md-accent-fg-color);
33
+ padding-left: 0.55rem;
34
+ margin-left: -0.7rem;
35
+ border-radius: 2px;
36
+ color: lavender;
37
+ transition: border-left 0.2s ease, padding-left 0.2s ease, opacity 0.25s ease;
38
+ }
39
+
40
+ h2:target ~ h2 {
41
+ opacity: 0.45;
42
+ }
@@ -295,7 +295,7 @@ class WhoWroteTextResponse(BaseModel):
295
295
  # Helpers
296
296
  # ─────────────────────────────────────────────────────────────────────────────
297
297
 
298
- BASE_URL = "https://funstat.info/api/v1"
298
+ BASE_URL = "/api/v1"
299
299
 
300
300
  def _clean_username(username: str) -> str:
301
301
  username = username.strip()
@@ -0,0 +1,63 @@
1
+ site_name: funstat-api
2
+ site_description: Python client for the Funstat API — Telegram user and group statistics
3
+ site_url: https://chizumeiji.github.io/funstat-api
4
+ repo_url: https://github.com/chizumeiji/funstat-api
5
+ repo_name: funstat-api
6
+ edit_uri: edit/main/docs/
7
+
8
+ theme:
9
+ name: material
10
+ language: en
11
+ palette:
12
+ - scheme: slate
13
+ primary: deep purple
14
+ accent: purple
15
+ toggle:
16
+ icon: material/brightness-4
17
+ name: Switch to light mode
18
+ - scheme: default
19
+ primary: deep purple
20
+ accent: purple
21
+ toggle:
22
+ icon: material/brightness-7
23
+ name: Switch to dark mode
24
+ features:
25
+ - navigation.tabs
26
+ - navigation.sections
27
+ - navigation.sidebar
28
+ - navigation.top
29
+ - navigation.indexes
30
+ - toc.integrate
31
+ - search.highlight
32
+ - content.code.copy
33
+
34
+ markdown_extensions:
35
+ - pymdownx.emoji:
36
+ emoji_index: !!python/name:material.extensions.emoji.twemoji
37
+ emoji_generator: !!python/name:material.extensions.emoji.to_svg
38
+ - pymdownx.highlight:
39
+ anchor_linenums: true
40
+ - pymdownx.superfences
41
+ - pymdownx.tabbed:
42
+ alternate_style: true
43
+ - admonition
44
+ - pymdownx.details
45
+ - attr_list
46
+ - md_in_html
47
+ - tables
48
+
49
+ nav:
50
+ - Home: index.md
51
+ - Getting Started: getting-started.md
52
+ - API Reference:
53
+ - Sync Client: api/sync.md
54
+ - Async Client: api/async.md
55
+ - Models: api/models.md
56
+ - Exceptions: api/exceptions.md
57
+ - Changelog: changelog.md
58
+
59
+ extra_css:
60
+ - stylesheets/extra.css
61
+
62
+ plugins:
63
+ - search
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "funstat-api"
7
- version = "0.1.0"
7
+ version = "0.1.2"
8
8
  description = "Sync and async Python client for the Funstat API (Telegram user/group statistics)"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -33,4 +33,5 @@ dependencies = [
33
33
 
34
34
  [project.urls]
35
35
  Homepage = "https://github.com/chizumeiji/funstat-api"
36
+ Documentation = "https://chizumeiji.github.io/funstat-api/"
36
37
  "Bug Tracker" = "https://github.com/chizumeiji/funstat-api/issues"
@@ -1,82 +0,0 @@
1
- # funstat-api
2
-
3
- Python client for the [Funstat](http://funstat.in/?start=0108FC1E9BEF75617466) API — Telegram user and group statistics.
4
-
5
- Supports both **sync** and **async** usage.
6
-
7
- ## Installation
8
-
9
- ```bash
10
- pip install funstat-api
11
- ```
12
-
13
- ## Quick Start
14
-
15
- ### Sync
16
-
17
- ```python
18
- from funstat_api import FunstatClient
19
-
20
- fs = FunstatClient("your_token")
21
-
22
- # Get user stats
23
- stats = fs.stats("durov")
24
- print(stats.data.total_msg_count)
25
-
26
- # Get group members
27
- members = fs.get_group_members("https://t.me/mychat")
28
-
29
- # Use as context manager
30
- with FunstatClient("your_token") as fs:
31
- print(fs.ping())
32
- ```
33
-
34
- ### Async
35
-
36
- ```python
37
- import asyncio
38
- from funstat_api import AsyncFunstatClient
39
-
40
- async def main():
41
- async with AsyncFunstatClient("your_token") as fs:
42
- stats = await fs.stats("durov")
43
- print(stats.data.total_msg_count)
44
-
45
- asyncio.run(main())
46
- ```
47
-
48
- ## Available Methods
49
-
50
- | Method | Description |
51
- |--------|-------------|
52
- | `ping()` | Check API availability and latency |
53
- | `get_balance()` | Get current token balance |
54
- | `resolve_username(username)` | Resolve username to user info |
55
- | `basic_info_by_id(ids)` | Get basic info by user ID(s) |
56
- | `stats_min(user)` | Get minimal user statistics |
57
- | `stats(user)` | Get full user statistics |
58
- | `messages_count(user)` | Get total message count |
59
- | `groups_count(user)` | Get number of groups |
60
- | `get_messages(user, ...)` | Get paginated message list |
61
- | `get_chats(user)` | Get user's chat list |
62
- | `get_names(user)` | Get name history |
63
- | `get_usernames(user)` | Get username history |
64
- | `get_stickers(user)` | Get used sticker packs |
65
- | `get_gifts(user)` | Get gift relations |
66
- | `common_groups(user)` | Get common groups stats |
67
- | `username_usage(username)` | Who uses or used a username |
68
- | `get_group_info(group)` | Get group/channel info |
69
- | `get_group_members(group)` | Get group members |
70
- | `search_text(query)` | Search messages by text |
71
-
72
- `user` and `group` arguments accept: numeric ID, `@username`, or `https://t.me/...` link.
73
-
74
- ## Dependencies
75
-
76
- - `pydantic >= 2.0`
77
- - `requests >= 2.28` (sync client)
78
- - `httpx >= 0.24` (async client)
79
-
80
- ## License
81
-
82
- MIT
File without changes