44 Commits

Author SHA1 Message Date
renovate[bot]
33a8698207 ⬆️ Upgrade hashicorp/setup-copywrite digest to c53d066 2025-12-31 17:34:33 +00:00
renovate[bot]
97548eb898 ⬆️ Update lock file (#53)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-31 18:34:03 +01:00
Paillat
3a111fc998 🐛 Update private attribute access in view handling logic (#52) 2025-12-10 19:48:43 +01:00
renovate[bot]
5295064483 ⬆️ Upgrade actions/checkout action to v6 (#49)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-10 18:16:05 +01:00
renovate[bot]
03daa7e075 ⬆️ Upgrade astral-sh/setup-uv action to v7 (#51)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-10 18:13:05 +01:00
renovate[bot]
093f40ee7a ⬆️ Update lock file (#46)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-10 14:56:32 +00:00
Paillat
6af19f0fb3 feat: Decode application ID from token during server initialization (#50) 2025-12-10 15:55:06 +01:00
Paillat
9e410d03f6 chore: Relax Python version requirement in pyproject.toml to >=3.12, <4.0 (#47) 2025-12-08 19:10:39 +01:00
renovate[bot]
d7d7ad62df ⬆️ Update lock file (#45)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-19 18:20:01 +01:00
renovate[bot]
aebdf2655d ⬆️ Update lock file (#43)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 21:37:20 +01:00
renovate[bot]
03d58369b8 ⬆️ Update lock file (#42)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-02 23:29:02 +02:00
renovate[bot]
444d9272f7 ⬆️ Update lock file (#41)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-23 00:20:27 +02:00
renovate[bot]
9d4859caba ⬆️ Update lock file (#40)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 10:58:30 +02:00
renovate[bot]
b37d736dc3 ⬆️ Upgrade actions/setup-python action to v6 (#37)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Paillat <paillat@pycord.dev>
2025-09-08 15:53:09 +02:00
renovate[bot]
5f3d6fab73 ⬆️ Update lock file (#38)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 15:51:30 +02:00
renovate[bot]
a63e620aad ⬆️ Update lock file (#36)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-31 12:55:23 +02:00
renovate[bot]
d4c54e236c ⬆️ Upgrade actions/checkout action to v5 (#35)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-31 12:52:15 +02:00
Paillat
7c4f032492 🔧 Update renovate.json to change base branch pattern from "dev" to "main" (#34) 2025-08-31 12:50:53 +02:00
renovate[bot]
041e8bb6e3 ⬆️: migrate renovate config (#33)
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-08-31 12:47:56 +02:00
Paillat
2e0e835aa3 🔧 Update renovate config to use nicebots config (#32) 2025-08-31 12:46:52 +02:00
Paillat
36b708f6c4 ⬆️ Upgrade to pycord 2.7 rc1 (#30) 2025-08-31 12:44:33 +02:00
Paillat
2318307378 🚨 Update pyproject.toml to fix type checking ignorer for examples/ (#31) 2025-08-31 12:43:37 +02:00
Paillat
e886d494d9 ⬆️ Update py-cord dependency to paillcord version 2.7.0a3 (#28) 2025-07-21 19:39:34 +02:00
Paillat
ec1efb1fb4 Revert "⬆️ Upgrade Python to ==3.13.* (#21)" (#26) 2025-06-18 10:39:44 +02:00
renovate[bot]
ed5ff75af6 ⬆️ Upgrade Python to ==3.13.* (#21)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Paillat <paillat@pycord.dev>
2025-06-18 10:35:31 +02:00
renovate[bot]
844e557fc9 ⬆️ Upgrade hashicorp/setup-copywrite digest to 32f9f1c (#25)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-18 10:34:19 +02:00
Paillat
99a516b0f0 📝 Remove pre-release from README.md (#23) 2025-05-27 19:56:45 +02:00
pre-commit-ci[bot]
c356d0f74d 👷 pre-commit autoupdate (#19)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Paillat <me@paillat.dev>
2025-05-27 19:53:20 +02:00
renovate[bot]
8394b56afe ⬆️ Upgrade astral-sh/setup-uv action to v6 (#20)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-25 00:40:48 +02:00
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
10 changed files with 849 additions and 309 deletions

View File

@@ -9,15 +9,15 @@ jobs:
runs-on: ubuntu-latest
environment: pypi
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: "Install uv"
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
- name: "Set up Python"
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version-file: "pyproject.toml"

View File

@@ -9,9 +9,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup Copywrite
uses: hashicorp/setup-copywrite@5e3e8a26d7b9f8a508848ad0a069dfd2f7aa5339
uses: hashicorp/setup-copywrite@c53d0661129178ae7ef8b8617c9a3bd135745725
- name: Check Header Compliance
run: copywrite headers --plan --config .copywrite.hcl
@@ -35,15 +35,15 @@ jobs:
name: ${{ matrix.name }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: "Install uv"
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
- name: "Set up Python"
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version-file: "pyproject.toml"

View File

@@ -21,7 +21,7 @@ repos:
exclude: \.(po|pot|yml|yaml)$
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.9.10
rev: v0.11.11
hooks:
# Run the linter.
- id: ruff
@@ -31,4 +31,4 @@ repos:
- repo: https://github.com/bhundven/copywrite # waiting for https://github.com/hashicorp/copywrite/pull/120 to be merged
rev: 937f17f09c46992447dfa8977bb96eda512588c4
hooks:
- id: add-headers
- id: add-headers

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)
@@ -52,12 +63,10 @@ Built on:
## Installation
```bash
pip install pycord-rest-bot --prerelease=allow
pip install pycord-rest-bot
```
<!-- prettier-ignore -->
> [!NOTE]
> The package is currently in pre-release.
<!-- quick-start -->
## Quick Start
@@ -104,7 +113,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 +241,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

@@ -21,7 +21,7 @@ class MyView(discord.ui.View):
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self.add_item(
discord.ui.Button(
discord.ui.Button( # pyright: ignore[reportUnknownArgumentType]
style=discord.ButtonStyle.link, label="GitHub", url="https://github.com/Paillat-dev/pycord-rest"
)
)

View File

@@ -1,17 +1,16 @@
[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" }
]
license = "MIT"
requires-python = "==3.12.*"
requires-python = ">=3.12, <4.0"
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
@@ -25,7 +24,7 @@ keywords = ["discord", "bot", "rest", "pycord"]
dependencies = [
"fastapi>=0.115.11",
"orjson>=3.10.15",
"py-cord==2.6.1",
"py-cord>=2.7.0rc1",
"pynacl>=1.5.0",
"uvicorn>=0.34.0",
]
@@ -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"
@@ -71,7 +98,7 @@ reportUnusedCallResult = false
reportAny = false
executionEnvironments = [
{ root = "src/pycord_rest/_version.py", reportDeprecated = false },
{ root = "examples", reportExplicitAny = false, reportUnknownMemberType = false, reportUnusedParameter = false, reportImplicitOverride = false }
{ root = "examples", reportExplicitAny = false, reportUnknownMemberType = false, reportUnusedParameter = false, reportImplicitOverride = false, reportAttributeAccessIssue = false, reportUnknownVariableType = false },
]
[tool.ruff]

View File

@@ -1,25 +1,13 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"baseBranches": ["main"],
"labels": ["deps"],
"ignorePaths": ["requirements.txt"],
"commitMessagePrefix": "⬆️",
"commitMessageAction": "Upgrade",
"packageRules": [
{
"updateTypes": ["pin"],
"commitMessagePrefix": "📌",
"commitMessageAction": "Pin"
},
{
"updateTypes": ["rollback"],
"commitMessagePrefix": "⬇️",
"commitMessageAction": "Downgrade"
},
{
"matchDatasources": ["pypi"],
"addLabels": ["pypi"]
}
]
"extends": [
"local>nicebots-xyz/renovate-config",
":semanticPrefixFixDepsChoreOthers",
":dependencyDashboard"
],
"forkProcessing": "enabled",
"baseBranchPatterns": ["main"],
"lockFileMaintenance": {
"enabled": true
}
}

View File

@@ -1,7 +1,10 @@
# Copyright (c) Paillat-dev
# SPDX-License-Identifier: MIT
import base64
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 +17,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 +38,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):
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]
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(prefix=path_prefix)
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:
@@ -60,23 +81,23 @@ class App(discord.Bot):
self._connection._view_store._ViewStore__verify_integrity() # noqa: SLF001 # pyright: ignore [reportUnknownMemberType, reportAttributeAccessIssue, reportPrivateUsage]
message_id: int | None = interaction.message and interaction.message.id
key = (component_type, message_id, custom_id)
value = self._connection._view_store._views.get(key) or self._connection._view_store._views.get( # pyright: ignore [reportUnknownVariableType, reportUnknownMemberType, reportPrivateUsage] # noqa: SLF001
value = self._connection._view_store._views.get(key) or self._connection._view_store._views.get( # pyright: ignore [reportPrivateUsage] # noqa: SLF001
(component_type, None, custom_id)
)
if value is None:
return
view, item = value # pyright: ignore [reportUnknownVariableType]
view, item = value
item.refresh_state(interaction)
# Code taken from View._dispatch_item
if view._View__stopped.done(): # noqa: SLF001 # pyright: ignore [reportAttributeAccessIssue, reportUnknownMemberType]
if view._stopped.done(): # noqa: SLF001 # pyright: ignore [reportPrivateUsage]
return
if interaction.message:
view.message = interaction.message
await view._scheduled_task(item, interaction) # noqa: SLF001 # pyright: ignore [reportPrivateUsage, reportUnknownMemberType]
await view._scheduled_task(item, interaction) # noqa: SLF001 # pyright: ignore [reportPrivateUsage]
async def _verify_request(self, request: Request) -> None:
signature = request.headers["X-Signature-Ed25519"]
@@ -247,8 +268,10 @@ 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)
uvicorn_options["server_header"] = uvicorn_options.get("server_header", False)
config = self._UvicornConfig(self._app, **uvicorn_options)
server = self._UvicornServer(config)
self._connection.application_id = int(base64.b64decode(token.split(".")[0] + "==").decode("utf-8"))
try:
self.dispatch("connect")
await server.serve()
@@ -258,7 +281,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

950
uv.lock generated

File diff suppressed because it is too large Load Diff