Skip to content
This repository has been archived by the owner on Mar 13, 2023. It is now read-only.

Commit

Permalink
Merge: pull request #606 from NAFTeam/dev
Browse files Browse the repository at this point in the history
Naff 1.8.0
  • Loading branch information
LordOfPolls authored Aug 15, 2022
2 parents 13aeb1a + b55e443 commit 0a45808
Show file tree
Hide file tree
Showing 31 changed files with 909 additions and 416 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[![PyPI](https://img.shields.io/pypi/v/naff)](https://pypi.org/project/naff/)
[![Downloads](https://static.pepy.tech/personalized-badge/dis-snek?period=total&units=abbreviation&left_color=grey&right_color=green&left_text=pip%20installs)](https://pepy.tech/project/dis-snek)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![black-formatted](https://img.shields.io/github/workflow/status/Discord-Snake-Pit/dis-snek/black-action/master?label=Black%20Format&logo=github)](https://github.com/Discord-Snake-Pit/Dis-Snek/actions/workflows/black.yml)
[![CodeQL](https://img.shields.io/github/workflow/status/Discord-Snake-Pit/dis-snek/CodeQL/master?label=CodeQL&logo=Github)](https://github.com/Discord-Snake-Pit/Dis-Snek/actions/workflows/codeql-analysis.yml)
[![black-formatted](https://img.shields.io/github/workflow/status/NAFTeam/NAFF/black-action/master?label=Black%20Format&logo=github)](https://github.com/NAFTeam/NAFF/actions/workflows/black.yml)
[![CodeQL](https://img.shields.io/github/workflow/status/NAFTeam/NAFF/CodeQL/master?label=CodeQL&logo=Github)](https://github.com/NAFTeam/NAFF/actions/workflows/codeql-analysis.yml)
[![Discord](https://img.shields.io/discord/870046872864165888?color=%235865F2&label=Server&logo=discord&logoColor=%235865F2)](https://discord.gg/naff)
[![Documentation Status](https://readthedocs.org/projects/dis-snek/badge/?version=latest)](https://dis-snek.readthedocs.io/en/latest/?badge=latest)
[![Documentation Status](https://readthedocs.org/projects/naff-docs/badge/?version=latest)](https://naff-docs.readthedocs.io/en/latest/?version=latest)

# What is this?
This is `NAFF`, a python API wrapper for Discord.
Expand Down Expand Up @@ -47,14 +47,14 @@ async def user_context(ctx):

bot.start("TOKEN")
```
For more examples check out [our examples repo](https://github.com/Discord-Snake-Pit/examples) or the [docs](https://dis-snek.readthedocs.io/). You also can [explore projects with the dis-snek topic](https://github.com/topics/naff).
For more examples check out [our examples repo](https://github.com/NAFTeam/examples) or the [docs](https://naff-docs.readthedocs.io/). You also can [explore projects with the `NAFF` topic](https://github.com/topics/naff) or older [`dis-snek` topic](https://github.com/topics/dis-snek).

If you get stuck join our [Discord server](https://discord.gg/naff).


## "Can I contribute to this project?"
Of course, we welcome all contributions to this library. Just ensure you follow our [requirements](/CONTRIBUTING.md).
If youre stuck for things to contribute, check out our [GitHub Projects](https://github.com/orgs/Discord-Snake-Pit/projects/1) for inspiration.
If youre stuck for things to contribute, check out our [GitHub Projects](https://github.com/orgs/NAFTeam/projects/1) for inspiration.

## Links:
- Support Server: https://discord.gg/naff
Expand Down
16 changes: 14 additions & 2 deletions naff/api/events/internal.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def on_guild_join(event):
"""
import re
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any, Optional

from naff.client.const import MISSING
from naff.models.discord.snowflake import to_snowflake
Expand All @@ -33,6 +33,7 @@ def on_guild_join(event):
"Component",
"Connect",
"Disconnect",
"Error",
"ShardConnect",
"ShardDisconnect",
"GuildEvent",
Expand All @@ -47,7 +48,7 @@ def on_guild_join(event):

if TYPE_CHECKING:
from naff import Client
from naff.models.naff.context import ComponentContext
from naff.models.naff.context import ComponentContext, Context
from naff.models.discord.snowflake import Snowflake_Type
from naff.models.discord.guild import Guild

Expand Down Expand Up @@ -161,3 +162,14 @@ class Button(Component):
@define(kw_only=False)
class Select(Component):
"""Dispatched when a user uses a Select."""


@define(kw_only=False)
class Error(BaseEvent):
"""Dispatched when the library encounters an error."""

source: str = field(metadata=docs("The source of the error"))
error: Exception = field(metadata=docs("The error that was encountered"))
args: tuple[Any] = field(factory=tuple)
kwargs: dict[str, Any] = field(factory=dict)
ctx: Optional["Context"] = field(default=None, metadata=docs("The Context, if one was active"))
6 changes: 5 additions & 1 deletion naff/api/events/processors/member_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@ async def _on_raw_guild_member_add(self, event: "RawGatewayEvent") -> None:
async def _on_raw_guild_member_remove(self, event: "RawGatewayEvent") -> None:
g_id = event.data.pop("guild_id")
user = self.cache.place_user_data(event.data["user"])
member = self.cache.get_member(g_id, user.id)

self.cache.delete_member(g_id, user.id)
guild = self.cache.get_guild(g_id)
guild.member_count -= 1
self.dispatch(events.MemberRemove(g_id, self.cache.get_member(g_id, user.id) or user))

self.dispatch(events.MemberRemove(g_id, member or user))

@Processor.define()
async def _on_raw_guild_member_update(self, event: "RawGatewayEvent") -> None:
Expand Down
14 changes: 11 additions & 3 deletions naff/api/gateway/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from typing import TypeVar, TYPE_CHECKING

from naff.api import events
from naff.client.const import logger
from naff.client.const import logger, MISSING
from naff.client.utils.input_utils import OverriddenJson
from naff.client.utils.serializer import dict_filter_none
from naff.models.discord.enums import Status
Expand Down Expand Up @@ -66,6 +66,7 @@ def __init__(self, state: "ConnectionState", shard: tuple[int, int]) -> None:
self.session_id = None

self.ws_url = state.gateway_url
self.ws_resume_url = MISSING

# This lock needs to be held to send something over the gateway, but is also held when
# reconnecting. That way there's no race conditions between sending and reconnecting.
Expand Down Expand Up @@ -192,11 +193,11 @@ async def dispatch_opcode(self, data, op: OPCODE) -> None:

case OPCODE.RECONNECT:
logger.info("Gateway requested reconnect. Reconnecting...")
return await self.reconnect(resume=True)
return await self.reconnect(resume=True, url=self.ws_resume_url)

case OPCODE.INVALIDATE_SESSION:
logger.warning("Gateway has invalidated session! Reconnecting...")
return await self.reconnect(resume=data)
return await self.reconnect(resume=data, url=self.ws_resume_url if data else None)

case _:
return logger.debug(f"Unhandled OPCODE: {op} = {OPCODE(op).name}")
Expand All @@ -208,8 +209,10 @@ async def dispatch_event(self, data, seq, event) -> None:
self._trace = data.get("_trace", [])
self.sequence = seq
self.session_id = data["session_id"]
self.ws_resume_url = data["resume_gateway_url"]
logger.info(f"Shard {self.shard[0]} has connected to gateway!")
logger.debug(f"Session ID: {self.session_id} Trace: {self._trace}")
# todo: future polls, improve guild caching here. run the debugger. you'll see why
return self.state.client.dispatch(events.WebsocketReady(data))

case "RESUMED":
Expand Down Expand Up @@ -263,6 +266,11 @@ async def _identify(self) -> None:
f"Shard ID {self.shard[0]} has identified itself to Gateway, requesting intents: {self.state.intents}!"
)

async def reconnect(self, *, resume: bool = False, code: int = 1012, url: str | None = None) -> None:
self.state.clear_ready()
self._ready.clear()
await super().reconnect(resume=resume, code=code, url=url)

async def _resume_connection(self) -> None:
"""Send a resume payload to the gateway."""
if self.ws is None:
Expand Down
5 changes: 5 additions & 0 deletions naff/api/gateway/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ async def stop(self) -> None:

self.gateway_started.clear()

def clear_ready(self) -> None:
"""Clear the ready event."""
self._shard_ready.clear()
self.client._ready.clear() # noinspection PyProtectedMember

async def _ws_connect(self) -> None:
"""Connect to the Discord Gateway."""
logger.info(f"Shard {self.shard_id} is attempting to connect to gateway...")
Expand Down
4 changes: 2 additions & 2 deletions naff/api/gateway/websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ async def receive(self, force: bool = False) -> str:

return msg

async def reconnect(self, *, resume: bool = False, code: int = 1012) -> None:
async def reconnect(self, *, resume: bool = False, code: int = 1012, url: str | None = None) -> None:
async with self._race_lock:
self._closed.clear()

Expand All @@ -246,7 +246,7 @@ async def reconnect(self, *, resume: bool = False, code: int = 1012) -> None:
self.ws = None
self._zlib = zlib.decompressobj()

self.ws = await self.state.client.http.websocket_connect(self.ws_url)
self.ws = await self.state.client.http.websocket_connect(url or self.ws_url)

hello = await self.receive(force=True)
self.heartbeat_interval = hello["d"]["heartbeat_interval"] / 1000
Expand Down
18 changes: 11 additions & 7 deletions naff/api/http/http_requests/guild.py
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,9 @@ async def delete_guild_template(
# why on earth does this return the deleted template object?
return await self.request(Route("DELETE", f"/guilds/{guild_id}/templates/{template_code}"))

async def get_auto_moderation_rules(self, guild_id: "Snowflake_Type") -> list[dict]:
async def get_auto_moderation_rules(
self, guild_id: "Snowflake_Type"
) -> list[discord_typings.AutoModerationRuleData]:
"""
Get this guilds auto moderation rules.
Expand All @@ -801,10 +803,11 @@ async def get_auto_moderation_rules(self, guild_id: "Snowflake_Type") -> list[di
Returns:
A list of auto moderation rules
"""
# todo: Add discord typings when added
return await self.request(Route("GET", f"/guilds/{guild_id}/auto-moderation/rules"))

async def get_auto_moderation_rule(self, guild_id: "Snowflake_Type", rule_id: "Snowflake_Type") -> dict:
async def get_auto_moderation_rule(
self, guild_id: "Snowflake_Type", rule_id: "Snowflake_Type"
) -> discord_typings.AutoModerationRuleData:
"""
Get a specific auto moderation rule.
Expand All @@ -815,10 +818,11 @@ async def get_auto_moderation_rule(self, guild_id: "Snowflake_Type", rule_id: "S
Returns:
The auto moderation rule
"""
# todo: Add discord typings when added
return await self.request(Route("GET", f"/guilds/{guild_id}/auto-moderation/rules/{rule_id}"))

async def create_auto_moderation_rule(self, guild_id: "Snowflake_Type", payload: dict) -> dict:
async def create_auto_moderation_rule(
self, guild_id: "Snowflake_Type", payload: discord_typings.AutoModerationRuleData
) -> discord_typings.AutoModerationRuleData:
"""
Create an auto moderation rule.
Expand All @@ -844,7 +848,7 @@ async def modify_auto_moderation_rule(
event_type: Absent[dict] = MISSING,
enabled: Absent[bool] = MISSING,
reason: Absent[str] = MISSING,
) -> dict:
) -> discord_typings.AutoModerationRuleData:
"""
Modify an existing auto moderation rule.
Expand Down Expand Up @@ -881,7 +885,7 @@ async def modify_auto_moderation_rule(

async def delete_auto_moderation_rule(
self, guild_id: "Snowflake_Type", rule_id: "Snowflake_Type", reason: Absent[str] = MISSING
) -> dict:
) -> discord_typings.AutoModerationRuleData:
"""
Delete an auto moderation rule.
Expand Down
28 changes: 25 additions & 3 deletions naff/api/voice/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def audio_complete(self) -> bool:
return False
return True

def _create_process(self) -> None:
def _create_process(self, *, block: bool = True) -> None:
before = (
self.ffmpeg_before_args if isinstance(self.ffmpeg_before_args, list) else self.ffmpeg_before_args.split()
)
Expand Down Expand Up @@ -162,8 +162,9 @@ def _create_process(self) -> None:
)
self.read_ahead_task.start()

# block until some data is in the buffer
self.buffer.initialised.wait()
if block:
# block until some data is in the buffer
self.buffer.initialised.wait()

def _read_ahead(self) -> None:
while self.process:
Expand All @@ -181,6 +182,24 @@ def _read_ahead(self) -> None:
self.buffer.initialised.set()
time.sleep(0.1)

def pre_buffer(self, duration: None | float = None) -> None:
"""
Start pre-buffering the audio.
Args:
duration: The duration of audio to pre-buffer.
"""
if duration:
self.buffer_seconds = duration

if self.process and self.process.poll() is None:
raise RuntimeError("Cannot pre-buffer an already running process")
# sanity value enforcement to prevent audio weirdness
self.buffer = AudioBuffer()
self.buffer.initialised.clear()

self._create_process(block=False)

def read(self, frame_size: int) -> bytes:
"""
Reads frame_size bytes of audio from the buffer.
Expand All @@ -190,6 +209,9 @@ def read(self, frame_size: int) -> bytes:
"""
if not self.process:
self._create_process()
if not self.buffer.initialised.is_set():
# we cannot start playing until the buffer is initialised
self.buffer.initialised.wait()

data = self.buffer.read(frame_size)

Expand Down
5 changes: 1 addition & 4 deletions naff/api/voice/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,7 @@ def play(self) -> None:
"""Start playing."""
self._stop_event.clear()
self._resume.set()
try:
self.start()
finally:
self.current_audio.cleanup()
self.start()

def run(self) -> None:
"""The main player loop to send audio to the voice websocket."""
Expand Down
2 changes: 1 addition & 1 deletion naff/client/auto_shard_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ async def _on_websocket_ready(self, event: events.RawGatewayEvent) -> None:
try:
await asyncio.gather(*self.async_startup_tasks)
except Exception as e:
await self.on_error("async-extension-loader", e)
self.dispatch(events.Error("async-extension-loader", e))

# cache slash commands
if not self._startup:
Expand Down
Loading

0 comments on commit 0a45808

Please sign in to comment.