funpaybotengine 0.3.6__tar.gz → 0.4.1__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.
Files changed (119) hide show
  1. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/PKG-INFO +5 -5
  2. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/dispatching/events/base.py +25 -5
  3. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/dispatching/events/builtin_events.py +27 -27
  4. funpaybotengine-0.4.1/funpaybotengine/runner/config.py +116 -0
  5. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/runner/runner.py +40 -10
  6. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/messages.py +0 -1
  7. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/orders.py +19 -12
  8. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/pyproject.toml +5 -5
  9. funpaybotengine-0.4.1/temp.py +0 -0
  10. funpaybotengine-0.3.6/comments.json +0 -1
  11. funpaybotengine-0.3.6/funpaybotengine/runner/config.py +0 -49
  12. funpaybotengine-0.3.6/html.html +0 -209
  13. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/.github/CODEOWNERS +0 -0
  14. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/.gitignore +0 -0
  15. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/.pre-commit-config.yaml +0 -0
  16. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/LICENSE +0 -0
  17. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/Makefile +0 -0
  18. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/README.md +0 -0
  19. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/examples/basic.py +0 -0
  20. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/examples/temp.py +0 -0
  21. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/examples/with_redis_storage.py +0 -0
  22. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/__init__.py +0 -0
  23. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/base.py +0 -0
  24. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/client/__init__.py +0 -0
  25. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/client/bot.py +0 -0
  26. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/client/default_hooks.py +0 -0
  27. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/client/session/__init__.py +0 -0
  28. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/client/session/aiohttp_session.py +0 -0
  29. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/client/session/base.py +0 -0
  30. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/client/session/http_methods.py +0 -0
  31. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/dispatching/__init__.py +0 -0
  32. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/dispatching/events/__init__.py +0 -0
  33. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/dispatching/filters/__init__.py +0 -0
  34. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/dispatching/filters/base.py +0 -0
  35. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/dispatching/filters/message_filters.py +0 -0
  36. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/dispatching/filters/order_filters.py +0 -0
  37. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/dispatching/handlers/__init__.py +0 -0
  38. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/dispatching/handlers/handler_manager.py +0 -0
  39. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/dispatching/routers/__init__.py +0 -0
  40. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/dispatching/routers/base.py +0 -0
  41. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/dispatching/routers/dispatcher.py +0 -0
  42. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/exceptions/__init__.py +0 -0
  43. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/exceptions/action_exceptions.py +0 -0
  44. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/exceptions/base.py +0 -0
  45. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/exceptions/bot_exceptions.py +0 -0
  46. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/exceptions/message_check_errors.py +0 -0
  47. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/exceptions/runner_exceptions.py +0 -0
  48. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/exceptions/session_exceptions.py +0 -0
  49. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/loggers.py +0 -0
  50. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/__init__.py +0 -0
  51. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/base.py +0 -0
  52. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/calc_chips.py +0 -0
  53. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/calc_lots.py +0 -0
  54. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/check_banned.py +0 -0
  55. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/delete_review.py +0 -0
  56. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/get_2fa_status.py +0 -0
  57. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/get_chat_history.py +0 -0
  58. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/get_chat_page.py +0 -0
  59. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/get_main_page.py +0 -0
  60. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/get_my_chips_page.py +0 -0
  61. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/get_my_offers_page.py +0 -0
  62. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/get_offer_fields.py +0 -0
  63. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/get_offer_page.py +0 -0
  64. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/get_order_page.py +0 -0
  65. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/get_profile_page.py +0 -0
  66. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/get_purchases.py +0 -0
  67. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/get_reviews.py +0 -0
  68. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/get_sales.py +0 -0
  69. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/get_settings_page.py +0 -0
  70. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/get_subcategory_page.py +0 -0
  71. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/get_telegram_connect_url.py +0 -0
  72. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/get_transactions.py +0 -0
  73. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/get_transactions_page.py +0 -0
  74. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/logout.py +0 -0
  75. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/mute_chat.py +0 -0
  76. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/raise_offers.py +0 -0
  77. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/refund.py +0 -0
  78. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/review.py +0 -0
  79. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/runner_request.py +0 -0
  80. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/save_offer_fields.py +0 -0
  81. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/set_offers_hidden.py +0 -0
  82. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/update_notice_channel.py +0 -0
  83. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/upload_avatar.py +0 -0
  84. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/methods/upload_image.py +0 -0
  85. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/py.typed +0 -0
  86. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/runner/__init__.py +0 -0
  87. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/runner/event_collector.py +0 -0
  88. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/storage/__init__.py +0 -0
  89. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/storage/base.py +0 -0
  90. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/storage/inmemory.py +0 -0
  91. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/storage/redis.py +0 -0
  92. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/__init__.py +0 -0
  93. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/base.py +0 -0
  94. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/calc.py +0 -0
  95. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/categories.py +0 -0
  96. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/chat.py +0 -0
  97. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/common.py +0 -0
  98. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/common_page_elements.py +0 -0
  99. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/enums.py +0 -0
  100. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/finances.py +0 -0
  101. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/offers.py +0 -0
  102. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/pages/__init__.py +0 -0
  103. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/pages/base.py +0 -0
  104. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/pages/chat_page.py +0 -0
  105. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/pages/main_page.py +0 -0
  106. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/pages/my_chips_page.py +0 -0
  107. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/pages/my_offers_page.py +0 -0
  108. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/pages/offer_page.py +0 -0
  109. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/pages/order_page.py +0 -0
  110. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/pages/profile_page.py +0 -0
  111. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/pages/settings_page.py +0 -0
  112. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/pages/subcategory_page.py +0 -0
  113. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/pages/transactions_page.py +0 -0
  114. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/requests/__init__.py +0 -0
  115. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/requests/runner.py +0 -0
  116. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/reviews.py +0 -0
  117. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/settings.py +0 -0
  118. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/types/updates.py +0 -0
  119. {funpaybotengine-0.3.6 → funpaybotengine-0.4.1}/funpaybotengine/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: funpaybotengine
