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 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.
@@ -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
@@ -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