Compare commits

...

2 Commits

Author SHA1 Message Date
Slipstreamm
e965a675c1 refactor(api): Re-export common symbols from top-level package
Some checks failed
Deploy MkDocs / deploy (push) Has been cancelled
Makes commonly used classes, functions, and decorators from `disagreement.ext` and `disagreement.ui` submodules directly accessible under the `disagreement` namespace.

This change simplifies import statements for users, leading to cleaner and more concise code. Documentation and examples have been updated to reflect these new, simplified import paths.
2025-06-14 18:57:12 -06:00
Slipstreamm
9237d12a24 docs(imports): Update import paths in documentation examples
Adjust examples to reflect the new top-level exposure of classes and enums, such as `Client`, `Permissions`, `Embed`, and `Button`, making imports simpler.
2025-06-14 18:44:04 -06:00
28 changed files with 212 additions and 116 deletions

View File

@ -67,6 +67,47 @@ from .hybrid_context import HybridContext
from .interactions import Interaction
from .logging_config import setup_logging
from . import ui, ext
from .ext.app_commands import (
AppCommand,
AppCommandContext,
AppCommandGroup,
MessageCommand,
OptionMetadata,
SlashCommand,
UserCommand,
group,
hybrid_command,
message_command,
slash_command,
subcommand,
subcommand_group,
)
from .ext.commands import (
BadArgument,
CheckAnyFailure,
CheckFailure,
Cog,
Command,
CommandContext,
CommandError,
CommandInvokeError,
CommandNotFound,
CommandOnCooldown,
MaxConcurrencyReached,
MissingRequiredArgument,
ArgumentParsingError,
check,
check_any,
command,
cooldown,
has_any_role,
has_role,
listener,
max_concurrency,
requires_permissions,
)
from .ext.tasks import Task, loop
from .ui import Item, Modal, Select, TextInput, View, button, select, text_input
import logging
@ -123,6 +164,51 @@ __all__ = [
"setup_logging",
"ui",
"ext",
"AppCommand",
"AppCommandContext",
"AppCommandGroup",
"MessageCommand",
"OptionMetadata",
"SlashCommand",
"UserCommand",
"group",
"hybrid_command",
"message_command",
"slash_command",
"subcommand",
"subcommand_group",
"BadArgument",
"CheckAnyFailure",
"CheckFailure",
"Cog",
"Command",
"CommandContext",
"CommandError",
"CommandInvokeError",
"CommandNotFound",
"CommandOnCooldown",
"MaxConcurrencyReached",
"MissingRequiredArgument",
"ArgumentParsingError",
"check",
"check_any",
"command",
"cooldown",
"has_any_role",
"has_role",
"listener",
"max_concurrency",
"requires_permissions",
"Task",
"loop",
"Item",
"Modal",
"Select",
"TextInput",
"View",
"button",
"select",
"text_input",
]

View File

@ -20,7 +20,7 @@ returns ``True``. Checks may be regular or async callables that accept a
`CommandContext`.
```python
from disagreement.ext.commands import command, check, CheckFailure
from disagreement import command, check, CheckFailure
def is_owner(ctx):
return ctx.author.id == "1"
@ -40,7 +40,7 @@ Commands can be rate limited using the ``cooldown`` decorator. The example
below restricts usage to once every three seconds per user:
```python
from disagreement.ext.commands import command, cooldown
from disagreement import command, cooldown
@command()
@cooldown(1, 3.0)
@ -56,8 +56,8 @@ Use `commands.requires_permissions` to ensure the invoking member has the
required permissions in the channel.
```python
from disagreement.ext.commands import command, requires_permissions
from disagreement.permissions import Permissions
from disagreement import command, requires_permissions
from disagreement import Permissions
@command()
@requires_permissions(Permissions.MANAGE_MESSAGES)

View File

@ -1,12 +1,11 @@
# Context Menu Commands
`disagreement` supports Discord's user and message context menu commands. Use the
`user_command` and `message_command` decorators from `ext.app_commands` to
define them.
`user_command` and `message_command` decorators to define them.
```python
from disagreement.ext.app_commands import user_command, message_command, AppCommandContext
from disagreement.models import User, Message
from disagreement import User, Message
from disagreement import User, Message, user_command, message_command, AppCommandContext
@user_command(name="User Info")
async def user_info(ctx: AppCommandContext, user: User) -> None:

View File

@ -13,8 +13,8 @@
```python
from disagreement.ext.commands import command
from disagreement import Member
from disagreement.ext.commands.core import CommandContext
from disagreement.models import Member
@command()
async def kick(ctx: CommandContext, target: Member):

View File

@ -4,7 +4,7 @@
These helper methods return the embed instance so you can chain calls.
```python
from disagreement.models import Embed
from disagreement import Embed
embed = (
Embed()

View File

@ -20,7 +20,7 @@ Triggered when a user's presence changes. The callback receives a `PresenceUpdat
```python
@client.event
async def on_presence_update(presence: disagreement.PresenceUpdate):
async def on_presence_update(presence: PresenceUpdate):
...
```
@ -30,7 +30,7 @@ Dispatched when a user begins typing in a channel. The callback receives a `Typi
```python
@client.event
async def on_typing_start(typing: disagreement.TypingStart):
async def on_typing_start(typing: TypingStart):
...
```
@ -40,7 +40,7 @@ Fired when a new member joins a guild. The callback receives a `Member` model.
```python
@client.event
async def on_guild_member_add(member: disagreement.Member):
async def on_guild_member_add(member: Member):
...
```
@ -51,7 +51,7 @@ receives a `GuildMemberRemove` model.
```python
@client.event
async def on_guild_member_remove(event: disagreement.GuildMemberRemove):
async def on_guild_member_remove(event: GuildMemberRemove):
...
```
@ -62,7 +62,7 @@ Dispatched when a user is banned from a guild. The callback receives a
```python
@client.event
async def on_guild_ban_add(event: disagreement.GuildBanAdd):
async def on_guild_ban_add(event: GuildBanAdd):
...
```
@ -73,7 +73,7 @@ Dispatched when a user's ban is lifted. The callback receives a
```python
@client.event
async def on_guild_ban_remove(event: disagreement.GuildBanRemove):
async def on_guild_ban_remove(event: GuildBanRemove):
...
```
@ -84,7 +84,7 @@ Sent when a channel's settings change. The callback receives an updated
```python
@client.event
async def on_channel_update(channel: disagreement.Channel):
async def on_channel_update(channel: Channel):
...
```
@ -95,7 +95,7 @@ Emitted when a guild role is updated. The callback receives a
```python
@client.event
async def on_guild_role_update(event: disagreement.GuildRoleUpdate):
async def on_guild_role_update(event: GuildRoleUpdate):
...
```
@ -138,6 +138,6 @@ Triggered when a user's voice connection state changes, such as joining or leavi
```python
@client.event
async def on_voice_state_update(state: disagreement.VoiceStateUpdate):
async def on_voice_state_update(state: VoiceStateUpdate):
...
```

View File

@ -8,6 +8,8 @@ You can control the maximum number of retries and the backoff cap when construct
These options are forwarded to `GatewayClient` as `max_retries` and `max_backoff`:
```python
from disagreement import Client
bot = Client(
token="your-token",
gateway_max_retries=10,

View File

@ -24,7 +24,7 @@ other supported session argument.
The HTTP client can list the guilds the bot user is in:
```python
from disagreement.http import HTTPClient
from disagreement import HTTPClient
http = HTTPClient(token="TOKEN")
guilds = await http.get_current_user_guilds()

View File

@ -39,14 +39,14 @@ pip install "disagreement[dev]"
import asyncio
import os
import disagreement
from disagreement import Client, GatewayIntent
from disagreement.ext import commands
from dotenv import load_dotenv
load_dotenv()
class Basics(commands.Cog):
def __init__(self, client: disagreement.Client) -> None:
def __init__(self, client: Client) -> None:
super().__init__(client)
@commands.command()
@ -58,8 +58,8 @@ token = os.getenv("DISCORD_BOT_TOKEN")
if not token:
raise RuntimeError("DISCORD_BOT_TOKEN environment variable not set")
intents = disagreement.GatewayIntent.default() | disagreement.GatewayIntent.MESSAGE_CONTENT
client = disagreement.Client(token=token, command_prefix="!", intents=intents, mention_replies=True)
intents = GatewayIntent.default() | GatewayIntent.MESSAGE_CONTENT
client = Client(token=token, command_prefix="!", intents=intents, mention_replies=True)
async def main() -> None:
client.add_cog(Basics(client))
await client.run()
@ -100,10 +100,10 @@ setup_logging(logging.DEBUG, file="bot.log")
### HTTP Session Options
Pass additional keyword arguments to ``aiohttp.ClientSession`` using the
``http_options`` parameter when constructing :class:`disagreement.Client`:
``http_options`` parameter when constructing :class:`Client`:
```python
client = disagreement.Client(
client = Client(
token=token,
http_options={"proxy": "http://localhost:8080"},
)
@ -119,7 +119,7 @@ Specify default mention behaviour for all outgoing messages when constructing th
```python
from disagreement.models import AllowedMentions
client = disagreement.Client(
client = Client(
token=token,
allowed_mentions=AllowedMentions.none().to_dict(),
)
@ -173,14 +173,15 @@ To run your bot across multiple gateway shards, pass ``shard_count`` when creati
the client:
```python
client = disagreement.Client(token=BOT_TOKEN, shard_count=2)
client = Client(token=BOT_TOKEN, shard_count=2)
```
If you want the library to determine the recommended shard count automatically,
use ``AutoShardedClient``:
```python
client = disagreement.AutoShardedClient(token=BOT_TOKEN)
from disagreement import AutoShardedClient
client = AutoShardedClient(token=BOT_TOKEN)
```
See `examples/sharded_bot.py` for a full example.

View File

@ -8,8 +8,8 @@ Use the ``allowed_mentions`` parameter of :class:`disagreement.Client` to set a
default for all messages:
```python
from disagreement.models import AllowedMentions
client = disagreement.Client(
from disagreement import AllowedMentions, Client
client = Client(
token="YOUR_TOKEN",
allowed_mentions=AllowedMentions.none().to_dict(),
)

View File

@ -10,7 +10,7 @@ Each attribute of ``Permissions`` represents a single permission bit. The value
is a power of two so multiple permissions can be combined using bitwise OR.
```python
from disagreement.permissions import Permissions
from disagreement import Permissions
value = Permissions.SEND_MESSAGES | Permissions.MANAGE_MESSAGES
```
@ -47,10 +47,10 @@ Return a list of permissions that ``current`` does not contain.
```python
from disagreement.permissions import (
Permissions,
has_permissions,
missing_permissions,
)
from disagreement import Permissions
current = Permissions.SEND_MESSAGES | Permissions.MANAGE_MESSAGES

View File

@ -26,7 +26,7 @@ An activity dictionary must include a `name` and a `type` field. The type value
Example using the provided activity classes:
```python
from disagreement.models import Game
from disagreement import Game
await client.change_presence(status="idle", activity=Game("with Discord"))
```
@ -34,7 +34,7 @@ await client.change_presence(status="idle", activity=Game("with Discord"))
You can also specify a streaming URL:
```python
from disagreement.models import Streaming
from disagreement import Streaming
await client.change_presence(status="online", activity=Streaming("My Stream", "https://twitch.tv/someone"))
```

View File

@ -48,7 +48,7 @@ The event handlers for these events receive both a `Reaction` object and the `Us
```python
import disagreement
from disagreement.models import Reaction, User, Member
from disagreement import Reaction, User, Member
@client.on_event("MESSAGE_REACTION_ADD")
async def on_reaction_add(reaction: Reaction, user: User | Member):

View File

@ -3,7 +3,7 @@
The `Client` provides helpers to manage guild scheduled events.
```python
from disagreement.client import Client
from disagreement import Client
client = Client(token="TOKEN")

View File

@ -1,9 +1,9 @@
# Using Slash Commands
The library provides a slash command framework via the `ext.app_commands` package. Define commands with decorators and register them with Discord.
The library provides a slash command framework to define commands with decorators and register them with Discord.
```python
from disagreement.ext.app_commands import AppCommandGroup
from disagreement import AppCommandGroup
bot_commands = AppCommandGroup("bot", "Bot commands")

View File

@ -1,11 +1,11 @@
# Task Loops
The tasks extension allows you to run functions periodically. Decorate an async function with `@tasks.loop` and start it using `.start()`.
The tasks extension allows you to run functions periodically. Decorate an async function with `@loop` and start it using `.start()`.
```python
from disagreement.ext import tasks
from disagreement import loop
@tasks.loop(minutes=1.0)
@loop(minutes=1.0)
async def announce():
print("Hello from a loop")
@ -19,7 +19,7 @@ You can provide the interval in seconds, minutes, hours or as a `datetime.timede
```python
import datetime
@tasks.loop(delta=datetime.timedelta(seconds=30))
@loop(delta=datetime.timedelta(seconds=30))
async def ping():
...
```
@ -30,7 +30,7 @@ Handle exceptions raised by the looped coroutine using `on_error`:
async def log_error(exc: Exception) -> None:
print("Loop failed:", exc)
@tasks.loop(seconds=5.0, on_error=log_error)
@loop(seconds=5.0, on_error=log_error)
async def worker():
...
```
@ -38,7 +38,7 @@ async def worker():
Run setup and teardown code using `before_loop` and `after_loop`:
```python
@tasks.loop(seconds=5.0)
@loop(seconds=5.0)
async def worker():
...
@ -58,7 +58,7 @@ from datetime import datetime, timedelta
time_to_run = (datetime.now() + timedelta(seconds=5)).time()
@tasks.loop(time_of_day=time_to_run)
@loop(time_of_day=time_to_run)
async def daily_task():
...
```

View File

@ -4,7 +4,7 @@
Use :class:`AutoArchiveDuration` to control when a thread is automatically archived.
```python
from disagreement.enums import AutoArchiveDuration
from disagreement import AutoArchiveDuration
await message.create_thread(
"discussion",

View File

@ -4,9 +4,9 @@ The library exposes an async context manager to send the typing indicator for a
```python
import asyncio
import disagreement
from disagreement import Client
client = disagreement.Client(token="YOUR_TOKEN")
client = Client(token="YOUR_TOKEN")
async def indicate(channel_id: str):
async with client.typing(channel_id):

View File

@ -19,8 +19,7 @@ The library exposes three broad categories of components:
`ActionRow` is a layout container. It may hold up to five buttons or a single select menu.
```python
from disagreement.models import ActionRow, Button
from disagreement.enums import ButtonStyle
from disagreement import ActionRow, Button, ButtonStyle
row = ActionRow(components=[
Button(style=ButtonStyle.PRIMARY, label="Click", custom_id="btn")
@ -32,8 +31,7 @@ row = ActionRow(components=[
Buttons provide a clickable UI element.
```python
from disagreement.models import Button
from disagreement.enums import ButtonStyle
from disagreement import Button, ButtonStyle
button = Button(
style=ButtonStyle.SUCCESS,
@ -47,8 +45,7 @@ button = Button(
`SelectMenu` lets the user choose one or more options. The `type` parameter controls the menu variety (`STRING_SELECT`, `USER_SELECT`, `ROLE_SELECT`, `MENTIONABLE_SELECT`, `CHANNEL_SELECT`).
```python
from disagreement.models import SelectMenu, SelectOption
from disagreement.enums import ComponentType, ChannelType
from disagreement import SelectMenu, SelectOption, ComponentType, ChannelType
menu = SelectMenu(
custom_id="example",
@ -70,7 +67,7 @@ For channel selects you may pass `channel_types` with a list of allowed `Channel
`Section` groups one or more `TextDisplay` components and can include an accessory `Button` or `Thumbnail`.
```python
from disagreement.models import Section, TextDisplay, Thumbnail, UnfurledMediaItem
from disagreement import Section, TextDisplay, Thumbnail, UnfurledMediaItem
section = Section(
components=[
@ -86,7 +83,7 @@ section = Section(
`TextDisplay` simply renders markdown text.
```python
from disagreement.models import TextDisplay
from disagreement import TextDisplay
text_display = TextDisplay(content="**Bold text**")
```
@ -96,7 +93,7 @@ text_display = TextDisplay(content="**Bold text**")
`Thumbnail` shows a small image. Set `spoiler=True` to hide the image until clicked.
```python
from disagreement.models import Thumbnail, UnfurledMediaItem
from disagreement import Thumbnail, UnfurledMediaItem
thumb = Thumbnail(
media=UnfurledMediaItem(url="https://example.com/image.png"),
@ -110,7 +107,7 @@ thumb = Thumbnail(
`MediaGallery` holds multiple `MediaGalleryItem` objects.
```python
from disagreement.models import MediaGallery, MediaGalleryItem, UnfurledMediaItem
from disagreement import MediaGallery, MediaGalleryItem, UnfurledMediaItem
gallery = MediaGallery(
items=[
@ -125,7 +122,7 @@ gallery = MediaGallery(
`File` displays an uploaded file. Use `spoiler=True` to mark it as a spoiler.
```python
from disagreement.models import File, UnfurledMediaItem
from disagreement import File, UnfurledMediaItem
file_component = File(
file=UnfurledMediaItem(url="attachment://file.zip"),
@ -138,7 +135,7 @@ file_component = File(
`Separator` adds vertical spacing or an optional divider line between components.
```python
from disagreement.models import Separator
from disagreement import Separator
separator = Separator(divider=True, spacing=2)
```
@ -148,7 +145,7 @@ separator = Separator(divider=True, spacing=2)
`Container` visually groups a set of components and can apply an accent colour or spoiler.
```python
from disagreement.models import Container, TextDisplay
from disagreement import Container, TextDisplay
container = Container(
components=[TextDisplay(content="Inside a container")],

View File

@ -5,7 +5,7 @@ The `HTTPClient` includes helper methods for creating, editing and deleting Disc
## Create a webhook
```python
from disagreement.http import HTTPClient
from disagreement import HTTPClient
http = HTTPClient(token="TOKEN")
payload = {"name": "My Webhook"}
@ -27,7 +27,7 @@ await http.delete_webhook("456")
The methods now return a `Webhook` object directly:
```python
from disagreement.models import Webhook
from disagreement import Webhook
print(webhook.id, webhook.name)
```
@ -37,7 +37,7 @@ print(webhook.id, webhook.name)
You can construct a `Webhook` object from an existing webhook URL without any API calls:
```python
from disagreement.models import Webhook
from disagreement import Webhook
webhook = Webhook.from_url("https://discord.com/api/webhooks/123/token")
print(webhook.id, webhook.token)

View File

@ -32,11 +32,12 @@ try:
GatewayIntent,
Message,
Guild,
ext,
AuthenticationError,
DisagreementException,
Cog,
command,
CommandContext,
)
from disagreement.ext import commands
except ImportError:
print(
"Failed to import disagreement. Make sure it's installed or PYTHONPATH is set correctly."
@ -87,26 +88,26 @@ client = Client(token=BOT_TOKEN, intents=intents, command_prefix="!")
# --- Define a Cog for example commands ---
class ExampleCog(commands.Cog): # Ensuring this uses commands.Cog
class ExampleCog(Cog): # Ensuring this uses commands.Cog
def __init__(
self, bot_client
): # Renamed client to bot_client to avoid conflict with self.client
super().__init__(bot_client) # Pass the client instance to the base Cog
@commands.command(name="hello", aliases=["hi"])
async def hello_command(self, ctx: commands.CommandContext, *, who: str = "world"):
@command(name="hello", aliases=["hi"])
async def hello_command(self, ctx: CommandContext, *, who: str = "world"):
"""Greets someone."""
await ctx.reply(f"Hello {ctx.author.mention} and {who}!")
print(f"Executed 'hello' command for {ctx.author.username}, greeting {who}")
@commands.command()
async def ping(self, ctx: commands.CommandContext):
@command()
async def ping(self, ctx: CommandContext):
"""Responds with Pong!"""
await ctx.reply("Pong!")
print(f"Executed 'ping' command for {ctx.author.username}")
@commands.command()
async def me(self, ctx: commands.CommandContext):
@command()
async def me(self, ctx: CommandContext):
"""Shows information about the invoking user."""
reply_content = (
f"Hello {ctx.author.mention}!\n"
@ -117,8 +118,8 @@ class ExampleCog(commands.Cog): # Ensuring this uses commands.Cog
await ctx.reply(reply_content)
print(f"Executed 'me' command for {ctx.author.username}")
@commands.command(name="add")
async def add_numbers(self, ctx: commands.CommandContext, num1: int, num2: int):
@command(name="add")
async def add_numbers(self, ctx: CommandContext, num1: int, num2: int):
"""Adds two numbers."""
result = num1 + num2
await ctx.reply(f"The sum of {num1} and {num2} is {result}.")
@ -126,16 +127,16 @@ class ExampleCog(commands.Cog): # Ensuring this uses commands.Cog
f"Executed 'add' command for {ctx.author.username}: {num1} + {num2} = {result}"
)
@commands.command(name="say")
async def say_something(self, ctx: commands.CommandContext, *, text_to_say: str):
@command(name="say")
async def say_something(self, ctx: CommandContext, *, text_to_say: str):
"""Repeats the text you provide."""
await ctx.reply(f"You said: {text_to_say}")
print(
f"Executed 'say' command for {ctx.author.username}, saying: {text_to_say}"
)
@commands.command(name="whois")
async def whois(self, ctx: commands.CommandContext, *, name: str):
@command(name="whois")
async def whois(self, ctx: CommandContext, *, name: str):
"""Looks up a member by username or nickname using the guild cache."""
if not ctx.guild:
await ctx.reply("This command can only be used in a guild.")
@ -149,8 +150,8 @@ class ExampleCog(commands.Cog): # Ensuring this uses commands.Cog
else:
await ctx.reply("Member not found in cache.")
@commands.command(name="quit")
async def quit_command(self, ctx: commands.CommandContext):
@command(name="quit")
async def quit_command(self, ctx: CommandContext):
"""Shuts down the bot (requires YOUR_USER_ID to be set)."""
# Replace YOUR_USER_ID with your actual Discord User ID for a safe shutdown command
your_user_id = "YOUR_USER_ID_REPLACE_ME" # IMPORTANT: Replace this

View File

@ -3,8 +3,6 @@ import asyncio
from typing import Union
from disagreement import (
Client,
ui,
ext,
HybridContext,
Message,
SelectOption,
@ -27,9 +25,14 @@ from disagreement import (
ChannelType,
MessageFlags,
Interaction,
Cog,
CommandContext,
AppCommandContext,
hybrid_command,
View,
button,
select,
)
from disagreement.ext.commands import Cog, CommandContext
from disagreement.ext.app_commands import AppCommandContext, hybrid_command
try:
from dotenv import load_dotenv
@ -69,12 +72,12 @@ STOCKS = [
# Define a View class that contains our components
class MyView(ui.View):
class MyView(View):
def __init__(self):
super().__init__(timeout=180) # 180-second timeout
self.click_count = 0
@ui.button(label="Click Me!", style=ButtonStyle.SUCCESS, emoji="🖱️")
@button(label="Click Me!", style=ButtonStyle.SUCCESS, emoji="🖱️")
async def click_me(self, interaction: Interaction):
self.click_count += 1
await interaction.respond(
@ -82,7 +85,7 @@ class MyView(ui.View):
ephemeral=True,
)
@ui.select(
@select(
custom_id="string_select",
placeholder="Choose an option",
options=[
@ -108,12 +111,12 @@ class MyView(ui.View):
# View for cycling through available stocks
class StockView(ui.View):
class StockView(View):
def __init__(self):
super().__init__(timeout=180)
self.index = 0
@ui.button(label="Next Stock", style=ButtonStyle.PRIMARY)
@button(label="Next Stock", style=ButtonStyle.PRIMARY)
async def next_stock(self, interaction: Interaction):
self.index = (self.index + 1) % len(STOCKS)
stock = STOCKS[self.index]

View File

@ -4,19 +4,18 @@
import asyncio
import os
from disagreement import Client, GatewayIntent
from disagreement.ext import commands
from disagreement import Client, GatewayIntent, Cog, command, CommandContext
from dotenv import load_dotenv
load_dotenv()
class Basics(commands.Cog):
class Basics(Cog):
def __init__(self, client: Client) -> None:
super().__init__(client)
@commands.command()
async def ping(self, ctx: commands.CommandContext) -> None:
@command()
async def ping(self, ctx: CommandContext) -> None:
await ctx.reply(f"Pong! Gateway Latency: {self.client.latency_ms} ms.") # type: ignore (latency is None during static analysis)

View File

@ -9,8 +9,7 @@ from typing import Set
if os.path.join(os.getcwd(), "examples") == os.path.dirname(os.path.abspath(__file__)):
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from disagreement import Client, GatewayIntent, Member, Message
from disagreement.ext import commands
from disagreement import Client, GatewayIntent, Member, Message, Cog, command, CommandContext
try:
from dotenv import load_dotenv
@ -37,21 +36,21 @@ client = Client(token=BOT_TOKEN, command_prefix="!", intents=intents)
BANNED_WORDS: Set[str] = {"badword1", "badword2"}
class ModerationCog(commands.Cog):
class ModerationCog(Cog):
def __init__(self, bot: Client) -> None:
super().__init__(bot)
@commands.command()
@command()
async def kick(
self, ctx: commands.CommandContext, member: Member, *, reason: str = ""
self, ctx: CommandContext, member: Member, *, reason: str = ""
) -> None:
"""Kick a member from the guild."""
await member.kick(reason=reason or None)
await ctx.reply(f"Kicked {member.display_name}")
@commands.command()
@command()
async def ban(
self, ctx: commands.CommandContext, member: Member, *, reason: str = ""
self, ctx: CommandContext, member: Member, *, reason: str = ""
) -> None:
"""Ban a member from the guild."""
await member.ban(reason=reason or None)

View File

@ -33,8 +33,10 @@ try:
Member,
HTTPException,
AuthenticationError,
Cog,
command,
CommandContext,
)
from disagreement.ext import commands
except ImportError:
print(
"Failed to import disagreement. Make sure it's installed or PYTHONPATH is set correctly."
@ -72,12 +74,12 @@ client = Client(token=BOT_TOKEN, intents=intents, command_prefix="!")
# --- Define a Cog for reaction-related commands ---
class ReactionCog(commands.Cog):
class ReactionCog(Cog):
def __init__(self, bot_client):
super().__init__(bot_client)
@commands.command(name="react")
async def react_command(self, ctx: commands.CommandContext):
@command(name="react")
async def react_command(self, ctx: CommandContext):
"""Reacts to the command message with a thumbs up."""
try:
# The emoji can be a standard Unicode emoji or a custom one in the format '<:name:id>'

View File

@ -1,7 +1,7 @@
from disagreement.ext import tasks
from disagreement import loop
@tasks.loop(seconds=2.0)
@loop(seconds=2.0)
async def ticker() -> None:
print("Extension tick")

View File

@ -8,12 +8,12 @@ import sys
if os.path.join(os.getcwd(), "examples") == os.path.dirname(os.path.abspath(__file__)):
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from disagreement.ext import tasks
from disagreement import loop
counter = 0
@tasks.loop(seconds=1.0)
@loop(seconds=1.0)
async def ticker() -> None:
global counter
counter += 1

View File

@ -24,8 +24,15 @@ if os.path.join(os.getcwd(), "examples") == os.path.dirname(os.path.abspath(__fi
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
try:
from disagreement import Client, GatewayIntent, HTTPException, AuthenticationError
from disagreement.ext import commands
from disagreement import (
Client,
GatewayIntent,
HTTPException,
AuthenticationError,
Cog,
command,
CommandContext,
)
except ImportError:
print(
"Failed to import disagreement. Make sure it's installed or PYTHONPATH is set correctly."
@ -60,12 +67,12 @@ client = Client(token=BOT_TOKEN, intents=intents, command_prefix="!")
# --- Define a Cog for the typing indicator command ---
class TypingCog(commands.Cog):
class TypingCog(Cog):
def __init__(self, bot_client):
super().__init__(bot_client)
@commands.command(name="typing")
async def typing_test_command(self, ctx: commands.CommandContext):
@command(name="typing")
async def typing_test_command(self, ctx: CommandContext):
"""Shows a typing indicator for 5 seconds."""
await ctx.reply("Showing typing indicator for 5 seconds...")
try: