Skip to content

Commit

Permalink
feat: subscriptions & related changes (#2564)
Browse files Browse the repository at this point in the history
* feat: subscriptions & related changes

* style(pre-commit): auto fixes from pre-commit.com hooks

* feat: changelog

* feat: changelog

* style(pre-commit): auto fixes from pre-commit.com hooks

* fix: move abc import to TYPE_CHECKING

* style(pre-commit): auto fixes from pre-commit.com hooks

* fix: circular import from Entitlement

* docs: correct directives from notice to note

* Update discord/enums.py

Signed-off-by: Lala Sabathil <[email protected]>

* despite what it looks like, this is a lazily written commit message

* despite what it looks like, this is a lazily written commit message 2

* fix bugs

* style(pre-commit): auto fixes from pre-commit.com hooks

* Update CHANGELOG.md

Signed-off-by: plun1331 <[email protected]>

* Update iterators.py

Signed-off-by: plun1331 <[email protected]>

* apply review suggestions

* Update monetization.py

Signed-off-by: plun1331 <[email protected]>

* Update monetization.py

Signed-off-by: plun1331 <[email protected]>

---------

Signed-off-by: Lala Sabathil <[email protected]>
Signed-off-by: plun1331 <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Lala Sabathil <[email protected]>
  • Loading branch information
3 people authored Jan 31, 2025
1 parent 19721e2 commit beeef7d
Show file tree
Hide file tree
Showing 11 changed files with 447 additions and 54 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ These changes are available on the `master` branch, but have not yet been releas
([#2659](https://github.com/Pycord-Development/pycord/pull/2659))
- Added `VoiceMessage` subclass of `File` to allow voice messages to be sent.
([#2579](https://github.com/Pycord-Development/pycord/pull/2579))
- Added new `Subscription` object and related methods/events.
([#2564](https://github.com/Pycord-Development/pycord/pull/2564))

### Fixed

Expand Down Expand Up @@ -104,6 +106,8 @@ These changes are available on the `master` branch, but have not yet been releas
([#2176](https://github.com/Pycord-Development/pycord/pull/2176))
- Updated `Guild.filesize_limit` to 10 MB instead of 25 MB following Discord's API
changes. ([#2671](https://github.com/Pycord-Development/pycord/pull/2671))
- `Entitlement.ends_at` can now be `None`.
([#2564](https://github.com/Pycord-Development/pycord/pull/2564))

### Deprecated

Expand All @@ -112,6 +116,11 @@ These changes are available on the `master` branch, but have not yet been releas
- Deprecated `Emoji` in favor of `GuildEmoji`.
([#2501](https://github.com/Pycord-Development/pycord/pull/2501))

### Fixed

- Fixed `AttributeError` when trying to consume a consumable entitlement.
([#2564](https://github.com/Pycord-Development/pycord/pull/2564))

## [2.6.1] - 2024-09-15

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion discord/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2087,7 +2087,7 @@ async def fetch_skus(self) -> list[SKU]:
The bot's SKUs.
"""
data = await self._connection.http.list_skus(self.application_id)
return [SKU(data=s) for s in data]
return [SKU(state=self._connection, data=s) for s in data]

def entitlements(
self,
Expand Down
8 changes: 8 additions & 0 deletions discord/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,14 @@ class PollLayoutType(Enum):
default = 1


class SubscriptionStatus(Enum):
"""The status of a subscription."""

active = 0
ending = 1
inactive = 2


T = TypeVar("T")


Expand Down
37 changes: 37 additions & 0 deletions discord/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -3081,6 +3081,43 @@ def delete_test_entitlement(
)
return self.request(r)

def list_sku_subscriptions(
self,
sku_id: Snowflake,
*,
before: Snowflake | None = None,
after: Snowflake | None = None,
limit: int = 50,
user_id: Snowflake | None = None,
) -> Response[list[monetization.Subscription]]:
params: dict[str, Any] = {}
if before is not None:
params["before"] = before
if after is not None:
params["after"] = after
if limit is not None:
params["limit"] = limit
if user_id is not None:
params["user_id"] = user_id
return self.request(
Route("GET", "/skus/{sku_id}/subscriptions", sku_id=sku_id),
params=params,
)

def get_subscription(
self,
sku_id: Snowflake,
subscription_id: Snowflake,
) -> Response[monetization.Subscription]:
return self.request(
Route(
"GET",
"/skus/{sku_id}/subscriptions/{subscription_id}",
sku_id=sku_id,
subscription_id=subscription_id,
)
)

# Onboarding

def get_onboarding(self, guild_id: Snowflake) -> Response[onboarding.Onboarding]:
Expand Down
115 changes: 112 additions & 3 deletions discord/iterators.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@

from .audit_logs import AuditLogEntry
from .errors import NoMoreItems
from .monetization import Entitlement
from .object import Object
from .utils import maybe_coroutine, snowflake_time, time_snowflake

Expand All @@ -52,19 +51,22 @@
"MemberIterator",
"ScheduledEventSubscribersIterator",
"EntitlementIterator",
"SubscriptionIterator",
)

if TYPE_CHECKING:
from .abc import Snowflake
from .guild import BanEntry, Guild
from .member import Member
from .message import Message
from .monetization import Entitlement, Subscription
from .scheduled_events import ScheduledEvent
from .threads import Thread
from .types.audit_log import AuditLog as AuditLogPayload
from .types.guild import Guild as GuildPayload
from .types.message import Message as MessagePayload
from .types.monetization import Entitlement as EntitlementPayload
from .types.monetization import Subscription as SubscriptionPayload
from .types.threads import Thread as ThreadPayload
from .types.user import PartialUser as PartialUserPayload
from .user import User
Expand Down Expand Up @@ -1031,6 +1033,11 @@ def _get_retrieve(self):
self.retrieve = r
return r > 0

def create_entitlement(self, data) -> Entitlement:
from .monetization import Entitlement

return Entitlement(data=data, state=self.state)

async def fill_entitlements(self):
if not self._get_retrieve():
return
Expand All @@ -1044,9 +1051,9 @@ async def fill_entitlements(self):
self.limit = 0 # terminate loop

for element in data:
await self.entitlements.put(Entitlement(data=element, state=self.state))
await self.entitlements.put(self.create_entitlement(element))

async def _retrieve_entitlements(self, retrieve) -> list[Entitlement]:
async def _retrieve_entitlements(self, retrieve) -> list[EntitlementPayload]:
"""Retrieve entitlements and update next parameters."""
raise NotImplementedError

Expand Down Expand Up @@ -1089,3 +1096,105 @@ async def _retrieve_entitlements_after_strategy(
self.limit -= retrieve
self.after = Object(id=int(data[-1]["id"]))
return data


class SubscriptionIterator(_AsyncIterator["Subscription"]):
def __init__(
self,
state,
sku_id: int,
limit: int = None,
before: datetime.datetime | None = None,
after: datetime.datetime | None = None,
user_id: int | None = None,
):
if isinstance(before, datetime.datetime):
before = Object(id=time_snowflake(before, high=False))
if isinstance(after, datetime.datetime):
after = Object(id=time_snowflake(after, high=True))

self.state = state
self.sku_id = sku_id
self.limit = limit
self.before = before
self.after = after
self.user_id = user_id

self._filter = None

self.get_subscriptions = state.http.list_sku_subscriptions
self.subscriptions = asyncio.Queue()

if self.before and self.after:
self._retrieve_subscriptions = self._retrieve_subscriptions_before_strategy
self._filter = lambda m: int(m["id"]) > self.after.id
elif self.after:
self._retrieve_subscriptions = self._retrieve_subscriptions_after_strategy
else:
self._retrieve_subscriptions = self._retrieve_subscriptions_before_strategy

async def next(self) -> Guild:
if self.subscriptions.empty():
await self.fill_subscriptions()

try:
return self.subscriptions.get_nowait()
except asyncio.QueueEmpty:
raise NoMoreItems()

def _get_retrieve(self):
l = self.limit
if l is None or l > 100:
r = 100
else:
r = l
self.retrieve = r
return r > 0

def create_subscription(self, data) -> Subscription:
from .monetization import Subscription

return Subscription(state=self.state, data=data)

async def fill_subscriptions(self):
if self._get_retrieve():
data = await self._retrieve_subscriptions(self.retrieve)
if self.limit is None or len(data) < 100:
self.limit = 0

if self._filter:
data = filter(self._filter, data)

for element in data:
await self.subscriptions.put(self.create_subscription(element))

async def _retrieve_subscriptions(self, retrieve) -> list[SubscriptionPayload]:
raise NotImplementedError

async def _retrieve_subscriptions_before_strategy(self, retrieve):
before = self.before.id if self.before else None
data: list[SubscriptionPayload] = await self.get_subscriptions(
self.sku_id,
limit=retrieve,
before=before,
user_id=self.user_id,
)
if len(data):
if self.limit is not None:
self.limit -= retrieve
self.before = Object(id=int(data[-1]["id"]))
return data

async def _retrieve_subscriptions_after_strategy(self, retrieve):
after = self.after.id if self.after else None
data: list[SubscriptionPayload] = await self.get_subscriptions(
self.sku_id,
limit=retrieve,
after=after,
user_id=self.user_id,
)
if len(data):
if self.limit is not None:
self.limit -= retrieve
self.after = Object(id=int(data[0]["id"]))
return data
Loading

0 comments on commit beeef7d

Please sign in to comment.