brizahub 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- brizahub-0.1.0/LICENSE +21 -0
- brizahub-0.1.0/PKG-INFO +236 -0
- brizahub-0.1.0/README.md +204 -0
- brizahub-0.1.0/brizahub/__init__.py +18 -0
- brizahub-0.1.0/brizahub/_version.py +1 -0
- brizahub-0.1.0/brizahub/aiogram.py +69 -0
- brizahub-0.1.0/brizahub/client.py +390 -0
- brizahub-0.1.0/brizahub/design.py +57 -0
- brizahub-0.1.0/brizahub/models.py +34 -0
- brizahub-0.1.0/brizahub/py.typed +0 -0
- brizahub-0.1.0/brizahub/webhook.py +34 -0
- brizahub-0.1.0/brizahub.egg-info/PKG-INFO +236 -0
- brizahub-0.1.0/brizahub.egg-info/SOURCES.txt +16 -0
- brizahub-0.1.0/brizahub.egg-info/dependency_links.txt +1 -0
- brizahub-0.1.0/brizahub.egg-info/requires.txt +4 -0
- brizahub-0.1.0/brizahub.egg-info/top_level.txt +1 -0
- brizahub-0.1.0/pyproject.toml +49 -0
- brizahub-0.1.0/setup.cfg +4 -0
brizahub-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Briza Hub
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
brizahub-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: brizahub
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Briza Hub API for Telegram bots: earn from your force-sub block. Turnkey check, your own sponsors block, aiogram middleware, webhooks.
|
|
5
|
+
Author: Briza Hub
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://brizahub.com/
|
|
8
|
+
Project-URL: Documentation, https://brizahub.com/docs/
|
|
9
|
+
Project-URL: Changelog, https://brizahub.com/docs/#changelog
|
|
10
|
+
Project-URL: Support, https://t.me/BrizaSupport
|
|
11
|
+
Keywords: telegram,telegram-bot,aiogram,force-sub,mandatory-subscription,sponsors,monetization,usdt
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Framework :: AsyncIO
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Communications :: Chat
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: aiohttp>=3.8
|
|
29
|
+
Provides-Extra: aiogram
|
|
30
|
+
Requires-Dist: aiogram>=3.0; extra == "aiogram"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
<p align="center">
|
|
34
|
+
<a href="https://brizahub.com/"><img src="https://brizahub.com/sdk/python-banner.png?v=1" width="800"
|
|
35
|
+
alt="brizahub — Python SDK of Briza Hub: force-sub that pays"></a>
|
|
36
|
+
</p>
|
|
37
|
+
|
|
38
|
+
<p align="center">
|
|
39
|
+
<a href="https://pypi.org/project/brizahub/"><img src="https://img.shields.io/pypi/v/brizahub?color=1f8bf0&label=pypi" alt="PyPI"></a>
|
|
40
|
+
<a href="https://pypi.org/project/brizahub/"><img src="https://img.shields.io/pypi/pyversions/brizahub?color=22d3ee" alt="Python versions"></a>
|
|
41
|
+
<a href="https://brizahub.com/docs/"><img src="https://img.shields.io/badge/docs-brizahub.com-1d3fd6" alt="Docs"></a>
|
|
42
|
+
<a href="https://t.me/BrizaSupport"><img src="https://img.shields.io/badge/support-%40BrizaSupport-26a5e4?logo=telegram&logoColor=white" alt="Support"></a>
|
|
43
|
+
<img src="https://img.shields.io/badge/license-MIT-3ddc84" alt="MIT License">
|
|
44
|
+
</p>
|
|
45
|
+
|
|
46
|
+
# brizahub
|
|
47
|
+
|
|
48
|
+
Earn from your Telegram bot's force-sub (mandatory subscription) block. [Briza Hub](https://brizahub.com/) fills it
|
|
49
|
+
with paying sponsors — channels and bots from advertisers all over the world — and pays you in USDT for every
|
|
50
|
+
subscriber who stays.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install brizahub # aiohttp only
|
|
54
|
+
pip install "brizahub[aiogram]" # with the aiogram 3 middleware
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Key:** [@BrizaHubBot](https://t.me/BrizaHubBot) → Sell traffic → Connect a bot.
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from brizahub import Briza
|
|
61
|
+
|
|
62
|
+
briza = Briza("your_key_here")
|
|
63
|
+
|
|
64
|
+
if not await briza.check(message.from_user): # Briza has shown the sponsors as your bot
|
|
65
|
+
return
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Three methods
|
|
69
|
+
|
|
70
|
+
| Method | What it does | Returns |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| `check(user)` | Briza checks the user and, if needed, shows the force-sub block **as your bot** | `True` — let the user in, `False` — the block is shown, stop |
|
|
73
|
+
| `get_sponsors(user)` | sponsors for **your own** block | `list[Sponsor]`; `[]` — let the user in |
|
|
74
|
+
| `check_sponsors(user)` | after «Check» under your block: who is not joined yet | `list[Sponsor]`; `[]` — let the user in |
|
|
75
|
+
|
|
76
|
+
None of them ever raises or makes the user wait because of Briza: no key, an outage or a timeout — the user is let in.
|
|
77
|
+
|
|
78
|
+
## Turnkey: `check`
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from brizahub import Briza
|
|
82
|
+
|
|
83
|
+
briza = Briza("your_key_here") # or the BRIZA_KEY environment variable
|
|
84
|
+
|
|
85
|
+
async def on_message(message):
|
|
86
|
+
if not await briza.check(message.from_user): # False — Briza has shown the block as your bot
|
|
87
|
+
return
|
|
88
|
+
...
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`check` also takes just the id — `await briza.check(user_id)` — or the message / button press itself. With the id
|
|
92
|
+
alone we don't know the user's language and Premium: Briza guesses the language, and Premium-only orders (they pay
|
|
93
|
+
more) come less often — so pass `message.from_user` when you have it.
|
|
94
|
+
|
|
95
|
+
Needs your bot token in @BrizaHubBot: with it we only send, update and delete our block message and read public
|
|
96
|
+
profiles — never `getUpdates` or `setWebhook`, your updates stay yours. The block updates live: a joined sponsor
|
|
97
|
+
disappears from it, and when none are left the block is deleted.
|
|
98
|
+
|
|
99
|
+
### Your design
|
|
100
|
+
|
|
101
|
+
Write the block text in Telegram — bold, links, quotes, premium emoji — send it to your bot once and keep it:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from brizahub import message_from
|
|
105
|
+
|
|
106
|
+
design = message_from(msg, button_join="Join", button_icon="5368324170671202286", button_style="success")
|
|
107
|
+
# a plain dict: store it as JSON, then
|
|
108
|
+
await briza.check(message.from_user, message=design)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Or by hand: `message={"text": '<tg-emoji emoji-id="5368324170671202286">⭐</tg-emoji> <b>$name</b>, join our
|
|
112
|
+
sponsors', "rows": 2}`.
|
|
113
|
+
|
|
114
|
+
| Key | |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `text` | up to 4096: HTML by default, `parse_mode="MarkdownV2"`, or `entities` (what `message_from` gives); `$name` — the user's first name |
|
|
117
|
+
| `rows` | sponsor buttons per row, 1–3 |
|
|
118
|
+
| `button_join`, or per type `button_channel` / `button_bot` / `button_url` | button labels, up to 64 |
|
|
119
|
+
| `button_icon`, or per type `button_channel_icon` / `button_bot_icon` / `button_url_icon` | premium-emoji icon on the buttons (custom emoji id) |
|
|
120
|
+
| `button_style` | `primary`, `success` or `danger` |
|
|
121
|
+
|
|
122
|
+
Premium emoji in the text and on the buttons show if your bot's owner has Telegram Premium (Telegram's rule). If
|
|
123
|
+
Telegram refuses the block, Briza sends it with regular emoji, then as plain text — users never see raw tags.
|
|
124
|
+
|
|
125
|
+
**aiogram 3 — every handler at once** (optional, instead of `check` in each handler):
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from brizahub.aiogram import BrizaMiddleware
|
|
129
|
+
|
|
130
|
+
dp.message.middleware(BrizaMiddleware(briza))
|
|
131
|
+
dp.callback_query.middleware(BrizaMiddleware(briza))
|
|
132
|
+
dp.shutdown.register(briza.close)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Groups are skipped (`private_only=False` — check them too, the block goes to the group); let admins through with
|
|
136
|
+
`skip=lambda event, data: data["event_from_user"].id in ADMINS`; your design — `BrizaMiddleware(briza, message=design)`.
|
|
137
|
+
|
|
138
|
+
## Your own block: `get_sponsors` + `check_sponsors`
|
|
139
|
+
|
|
140
|
+
For bots without a token, a custom design, or a block that mixes sponsors from several sources.
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from aiogram.types import InlineKeyboardButton as Button, InlineKeyboardMarkup as Markup
|
|
144
|
+
|
|
145
|
+
def block(sponsors):
|
|
146
|
+
rows = [[Button(text=s.title or "Subscribe", url=s.link)] for s in sponsors]
|
|
147
|
+
rows.append([Button(text="✅ Check", callback_data="briza_check")])
|
|
148
|
+
return Markup(inline_keyboard=rows)
|
|
149
|
+
|
|
150
|
+
async def on_message(message):
|
|
151
|
+
sponsors = await briza.get_sponsors(message.from_user, limit=3)
|
|
152
|
+
if sponsors:
|
|
153
|
+
await message.answer("Join our sponsors to use the bot:", reply_markup=block(sponsors))
|
|
154
|
+
return
|
|
155
|
+
... # let the user in
|
|
156
|
+
|
|
157
|
+
async def on_check(call): # callback_data == "briza_check"
|
|
158
|
+
left = await briza.check_sponsors(call.from_user)
|
|
159
|
+
if left:
|
|
160
|
+
await call.message.edit_reply_markup(reply_markup=block(left))
|
|
161
|
+
await call.answer("Join all sponsors first", show_alert=True)
|
|
162
|
+
return
|
|
163
|
+
await call.message.delete() # done — let the user in
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`check_sponsors` takes the user's current sponsors from Briza itself, so you store nothing; pass the list you showed
|
|
167
|
+
(`check_sponsors(user, sponsors)`) to save a call. Subscriptions through our links are counted even without
|
|
168
|
+
«Check» — the next `get_sponsors` returns only what is left.
|
|
169
|
+
|
|
170
|
+
## What you pass
|
|
171
|
+
|
|
172
|
+
Pass the Telegram user object (aiogram, python-telegram-bot, Pyrogram), the message or button press it came with,
|
|
173
|
+
or just the id — the package takes what it needs. Values passed by hand win over the object. Something without a
|
|
174
|
+
user id (a mistake) lets the user in and is reported by the `brizahub` logger.
|
|
175
|
+
|
|
176
|
+
| From the user object | By hand | Why |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| `user.id` | `user_id=` | required |
|
|
179
|
+
| `user.language_code` | `language_code=` | block language, language-targeted orders |
|
|
180
|
+
| `user.is_premium` | `is_premium=` | Premium-only orders pay more |
|
|
181
|
+
| `user.first_name` | `first_name=` | we compare it with Telegram after the subscription |
|
|
182
|
+
| — | `has_photo=` | orders for users with a profile photo |
|
|
183
|
+
|
|
184
|
+
Options: `limit` — sponsors at a time, 1–10 (default — your bot setting in @BrizaHubBot); `min_price` — only
|
|
185
|
+
sponsors paying you at least this, USDT; `smart_links` — `True` recommended: visit tasks and device-targeted orders
|
|
186
|
+
pay more. `check` only: `chat_id` — where to show the block (default: the private chat), `message` — your design
|
|
187
|
+
(a dict or a Telegram message, see above).
|
|
188
|
+
|
|
189
|
+
## What you get: `Sponsor`
|
|
190
|
+
|
|
191
|
+
| Field | |
|
|
192
|
+
|---|---|
|
|
193
|
+
| `id` | the same as `task_id` in webhooks |
|
|
194
|
+
| `type` | `channel`, `group`, `bot` or `link` (a visit) |
|
|
195
|
+
| `title` | text for the button |
|
|
196
|
+
| `link` | URL for the button |
|
|
197
|
+
| `price` | what you earn when the user does it, USDT (`Decimal`) |
|
|
198
|
+
|
|
199
|
+
## Webhooks
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
from brizahub import verify_webhook
|
|
203
|
+
|
|
204
|
+
async def briza_hook(request): # aiohttp
|
|
205
|
+
body = await request.read()
|
|
206
|
+
if not verify_webhook(WEBHOOK_SECRET, request.headers, body):
|
|
207
|
+
return web.Response(status=401)
|
|
208
|
+
event = json.loads(body) # event_id, event (task.pending | task.completed | task.left | ping), task_id, user_id
|
|
209
|
+
... # the same event_id may come again — process it once
|
|
210
|
+
return web.Response(text="ok") # any 2xx within 5 s
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Your numbers
|
|
214
|
+
|
|
215
|
+
`await briza.me()` — your bot and today's numbers; `await briza.stats(days=7)` — per day; `await briza.balance()` —
|
|
216
|
+
earnings and payouts (USDT as decimal strings). These raise `BrizaError` (`.status`, `.error`).
|
|
217
|
+
|
|
218
|
+
## When Briza is down
|
|
219
|
+
|
|
220
|
+
- every call fits into `timeout` (3 s); a connection that does not open within 1 s is opened again at once;
|
|
221
|
+
- no answer or an error: the user is let in, then no calls for 5 s; key problems (wrong key, paused bot, no bot
|
|
222
|
+
token) go to the `brizahub` logger once a minute;
|
|
223
|
+
- a user who passed `check()` is let in for 60 s without a call; one kept-alive connection for all calls.
|
|
224
|
+
|
|
225
|
+
| `Briza(...)` | Default | |
|
|
226
|
+
|---|---|---|
|
|
227
|
+
| `key` | `BRIZA_KEY` | your API key |
|
|
228
|
+
| `base_url` | `BRIZA_API_URL` or `https://api.brizahub.com` | change only if we announce a new address |
|
|
229
|
+
| `timeout` | `3.0` | seconds for a call |
|
|
230
|
+
| `cache_ttl` | `60.0` | seconds a passed user is let in without a call; `0` — always ask |
|
|
231
|
+
| `fail_pause` | `5.0` | seconds without calls after Briza did not answer |
|
|
232
|
+
| `smart_links` | API default | default for every call |
|
|
233
|
+
| `session` | own | your `aiohttp.ClientSession` |
|
|
234
|
+
|
|
235
|
+
[Docs](https://brizahub.com/docs/) (English, Russian, Persian, Arabic) · [@BrizaHubBot](https://t.me/BrizaHubBot) ·
|
|
236
|
+
[@BrizaHub](https://t.me/BrizaHub) · support [@BrizaSupport](https://t.me/BrizaSupport) · MIT License
|
brizahub-0.1.0/README.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://brizahub.com/"><img src="https://brizahub.com/sdk/python-banner.png?v=1" width="800"
|
|
3
|
+
alt="brizahub — Python SDK of Briza Hub: force-sub that pays"></a>
|
|
4
|
+
</p>
|
|
5
|
+
|
|
6
|
+
<p align="center">
|
|
7
|
+
<a href="https://pypi.org/project/brizahub/"><img src="https://img.shields.io/pypi/v/brizahub?color=1f8bf0&label=pypi" alt="PyPI"></a>
|
|
8
|
+
<a href="https://pypi.org/project/brizahub/"><img src="https://img.shields.io/pypi/pyversions/brizahub?color=22d3ee" alt="Python versions"></a>
|
|
9
|
+
<a href="https://brizahub.com/docs/"><img src="https://img.shields.io/badge/docs-brizahub.com-1d3fd6" alt="Docs"></a>
|
|
10
|
+
<a href="https://t.me/BrizaSupport"><img src="https://img.shields.io/badge/support-%40BrizaSupport-26a5e4?logo=telegram&logoColor=white" alt="Support"></a>
|
|
11
|
+
<img src="https://img.shields.io/badge/license-MIT-3ddc84" alt="MIT License">
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
# brizahub
|
|
15
|
+
|
|
16
|
+
Earn from your Telegram bot's force-sub (mandatory subscription) block. [Briza Hub](https://brizahub.com/) fills it
|
|
17
|
+
with paying sponsors — channels and bots from advertisers all over the world — and pays you in USDT for every
|
|
18
|
+
subscriber who stays.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install brizahub # aiohttp only
|
|
22
|
+
pip install "brizahub[aiogram]" # with the aiogram 3 middleware
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Key:** [@BrizaHubBot](https://t.me/BrizaHubBot) → Sell traffic → Connect a bot.
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from brizahub import Briza
|
|
29
|
+
|
|
30
|
+
briza = Briza("your_key_here")
|
|
31
|
+
|
|
32
|
+
if not await briza.check(message.from_user): # Briza has shown the sponsors as your bot
|
|
33
|
+
return
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Three methods
|
|
37
|
+
|
|
38
|
+
| Method | What it does | Returns |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `check(user)` | Briza checks the user and, if needed, shows the force-sub block **as your bot** | `True` — let the user in, `False` — the block is shown, stop |
|
|
41
|
+
| `get_sponsors(user)` | sponsors for **your own** block | `list[Sponsor]`; `[]` — let the user in |
|
|
42
|
+
| `check_sponsors(user)` | after «Check» under your block: who is not joined yet | `list[Sponsor]`; `[]` — let the user in |
|
|
43
|
+
|
|
44
|
+
None of them ever raises or makes the user wait because of Briza: no key, an outage or a timeout — the user is let in.
|
|
45
|
+
|
|
46
|
+
## Turnkey: `check`
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from brizahub import Briza
|
|
50
|
+
|
|
51
|
+
briza = Briza("your_key_here") # or the BRIZA_KEY environment variable
|
|
52
|
+
|
|
53
|
+
async def on_message(message):
|
|
54
|
+
if not await briza.check(message.from_user): # False — Briza has shown the block as your bot
|
|
55
|
+
return
|
|
56
|
+
...
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`check` also takes just the id — `await briza.check(user_id)` — or the message / button press itself. With the id
|
|
60
|
+
alone we don't know the user's language and Premium: Briza guesses the language, and Premium-only orders (they pay
|
|
61
|
+
more) come less often — so pass `message.from_user` when you have it.
|
|
62
|
+
|
|
63
|
+
Needs your bot token in @BrizaHubBot: with it we only send, update and delete our block message and read public
|
|
64
|
+
profiles — never `getUpdates` or `setWebhook`, your updates stay yours. The block updates live: a joined sponsor
|
|
65
|
+
disappears from it, and when none are left the block is deleted.
|
|
66
|
+
|
|
67
|
+
### Your design
|
|
68
|
+
|
|
69
|
+
Write the block text in Telegram — bold, links, quotes, premium emoji — send it to your bot once and keep it:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from brizahub import message_from
|
|
73
|
+
|
|
74
|
+
design = message_from(msg, button_join="Join", button_icon="5368324170671202286", button_style="success")
|
|
75
|
+
# a plain dict: store it as JSON, then
|
|
76
|
+
await briza.check(message.from_user, message=design)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Or by hand: `message={"text": '<tg-emoji emoji-id="5368324170671202286">⭐</tg-emoji> <b>$name</b>, join our
|
|
80
|
+
sponsors', "rows": 2}`.
|
|
81
|
+
|
|
82
|
+
| Key | |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `text` | up to 4096: HTML by default, `parse_mode="MarkdownV2"`, or `entities` (what `message_from` gives); `$name` — the user's first name |
|
|
85
|
+
| `rows` | sponsor buttons per row, 1–3 |
|
|
86
|
+
| `button_join`, or per type `button_channel` / `button_bot` / `button_url` | button labels, up to 64 |
|
|
87
|
+
| `button_icon`, or per type `button_channel_icon` / `button_bot_icon` / `button_url_icon` | premium-emoji icon on the buttons (custom emoji id) |
|
|
88
|
+
| `button_style` | `primary`, `success` or `danger` |
|
|
89
|
+
|
|
90
|
+
Premium emoji in the text and on the buttons show if your bot's owner has Telegram Premium (Telegram's rule). If
|
|
91
|
+
Telegram refuses the block, Briza sends it with regular emoji, then as plain text — users never see raw tags.
|
|
92
|
+
|
|
93
|
+
**aiogram 3 — every handler at once** (optional, instead of `check` in each handler):
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from brizahub.aiogram import BrizaMiddleware
|
|
97
|
+
|
|
98
|
+
dp.message.middleware(BrizaMiddleware(briza))
|
|
99
|
+
dp.callback_query.middleware(BrizaMiddleware(briza))
|
|
100
|
+
dp.shutdown.register(briza.close)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Groups are skipped (`private_only=False` — check them too, the block goes to the group); let admins through with
|
|
104
|
+
`skip=lambda event, data: data["event_from_user"].id in ADMINS`; your design — `BrizaMiddleware(briza, message=design)`.
|
|
105
|
+
|
|
106
|
+
## Your own block: `get_sponsors` + `check_sponsors`
|
|
107
|
+
|
|
108
|
+
For bots without a token, a custom design, or a block that mixes sponsors from several sources.
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from aiogram.types import InlineKeyboardButton as Button, InlineKeyboardMarkup as Markup
|
|
112
|
+
|
|
113
|
+
def block(sponsors):
|
|
114
|
+
rows = [[Button(text=s.title or "Subscribe", url=s.link)] for s in sponsors]
|
|
115
|
+
rows.append([Button(text="✅ Check", callback_data="briza_check")])
|
|
116
|
+
return Markup(inline_keyboard=rows)
|
|
117
|
+
|
|
118
|
+
async def on_message(message):
|
|
119
|
+
sponsors = await briza.get_sponsors(message.from_user, limit=3)
|
|
120
|
+
if sponsors:
|
|
121
|
+
await message.answer("Join our sponsors to use the bot:", reply_markup=block(sponsors))
|
|
122
|
+
return
|
|
123
|
+
... # let the user in
|
|
124
|
+
|
|
125
|
+
async def on_check(call): # callback_data == "briza_check"
|
|
126
|
+
left = await briza.check_sponsors(call.from_user)
|
|
127
|
+
if left:
|
|
128
|
+
await call.message.edit_reply_markup(reply_markup=block(left))
|
|
129
|
+
await call.answer("Join all sponsors first", show_alert=True)
|
|
130
|
+
return
|
|
131
|
+
await call.message.delete() # done — let the user in
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`check_sponsors` takes the user's current sponsors from Briza itself, so you store nothing; pass the list you showed
|
|
135
|
+
(`check_sponsors(user, sponsors)`) to save a call. Subscriptions through our links are counted even without
|
|
136
|
+
«Check» — the next `get_sponsors` returns only what is left.
|
|
137
|
+
|
|
138
|
+
## What you pass
|
|
139
|
+
|
|
140
|
+
Pass the Telegram user object (aiogram, python-telegram-bot, Pyrogram), the message or button press it came with,
|
|
141
|
+
or just the id — the package takes what it needs. Values passed by hand win over the object. Something without a
|
|
142
|
+
user id (a mistake) lets the user in and is reported by the `brizahub` logger.
|
|
143
|
+
|
|
144
|
+
| From the user object | By hand | Why |
|
|
145
|
+
|---|---|---|
|
|
146
|
+
| `user.id` | `user_id=` | required |
|
|
147
|
+
| `user.language_code` | `language_code=` | block language, language-targeted orders |
|
|
148
|
+
| `user.is_premium` | `is_premium=` | Premium-only orders pay more |
|
|
149
|
+
| `user.first_name` | `first_name=` | we compare it with Telegram after the subscription |
|
|
150
|
+
| — | `has_photo=` | orders for users with a profile photo |
|
|
151
|
+
|
|
152
|
+
Options: `limit` — sponsors at a time, 1–10 (default — your bot setting in @BrizaHubBot); `min_price` — only
|
|
153
|
+
sponsors paying you at least this, USDT; `smart_links` — `True` recommended: visit tasks and device-targeted orders
|
|
154
|
+
pay more. `check` only: `chat_id` — where to show the block (default: the private chat), `message` — your design
|
|
155
|
+
(a dict or a Telegram message, see above).
|
|
156
|
+
|
|
157
|
+
## What you get: `Sponsor`
|
|
158
|
+
|
|
159
|
+
| Field | |
|
|
160
|
+
|---|---|
|
|
161
|
+
| `id` | the same as `task_id` in webhooks |
|
|
162
|
+
| `type` | `channel`, `group`, `bot` or `link` (a visit) |
|
|
163
|
+
| `title` | text for the button |
|
|
164
|
+
| `link` | URL for the button |
|
|
165
|
+
| `price` | what you earn when the user does it, USDT (`Decimal`) |
|
|
166
|
+
|
|
167
|
+
## Webhooks
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from brizahub import verify_webhook
|
|
171
|
+
|
|
172
|
+
async def briza_hook(request): # aiohttp
|
|
173
|
+
body = await request.read()
|
|
174
|
+
if not verify_webhook(WEBHOOK_SECRET, request.headers, body):
|
|
175
|
+
return web.Response(status=401)
|
|
176
|
+
event = json.loads(body) # event_id, event (task.pending | task.completed | task.left | ping), task_id, user_id
|
|
177
|
+
... # the same event_id may come again — process it once
|
|
178
|
+
return web.Response(text="ok") # any 2xx within 5 s
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Your numbers
|
|
182
|
+
|
|
183
|
+
`await briza.me()` — your bot and today's numbers; `await briza.stats(days=7)` — per day; `await briza.balance()` —
|
|
184
|
+
earnings and payouts (USDT as decimal strings). These raise `BrizaError` (`.status`, `.error`).
|
|
185
|
+
|
|
186
|
+
## When Briza is down
|
|
187
|
+
|
|
188
|
+
- every call fits into `timeout` (3 s); a connection that does not open within 1 s is opened again at once;
|
|
189
|
+
- no answer or an error: the user is let in, then no calls for 5 s; key problems (wrong key, paused bot, no bot
|
|
190
|
+
token) go to the `brizahub` logger once a minute;
|
|
191
|
+
- a user who passed `check()` is let in for 60 s without a call; one kept-alive connection for all calls.
|
|
192
|
+
|
|
193
|
+
| `Briza(...)` | Default | |
|
|
194
|
+
|---|---|---|
|
|
195
|
+
| `key` | `BRIZA_KEY` | your API key |
|
|
196
|
+
| `base_url` | `BRIZA_API_URL` or `https://api.brizahub.com` | change only if we announce a new address |
|
|
197
|
+
| `timeout` | `3.0` | seconds for a call |
|
|
198
|
+
| `cache_ttl` | `60.0` | seconds a passed user is let in without a call; `0` — always ask |
|
|
199
|
+
| `fail_pause` | `5.0` | seconds without calls after Briza did not answer |
|
|
200
|
+
| `smart_links` | API default | default for every call |
|
|
201
|
+
| `session` | own | your `aiohttp.ClientSession` |
|
|
202
|
+
|
|
203
|
+
[Docs](https://brizahub.com/docs/) (English, Russian, Persian, Arabic) · [@BrizaHubBot](https://t.me/BrizaHubBot) ·
|
|
204
|
+
[@BrizaHub](https://t.me/BrizaHub) · support [@BrizaSupport](https://t.me/BrizaSupport) · MIT License
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Briza Hub API for Telegram bots: earn from your force-sub block — https://brizahub.com/docs/.
|
|
2
|
+
|
|
3
|
+
from brizahub import Briza
|
|
4
|
+
|
|
5
|
+
briza = Briza("your_key_here") # @BrizaHubBot → Sell traffic → Connect a bot
|
|
6
|
+
if not await briza.check(message.from_user): # False — Briza has shown the force-sub block as your bot
|
|
7
|
+
return
|
|
8
|
+
|
|
9
|
+
Your own block: ``get_sponsors`` + ``check_sponsors``. aiogram 3, every handler at once:
|
|
10
|
+
``brizahub.aiogram.BrizaMiddleware``.
|
|
11
|
+
"""
|
|
12
|
+
from ._version import __version__
|
|
13
|
+
from .client import Briza, BrizaError
|
|
14
|
+
from .design import message_from
|
|
15
|
+
from .models import Sponsor
|
|
16
|
+
from .webhook import verify_webhook
|
|
17
|
+
|
|
18
|
+
__all__ = ["Briza", "BrizaError", "Sponsor", "message_from", "verify_webhook", "__version__"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""aiogram 3: the force-sub for every handler in one place.
|
|
2
|
+
|
|
3
|
+
from brizahub import Briza
|
|
4
|
+
from brizahub.aiogram import BrizaMiddleware
|
|
5
|
+
|
|
6
|
+
briza = Briza("your_key_here")
|
|
7
|
+
dp.message.middleware(BrizaMiddleware(briza))
|
|
8
|
+
dp.callback_query.middleware(BrizaMiddleware(briza))
|
|
9
|
+
dp.shutdown.register(briza.close)
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import inspect
|
|
14
|
+
from typing import Any, Awaitable, Callable, Dict, Optional
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from aiogram import BaseMiddleware
|
|
18
|
+
from aiogram.types import CallbackQuery, TelegramObject
|
|
19
|
+
except ImportError as e: # pragma: no cover
|
|
20
|
+
raise ImportError("brizahub.aiogram needs aiogram 3: pip install 'brizahub[aiogram]'") from e
|
|
21
|
+
|
|
22
|
+
from .client import Briza
|
|
23
|
+
|
|
24
|
+
Handler = Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]]
|
|
25
|
+
Skip = Callable[[TelegramObject, Dict[str, Any]], Any]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BrizaMiddleware(BaseMiddleware):
|
|
29
|
+
"""Runs ``briza.check`` before your handler: the user passed — the handler runs; Briza has shown the force-sub
|
|
30
|
+
block — the update stops here.
|
|
31
|
+
|
|
32
|
+
* ``private_only`` — check private chats only (default); ``False`` — groups too, the block goes to the group;
|
|
33
|
+
* ``skip(event, data)`` — return True to let an update through without the check (admins, /help, payments…),
|
|
34
|
+
may be async;
|
|
35
|
+
* other keyword arguments go to ``check``: ``limit``, ``message``, ``smart_links``, ``min_price``.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, briza: Briza, *, private_only: bool = True, skip: Optional[Skip] = None,
|
|
39
|
+
**check: Any) -> None:
|
|
40
|
+
self.briza = briza
|
|
41
|
+
self.private_only = private_only
|
|
42
|
+
self.skip = skip
|
|
43
|
+
self.check_kwargs = check
|
|
44
|
+
|
|
45
|
+
async def __call__(self, handler: Handler, event: TelegramObject, data: Dict[str, Any]) -> Any:
|
|
46
|
+
user = data.get("event_from_user")
|
|
47
|
+
chat = data.get("event_chat")
|
|
48
|
+
if user is None or getattr(user, "is_bot", False):
|
|
49
|
+
return await handler(event, data)
|
|
50
|
+
private = chat is None or chat.type == "private"
|
|
51
|
+
if self.private_only and not private:
|
|
52
|
+
return await handler(event, data)
|
|
53
|
+
if self.skip is not None:
|
|
54
|
+
verdict = self.skip(event, data)
|
|
55
|
+
if inspect.isawaitable(verdict):
|
|
56
|
+
verdict = await verdict
|
|
57
|
+
if verdict:
|
|
58
|
+
return await handler(event, data)
|
|
59
|
+
kwargs = dict(self.check_kwargs)
|
|
60
|
+
if not private:
|
|
61
|
+
kwargs.setdefault("chat_id", chat.id)
|
|
62
|
+
if await self.briza.check(user, **kwargs):
|
|
63
|
+
return await handler(event, data)
|
|
64
|
+
if isinstance(event, CallbackQuery): # stop the button's spinner: the block is already in the chat
|
|
65
|
+
try:
|
|
66
|
+
await event.answer()
|
|
67
|
+
except Exception: # noqa: BLE001
|
|
68
|
+
pass
|
|
69
|
+
return None
|