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.
- funstat_api-0.1.2/.github/workflows/docs.yml +25 -0
- {funstat_api-0.1.0 → funstat_api-0.1.2}/PKG-INFO +4 -1
- {funstat_api-0.1.0 → funstat_api-0.1.2}/README.md +2 -0
- funstat_api-0.1.2/README_RU.md +82 -0
- funstat_api-0.1.2/docs/api/async.md +57 -0
- funstat_api-0.1.2/docs/api/exceptions.md +66 -0
- funstat_api-0.1.2/docs/api/models.md +360 -0
- funstat_api-0.1.2/docs/api/sync.md +257 -0
- funstat_api-0.1.2/docs/changelog.md +9 -0
- funstat_api-0.1.2/docs/getting-started.md +78 -0
- funstat_api-0.1.2/docs/index.md +61 -0
- funstat_api-0.1.2/docs/stylesheets/extra.css +42 -0
- {funstat_api-0.1.0 → funstat_api-0.1.2}/funstat_api/funstat.py +1 -1
- funstat_api-0.1.2/mkdocs.yml +63 -0
- {funstat_api-0.1.0 → funstat_api-0.1.2}/pyproject.toml +2 -1
- funstat_api-0.1.0/readme.md +0 -82
- {funstat_api-0.1.0 → funstat_api-0.1.2}/LICENSE +0 -0
- {funstat_api-0.1.0 → funstat_api-0.1.2}/funstat_api/__init__.py +0 -0
|
@@ -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.
|
|
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
|
|
@@ -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 = "
|
|
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.
|
|
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"
|
funstat_api-0.1.0/readme.md
DELETED
|
@@ -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
|
|
File without changes
|