3
- Version: 0.3.6
3
+ Version: 0.4.1
4
4
  Summary: Framework for creating FunPay bots.
5
5
  Project-URL: Telegram, https://t.me/funpay_hub
6
6
  Project-URL: Issues, https://github.com/funpayhub/funpaybotengine/issues
@@ -18,11 +18,11 @@ Classifier: Programming Language :: Python :: 3.13
18
18
  Classifier: Programming Language :: Python :: 3.14
19
19
  Classifier: Programming Language :: Python :: 3.15
20
20
  Requires-Python: >=3.10
21
- Requires-Dist: aiohttp-socks~=0.10.1
22
- Requires-Dist: aiohttp>=3.13.3
23
- Requires-Dist: eventry<0.4.0,>=0.3.3
21
+ Requires-Dist: aiohttp-socks>=0.11.0
22
+ Requires-Dist: aiohttp<3.14,>=3.13.3
23
+ Requires-Dist: eventry<1.0.0,>=0.3.6
24
24
  Requires-Dist: funpayparsers<1.0.0,>=0.5.14
25
- Requires-Dist: pydantic~=2.11.7
25
+ Requires-Dist: pydantic<2.13,>=2.12
26
26
  Provides-Extra: dev
27
27
  Requires-Dist: mypy>=1.10.1; extra == 'dev'
28
28
  Requires-Dist: pre-commit>=4.5.1; extra == 'dev'
@@ -1,7 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
3
 
4
- __all__ = ('Event', 'RunnerEvent', 'BotEngineEvent', 'ExceptionEvent')
4
+ __all__ = (
5
+ 'Event',
6
+ 'RunnerEvent',
7
+ 'BotEngineEvent',
8
+ 'ExceptionEvent',
9
+ 'BotUnauthenticatedEvent',
10
+ 'BotAuthenticatedEvent',
11
+ )
5
12
 
6
13
  from typing import Any, Generic, TypeVar
7
14
  from types import MappingProxyType
@@ -16,7 +23,7 @@ EventObject = TypeVar('EventObject')
16
23
 
17
24
 
18
25
  # Not inheriting from ExtendedEvent, cz __iter__ conflict is pydantic BaseModel.
19
- class Event(EventryEvent, BindableObject, Generic[EventObject], name='event'):
26
+ class Event(EventryEvent, BindableObject, Generic[EventObject], event_name='event'):
20
27
  model_config = {
21
28
  'arbitrary_types_allowed': True,
22
29
  }
@@ -66,7 +73,7 @@ class Event(EventryEvent, BindableObject, Generic[EventObject], name='event'):
66
73
  return MappingProxyType(self._data)
67
74
 
68
75
 
69
- class RunnerEvent(Event[EventObject], name='runner'):
76
+ class RunnerEvent(Event[EventObject], event_name='runner'):
70
77
  tag: str | None = Field(frozen=True)
71
78
 
72
79
  @property
@@ -76,10 +83,10 @@ class RunnerEvent(Event[EventObject], name='runner'):
76
83
  return injection
77
84
 
78
85
 
79
- class BotEngineEvent(Event[EventObject], name='funpaybotengine'): ...
86
+ class BotEngineEvent(Event[EventObject], event_name='funpaybotengine'): ...
80
87
 
81
88
 
82
- class ExceptionEvent(BotEngineEvent[Exception], name='error'):
89
+ class ExceptionEvent(BotEngineEvent[Exception], event_name='error'):
83
90
  event: Event[Any] = Field(frozen=True)
84
91
 
85
92
  @property
@@ -87,3 +94,16 @@ class ExceptionEvent(BotEngineEvent[Exception], name='error'):
87
94
  injection = super().event_context_injection
88
95
  injection.update({'on_event': self.event, 'exception': self.object})
89
96
  return injection
97
+
98
+
99
+ class BotUnauthenticatedEvent(BotEngineEvent[float], event_name='unauthorized'):
100
+ delay: float
101
+
102
+ @property
103
+ def event_context_injection(self) -> dict[str, Any]:
104
+ injection = super().event_context_injection
105
+ injection.update({'delay': self.delay})
106
+ return injection
107
+
108
+
109
+ class BotAuthenticatedEvent(BotEngineEvent[None], event_name='authorized'): ...
@@ -43,7 +43,7 @@ from .base import RunnerEvent
43
43
  from ...types.pages import OrderPage
44
44
 
45
45
 
46
- class ChatChangedEvent(RunnerEvent[PrivateChatPreview], name='chat_changed'):
46
+ class ChatChangedEvent(RunnerEvent[PrivateChatPreview], event_name='chat_changed'):
47
47
  previous: PrivateChatPreview | None = None
48
48
 
49
49
  @property
@@ -57,7 +57,7 @@ class ChatChangedEvent(RunnerEvent[PrivateChatPreview], name='chat_changed'):
57
57
  }
58
58
 
59
59
 
60
- class NewMessageEvent(RunnerEvent[Message], name='new_message'):
60
+ class NewMessageEvent(RunnerEvent[Message], event_name='new_message'):
61
61
  @property
62
62
  def message(self) -> Message:
63
63
  return self.object
@@ -69,7 +69,7 @@ class NewMessageEvent(RunnerEvent[Message], name='new_message'):
69
69
  }
70
70
 
71
71
 
72
- class FromMessageEvent(NewMessageEvent, name='__from_message__'):
72
+ class FromMessageEvent(NewMessageEvent, event_name='__from_message__'):
73
73
  related_new_message_event: NewMessageEvent
74
74
 
75
75
  @property
@@ -79,14 +79,14 @@ class FromMessageEvent(NewMessageEvent, name='__from_message__'):
79
79
  return val
80
80
 
81
81
 
82
- class OrderEvent(FromMessageEvent, name='__order_event__'):
82
+ class OrderEvent(FromMessageEvent, event_name='__order_event__'):
83
83
  _order_preview: OrderPreview | None = PrivateAttr(default=None)
84
84
 
85
85
  async def get_order_preview(self, update: bool = False) -> OrderPreview:
86
86
  raise NotImplementedError
87
87
 
88
88
 
