45 Commits

Author SHA1 Message Date
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
08bf3e9521 📝 Enhance README.md with badges 2025-03-09 19:30:24 +01:00
f6e7567d27 🔧 Update pyproject.toml to include additional metadata for typing and OS compatibility 2025-03-09 19:28:40 +01:00
06885ace8c 📝 Fix webhook_events example docstring and remove unnecessary logging setup 2025-03-09 18:25:57 +01:00
b840984780 🔧 Add ISC003 to pyproject.toml ruff ignore as it conflicts with basedpyright 2025-03-09 18:25:20 +01:00
7874951a18 ✏️ Ops 2025-03-09 18:22:10 +01:00
1ae66d05ee Add custom error handling for invalid credentials in the application 2025-03-09 18:04:00 +01:00
11 changed files with 886 additions and 318 deletions

View File

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

View File

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

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.11
hooks: hooks:
# Run the linter. # Run the linter.
- id: ruff - id: ruff

View File

@@ -1,8 +1,28 @@
# Pycord REST <div align="center">
<h1>Pycord REST</h1>
<!-- 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 A lightweight wrapper for Discord's HTTP interactions and webhook events using py-cord
and FastAPI. and FastAPI.
<!-- end short description -->
</div>
<!-- toc -->
## Table of Contents ## Table of Contents
- [Overview](#overview) - [Overview](#overview)
@@ -14,12 +34,14 @@ and FastAPI.
- [Features](#features) - [Features](#features)
- [Interaction Handling](#interaction-handling) - [Interaction Handling](#interaction-handling)
- [Webhook Events](#webhook-events) - [Webhook Events](#webhook-events)
- [Type Safety](#type-safety)
- [Usage Examples](#usage-examples) - [Usage Examples](#usage-examples)
- [Basic Commands](#basic-commands) - [Basic Commands](#basic-commands)
- [Event Handling](#event-handling) - [Event Handling](#event-handling)
- [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)
@@ -41,12 +63,10 @@ Built on:
## Installation ## Installation
```bash ```bash
pip install pycord-rest-bot --prerelease=allow pip install pycord-rest-bot
``` ```
<!-- prettier-ignore --> <!-- quick-start -->
> [!NOTE]
> The package is currently in pre-release.
## Quick Start ## Quick Start
@@ -93,7 +113,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
@@ -124,6 +144,15 @@ Handle Discord webhook events such as:
user user
- **Entitlement creation** - When a user subscribes to your app's premium features - **Entitlement creation** - When a user subscribes to your app's premium features
### Type Safety
Pycord REST is fully type-annotated and type-safe. It uses `basedpyright` for type
checking.
<!-- prettier-ignore -->
> [!NOTE]
> While Pycord REST itself is fully typed, the underlying py-cord library has limited type annotations, which may affect type checking in some areas.
## Usage Examples ## Usage Examples
<!-- prettier-ignore --> <!-- prettier-ignore -->
@@ -212,6 +241,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
@@ -243,6 +288,7 @@ These tools provide temporary URLs for testing without deploying to production.
**Development Tools**: **Development Tools**:
- **uv**: For dependency management
- **Ruff**: For linting and formatting - **Ruff**: For linting and formatting
- **HashiCorp Copywrite**: For managing license headers - **HashiCorp Copywrite**: For managing license headers
- **basedpyright**: For type checking - **basedpyright**: For type checking

View File

@@ -21,7 +21,7 @@ class MyView(discord.ui.View):
def __init__(self, *args: Any, **kwargs: Any) -> None: def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.add_item( 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" style=discord.ButtonStyle.link, label="GitHub", url="https://github.com/Paillat-dev/pycord-rest"
) )
) )

View File

@@ -1,12 +1,8 @@
# Copyright (c) Paillat-dev # Copyright (c) Paillat-dev
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
"""Basic Discord bot example using Pycord REST. """Example showing how to work with webhook events in Pycord REST."""
This is a minimal example showing how to create slash commands.
"""
import logging
import os import os
from dotenv import load_dotenv from dotenv import load_dotenv
@@ -16,10 +12,6 @@ from pycord_rest import App, ApplicationAuthorizedEvent
# Load environment variables from .env file # Load environment variables from .env file
load_dotenv() load_dotenv()
# Set up logging
logging.basicConfig(level=logging.DEBUG)
app = App() app = App()
@@ -31,7 +23,7 @@ async def on_application_authorized(event: ApplicationAuthorizedEvent) -> None:
else: else:
print( print(
f"Bot {event.user.display_name} ({event.user.id}) installed the application" # noqa: ISC003 f"Bot {event.user.display_name} ({event.user.id}) installed the application"
+ f" to guild {event.guild.name} ({event.guild.id})." + f" to guild {event.guild.name} ({event.guild.id})."
) )

View File

@@ -1,29 +1,30 @@
[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" }
] ]
license = "MIT" license = "MIT"
requires-python = "==3.12.*" requires-python = ">=3.12, <4.0"
classifiers = [ classifiers = [
"Development Status :: 3 - Alpha", "Development Status :: 3 - Alpha",
"Intended Audience :: Developers", "Intended Audience :: Developers",
"License :: OSI Approved :: MIT License", "License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.12" "Programming Language :: Python :: 3.12",
"Typing :: Typed",
"Operating System :: OS Independent",
] ]
keywords = ["discord", "bot", "rest", "pycord"] 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.7.0rc1",
"pynacl>=1.5.0", "pynacl>=1.5.0",
"uvicorn>=0.34.0", "uvicorn>=0.34.0",
] ]
@@ -45,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"
@@ -69,7 +98,7 @@ reportUnusedCallResult = false
reportAny = false reportAny = false
executionEnvironments = [ executionEnvironments = [
{ root = "src/pycord_rest/_version.py", reportDeprecated = false }, { 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] [tool.ruff]
@@ -116,5 +145,6 @@ extend-ignore = [
"FBT002", "FBT002",
"PLR2004", "PLR2004",
"PLR0913", "PLR0913",
"C901" "C901",
"ISC003" # conflicts with basedpyright reportImplicitStringConcatenation
] ]

View File

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

View File

@@ -1,7 +1,10 @@
# Copyright (c) Paillat-dev # Copyright (c) Paillat-dev
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
import base64
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
@@ -11,10 +14,10 @@ import discord
import uvicorn import uvicorn
from discord import Entitlement, Interaction, InteractionType from discord import Entitlement, Interaction, InteractionType
from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request, Response from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request, Response
from fastapi.exceptions import FastAPIError
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")
@@ -35,31 +38,56 @@ class ApplicationAuthorizedEvent:
) )
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)
return inner
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:
raise FastAPIError("No public key provided") raise InvalidCredentialsError("No public key provided")
return VerifyKey(bytes.fromhex(self.public_key)) return VerifyKey(bytes.fromhex(self._public_key))
async def _dispatch_view(self, component_type: int, custom_id: str, interaction: Interaction) -> None: async def _dispatch_view(self, component_type: int, custom_id: str, interaction: Interaction) -> None:
# Code taken from ViewStore.dispatch # Code taken from ViewStore.dispatch
self._connection._view_store._ViewStore__verify_integrity() # noqa: SLF001 # pyright: ignore [reportUnknownMemberType, reportAttributeAccessIssue, reportPrivateUsage] self._connection._view_store._ViewStore__verify_integrity() # noqa: SLF001 # pyright: ignore [reportUnknownMemberType, reportAttributeAccessIssue, reportPrivateUsage]
message_id: int | None = interaction.message and interaction.message.id message_id: int | None = interaction.message and interaction.message.id
key = (component_type, message_id, custom_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) (component_type, None, custom_id)
) )
if value is None: if value is None:
return return
view, item = value # pyright: ignore [reportUnknownVariableType] view, item = value
item.refresh_state(interaction) item.refresh_state(interaction)
# Code taken from View._dispatch_item # Code taken from View._dispatch_item
@@ -69,7 +97,7 @@ class App(discord.Bot):
if interaction.message: if interaction.message:
view.message = 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: async def _verify_request(self, request: Request) -> None:
signature = request.headers["X-Signature-Ed25519"] signature = request.headers["X-Signature-Ed25519"]
@@ -110,6 +138,7 @@ class App(discord.Bot):
async def process_application_commands( # noqa: PLR0912 async def process_application_commands( # noqa: PLR0912
self, interaction: Interaction, auto_sync: bool | None = None self, interaction: Interaction, auto_sync: bool | None = None
) -> None: ) -> None:
# Code taken from super().process_application_commands
if auto_sync is None: if auto_sync is None:
auto_sync = self._bot.auto_sync_commands # pyright: ignore [reportUnknownVariableType, reportUnknownMemberType] auto_sync = self._bot.auto_sync_commands # pyright: ignore [reportUnknownVariableType, reportUnknownMemberType]
# TODO: find out why the isinstance check below doesn't stop the type errors below # noqa: FIX002, TD002, TD003 # TODO: find out why the isinstance check below doesn't stop the type errors below # noqa: FIX002, TD002, TD003
@@ -231,16 +260,18 @@ class App(discord.Bot):
uvicorn_options: dict[str, Any] | None = None, # pyright: ignore [reportExplicitAny] uvicorn_options: dict[str, Any] | None = None, # pyright: ignore [reportExplicitAny]
health: bool = True, health: bool = True,
) -> None: ) -> None:
self.public_key = public_key self._public_key = public_key
_ = self._process_interaction_factory() _ = self._process_interaction_factory()
_ = self._webhook_event_factory() _ = self._webhook_event_factory()
if health: if health:
_ = self._health_factory() _ = self._health_factory()
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)
self._connection.application_id = int(base64.b64decode(token.split(".")[0] + "==").decode("utf-8"))
try: try:
self.dispatch("connect") self.dispatch("connect")
await server.serve() await server.serve()
@@ -250,7 +281,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]
@@ -260,6 +294,10 @@ class App(discord.Bot):
uvicorn_options: dict[str, Any] | None = None, # pyright: ignore [reportExplicitAny] uvicorn_options: dict[str, Any] | None = None, # pyright: ignore [reportExplicitAny]
health: bool = True, health: bool = True,
) -> None: ) -> None:
if not token:
raise InvalidCredentialsError("No token provided")
if not public_key:
raise InvalidCredentialsError("No public key provided")
await self.login(token) await self.login(token)
await self.connect( await self.connect(
token=token, token=token,

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