Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pillow
pip install pytest
pip install pytest-asyncio
pip install -r requirements/dev.txt

- name: Test API
env:
Expand Down
2 changes: 1 addition & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ sphinx:

python:
install:
- requirements: docs/requirements.txt
- requirements: requirements/docs.txt
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,12 @@ async def main():
user_stats = await con.get_user_stats(123456789) # Replace with user ID
```

## ⚡ FastAPI Docs
If you want to use the API without this wrapper, you can find the FastAPI docs [here](https://api.cookie-bot.xyz/docs).
## ⚡ OpenAPI Docs
If you want to use the API without this wrapper, you can find the OpenAPI docs [here](https://api.cookieapp.me/docs).


## 🗿 Models
The models are automatically generated using the OpenAPI spec:
```
python cookie/_internal/model_generator.py
```
1 change: 1 addition & 0 deletions cookie/_internal/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .custom_models import BaseChart
29 changes: 29 additions & 0 deletions cookie/_internal/custom_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from pydantic import BaseModel


class BaseChart(BaseModel):
"""Base class for all charts that allows dictionary usage."""

def to_dict(self):
try:
return {d: value for d, value in zip(getattr(self, "x"), getattr(self, "y"))}
except AttributeError:
return self.model_dump()

def __getitem__(self, item):
return self.to_dict()[item]

def values(self):
return self.to_dict().values()

def keys(self):
return self.to_dict().keys()

def items(self):
return self.to_dict().items()

def __iter__(self):
return iter(self.to_dict())

def __len__(self):
return len(self.to_dict())
12 changes: 12 additions & 0 deletions cookie/_internal/formatter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from datamodel_code_generator.format import CustomCodeFormatter


class CodeFormatter(CustomCodeFormatter):
def apply(self, code: str) -> str:
# Import BaseChart
code = code.replace("\nclass", "from ._internal import BaseChart\n\n\nclass", 1)

# Let BaseChart inherit from BaseModel
code = code.replace("class Chart(BaseModel)", "class Chart(BaseChart)")

return code
24 changes: 24 additions & 0 deletions cookie/_internal/model_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""Code for model generation."""

from pathlib import Path
from urllib.parse import urlparse

from datamodel_code_generator import (
DataModelType,
InputFileType,
PythonVersion,
generate,
)

generate(
urlparse("https://api.cookieapp.me/openapi.json"),
input_file_type=InputFileType.OpenAPI,
use_union_operator=True,
use_double_quotes=True,
use_standard_collections=True,
target_python_version=PythonVersion.PY_39,
custom_formatters=["formatter"],
output=Path("cookie/models.py"),
output_model_type=DataModelType.PydanticV2BaseModel,
disable_timestamp=True,
)
51 changes: 19 additions & 32 deletions cookie/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,18 @@

import json
import os
from datetime import date, datetime
from typing import overload

import httpx
from dotenv import load_dotenv

from .errors import CookieError, InvalidAPIKey, NoGuildAccess, NotFound, QuotaExceeded
from .models import GuildActivity, MemberActivity, MemberStats, UserStats
from .models import GuildActivity, GuildStats, MemberActivity, MemberStats, UserStats

DEFAULT_DAYS = 14
BASE_URL = "https://api.cookieapp.me/v1/"


def _stats_dict(data: dict[str, int]) -> dict[date, int]:
return {datetime.strptime(d, "%Y-%m-%d").date(): count for d, count in data.items()}


def _handle_error(response: httpx.Response):
try:
data = response.json()
Expand Down Expand Up @@ -101,7 +96,7 @@ async def _get(self, endpoint: str, stream: bool = False):

return response.json()

async def get_member_count(self, guild_id: int, days: int = DEFAULT_DAYS) -> dict[date, int]:
async def get_guild_stats(self, guild_id: int, days: int = DEFAULT_DAYS) -> GuildStats:
"""Get the history of the guild member count for the provided number of days.

Parameters
Expand All @@ -116,9 +111,8 @@ async def get_member_count(self, guild_id: int, days: int = DEFAULT_DAYS) -> dic
NoGuildAccess:
You don't have access to that guild.
"""
message_data = await self._get(f"member_count/{guild_id}?days={days}")

return _stats_dict(message_data)
data = await self._get(f"stats/guild/{guild_id}?days={days}")
return GuildStats(**data)

async def get_user_stats(self, user_id: int) -> UserStats:
"""Get the user's level stats.
Expand All @@ -134,7 +128,7 @@ async def get_user_stats(self, user_id: int) -> UserStats:
The user was not found.
"""
data = await self._get(f"stats/user/{user_id}")
return UserStats(user_id, **data)
return UserStats(**data)

async def get_member_stats(self, user_id: int, guild_id: int) -> MemberStats:
"""Get the member's level stats.
Expand All @@ -152,10 +146,10 @@ async def get_member_stats(self, user_id: int, guild_id: int) -> MemberStats:
The user was not found.
"""
data = await self._get(f"stats/member/{user_id}/{guild_id}")
return MemberStats(user_id, guild_id, **data)
return MemberStats(**data)

async def get_member_activity(
self, user_id: int, guild_id: int, days: int = 14
self, user_id: int, guild_id: int, days: int = DEFAULT_DAYS
) -> MemberActivity:
"""Get the member's activity for the provided number of days.

Expand All @@ -174,9 +168,7 @@ async def get_member_activity(
The user was not found.
"""
data = await self._get(f"activity/member/{user_id}/{guild_id}?days={days}")
msg_activity = _stats_dict(data.pop("msg_activity"))
voice_activity = _stats_dict(data.pop("voice_activity"))
return MemberActivity(days, user_id, guild_id, msg_activity, voice_activity, **data)
return MemberActivity(**data)

async def get_guild_activity(self, guild_id: int, days: int = DEFAULT_DAYS) -> GuildActivity:
"""Get the guild's activity for the provided number of days.
Expand All @@ -194,9 +186,7 @@ async def get_guild_activity(self, guild_id: int, days: int = DEFAULT_DAYS) -> G
You don't have access to that guild.
"""
data = await self._get(f"activity/guild/{guild_id}?days={days}")
msg_activity = _stats_dict(data.pop("msg_activity"))
voice_activity = _stats_dict(data.pop("voice_activity"))
return GuildActivity(days, guild_id, msg_activity, voice_activity, **data)
return GuildActivity(**data)

async def get_guild_image(self, guild_id: int, days: int = DEFAULT_DAYS) -> bytes:
"""Get the guild's activity image for the provided number of days.
Expand Down Expand Up @@ -281,7 +271,7 @@ def _get(self, endpoint: str, stream: bool = False):

return response.json()

def get_member_count(self, guild_id: int, days: int = DEFAULT_DAYS) -> dict[date, int]:
def get_guild_stats(self, guild_id: int, days: int = DEFAULT_DAYS) -> GuildStats:
"""Get the history of the guild member count for the provided number of days.

Parameters
Expand All @@ -296,9 +286,8 @@ def get_member_count(self, guild_id: int, days: int = DEFAULT_DAYS) -> dict[date
NoGuildAccess:
You don't have access to that guild.
"""
message_data = self._get(f"member_count/{guild_id}?days={days}")

return _stats_dict(message_data)
data = self._get(f"stats/guild/{guild_id}?days={days}")
return GuildStats(**data)

def get_user_stats(self, user_id: int) -> UserStats:
"""Get the user's level stats.
Expand All @@ -314,7 +303,7 @@ def get_user_stats(self, user_id: int) -> UserStats:
The user was not found.
"""
data = self._get(f"stats/user/{user_id}")
return UserStats(user_id, **data)
return UserStats(**data)

def get_member_stats(self, user_id: int, guild_id: int) -> MemberStats:
"""Get the member's level stats.
Expand All @@ -332,9 +321,11 @@ def get_member_stats(self, user_id: int, guild_id: int) -> MemberStats:
The user was not found.
"""
data = self._get(f"stats/member/{user_id}/{guild_id}")
return MemberStats(user_id, guild_id, **data)
return MemberStats(**data)

def get_member_activity(self, user_id: int, guild_id: int, days: int = 14) -> MemberActivity:
def get_member_activity(
self, user_id: int, guild_id: int, days: int = DEFAULT_DAYS
) -> MemberActivity:
"""Get the member's activity for the provided number of days.

Parameters
Expand All @@ -352,9 +343,7 @@ def get_member_activity(self, user_id: int, guild_id: int, days: int = 14) -> Me
The user was not found.
"""
data = self._get(f"activity/member/{user_id}/{guild_id}?days={days}")
msg_activity = _stats_dict(data.pop("msg_activity"))
voice_activity = _stats_dict(data.pop("voice_activity"))
return MemberActivity(days, user_id, guild_id, msg_activity, voice_activity, **data)
return MemberActivity(**data)

def get_guild_activity(self, guild_id: int, days: int = DEFAULT_DAYS) -> GuildActivity:
"""Get the guild's activity for the provided number of days.
Expand All @@ -372,9 +361,7 @@ def get_guild_activity(self, guild_id: int, days: int = DEFAULT_DAYS) -> GuildAc
You don't have access to that guild.
"""
data = self._get(f"activity/guild/{guild_id}?days={days}")
msg_activity = _stats_dict(data.pop("msg_activity"))
voice_activity = _stats_dict(data.pop("voice_activity"))
return GuildActivity(days, guild_id, msg_activity, voice_activity, **data)
return GuildActivity(**data)

def get_guild_image(self, guild_id: int, days: int = DEFAULT_DAYS) -> bytes:
"""Get the guild's activity image for the provided number of days.
Expand Down
Loading