89
- class SaleEvent(OrderEvent, name='__sale_event__'):
89
+ class SaleEvent(OrderEvent, event_name='__sale_event__'):
90
90
  async def get_order_preview(self, update: bool = False) -> OrderPreview:
91
91
  if self._order_preview is not None and not update:
92
92
  return self._order_preview
@@ -95,7 +95,7 @@ class SaleEvent(OrderEvent, name='__sale_event__'):
95
95
  return orders.orders[0]
96
96
 
97
97
 
98
- class PurchaseEvent(OrderEvent, name='__purchase_event__'):
98
+ class PurchaseEvent(OrderEvent, event_name='__purchase_event__'):
99
99
  async def get_order_preview(self, update: bool = False) -> OrderPreview:
100
100
  if self._order_preview is not None and not update:
101
101
  return self._order_preview
@@ -106,55 +106,55 @@ class PurchaseEvent(OrderEvent, name='__purchase_event__'):
106
106
  return orders.orders[0]
107
107
 
108
108
 
109
- class NewSaleEvent(SaleEvent, name='new_sale'):
109
+ class NewSaleEvent(SaleEvent, event_name='new_sale'):
110
110
  related_auto_message_events: list[NewMessageEvent] = Field(default_factory=list)
111
111
 
112
112
 
113
- class SaleStatusChangedEvent(SaleEvent, name='sale_status_changed'):
113
+ class SaleStatusChangedEvent(SaleEvent, event_name='sale_status_changed'):
114
114
  previous: OrderPreview | None = None
115
115
 
116
116
 
117
- class SaleClosedEvent(SaleStatusChangedEvent, name='sale_closed'): ...
117
+ class SaleClosedEvent(SaleStatusChangedEvent, event_name='sale_closed'): ...
118
118
 
119
119
 
120
- class SaleClosedByAdminEvent(SaleClosedEvent, name='sale_closed_by_admin'): ...
120
+ class SaleClosedByAdminEvent(SaleClosedEvent, event_name='sale_closed_by_admin'): ...
121
121
 
122
122
 
123
- class SaleRefundedEvent(SaleStatusChangedEvent, name='sale_refunded'): ...
123
+ class SaleRefundedEvent(SaleStatusChangedEvent, event_name='sale_refunded'): ...
124
124
 
125
125
 
126
- class SalePartiallyRefundedEvent(SaleRefundedEvent, name='sale_partially_refunded'): ...
126
+ class SalePartiallyRefundedEvent(SaleRefundedEvent, event_name='sale_partially_refunded'): ...
127
127
 
128
128
 
129
- class SaleReopenedEvent(SaleStatusChangedEvent, name='sale_reopened'): ...
129
+ class SaleReopenedEvent(SaleStatusChangedEvent, event_name='sale_reopened'): ...
130
130
 
131
131
 
132
- class NewPurchaseEvent(PurchaseEvent, name='new_purchase'):
132
+ class NewPurchaseEvent(PurchaseEvent, event_name='new_purchase'):
133
133
  related_auto_message_events: list[NewMessageEvent] = Field(default_factory=list)
134
134
 
135
135
 
136
- class PurchaseStatusChangedEvent(PurchaseEvent, name='purchase_status_changed'):
136
+ class PurchaseStatusChangedEvent(PurchaseEvent, event_name='purchase_status_changed'):
137
137
  previous: OrderPreview | None = None
138
138
 
139
139
 
140
- class PurchaseClosedEvent(PurchaseStatusChangedEvent, name='purchase_closed'): ...
140
+ class PurchaseClosedEvent(PurchaseStatusChangedEvent, event_name='purchase_closed'): ...
141
141
 
142
142
 
143
- class PurchaseClosedByAdminEvent(PurchaseClosedEvent, name='purchase_closed_by_admin'): ...
143
+ class PurchaseClosedByAdminEvent(PurchaseClosedEvent, event_name='purchase_closed_by_admin'): ...
144
144
 
145
145
 
146
- class PurchaseRefundedEvent(PurchaseStatusChangedEvent, name='purchase_refunded'): ...
146
+ class PurchaseRefundedEvent(PurchaseStatusChangedEvent, event_name='purchase_refunded'): ...
147
147
 
148
148
 
149
149
  class PurchasePartiallyRefundedEvent(
150
- PurchaseRefundedEvent, name='purchase_partially_refunded'
150
+ PurchaseRefundedEvent, event_name='purchase_partially_refunded'
151
151
  ): ...
152
152
 
153
153
 
154
- class PurchaseReopenedEvent(PurchaseStatusChangedEvent, name='purchase_reopened'): ...
154
+ class PurchaseReopenedEvent(PurchaseStatusChangedEvent, event_name='purchase_reopened'): ...
155
155
 
156
156
 
157
- class ReviewEvent(FromMessageEvent, name='__review_event__'):
157
+ class ReviewEvent(FromMessageEvent, event_name='__review_event__'):
158
158
  _order_page: OrderPage | None = PrivateAttr(default=None)
159
159
 
160
160
  async def get_order_page(self, update: bool = False) -> OrderPage:
@@ -169,19 +169,19 @@ class ReviewEvent(FromMessageEvent, name='__review_event__'):
169
169
  return order_page.review
170
170
 
171
171
 
172
- class NewReviewEvent(ReviewEvent, name='new_review'): ...
172
+ class NewReviewEvent(ReviewEvent, event_name='new_review'): ...
173
173
 
174
174
 
175
- class ReviewChangedEvent(ReviewEvent, name='review_changed'): ...
175
+ class ReviewChangedEvent(ReviewEvent, event_name='review_changed'): ...
176
176
 
177
177
 
178
- class ReviewDeletedEvent(ReviewEvent, name='review_deleted'): ...
178
+ class ReviewDeletedEvent(ReviewEvent, event_name='review_deleted'): ...
179
179
 
180
180
 
181
- class NewReviewResponseEvent(ReviewEvent, name='new_review_response'): ...
181
+ class NewReviewResponseEvent(ReviewEvent, event_name='new_review_response'): ...
182
182
 
183
183
 
184
- class ReviewResponseChangedEvent(ReviewEvent, name='review_response_changed'): ...
184
+ class ReviewResponseChangedEvent(ReviewEvent, event_name='review_response_changed'): ...
185
185
 
186
186
 
187
- class ReviewResponseDeletedEvent(ReviewEvent, name='review_response_deleted'): ...
187
+ class ReviewResponseDeletedEvent(ReviewEvent, event_name='review_response_deleted'): ...
@@ -0,0 +1,116 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ __all__ = ('RunnerConfig', 'BackoffConfig', 'Backoff')
5
+
6
+
7
+ import asyncio
8
+ from typing import Literal
9
+ from dataclasses import dataclass
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class BackoffConfig:
14
+ min_delay: float = 6.0
15
+ max_delay: float = 18.0
16
+ factor: float = 1.3
17
+
18
+ def __post_init__(self) -> None:
19
+ if self.max_delay <= self.min_delay:
20
+ raise ValueError('`max_delay` must be greater than `min_delay`.')
21
+ if self.factor <= 1:
22
+ raise ValueError('`factor` must be greater than 1')
23
+
24
+
25
+ @dataclass
26
+ class RunnerConfig:
27
+ interval: int | float = 4.0
28
+ """
29
+ Interval between updates requests.
30
+ Must be more than ``0``.
31
+
32
+ Defaults to ``4.0``.
33
+ """
34
+
35
+ discover_sales: bool = True
36
+ """Whether to discover new sales or not."""
37
+
38
+ discover_purchases: bool = True
39
+ """Whether to discover new purchases or not."""
40
+
41
+ keep_unread: bool = False
42
+ """
43
+ Whether to preserve the unread status of chats when getting updates.
44
+
45
+ If ``False`` (default), a batch method is used: up to 10 chats can be requested in a single
46
+ request, but they will be marked as read.
47
+
48
+ If ``True``, only one chat can be fetched per request to keep it unread.
49
+ With high incoming message volume this leads to many requests and may cause
50
+ ``429 Too Many Requests`` errors.
51
+ """
52
+
53
+ on_unauthenticated_error_policy: Literal['ignore', 'event', 'stop'] = 'ignore'
54
+ """
55
+ What to do when an `BotUnauthenticatedError` occurred during fetching updates process?
56
+
57
+ - ``ignore``: ignore the error and continue fetching updates.
58
+ - ``event``: yield an error event and continue fetching updates.
59
+ - ``stop``: stop fetching updates, exit a function.
60
+
61
+ Defaults to ``ignore``.
62
+ """
63
+
64
+ backoff_config: BackoffConfig = BackoffConfig()
65
+
66
+
67
+ class Backoff:
68
+ def __init__(self, config: BackoffConfig) -> None:
69
+ self.config = config
70
+ self._next_delay = config.min_delay
71
+ self._current_delay = 0.0
72
+ self._counter = 0
73
+
74
+ def __iter__(self) -> Backoff:
75
+ return self
76
+
77
+ @property
78
+ def min_delay(self) -> float:
79
+ return self.config.min_delay
80
+
81
+ @property
82
+ def max_delay(self) -> float:
83
+ return self.config.max_delay
84
+
85
+ @property
86
+ def factor(self) -> float:
87
+ return self.config.factor
88
+
89
+ @property
90
+ def next_delay(self) -> float:
91
+ return self._next_delay
92
+
93
+ @property
94
+ def current_delay(self) -> float:
95
+ return self._current_delay
96
+
97
+ @property
98
+ def counter(self) -> int:
99
+ return self._counter
100
+
101
+ async def asleep(self) -> None:
102
+ await asyncio.sleep(next(self))
103
+
104
+ def _calculate_next(self, value: float) -> float:
105
+ return min(value * self.factor, self.max_delay)
106
+
107
+ def __next__(self) -> float:
108
+ self._current_delay = self._next_delay
109
+ self._next_delay = self._calculate_next(self._next_delay)
110
+ self._counter += 1
111
+ return self._current_delay
112
+
113
+ def reset(self) -> None:
114
+ self._current_delay = 0.0
115
+ self._counter = 0
116
+ self._next_delay = self.min_delay
@@ -14,13 +14,15 @@ from collections.abc import Iterator, AsyncGenerator
14
14
  from funpaybotengine.loggers import runner_logger
15
15
  from funpaybotengine.exceptions import UnauthorizedError, BotUnauthenticatedError
16
16
  from funpaybotengine.storage.base import Storage
17
- from funpaybotengine.runner.config import RunnerConfig
18
- from funpaybotengine.runner.event_collector import EventCollector
19
- from funpaybotengine.dispatching.events.base import RunnerEvent
17
+ from funpaybotengine.dispatching.events import BotAuthenticatedEvent, BotUnauthenticatedEvent
18
+
19
+ from .config import Backoff, RunnerConfig
20
+ from .event_collector import EventCollector
20
21
 
21
22
 
22
23
  if TYPE_CHECKING:
23
24
  from funpaybotengine.client.bot import Bot
25
+ from funpaybotengine.dispatching.events import RunnerEvent, BotEngineEvent
24
26
 
25
27
 
26
28
  @dataclass
@@ -64,34 +66,62 @@ class Runner:
64
66
  config,
65
67
  session_storage=session_storage,
66
68
  )
69
+ backoff = Backoff(config.backoff_config)
67
70
 
68
71
  await collector.init_chats()
69
72
 
73
+ sleep_time: float | None = None
70
74
  while True:
71
75
  start = time.time()
76
+ result: list[RunnerEvent[Any] | BotEngineEvent[Any]] = []
72
77
 
73
78
  try:
74
79
  result = await collector.get_events()
80
+ if backoff.counter:
81
+ backoff.reset()
82
+ runner_logger.info('Connection established. Continuing collecting events.')
83
+ if config.on_unauthenticated_error_policy == 'event':
84
+ result.insert(0, BotAuthenticatedEvent(object=None))
85
+
75
86
  except (BotUnauthenticatedError, UnauthorizedError) as e:
76
87
  runner_logger.warning(
77
88
  'Bot is unauthenticated (%s). Executing current policy %r.',
78
89
  e.__class__.__name__,
79
90
  config.on_unauthenticated_error_policy,
80
91
  )
