15 Commits

Author SHA1 Message Date
e971ba5f19 ⬆️ Update py-cord dependency to allow for newer versions (#18) 2025-04-17 00:52:16 +02:00
fd45c0305e 📌 Update py-cord dependency to allow for newer versions (#17) 2025-04-17 00:40:12 +02:00
pre-commit-ci[bot]
57c6a3c4cd 👷 pre-commit autoupdate (#3)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Paillat <me@paillat.dev>
2025-04-15 22:04:51 +02:00
renovate[bot]
013e4aba14 ⬆️: migrate renovate config (#15)
* ⬆️: migrate config renovate.json

* 🎨 auto fixes from pre-commit.com hooks

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-03-24 17:14:14 +01:00
124ce383bb Disable uvicorn server_header (#14) 2025-03-17 08:28:17 +01:00
5827a2e98a Add path_prefix parameter to constructor for customizable router prefix (#13) 2025-03-13 14:11:59 +01:00
5e84515c03 📝 Update pyproject.toml and README.md to use fancy pypi readme (#12) 2025-03-13 10:02:12 +01:00
190dce6f5d Refactor error handling by moving errors to a new file (#9)
*  Refactor error handling by moving errors to a new file
2025-03-13 09:23:56 +01:00
7a98827a23 Enhance not_supported decorator to issue a SyntaxWarning for unsupported functions (#10) 2025-03-13 09:22:16 +01:00
fb9e506d15 Implement close method to properly handle resource cleanup in App class (#11) 2025-03-13 09:21:01 +01:00
ad41014c94 Use ClassVars for constructor classes to allow more customization when subclassing (#8) 2025-03-13 08:47:21 +01:00
cd444d51d1 📝 Make badges link to something (#7) 2025-03-13 08:45:31 +01:00
a94ffb6729 Add not_supported decorator and override latency property in App class (#4) 2025-03-10 23:31:49 +01:00
353ae04dac 📝 Update README.md to clarify running the application with Pycord REST app 2025-03-09 20:54:10 +01:00
4fe7cb47a7 📝 Add "Getting Help" section to README.md with support resources 2025-03-09 20:14:40 +01:00
7 changed files with 119 additions and 27 deletions

View File

@@ -21,7 +21,7 @@ repos:
exclude: \.(po|pot|yml|yaml)$ exclude: \.(po|pot|yml|yaml)$
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version. # Ruff version.
rev: v0.9.10 rev: v0.11.5
hooks: hooks:
# Run the linter. # Run the linter.
- id: ruff - id: ruff

View File

@@ -1,18 +1,28 @@
<div align="center"> <div align="center">
<h1>Pycord REST</h1> <h1>Pycord REST</h1>
![PyPI - Version](https://img.shields.io/pypi/v/pycord-rest-bot) <!-- badges -->
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pycord-rest-bot)
![PyPI - Types](https://img.shields.io/pypi/types/pycord-rest-bot) [![PyPI - Version](https://img.shields.io/pypi/v/pycord-rest-bot)](https://pypi.org/project/pycord-rest-bot/)
![PyPI - License](https://img.shields.io/pypi/l/pycord-rest-bot) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pycord-rest-bot)](https://pypi.org/project/pycord-rest-bot/)
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/Paillat-dev/pycord-rest/CI.yaml) [![PyPI - Types](https://img.shields.io/pypi/types/pycord-rest-bot)](https://pypi.org/project/pycord-rest-bot/)
[![PyPI - License](https://img.shields.io/pypi/l/pycord-rest-bot)](https://pypi.org/project/pycord-rest-bot/)
[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/Paillat-dev/pycord-rest/CI.yaml)](https://github.com/Paillat-dev/pycord-rest/actions/workflows/CI.yaml)
[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/Paillat-dev/pycord-rest/main.svg)](https://results.pre-commit.ci/latest/github/Paillat-dev/pycord-rest/main) [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/Paillat-dev/pycord-rest/main.svg)](https://results.pre-commit.ci/latest/github/Paillat-dev/pycord-rest/main)
<!-- end badges -->
<!-- short description -->
A lightweight wrapper for Discord's HTTP interactions and webhook events using py-cord A lightweight wrapper for Discord's HTTP interactions and webhook events using py-cord
and FastAPI. and FastAPI.
<!-- end short description -->
</div> </div>
<!-- toc -->
## Table of Contents ## Table of Contents
- [Overview](#overview) - [Overview](#overview)
@@ -31,6 +41,7 @@ and FastAPI.
- [Custom Routes](#custom-routes) - [Custom Routes](#custom-routes)
- [Configuration](#configuration) - [Configuration](#configuration)
- [Limitations](#limitations) - [Limitations](#limitations)
- [Getting Help](#getting-help)
- [Development](#development) - [Development](#development)
- [Local Testing](#local-testing) - [Local Testing](#local-testing)
- [Contributing](#contributing) - [Contributing](#contributing)
@@ -59,6 +70,8 @@ pip install pycord-rest-bot --prerelease=allow
> [!NOTE] > [!NOTE]
> The package is currently in pre-release. > The package is currently in pre-release.
<!-- quick-start -->
## Quick Start ## Quick Start
```python ```python
@@ -104,7 +117,7 @@ Unlike traditional WebSocket-based Discord bots, HTTP-based applications:
1. Create an application on the 1. Create an application on the
[Discord Developer Portal](https://discord.com/developers/applications) [Discord Developer Portal](https://discord.com/developers/applications)
2. Copy your public key to verify signatures 2. Copy your public key to verify signatures
3. Run your FastAPI server 3. Run the Pycord REST app
4. Configure the endpoints: 4. Configure the endpoints:
- **Interactions Endpoint URL** - For slash commands and component interactions - **Interactions Endpoint URL** - For slash commands and component interactions
@@ -232,6 +245,22 @@ Since Pycord REST doesn't use Discord's WebSocket gateway:
- Member tracking - Member tracking
- **Limited Events** - Only interaction-based and webhook events work - **Limited Events** - Only interaction-based and webhook events work
## Getting Help
If you encounter issues or have questions about pycord-rest:
- **GitHub Issues**:
[Submit a bug report or feature request](https://github.com/Paillat-dev/pycord-rest/issues)
- **Discord Support**:
- For py-cord related questions: Join the
[Pycord Official Server](https://discord.gg/pycord)
- For pycord-rest specific help: Join the
[Pycord Official Server](https://discord.gg/pycord) and mention `@paillat`
<!-- prettier-ignore -->
> [!TIP]
> Before asking for help, check if your question is already answered in the [examples directory](/examples) or existing GitHub issues.
## Development ## Development
### Local Testing ### Local Testing

View File

@@ -1,12 +1,11 @@
[build-system] [build-system]
requires = ["hatchling", "hatch-vcs"] requires = ["hatchling", "hatch-vcs", "hatch-fancy-pypi-readme"]
build-backend = "hatchling.build" build-backend = "hatchling.build"
[project] [project]
name = "pycord-rest-bot" name = "pycord-rest-bot"
dynamic = ["version", "urls"] dynamic = ["version", "urls", "readme"]
description = "A discord rest-bot wrapper for pycord" description = "A lightweight wrapper for Discord's HTTP interactions and webhook events using py-cord and FastAPI"
readme = "README.md"
authors = [ authors = [
{ name = "Paillat-dev", email = "me@paillat.dev" } { name = "Paillat-dev", email = "me@paillat.dev" }
] ]
@@ -25,7 +24,7 @@ keywords = ["discord", "bot", "rest", "pycord"]
dependencies = [ dependencies = [
"fastapi>=0.115.11", "fastapi>=0.115.11",
"orjson>=3.10.15", "orjson>=3.10.15",
"py-cord==2.6.1", "py-cord>=2.6.1",
"pynacl>=1.5.0", "pynacl>=1.5.0",
"uvicorn>=0.34.0", "uvicorn>=0.34.0",
] ]
@@ -47,6 +46,34 @@ version-file = "src/pycord_rest/_version.py"
Homepage = "https://github.com/Paillat-dev/pycord-rest" Homepage = "https://github.com/Paillat-dev/pycord-rest"
source_archive = "https://github.com/Paillat-dev/pycord-rest/archive/{commit_hash}.zip" source_archive = "https://github.com/Paillat-dev/pycord-rest/archive/{commit_hash}.zip"
[tool.hatch.metadata.hooks.fancy-pypi-readme]
content-type = "text/markdown"
[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
path = "README.md"
start-after = "<!-- badges -->\n"
end-before = "\n<!-- end badges -->"
[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
text = "\n\n---\n"
[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
path = "README.md"
start-after = "## Overview\n"
end-before = "\n## Installation"
[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
path = "README.md"
start-after = "<!-- quick-start -->"
[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
pattern = '\[(.+?)\]\(((?!https?://)\S+?)\)'
replacement = '[\1](https://github.com/Paillat-dev/pycord-rest/tree/main\g<2>)'
[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
pattern = '\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]'
replacement = '**\1**:'
[tool.hatchling] [tool.hatchling]
name = "pycord-rest-bot" name = "pycord-rest-bot"

View File

@@ -8,12 +8,12 @@
"commitMessageAction": "Upgrade", "commitMessageAction": "Upgrade",
"packageRules": [ "packageRules": [
{ {
"updateTypes": ["pin"], "matchUpdateTypes": ["pin"],
"commitMessagePrefix": "📌", "commitMessagePrefix": "📌",
"commitMessageAction": "Pin" "commitMessageAction": "Pin"
}, },
{ {
"updateTypes": ["rollback"], "matchUpdateTypes": ["rollback"],
"commitMessagePrefix": "⬇️", "commitMessagePrefix": "⬇️",
"commitMessageAction": "Downgrade" "commitMessageAction": "Downgrade"
}, },

View File

@@ -1,7 +1,9 @@
# Copyright (c) Paillat-dev # Copyright (c) Paillat-dev
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
import functools
import logging import logging
import warnings
from collections.abc import Callable, Coroutine from collections.abc import Callable, Coroutine
from functools import cached_property from functools import cached_property
from typing import Any, Never, override from typing import Any, Never, override
@@ -14,6 +16,7 @@ from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request, Respons
from nacl.exceptions import BadSignatureError from nacl.exceptions import BadSignatureError
from nacl.signing import VerifyKey from nacl.signing import VerifyKey
from .errors import InvalidCredentialsError
from .models import EventType, WebhookEventPayload, WebhookType from .models import EventType, WebhookEventPayload, WebhookType
logger = logging.getLogger("pycord.rest") logger = logging.getLogger("pycord.rest")
@@ -34,21 +37,38 @@ class ApplicationAuthorizedEvent:
) )
class PycordRestError(discord.DiscordException): def not_supported[T, U](func: Callable[[T], U]) -> Callable[[T], U]:
pass @functools.wraps(func)
def inner(*args: T, **kwargs: T) -> U:
logger.warning(f"{func.__qualname__} is not supported by REST apps.")
warnings.warn(
f"{func.__qualname__} is not supported by REST apps.",
SyntaxWarning,
stacklevel=2,
)
return func(*args, **kwargs)
return inner
class InvalidCredentialsError(PycordRestError):
pass
class App(discord.Bot): class App(discord.Bot):
def __init__(self, *args: Any, **options: Any) -> None: # pyright: ignore [reportExplicitAny] _UvicornConfig: type[uvicorn.Config] = uvicorn.Config
_UvicornServer: type[uvicorn.Server] = uvicorn.Server
_FastAPI: type[FastAPI] = FastAPI
_APIRouter: type[APIRouter] = APIRouter
def __init__(self, *args: Any, path_prefix: str = "", **options: Any) -> None: # pyright: ignore [reportExplicitAny]
super().__init__(*args, **options) # pyright: ignore [reportUnknownMemberType] super().__init__(*args, **options) # pyright: ignore [reportUnknownMemberType]
self._app: FastAPI = FastAPI(openapi_url=None, docs_url=None, redoc_url=None) self._app: FastAPI = self._FastAPI(openapi_url=None, docs_url=None, redoc_url=None)
self.router: APIRouter = APIRouter() self.router: APIRouter = self._APIRouter(prefix=path_prefix)
self._public_key: str | None = None self._public_key: str | None = None
@property
@override
@not_supported
def latency(self) -> float:
return 0.0
@cached_property @cached_property
def _verify_key(self) -> VerifyKey: def _verify_key(self) -> VerifyKey:
if self._public_key is None: if self._public_key is None:
@@ -247,8 +267,9 @@ class App(discord.Bot):
self._app.include_router(self.router) self._app.include_router(self.router)
uvicorn_options = uvicorn_options or {} uvicorn_options = uvicorn_options or {}
uvicorn_options["log_level"] = uvicorn_options.get("log_level", logging.root.level) uvicorn_options["log_level"] = uvicorn_options.get("log_level", logging.root.level)
config = uvicorn.Config(self._app, **uvicorn_options) uvicorn_options["server_header"] = uvicorn_options.get("server_header", False)
server = uvicorn.Server(config) config = self._UvicornConfig(self._app, **uvicorn_options)
server = self._UvicornServer(config)
try: try:
self.dispatch("connect") self.dispatch("connect")
await server.serve() await server.serve()
@@ -258,7 +279,10 @@ class App(discord.Bot):
@override @override
async def close(self) -> None: async def close(self) -> None:
pass self._closed: bool = True
await self.http.close()
self._ready.clear()
@override @override
async def start( # pyright: ignore [reportIncompatibleMethodOverride] async def start( # pyright: ignore [reportIncompatibleMethodOverride]

12
src/pycord_rest/errors.py Normal file
View File

@@ -0,0 +1,12 @@
# Copyright (c) Paillat-dev
# SPDX-License-Identifier: MIT
import discord
class PycordRestError(discord.DiscordException):
pass
class InvalidCredentialsError(PycordRestError):
pass

2
uv.lock generated
View File

@@ -319,7 +319,7 @@ dev = [
requires-dist = [ requires-dist = [
{ name = "fastapi", specifier = ">=0.115.11" }, { name = "fastapi", specifier = ">=0.115.11" },
{ name = "orjson", specifier = ">=3.10.15" }, { name = "orjson", specifier = ">=3.10.15" },
{ name = "py-cord", specifier = "==2.6.1" }, { name = "py-cord", specifier = ">=2.6.1" },
{ name = "pynacl", specifier = ">=1.5.0" }, { name = "pynacl", specifier = ">=1.5.0" },
{ name = "uvicorn", specifier = ">=0.34.0" }, { name = "uvicorn", specifier = ">=0.34.0" },
] ]