9 Commits

4 changed files with 111 additions and 20 deletions

View File

@@ -1,18 +1,28 @@
<div align="center">
<h1>Pycord REST</h1>
![PyPI - Version](https://img.shields.io/pypi/v/pycord-rest-bot)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pycord-rest-bot)
![PyPI - Types](https://img.shields.io/pypi/types/pycord-rest-bot)
![PyPI - License](https://img.shields.io/pypi/l/pycord-rest-bot)
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/Paillat-dev/pycord-rest/CI.yaml)
<!-- badges -->
[![PyPI - Version](https://img.shields.io/pypi/v/pycord-rest-bot)](https://pypi.org/project/pycord-rest-bot/)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pycord-rest-bot)](https://pypi.org/project/pycord-rest-bot/)
[![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)
<!-- end badges -->
<!-- short description -->
A lightweight wrapper for Discord's HTTP interactions and webhook events using py-cord
and FastAPI.
<!-- end short description -->
</div>
<!-- toc -->
## Table of Contents
- [Overview](#overview)
@@ -31,6 +41,7 @@ and FastAPI.
- [Custom Routes](#custom-routes)
- [Configuration](#configuration)
- [Limitations](#limitations)
- [Getting Help](#getting-help)
- [Development](#development)
- [Local Testing](#local-testing)
- [Contributing](#contributing)
@@ -59,6 +70,8 @@ pip install pycord-rest-bot --prerelease=allow
> [!NOTE]
> The package is currently in pre-release.
<!-- quick-start -->
## Quick Start
```python
@@ -104,7 +117,7 @@ Unlike traditional WebSocket-based Discord bots, HTTP-based applications:
1. Create an application on the
[Discord Developer Portal](https://discord.com/developers/applications)
2. Copy your public key to verify signatures
3. Run your FastAPI server
3. Run the Pycord REST app
4. Configure the endpoints:
- **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
- **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
### Local Testing

View File

@@ -1,12 +1,11 @@
[build-system]
requires = ["hatchling", "hatch-vcs"]
requires = ["hatchling", "hatch-vcs", "hatch-fancy-pypi-readme"]
build-backend = "hatchling.build"
[project]
name = "pycord-rest-bot"
dynamic = ["version", "urls"]
description = "A discord rest-bot wrapper for pycord"
readme = "README.md"
dynamic = ["version", "urls", "readme"]
description = "A lightweight wrapper for Discord's HTTP interactions and webhook events using py-cord and FastAPI"
authors = [
{ name = "Paillat-dev", email = "me@paillat.dev" }
]
@@ -47,6 +46,34 @@ version-file = "src/pycord_rest/_version.py"
Homepage = "https://github.com/Paillat-dev/pycord-rest"
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]
name = "pycord-rest-bot"

View File

@@ -1,7 +1,9 @@
# Copyright (c) Paillat-dev
# SPDX-License-Identifier: MIT
import functools
import logging
import warnings
from collections.abc import Callable, Coroutine
from functools import cached_property
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.signing import VerifyKey
from .errors import InvalidCredentialsError
from .models import EventType, WebhookEventPayload, WebhookType
logger = logging.getLogger("pycord.rest")
@@ -34,21 +37,38 @@ class ApplicationAuthorizedEvent:
)
class PycordRestError(discord.DiscordException):
pass
def not_supported[T, U](func: Callable[[T], U]) -> Callable[[T], U]:
@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)
class InvalidCredentialsError(PycordRestError):
pass
return inner
class App(discord.Bot):
_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, **options: Any) -> None: # pyright: ignore [reportExplicitAny]
super().__init__(*args, **options) # pyright: ignore [reportUnknownMemberType]
self._app: FastAPI = FastAPI(openapi_url=None, docs_url=None, redoc_url=None)
self.router: APIRouter = APIRouter()
self._app: FastAPI = self._FastAPI(openapi_url=None, docs_url=None, redoc_url=None)
self.router: APIRouter = self._APIRouter()
self._public_key: str | None = None
@property
@override
@not_supported
def latency(self) -> float:
return 0.0
@cached_property
def _verify_key(self) -> VerifyKey:
if self._public_key is None:
@@ -247,8 +267,8 @@ class App(discord.Bot):
self._app.include_router(self.router)
uvicorn_options = uvicorn_options or {}
uvicorn_options["log_level"] = uvicorn_options.get("log_level", logging.root.level)
config = uvicorn.Config(self._app, **uvicorn_options)
server = uvicorn.Server(config)
config = self._UvicornConfig(self._app, **uvicorn_options)
server = self._UvicornServer(config)
try:
self.dispatch("connect")
await server.serve()
@@ -258,7 +278,10 @@ class App(discord.Bot):
@override
async def close(self) -> None:
pass
self._closed: bool = True
await self.http.close()
self._ready.clear()
@override
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