81
- if config.on_unauthenticated_error_policy == 'event':
82
- ...
92
+
93
+ if config.on_unauthenticated_error_policy in ['event', 'ignore']:
94
+ next(backoff)
95
+ sleep_time = backoff.current_delay
96
+ runner_logger.warning(
97
+ 'Current attempt: %d. Delay: %f.', backoff.counter, backoff.current_delay
98
+ )
99
+ if backoff.counter == 1 and config.on_unauthenticated_error_policy == 'event':
100
+ result = [
101
+ BotUnauthenticatedEvent(object=None, delay=backoff.current_delay)
102
+ ]
103
+
83
104
  elif config.on_unauthenticated_error_policy == 'stop':
84
105
  return
85
- elif config.on_unauthenticated_error_policy == 'stop+event':
86
- return # todo yield event
87
- await _sleep(start, config.interval)
88
- continue
106
+ except Exception:
107
+ next(backoff)
108
+ runner_logger.error(
109
+ 'Failed to collect events. Current attempt: %d. Delay: %f.',
110
+ backoff.counter,
111
+ backoff.current_delay,
112
+ exc_info=True,
113
+ )
114
+ sleep_time = backoff.current_delay
89
115
 
90
116
  events_stack = EventsStack(events=tuple(result))
91
117
  for i in events_stack:
92
118
  yield i, events_stack
93
119
 
94
- await _sleep(start, config.interval)
120
+ if sleep_time:
121
+ await asyncio.sleep(sleep_time)
122
+ sleep_time = None
123
+ else:
124
+ await _sleep(start, config.interval)
95
125
 
96
126
 
97
127
  async def _sleep(start_time: int | float, interval: int | float) -> None:
@@ -216,7 +216,6 @@ class Message(FunPayObject, BaseModel):
216
216
 
217
217
  return info.context.get('chat_id') if info.context else None
218
218
 
219
-
220
219
  @field_validator('chat_name', mode='before')
221
220
  @classmethod
222
221
  def _get_chat_name_from_context(cls, value: Any, info: ValidationInfo) -> Any:
@@ -6,7 +6,8 @@ __all__ = ('OrderPreview', 'OrderPreviewsBatch')
6
6
 
7
7
  from typing import Any
8
8
 
9
- from pydantic import BaseModel, PrivateAttr, computed_field
9
+ from pydantic import BaseModel, PrivateAttr
10
+ from funpayparsers.parsers.utils import parse_date_string
10
11
 
11
12
  from funpaybotengine.types.base import FunPayObject
12
13
  from funpaybotengine.types.enums import OrderStatus, OrderPreviewType
@@ -18,7 +19,11 @@ class OrderPreview(FunPayObject, BaseModel):
18
19
 
19
20
  def model_post_init(self, context: dict[Any, Any]) -> None:
20
21
  super().model_post_init(context)
21
- if context and context.get('order_preview_type') is not None:
22
+ if (
23
+ self.type is OrderPreviewType.UNKNOWN
24
+ and context
25
+ and context.get('order_preview_type') is not None
26
+ ):
22
27
  self._type = context['order_preview_type']
23
28
 
24
29
  id: str
@@ -42,12 +47,20 @@ class OrderPreview(FunPayObject, BaseModel):
42
47
  counterparty: UserPreview
43
48
  """Associated counterparty info."""
44
49
 
45
- _type: OrderPreviewType = PrivateAttr(OrderPreviewType.UNKNOWN)
50
+ type: OrderPreviewType = OrderPreviewType.UNKNOWN
51
+ """Order preview type."""
46
52
 
47
- @computed_field # type: ignore[prop-decorator]
48
53
  @property
49
- def type(self) -> OrderPreviewType:
50
- return self._type
54
+ def timestamp(self) -> int:
55
+ """
56
+ Order timestamp.
57
+
58
+ ``0``, if an error occurred while parsing.
59
+ """
60
+ try:
61
+ return parse_date_string(self.date_text)
62
+ except ValueError:
63
+ return 0
51
64
 
52
65
 
