nativeads 0.1.0__py3-none-any.whl
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.
- nativeads/__init__.py +5 -0
- nativeads/client.py +223 -0
- nativeads/keyboard.py +77 -0
- nativeads/middleware.py +68 -0
- nativeads-0.1.0.dist-info/METADATA +88 -0
- nativeads-0.1.0.dist-info/RECORD +7 -0
- nativeads-0.1.0.dist-info/WHEEL +4 -0
nativeads/__init__.py
ADDED
nativeads/client.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
from .keyboard import merge_keyboard
|
|
9
|
+
|
|
10
|
+
__version__ = "0.1.0"
|
|
11
|
+
_TELEGRAM_MAX_MESSAGE_LENGTH = 4096
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class Ad:
|
|
16
|
+
ad_text: str
|
|
17
|
+
button_text: str
|
|
18
|
+
button_url: str
|
|
19
|
+
ad_text_formatted: Optional[str] = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class InjectResult:
|
|
24
|
+
message: str
|
|
25
|
+
has_ad: bool
|
|
26
|
+
impression_id: Optional[int]
|
|
27
|
+
ad: Optional[Ad]
|
|
28
|
+
keyboard: Any
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class FetchResult:
|
|
33
|
+
has_ad: bool
|
|
34
|
+
impression_id: Optional[int]
|
|
35
|
+
ad: Optional[Ad]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class NativeAds:
|
|
39
|
+
"""NativeAds client for Telegram bots.
|
|
40
|
+
|
|
41
|
+
``inject()`` and ``fetch()`` NEVER raise — on any error (timeout, network,
|
|
42
|
+
5xx) they return your original message/keyboard unchanged, so ads can never
|
|
43
|
+
break the bot. Default timeout is 3 seconds.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
api_key: str,
|
|
49
|
+
platform_id: Optional[str] = None,
|
|
50
|
+
*,
|
|
51
|
+
base_url: str = "https://nativeads.cloud",
|
|
52
|
+
timeout: float = 3.0,
|
|
53
|
+
platform: str = "telegram",
|
|
54
|
+
show_every: int = 1,
|
|
55
|
+
skip_first: int = 0,
|
|
56
|
+
) -> None:
|
|
57
|
+
if not api_key:
|
|
58
|
+
raise ValueError("NativeAds: api_key is required")
|
|
59
|
+
self._api_key = api_key
|
|
60
|
+
self._platform_id = platform_id
|
|
61
|
+
self._base_url = base_url.rstrip("/")
|
|
62
|
+
self._timeout = timeout
|
|
63
|
+
self._platform = platform
|
|
64
|
+
# Show an ad on every Nth message per user (1 = every; 0 = disabled).
|
|
65
|
+
# With N>1 the first N-1 messages are ad-free. skip_first never shows an
|
|
66
|
+
# ad on a user's first M messages.
|
|
67
|
+
self._show_every = show_every
|
|
68
|
+
self._skip_first = skip_first
|
|
69
|
+
self._counts: dict[int, int] = {}
|
|
70
|
+
self._client: Optional[httpx.AsyncClient] = None
|
|
71
|
+
|
|
72
|
+
def _should_show(self, user_id: int) -> bool:
|
|
73
|
+
"""Per-user message counter → whether this turn should carry an ad.
|
|
74
|
+
In-memory (per process) — cadence is approximate across restarts."""
|
|
75
|
+
n = self._counts.get(user_id, 0) + 1
|
|
76
|
+
self._counts[user_id] = n
|
|
77
|
+
if self._show_every <= 0:
|
|
78
|
+
return False
|
|
79
|
+
if n <= self._skip_first:
|
|
80
|
+
return False
|
|
81
|
+
return (n - self._skip_first) % self._show_every == 0
|
|
82
|
+
|
|
83
|
+
def _ensure_client(self) -> httpx.AsyncClient:
|
|
84
|
+
if self._client is None or self._client.is_closed:
|
|
85
|
+
self._client = httpx.AsyncClient(
|
|
86
|
+
base_url=self._base_url,
|
|
87
|
+
headers={
|
|
88
|
+
"Authorization": f"Bearer {self._api_key}",
|
|
89
|
+
"Content-Type": "application/json",
|
|
90
|
+
"User-Agent": f"nativeads-python/{__version__}",
|
|
91
|
+
},
|
|
92
|
+
timeout=httpx.Timeout(self._timeout),
|
|
93
|
+
)
|
|
94
|
+
return self._client
|
|
95
|
+
|
|
96
|
+
async def aclose(self) -> None:
|
|
97
|
+
if self._client is not None and not self._client.is_closed:
|
|
98
|
+
await self._client.aclose()
|
|
99
|
+
self._client = None
|
|
100
|
+
|
|
101
|
+
async def inject(
|
|
102
|
+
self,
|
|
103
|
+
*,
|
|
104
|
+
user_id: int,
|
|
105
|
+
message: str,
|
|
106
|
+
language_code: Optional[str] = None,
|
|
107
|
+
is_premium: Optional[bool] = None,
|
|
108
|
+
keyboard: Any = None,
|
|
109
|
+
ad_button_position: str = "bottom",
|
|
110
|
+
parse_mode: Optional[str] = None,
|
|
111
|
+
) -> InjectResult:
|
|
112
|
+
"""Pass the LLM reply (and optionally the keyboard you were going to send);
|
|
113
|
+
get them back with a native ad injected. Never raises."""
|
|
114
|
+
fallback = InjectResult(
|
|
115
|
+
message=message, has_ad=False, impression_id=None, ad=None, keyboard=keyboard
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Frequency gate: skip this turn (no server call, no impression).
|
|
119
|
+
if not self._should_show(user_id):
|
|
120
|
+
return fallback
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
payload = {
|
|
124
|
+
"user_id": user_id,
|
|
125
|
+
"message": message,
|
|
126
|
+
"platform_id": self._platform_id,
|
|
127
|
+
"language_code": language_code or "",
|
|
128
|
+
"is_premium": bool(is_premium),
|
|
129
|
+
"platform": self._platform,
|
|
130
|
+
}
|
|
131
|
+
if parse_mode:
|
|
132
|
+
payload["parse_mode"] = parse_mode
|
|
133
|
+
|
|
134
|
+
resp = await self._ensure_client().post("/api/v1/ad", json=payload)
|
|
135
|
+
resp.raise_for_status()
|
|
136
|
+
data = resp.json()
|
|
137
|
+
|
|
138
|
+
if not data.get("has_ad") or not data.get("ad"):
|
|
139
|
+
return InjectResult(
|
|
140
|
+
message=data.get("message", message),
|
|
141
|
+
has_ad=False,
|
|
142
|
+
impression_id=data.get("impression_id"),
|
|
143
|
+
ad=None,
|
|
144
|
+
keyboard=keyboard,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
raw = data["ad"]
|
|
148
|
+
ad = Ad(
|
|
149
|
+
ad_text=raw["ad_text"],
|
|
150
|
+
button_text=raw["button_text"],
|
|
151
|
+
button_url=raw["button_url"],
|
|
152
|
+
ad_text_formatted=raw.get("ad_text_formatted"),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
result_message = data.get("message", message)
|
|
156
|
+
if len(result_message) > _TELEGRAM_MAX_MESSAGE_LENGTH:
|
|
157
|
+
result_message = message
|
|
158
|
+
|
|
159
|
+
result_keyboard = keyboard
|
|
160
|
+
if ad.button_text and ad.button_url:
|
|
161
|
+
result_keyboard = merge_keyboard(
|
|
162
|
+
keyboard, ad.button_text, ad.button_url, ad_button_position
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if result_keyboard is keyboard and result_message == message:
|
|
166
|
+
return fallback
|
|
167
|
+
|
|
168
|
+
return InjectResult(
|
|
169
|
+
message=result_message,
|
|
170
|
+
has_ad=True,
|
|
171
|
+
impression_id=data.get("impression_id"),
|
|
172
|
+
ad=ad,
|
|
173
|
+
keyboard=result_keyboard,
|
|
174
|
+
)
|
|
175
|
+
except Exception:
|
|
176
|
+
return fallback
|
|
177
|
+
|
|
178
|
+
async def fetch(
|
|
179
|
+
self,
|
|
180
|
+
*,
|
|
181
|
+
user_id: int,
|
|
182
|
+
language_code: Optional[str] = None,
|
|
183
|
+
is_premium: Optional[bool] = None,
|
|
184
|
+
parse_mode: Optional[str] = None,
|
|
185
|
+
) -> FetchResult:
|
|
186
|
+
"""Privacy-friendly alternative: get the ad as separate fields without
|
|
187
|
+
sending the LLM reply text. You render it yourself. Never raises."""
|
|
188
|
+
empty = FetchResult(has_ad=False, impression_id=None, ad=None)
|
|
189
|
+
try:
|
|
190
|
+
payload = {
|
|
191
|
+
"user_id": user_id,
|
|
192
|
+
"platform_id": self._platform_id,
|
|
193
|
+
"language_code": language_code or "",
|
|
194
|
+
"is_premium": bool(is_premium),
|
|
195
|
+
"platform": self._platform,
|
|
196
|
+
}
|
|
197
|
+
if parse_mode:
|
|
198
|
+
payload["parse_mode"] = parse_mode
|
|
199
|
+
|
|
200
|
+
resp = await self._ensure_client().post("/api/v1/ad/fetch", json=payload)
|
|
201
|
+
resp.raise_for_status()
|
|
202
|
+
data = resp.json()
|
|
203
|
+
|
|
204
|
+
if not data.get("has_ad") or not data.get("ad"):
|
|
205
|
+
return empty
|
|
206
|
+
|
|
207
|
+
raw = data["ad"]
|
|
208
|
+
return FetchResult(
|
|
209
|
+
has_ad=True,
|
|
210
|
+
impression_id=data.get("impression_id"),
|
|
211
|
+
ad=Ad(
|
|
212
|
+
ad_text=raw["ad_text"],
|
|
213
|
+
button_text=raw["button_text"],
|
|
214
|
+
button_url=raw["button_url"],
|
|
215
|
+
ad_text_formatted=raw.get("ad_text_formatted"),
|
|
216
|
+
),
|
|
217
|
+
)
|
|
218
|
+
except Exception:
|
|
219
|
+
return empty
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
# Deprecated alias, kept for compatibility with code written against the old name.
|
|
223
|
+
AibotAds = NativeAds
|
nativeads/keyboard.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Framework-aware inline-keyboard merging.
|
|
2
|
+
|
|
3
|
+
Adds the ad button as its own row, never touching the publisher's buttons.
|
|
4
|
+
Supports aiogram 3.x, python-telegram-bot v20+, and the raw Telegram dict shape.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Any, Optional, Tuple
|
|
9
|
+
|
|
10
|
+
# Telegram's hard limit on inline keyboard rows.
|
|
11
|
+
_MAX_ROWS = 13
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _aiogram_types() -> Optional[Tuple[Any, Any]]:
|
|
15
|
+
try:
|
|
16
|
+
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
|
17
|
+
return InlineKeyboardMarkup, InlineKeyboardButton
|
|
18
|
+
except ImportError:
|
|
19
|
+
return None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _ptb_types() -> Optional[Tuple[Any, Any]]:
|
|
23
|
+
try:
|
|
24
|
+
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
|
|
25
|
+
return InlineKeyboardMarkup, InlineKeyboardButton
|
|
26
|
+
except ImportError:
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def merge_keyboard(keyboard: Any, button_text: str, button_url: str, position: str = "bottom") -> Any:
|
|
31
|
+
"""Return a new keyboard with the ad button row inserted (top/bottom), in the
|
|
32
|
+
same framework type it was given. Returns the original unchanged when the row
|
|
33
|
+
limit is reached or the shape is unrecognized."""
|
|
34
|
+
aiogram = _aiogram_types()
|
|
35
|
+
ptb = _ptb_types()
|
|
36
|
+
|
|
37
|
+
# --- existing aiogram markup ---
|
|
38
|
+
if aiogram is not None and isinstance(keyboard, aiogram[0]):
|
|
39
|
+
markup_cls, button_cls = aiogram
|
|
40
|
+
rows = list(keyboard.inline_keyboard)
|
|
41
|
+
if len(rows) >= _MAX_ROWS:
|
|
42
|
+
return keyboard
|
|
43
|
+
ad_row = [button_cls(text=button_text, url=button_url)]
|
|
44
|
+
rows = [ad_row] + rows if position == "top" else rows + [ad_row]
|
|
45
|
+
return markup_cls(inline_keyboard=rows)
|
|
46
|
+
|
|
47
|
+
# --- existing python-telegram-bot markup ---
|
|
48
|
+
if ptb is not None and isinstance(keyboard, ptb[0]):
|
|
49
|
+
markup_cls, button_cls = ptb
|
|
50
|
+
rows = [list(r) for r in keyboard.inline_keyboard]
|
|
51
|
+
if len(rows) >= _MAX_ROWS:
|
|
52
|
+
return keyboard
|
|
53
|
+
ad_row = [button_cls(text=button_text, url=button_url)]
|
|
54
|
+
rows = [ad_row] + rows if position == "top" else rows + [ad_row]
|
|
55
|
+
return markup_cls(rows)
|
|
56
|
+
|
|
57
|
+
# --- existing raw dict markup ---
|
|
58
|
+
if isinstance(keyboard, dict) and isinstance(keyboard.get("inline_keyboard"), list):
|
|
59
|
+
rows = list(keyboard["inline_keyboard"])
|
|
60
|
+
if len(rows) >= _MAX_ROWS:
|
|
61
|
+
return keyboard
|
|
62
|
+
ad_row = [{"text": button_text, "url": button_url}]
|
|
63
|
+
rows = [ad_row] + rows if position == "top" else rows + [ad_row]
|
|
64
|
+
return {**keyboard, "inline_keyboard": rows}
|
|
65
|
+
|
|
66
|
+
# --- no keyboard → build a fresh one in whatever framework is available ---
|
|
67
|
+
if keyboard is None:
|
|
68
|
+
if aiogram is not None:
|
|
69
|
+
markup_cls, button_cls = aiogram
|
|
70
|
+
return markup_cls(inline_keyboard=[[button_cls(text=button_text, url=button_url)]])
|
|
71
|
+
if ptb is not None:
|
|
72
|
+
markup_cls, button_cls = ptb
|
|
73
|
+
return markup_cls([[button_cls(text=button_text, url=button_url)]])
|
|
74
|
+
return {"inline_keyboard": [[{"text": button_text, "url": button_url}]]}
|
|
75
|
+
|
|
76
|
+
# Unrecognized shape — never break the bot.
|
|
77
|
+
return keyboard
|
nativeads/middleware.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Optional aiogram 3.x middleware that injects a configured ``NativeAds`` into
|
|
2
|
+
handler data, so handlers receive it via dependency injection.
|
|
3
|
+
|
|
4
|
+
from nativeads import NativeAds
|
|
5
|
+
from nativeads.middleware import NativeAdsMiddleware
|
|
6
|
+
|
|
7
|
+
mw = NativeAdsMiddleware(api_key="sk_live_...", platform_id="plt_...")
|
|
8
|
+
dp.message.middleware(mw)
|
|
9
|
+
dp.shutdown.register(mw.aclose)
|
|
10
|
+
|
|
11
|
+
async def handler(message: Message, nativeads: NativeAds):
|
|
12
|
+
result = await nativeads.inject(user_id=message.from_user.id, message=answer)
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Any, Awaitable, Callable, Dict, Optional
|
|
17
|
+
|
|
18
|
+
from .client import NativeAds
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class NativeAdsMiddleware:
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
api_key: Optional[str] = None,
|
|
25
|
+
platform_id: Optional[str] = None,
|
|
26
|
+
*,
|
|
27
|
+
client: Optional[NativeAds] = None,
|
|
28
|
+
base_url: str = "https://nativeads.cloud",
|
|
29
|
+
timeout: float = 3.0,
|
|
30
|
+
platform: str = "telegram",
|
|
31
|
+
show_every: int = 1,
|
|
32
|
+
skip_first: int = 0,
|
|
33
|
+
) -> None:
|
|
34
|
+
if client is not None:
|
|
35
|
+
self.client = client
|
|
36
|
+
self._owned = False
|
|
37
|
+
else:
|
|
38
|
+
if not api_key:
|
|
39
|
+
raise TypeError("NativeAdsMiddleware requires api_key or an existing client=")
|
|
40
|
+
self.client = NativeAds(
|
|
41
|
+
api_key=api_key,
|
|
42
|
+
platform_id=platform_id,
|
|
43
|
+
base_url=base_url,
|
|
44
|
+
timeout=timeout,
|
|
45
|
+
platform=platform,
|
|
46
|
+
show_every=show_every,
|
|
47
|
+
skip_first=skip_first,
|
|
48
|
+
)
|
|
49
|
+
self._owned = True
|
|
50
|
+
|
|
51
|
+
async def aclose(self) -> None:
|
|
52
|
+
if self._owned:
|
|
53
|
+
await self.client.aclose()
|
|
54
|
+
|
|
55
|
+
async def __call__(
|
|
56
|
+
self,
|
|
57
|
+
handler: Callable[[Any, Dict[str, Any]], Awaitable[Any]],
|
|
58
|
+
event: Any,
|
|
59
|
+
data: Dict[str, Any],
|
|
60
|
+
) -> Any:
|
|
61
|
+
# Inject under the new key; keep the old key for backward compatibility.
|
|
62
|
+
data["nativeads"] = self.client
|
|
63
|
+
data["aibotads"] = self.client
|
|
64
|
+
return await handler(event, data)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# Deprecated alias, kept for compatibility with code written against the old name.
|
|
68
|
+
AibotAdsMiddleware = NativeAdsMiddleware
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nativeads
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: NativeAds SDK for Telegram bots — inject ads into LLM replies (aiogram / python-telegram-bot / raw).
|
|
5
|
+
Project-URL: Homepage, https://nativeads.cloud
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: ads,aiogram,bot,monetization,python-telegram-bot,telegram
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Requires-Dist: httpx>=0.25.0
|
|
10
|
+
Provides-Extra: aiogram
|
|
11
|
+
Requires-Dist: aiogram>=3.0; extra == 'aiogram'
|
|
12
|
+
Provides-Extra: ptb
|
|
13
|
+
Requires-Dist: python-telegram-bot>=20.0; extra == 'ptb'
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# nativeads (Python)
|
|
17
|
+
|
|
18
|
+
Python SDK for [NativeAds](https://nativeads.cloud) — monetize your Telegram bot by
|
|
19
|
+
injecting ads into LLM replies. Works with **aiogram 3.x**, **python-telegram-bot v20+**,
|
|
20
|
+
or raw bot code.
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install nativeads # core (httpx only)
|
|
24
|
+
pip install nativeads[aiogram] # + aiogram 3.x
|
|
25
|
+
pip install nativeads[ptb] # + python-telegram-bot v20+
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick start (`inject`)
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from nativeads import NativeAds
|
|
32
|
+
|
|
33
|
+
ads = NativeAds(api_key="sk_live_xxx", platform_id="plt_xxx")
|
|
34
|
+
|
|
35
|
+
# inside an async message handler, after generating the LLM answer:
|
|
36
|
+
result = await ads.inject(
|
|
37
|
+
user_id=message.from_user.id,
|
|
38
|
+
message=llm_answer,
|
|
39
|
+
language_code=message.from_user.language_code,
|
|
40
|
+
is_premium=message.from_user.is_premium,
|
|
41
|
+
keyboard=my_keyboard, # optional — aiogram / PTB / raw dict
|
|
42
|
+
# ad_button_position="top", # default "bottom"
|
|
43
|
+
)
|
|
44
|
+
await message.answer(result.message, reply_markup=result.keyboard)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### aiogram middleware (DI)
|
|
48
|
+
```python
|
|
49
|
+
from nativeads import NativeAds
|
|
50
|
+
from nativeads.middleware import NativeAdsMiddleware
|
|
51
|
+
|
|
52
|
+
mw = NativeAdsMiddleware(api_key="sk_live_xxx", platform_id="plt_xxx")
|
|
53
|
+
dp.message.middleware(mw)
|
|
54
|
+
dp.shutdown.register(mw.aclose)
|
|
55
|
+
|
|
56
|
+
async def handler(message: Message, nativeads: NativeAds):
|
|
57
|
+
result = await nativeads.inject(user_id=message.from_user.id, message=answer)
|
|
58
|
+
await message.answer(result.message, reply_markup=result.keyboard)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Privacy-friendly mode (`fetch`)
|
|
62
|
+
```python
|
|
63
|
+
res = await ads.fetch(user_id=uid, language_code="ru")
|
|
64
|
+
if res.has_ad:
|
|
65
|
+
text = f"{llm_answer}\n\n{res.ad.ad_text}"
|
|
66
|
+
# render res.ad.button_text / res.ad.button_url yourself
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Guarantees
|
|
70
|
+
- **Never raises.** On any error (timeout, network, 5xx) `inject()`/`fetch()` return your
|
|
71
|
+
original message and keyboard unchanged. Default timeout 3s.
|
|
72
|
+
- **Your buttons are preserved** — the ad button is a separate row (top/bottom), respecting
|
|
73
|
+
Telegram's 4096-char and 13-row limits.
|
|
74
|
+
- **Click tracking is automatic** — `button_url` is a tracking redirect.
|
|
75
|
+
|
|
76
|
+
## API
|
|
77
|
+
- `NativeAds(api_key, platform_id=None, *, base_url=..., timeout=3.0, platform="telegram", show_every=1, skip_first=0)`
|
|
78
|
+
- `show_every` — ad on every Nth message (5 = optimal, 0 = off); N>1 keeps the first message ad-free.
|
|
79
|
+
- `skip_first` — never show an ad on a user's first N messages.
|
|
80
|
+
- Frequency is client-side (in-memory per-user counter); skipped turns return your message without a server call.
|
|
81
|
+
- `NativeAdsMiddleware(...)` accepts the same `show_every` / `skip_first`.
|
|
82
|
+
- `await ads.inject(*, user_id, message, language_code=None, is_premium=None, keyboard=None, ad_button_position="bottom", parse_mode=None) -> InjectResult`
|
|
83
|
+
- `await ads.fetch(*, user_id, language_code=None, is_premium=None, parse_mode=None) -> FetchResult`
|
|
84
|
+
- `await ads.aclose()`
|
|
85
|
+
|
|
86
|
+
`InjectResult(message, has_ad, impression_id, ad, keyboard)` ·
|
|
87
|
+
`FetchResult(has_ad, impression_id, ad)` ·
|
|
88
|
+
`Ad(ad_text, button_text, button_url, ad_text_formatted)`
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
nativeads/__init__.py,sha256=KZXVut1Xm0NbaZANnBbChoxD10gUSlT61oDv9tVMeTY,222
|
|
2
|
+
nativeads/client.py,sha256=8gEV20ldCtmomzGlx60B7wWtED3AqOKxsWPc0LKeXQ4,7309
|
|
3
|
+
nativeads/keyboard.py,sha256=lKj52LETxyU61b8r1h0W10YMj5RW1H_p_l6jv09Ms8U,3081
|
|
4
|
+
nativeads/middleware.py,sha256=-Fryc0pTdz4RICazHl__Rh9L6jE752o4wmc4O209yW0,2215
|
|
5
|
+
nativeads-0.1.0.dist-info/METADATA,sha256=NTeuJ_xiYraT8XDT6jYGBdppchJ_dpZ-hnX0iSoWXgQ,3545
|
|
6
|
+
nativeads-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
7
|
+
nativeads-0.1.0.dist-info/RECORD,,
|