procketapi 0.3.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.
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: procketapi
3
+ Version: 0.3.0
4
+ Summary: PRocket integration library for Telegram bots
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+
8
+ # PRocket API Library
9
+
10
+ This package is prepared for simple installation as:
11
+
12
+ ```bash
13
+ python -m pip install procketapi
14
+ ```
15
+
16
+ Important:
17
+ this short command starts working only after you publish the package to PyPI.
18
+
19
+ ## For the service owner
20
+
21
+ You run the PRocket backend on your own server:
22
+
23
+ 1. `python main.py`
24
+ 2. `python -m integration_service.api_server`
25
+
26
+ The connected bot owner does not run your backend.
27
+ They only:
28
+
29
+ 1. get an approved API key
30
+ 2. get your public `base_url`
31
+ 3. install your library
32
+ 4. add the integration into their own bot
33
+
34
+ ## Recommended import
35
+
36
+ ```python
37
+ from procketapi import PRocketAiogramIntegration
38
+ ```
39
+
40
+ Compatibility imports also work:
41
+
42
+ ```python
43
+ from procket_integration import PRocketAiogramIntegration
44
+ from procket_integration_sdk import PRocketAiogramIntegration
45
+ ```
46
+
47
+ ## Easy aiogram integration
48
+
49
+ ```python
50
+ from aiogram import F, Router
51
+ from aiogram.types import CallbackQuery, Message
52
+ from procketapi import PRocketAiogramIntegration
53
+
54
+ router = Router()
55
+ integration = PRocketAiogramIntegration(
56
+ api_key="YOUR_API_KEY",
57
+ base_url="https://your-public-api.example.com",
58
+ callback_prefix="sponsor",
59
+ currency="RUB",
60
+ )
61
+
62
+
63
+ @router.message(F.text == "📢 Задания от спонсоров")
64
+ async def sponsor_tasks(message: Message):
65
+ await integration.show_tasks(message)
66
+
67
+
68
+ @router.callback_query(F.data.startswith("sponsor:"))
69
+ async def sponsor_callbacks(call: CallbackQuery):
70
+ await integration.handle_callback(call)
71
+ ```
72
+
73
+ ## Until the package is published
74
+
75
+ Before PyPI publication, installation is possible from a local wheel:
76
+
77
+ ```bash
78
+ python -m pip install ./procketapi-0.3.0-py3-none-any.whl
79
+ ```
@@ -0,0 +1,72 @@
1
+ # PRocket API Library
2
+
3
+ This package is prepared for simple installation as:
4
+
5
+ ```bash
6
+ python -m pip install procketapi
7
+ ```
8
+
9
+ Important:
10
+ this short command starts working only after you publish the package to PyPI.
11
+
12
+ ## For the service owner
13
+
14
+ You run the PRocket backend on your own server:
15
+
16
+ 1. `python main.py`
17
+ 2. `python -m integration_service.api_server`
18
+
19
+ The connected bot owner does not run your backend.
20
+ They only:
21
+
22
+ 1. get an approved API key
23
+ 2. get your public `base_url`
24
+ 3. install your library
25
+ 4. add the integration into their own bot
26
+
27
+ ## Recommended import
28
+
29
+ ```python
30
+ from procketapi import PRocketAiogramIntegration
31
+ ```
32
+
33
+ Compatibility imports also work:
34
+
35
+ ```python
36
+ from procket_integration import PRocketAiogramIntegration
37
+ from procket_integration_sdk import PRocketAiogramIntegration
38
+ ```
39
+
40
+ ## Easy aiogram integration
41
+
42
+ ```python
43
+ from aiogram import F, Router
44
+ from aiogram.types import CallbackQuery, Message
45
+ from procketapi import PRocketAiogramIntegration
46
+
47
+ router = Router()
48
+ integration = PRocketAiogramIntegration(
49
+ api_key="YOUR_API_KEY",
50
+ base_url="https://your-public-api.example.com",
51
+ callback_prefix="sponsor",
52
+ currency="RUB",
53
+ )
54
+
55
+
56
+ @router.message(F.text == "📢 Задания от спонсоров")
57
+ async def sponsor_tasks(message: Message):
58
+ await integration.show_tasks(message)
59
+
60
+
61
+ @router.callback_query(F.data.startswith("sponsor:"))
62
+ async def sponsor_callbacks(call: CallbackQuery):
63
+ await integration.handle_callback(call)
64
+ ```
65
+
66
+ ## Until the package is published
67
+
68
+ Before PyPI publication, installation is possible from a local wheel:
69
+
70
+ ```bash
71
+ python -m pip install ./procketapi-0.3.0-py3-none-any.whl
72
+ ```
@@ -0,0 +1,13 @@
1
+ from procket_integration_sdk import (
2
+ PRocketAiogramIntegration,
3
+ PRocketIntegrationClient,
4
+ build_offer_keyboard,
5
+ format_offer_text,
6
+ )
7
+
8
+ __all__ = [
9
+ "PRocketAiogramIntegration",
10
+ "PRocketIntegrationClient",
11
+ "build_offer_keyboard",
12
+ "format_offer_text",
13
+ ]
@@ -0,0 +1,13 @@
1
+ from .client import (
2
+ PRocketAiogramIntegration,
3
+ PRocketIntegrationClient,
4
+ build_offer_keyboard,
5
+ format_offer_text,
6
+ )
7
+
8
+ __all__ = [
9
+ "PRocketAiogramIntegration",
10
+ "PRocketIntegrationClient",
11
+ "build_offer_keyboard",
12
+ "format_offer_text",
13
+ ]
@@ -0,0 +1,308 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from dataclasses import dataclass
5
+ import json
6
+ from typing import Any
7
+ import urllib.error
8
+ import urllib.request
9
+
10
+
11
+ @dataclass(slots=True)
12
+ class OfferSession:
13
+ offers: list[dict[str, Any]]
14
+ index: int = 0
15
+
16
+
17
+ class PRocketIntegrationClient:
18
+ def __init__(self, api_key: str, base_url: str, timeout: int = 12):
19
+ self.api_key = api_key.strip()
20
+ self.base_url = base_url.rstrip("/")
21
+ self.timeout = max(3, int(timeout))
22
+
23
+ def _post_sync(self, path: str, payload: dict[str, Any]) -> dict[str, Any]:
24
+ request = urllib.request.Request(
25
+ f"{self.base_url}{path}",
26
+ data=json.dumps(payload).encode("utf-8"),
27
+ headers={
28
+ "X-API-Key": self.api_key,
29
+ "Content-Type": "application/json",
30
+ "Accept": "application/json",
31
+ },
32
+ method="POST",
33
+ )
34
+ try:
35
+ with urllib.request.urlopen(request, timeout=self.timeout) as response:
36
+ raw_body = response.read().decode("utf-8", errors="replace")
37
+ status_code = getattr(response, "status", 200)
38
+ except urllib.error.HTTPError as exc:
39
+ raw_body = exc.read().decode("utf-8", errors="replace")
40
+ status_code = exc.code
41
+ except urllib.error.URLError as exc:
42
+ raise RuntimeError(f"connection_failed: {exc.reason}") from exc
43
+
44
+ try:
45
+ data = json.loads(raw_body) if raw_body else {}
46
+ except json.JSONDecodeError:
47
+ data = {"message": raw_body}
48
+
49
+ if status_code >= 400:
50
+ raise RuntimeError(str(data.get("message") or data.get("error") or "request_failed"))
51
+ return data
52
+
53
+ async def _post(self, path: str, payload: dict[str, Any]) -> dict[str, Any]:
54
+ return await asyncio.to_thread(self._post_sync, path, payload)
55
+
56
+ async def get_bot_info(self) -> dict[str, Any]:
57
+ return await self._post("/api/v1/bot", {})
58
+
59
+ async def get_tasks(
60
+ self,
61
+ *,
62
+ user_id: int,
63
+ username: str | None = None,
64
+ language_code: str | None = "ru",
65
+ is_premium: bool = False,
66
+ limit: int = 5,
67
+ ) -> list[dict[str, Any]]:
68
+ data = await self._post(
69
+ "/api/v1/tasks",
70
+ {
71
+ "user": {
72
+ "id": user_id,
73
+ "username": username,
74
+ "language_code": language_code,
75
+ "is_premium": is_premium,
76
+ },
77
+ "limit": limit,
78
+ },
79
+ )
80
+ return data.get("tasks", [])
81
+
82
+ async def check_task(
83
+ self,
84
+ *,
85
+ user_id: int,
86
+ offer_id: int,
87
+ username: str | None = None,
88
+ language_code: str | None = "ru",
89
+ is_premium: bool = False,
90
+ ) -> dict[str, Any]:
91
+ return await self._post(
92
+ "/api/v1/tasks/check",
93
+ {
94
+ "offer_id": offer_id,
95
+ "user": {
96
+ "id": user_id,
97
+ "username": username,
98
+ "language_code": language_code,
99
+ "is_premium": is_premium,
100
+ },
101
+ },
102
+ )
103
+
104
+
105
+ def format_offer_text(offer: dict[str, Any], currency: str = "RUB") -> str:
106
+ resource_type = str(offer.get("resource_type") or "").strip() or "task"
107
+ type_titles = {
108
+ "channel": "Подписка на канал",
109
+ "group": "Вступление в группу",
110
+ "bot": "Переход в бота",
111
+ }
112
+ reward = float(offer.get("executor_reward") or 0)
113
+ hold_days = int(offer.get("required_days") or 0)
114
+ title = str(offer.get("title") or "").strip() or type_titles.get(resource_type, "Задание")
115
+ description = str(offer.get("description") or "").strip()
116
+
117
+ lines = [
118
+ "Задание от спонсора",
119
+ "",
120
+ title,
121
+ ]
122
+ if description:
123
+ lines.extend(["", description])
124
+ lines.extend(
125
+ [
126
+ "",
127
+ f"Тип: {type_titles.get(resource_type, resource_type)}",
128
+ f"Награда: {reward:.2f} {currency}",
129
+ ]
130
+ )
131
+ if hold_days > 0:
132
+ lines.append(f"Удержание: {hold_days} дн.")
133
+ lines.append(f"ID: {offer.get('id')}")
134
+ return "\n".join(lines)
135
+
136
+
137
+ def build_offer_keyboard(
138
+ offer: dict[str, Any],
139
+ *,
140
+ check_callback_data: str,
141
+ next_callback_data: str | None = None,
142
+ open_label: str = "Открыть",
143
+ check_label: str = "Проверить",
144
+ next_label: str = "Следующее",
145
+ ):
146
+ try:
147
+ from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
148
+ except Exception as exc:
149
+ raise RuntimeError("aiogram is required for build_offer_keyboard") from exc
150
+
151
+ rows = [
152
+ [InlineKeyboardButton(text=open_label, url=str(offer.get("resource_link") or ""))],
153
+ [InlineKeyboardButton(text=check_label, callback_data=check_callback_data)],
154
+ ]
155
+ if next_callback_data:
156
+ rows.append([InlineKeyboardButton(text=next_label, callback_data=next_callback_data)])
157
+ return InlineKeyboardMarkup(inline_keyboard=rows)
158
+
159
+
160
+ class PRocketAiogramIntegration:
161
+ def __init__(
162
+ self,
163
+ *,
164
+ api_key: str,
165
+ base_url: str,
166
+ callback_prefix: str = "prkt",
167
+ currency: str = "RUB",
168
+ limit: int = 10,
169
+ empty_text: str = "Сейчас нет доступных заданий от спонсоров.",
170
+ open_label: str = "Открыть",
171
+ check_label: str = "Проверить",
172
+ next_label: str = "Следующее",
173
+ refresh_label: str = "Обновить",
174
+ ):
175
+ self.client = PRocketIntegrationClient(api_key=api_key, base_url=base_url)
176
+ self.callback_prefix = callback_prefix
177
+ self.currency = currency
178
+ self.limit = max(1, min(limit, 25))
179
+ self.empty_text = empty_text
180
+ self.open_label = open_label
181
+ self.check_label = check_label
182
+ self.next_label = next_label
183
+ self.refresh_label = refresh_label
184
+ self._sessions: dict[int, OfferSession] = {}
185
+
186
+ def _cb(self, action: str, offer_id: int | None = None) -> str:
187
+ if offer_id is None:
188
+ return f"{self.callback_prefix}:{action}"
189
+ return f"{self.callback_prefix}:{action}:{offer_id}"
190
+
191
+ async def _answer_call(self, call: Any, text: str | None = None, *, show_alert: bool = False) -> None:
192
+ try:
193
+ await call.answer(text or "", show_alert=show_alert)
194
+ except Exception:
195
+ return
196
+
197
+ async def _load_session(self, user: Any) -> OfferSession:
198
+ offers = await self.client.get_tasks(
199
+ user_id=int(user.id),
200
+ username=getattr(user, "username", None),
201
+ language_code=getattr(user, "language_code", None) or "ru",
202
+ is_premium=bool(getattr(user, "is_premium", False)),
203
+ limit=self.limit,
204
+ )
205
+ session = OfferSession(offers=offers, index=0)
206
+ self._sessions[int(user.id)] = session
207
+ return session
208
+
209
+ async def _get_session(self, user: Any, *, force_refresh: bool = False) -> OfferSession:
210
+ user_id = int(user.id)
211
+ session = self._sessions.get(user_id)
212
+ if force_refresh or session is None or not session.offers:
213
+ session = await self._load_session(user)
214
+ return session
215
+
216
+ def _current_offer(self, session: OfferSession) -> dict[str, Any] | None:
217
+ if not session.offers:
218
+ return None
219
+ session.index %= len(session.offers)
220
+ return session.offers[session.index]
221
+
222
+ async def _respond(self, target: Any, text: str, reply_markup: Any) -> None:
223
+ if hasattr(target, "edit_text"):
224
+ try:
225
+ await target.edit_text(text, reply_markup=reply_markup)
226
+ return
227
+ except Exception:
228
+ pass
229
+ await target.answer(text, reply_markup=reply_markup)
230
+
231
+ async def _render_offer(self, target: Any, user: Any, *, force_refresh: bool = False) -> None:
232
+ session = await self._get_session(user, force_refresh=force_refresh)
233
+ offer = self._current_offer(session)
234
+ try:
235
+ from aiogram.utils.keyboard import InlineKeyboardBuilder
236
+ except Exception as exc:
237
+ raise RuntimeError("aiogram is required for PRocketAiogramIntegration") from exc
238
+
239
+ if offer is None:
240
+ kb = InlineKeyboardBuilder()
241
+ kb.button(text=self.refresh_label, callback_data=self._cb("refresh"))
242
+ await self._respond(target, self.empty_text, kb.as_markup())
243
+ return
244
+
245
+ text = format_offer_text(offer, currency=self.currency)
246
+ markup = build_offer_keyboard(
247
+ offer,
248
+ check_callback_data=self._cb("check", int(offer["id"])),
249
+ next_callback_data=self._cb("next", int(offer["id"])),
250
+ open_label=self.open_label,
251
+ check_label=self.check_label,
252
+ next_label=self.next_label,
253
+ )
254
+ await self._respond(target, text, markup)
255
+
256
+ async def show_tasks(self, target: Any) -> None:
257
+ user = target.from_user
258
+ if hasattr(target, "answer") and hasattr(target, "message"):
259
+ await self._answer_call(target)
260
+ await self._render_offer(target.message, user, force_refresh=True)
261
+ return
262
+ await self._render_offer(target, user, force_refresh=True)
263
+
264
+ async def handle_callback(self, call: Any) -> bool:
265
+ data = str(getattr(call, "data", "") or "")
266
+ prefix = f"{self.callback_prefix}:"
267
+ if not data.startswith(prefix):
268
+ return False
269
+
270
+ user = call.from_user
271
+ parts = data.split(":")
272
+ action = parts[1] if len(parts) > 1 else ""
273
+ offer_id = int(parts[2]) if len(parts) > 2 and parts[2].isdigit() else None
274
+
275
+ if action == "refresh":
276
+ await self._answer_call(call)
277
+ await self._render_offer(call.message, user, force_refresh=True)
278
+ return True
279
+
280
+ session = await self._get_session(user)
281
+ current = self._current_offer(session)
282
+
283
+ if action == "next":
284
+ await self._answer_call(call)
285
+ if session.offers:
286
+ session.index = (session.index + 1) % len(session.offers)
287
+ await self._render_offer(call.message, user)
288
+ return True
289
+
290
+ if action == "check":
291
+ if offer_id is None:
292
+ await self._answer_call(call, "Некорректный ID задания.", show_alert=True)
293
+ return True
294
+ result = await self.client.check_task(
295
+ user_id=int(user.id),
296
+ offer_id=offer_id,
297
+ username=getattr(user, "username", None),
298
+ language_code=getattr(user, "language_code", None) or "ru",
299
+ is_premium=bool(getattr(user, "is_premium", False)),
300
+ )
301
+ await self._answer_call(call, str(result.get("message") or "Проверено"), show_alert=True)
302
+ await self._render_offer(call.message, user, force_refresh=True)
303
+ return True
304
+
305
+ await self._answer_call(call)
306
+ if current and offer_id and int(current.get("id") or 0) != offer_id:
307
+ await self._render_offer(call.message, user, force_refresh=True)
308
+ return True
@@ -0,0 +1,13 @@
1
+ from procket_integration_sdk import (
2
+ PRocketAiogramIntegration,
3
+ PRocketIntegrationClient,
4
+ build_offer_keyboard,
5
+ format_offer_text,
6
+ )
7
+
8
+ __all__ = [
9
+ "PRocketAiogramIntegration",
10
+ "PRocketIntegrationClient",
11
+ "build_offer_keyboard",
12
+ "format_offer_text",
13
+ ]
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: procketapi
3
+ Version: 0.3.0
4
+ Summary: PRocket integration library for Telegram bots
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+
8
+ # PRocket API Library
9
+
10
+ This package is prepared for simple installation as:
11
+
12
+ ```bash
13
+ python -m pip install procketapi
14
+ ```
15
+
16
+ Important:
17
+ this short command starts working only after you publish the package to PyPI.
18
+
19
+ ## For the service owner
20
+
21
+ You run the PRocket backend on your own server:
22
+
23
+ 1. `python main.py`
24
+ 2. `python -m integration_service.api_server`
25
+
26
+ The connected bot owner does not run your backend.
27
+ They only:
28
+
29
+ 1. get an approved API key
30
+ 2. get your public `base_url`
31
+ 3. install your library
32
+ 4. add the integration into their own bot
33
+
34
+ ## Recommended import
35
+
36
+ ```python
37
+ from procketapi import PRocketAiogramIntegration
38
+ ```
39
+
40
+ Compatibility imports also work:
41
+
42
+ ```python
43
+ from procket_integration import PRocketAiogramIntegration
44
+ from procket_integration_sdk import PRocketAiogramIntegration
45
+ ```
46
+
47
+ ## Easy aiogram integration
48
+
49
+ ```python
50
+ from aiogram import F, Router
51
+ from aiogram.types import CallbackQuery, Message
52
+ from procketapi import PRocketAiogramIntegration
53
+
54
+ router = Router()
55
+ integration = PRocketAiogramIntegration(
56
+ api_key="YOUR_API_KEY",
57
+ base_url="https://your-public-api.example.com",
58
+ callback_prefix="sponsor",
59
+ currency="RUB",
60
+ )
61
+
62
+
63
+ @router.message(F.text == "📢 Задания от спонсоров")
64
+ async def sponsor_tasks(message: Message):
65
+ await integration.show_tasks(message)
66
+
67
+
68
+ @router.callback_query(F.data.startswith("sponsor:"))
69
+ async def sponsor_callbacks(call: CallbackQuery):
70
+ await integration.handle_callback(call)
71
+ ```
72
+
73
+ ## Until the package is published
74
+
75
+ Before PyPI publication, installation is possible from a local wheel:
76
+
77
+ ```bash
78
+ python -m pip install ./procketapi-0.3.0-py3-none-any.whl
79
+ ```
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ procket_integration/__init__.py
4
+ procket_integration_sdk/__init__.py
5
+ procket_integration_sdk/client.py
6
+ procketapi/__init__.py
7
+ procketapi.egg-info/PKG-INFO
8
+ procketapi.egg-info/SOURCES.txt
9
+ procketapi.egg-info/dependency_links.txt
10
+ procketapi.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ procket_integration
2
+ procket_integration_sdk
3
+ procketapi
@@ -0,0 +1,13 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "procketapi"
7
+ version = "0.3.0"
8
+ description = "PRocket integration library for Telegram bots"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+
12
+ [tool.setuptools]
13
+ packages = ["procket_integration_sdk", "procket_integration", "procketapi"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+