53
66
  class OrderPreviewsBatch(FunPayObject):
@@ -114,32 +127,26 @@ class OrderPreviewsBatch(FunPayObject):
114
127
 
115
128
  return await method_coroutine
116
129
 
117
- @computed_field # type: ignore[prop-decorator]
118
130
  @property
119
131
  def order_id_filter(self) -> str | None:
120
132
  return self._order_id_filter
121
133
 
122
- @computed_field # type: ignore[prop-decorator]
123
134
  @property
124
135
  def buyer_username_filter(self) -> str | None:
125
136
  return self._buyer_username_filter
126
137
 
127
- @computed_field # type: ignore[prop-decorator]
128
138
  @property
129
139
  def status_filter(self) -> OrderStatus | str | None:
130
140
  return self._status_filter
131
141
 
132
- @computed_field # type: ignore[prop-decorator]
133
142
  @property
134
143
  def game_id_filter(self) -> int | None:
135
144
  return self._game_id_filter
136
145
 
137
- @computed_field # type: ignore[prop-decorator]
138
146
  @property
139
147
  def other_filters(self) -> dict[str, str] | None:
140
148
  return self._other_filters
141
149
 
142
- @computed_field # type: ignore[prop-decorator]
143
150
  @property
144
151
  def type(self) -> OrderPreviewType:
145
152
  return self._type
@@ -1,16 +1,16 @@
1
1
  [project]
2
2
  name = "funpaybotengine"
3
- version = "0.3.6"
3
+ version = "0.4.1"
4
4
  description = "Framework for creating FunPay bots."
5
5
  requires-python = ">=3.10"
6
6
  readme = "README.md"
7
7
  license = "MIT"
8
8
  dependencies = [
9
- "aiohttp>=3.13.3",
10
- "aiohttp-socks~=0.10.1",
11
- "eventry>=0.3.3,<0.4.0",
9
+ "aiohttp>=3.13.3,<3.14",
10
+ "aiohttp-socks>=0.11.0",
11
+ "eventry>=0.3.6,<1.0.0",
12
12
  "funpayparsers>=0.5.14,<1.0.0",
13
- "pydantic~=2.11.7",
13
+ "pydantic>=2.12,<2.13",
14
14
  ]
15
15
  authors = [
16
16
  {name = "Qvvonk", email="qvvonk@gmail.com"}
File without changes
@@ -1 +0,0 @@
1
- []
@@ -1,49 +0,0 @@
1
- from __future__ import annotations
2
-
3
-
4
- __all__ = ('RunnerConfig',)
5
-
6
-
7
- from typing import Literal
8
- from dataclasses import dataclass
9
-
10
-
11
- @dataclass
12
- class RunnerConfig:
13
- interval: int | float = 4.0
14
- """
15
- Interval between updates requests.
16
- Must be more than ``0``.
17
-
18
- Defaults to ``4.0``.
19
- """
20
-
21
- discover_sales: bool = True
22
- """Whether to discover new sales or not."""
23
-
24
- discover_purchases: bool = True
25
- """Whether to discover new purchases or not."""
26
-
27
- keep_unread: bool = False
28
- """
29
- Whether to preserve the unread status of chats when getting updates.
30
-
31
- If ``False`` (default), a batch method is used: up to 10 chats can be requested in a single
32
- request, but they will be marked as read.
33
-
34
- If ``True``, only one chat can be fetched per request to keep it unread.
35
- With high incoming message volume this leads to many requests and may cause
36
- ``429 Too Many Requests`` errors.
37
- """
38
-
39
- on_unauthenticated_error_policy: Literal['ignore', 'event', 'stop', 'stop+event'] = 'ignore'
40
- """
41
- What to do when an `BotUnauthenticatedError` occurred during fetching updates process?
42
-
43
- - ``ignore``: ignore the error and continue fetching updates.
44
- - ``event``: yield an error event and continue fetching updates.
45
- - ``stop``: stop fetching updates, exit a function.
46
- - ``stop+event``: yield an error event and stop fetching updates, exit a function.
47
-
48
- Defaults to ``ignore``.
49
- """