Skip to content

Better version of cowsay #1028

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

ygweygyigyigyigerig
Copy link

@ygweygyigyigyigerig ygweygyigyigyigerig commented Aug 18, 2025

changes: added cowsay.py

Summary by Sourcery

Introduce a new cowsay cog that allows users to generate ASCII cow messages with input validation and a per-user cooldown

New Features:

  • Add a Cowsay cog with a hybrid cowsay command to render ASCII cow speech in Discord
  • Enforce a 180-second per-user cooldown for the cowsay command
  • Sanitize user input to printable characters and cap messages at 200 characters
  • Automatically generate and attach usage information for the command

Copy link
Contributor

sourcery-ai bot commented Aug 18, 2025

Reviewer's Guide

Introduces a new Cowsay cog that defines a hybrid_command with usage generation, enforces per-user cooldowns, sanitizes and length-limits input, and generates ASCII art output using the cowsay library.

Sequence diagram for the cowsay command execution with cooldown and input validation

sequenceDiagram
    actor User
    participant DiscordBot as Bot
    participant CowsayCog
    participant cowsay
    User->>Bot: Invoke /cowsay command with text
    Bot->>CowsayCog: cowsay_command(ctx, text)
    CowsayCog->>CowsayCog: Check user cooldown
    alt On cooldown
        CowsayCog->>Bot: Respond with cooldown message (ephemeral)
    else Not on cooldown
        CowsayCog->>CowsayCog: Sanitize and check text length
        alt Text too long
            CowsayCog->>Bot: Send error message
        else Text valid
            CowsayCog->>cowsay: get_output_string("cow", sanitized_text)
            cowsay-->>CowsayCog: ASCII cow text
            CowsayCog->>Bot: Send cow ASCII art
        end
    end
Loading

Class diagram for the new Cowsay cog and command

classDiagram
    class Cowsay {
        +__init__(bot: Tux)
        +cowsay_command(ctx: commands.Context[Tux], text: str)
    }
    class Tux
    class commands.Cog
    Cowsay --|> commands.Cog
    Cowsay o-- Tux
    class cowsay {
        +get_output_string(animal: str, text: str)
    }
    Cowsay ..> cowsay : uses
    class user_cooldowns {
        +user_id: float (timestamp)
    }
    Cowsay ..> user_cooldowns : uses
    class generate_usage {
        +generate_usage(command)
    }
    Cowsay ..> generate_usage : uses
Loading

File-Level Changes

Change Details Files
Define the Cowsay cog and command with usage metadata
  • Created Cowsay class inheriting from commands.Cog
  • Added hybrid_command decorator with name and description
  • Initialized usage attribute via generate_usage in init
tux/cogs/fun/cowsay.py
Implement per-user cooldown tracking
  • Added user_cooldowns dict and cooldown_duration constant
  • Checked elapsed time per user and prevented command if still cooling down
  • Sent ephemeral cooldown message with remaining seconds
tux/cogs/fun/cowsay.py
Sanitize and enforce input length
  • Filtered text to printable characters and trimmed whitespace
  • Defined max_length and compared against sanitized text
  • Sent error message when input exceeds limit
tux/cogs/fun/cowsay.py
Generate and send cowsay ASCII output
  • Called cowsay.get_output_string with sanitized text
  • Wrapped output in code block and sent via ctx.send
tux/cogs/fun/cowsay.py
Register Cowsay cog in bot setup
  • Added async setup function
  • Used bot.add_cog to load the Cowsay cog
tux/cogs/fun/cowsay.py

Possibly linked issues

  • Server guide/quick links #123: The PR adds a cowsay command that allows users to provide text input and respond with a cowsay message, directly addressing the issue.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `tux/cogs/fun/cowsay.py:44` </location>
<code_context>
+        
+        # Enforce character limit and sanitize input
+        max_length = 200
+        sanitized_text = ''.join(c for c in text if c.isprintable()).strip()
+        if len(sanitized_text) > max_length:
+            await ctx.send(content=f"❌ Input too long! Please limit your message to {max_length} characters.")
</code_context>

<issue_to_address>
Sanitization may not prevent all unwanted input (e.g., backticks or code block escapes).

Consider escaping or removing backticks and other Markdown-sensitive characters to avoid Discord code block formatting issues.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
        # Enforce character limit and sanitize input
        max_length = 200
        sanitized_text = ''.join(c for c in text if c.isprintable()).strip()
        if len(sanitized_text) > max_length:
            await ctx.send(content=f"❌ Input too long! Please limit your message to {max_length} characters.")
            return

        cow_text = cowsay.get_output_string("cow", sanitized_text)
        await ctx.send(content=f"```{cow_text}```")
=======
        # Enforce character limit and sanitize input
        max_length = 200
        sanitized_text = ''.join(c for c in text if c.isprintable()).strip()
        # Remove Markdown-sensitive characters (backticks and triple backticks)
        sanitized_text = sanitized_text.replace("`", "")
        sanitized_text = sanitized_text.replace("```", "")
        if len(sanitized_text) > max_length:
            await ctx.send(content=f"❌ Input too long! Please limit your message to {max_length} characters.")
            return

        cow_text = cowsay.get_output_string("cow", sanitized_text)
        await ctx.send(content=f"```{cow_text}```")
>>>>>>> REPLACE

</suggested_fix>

### Comment 2
<location> `tux/cogs/fun/cowsay.py:49` </location>
<code_context>
+            await ctx.send(content=f"❌ Input too long! Please limit your message to {max_length} characters.")
+            return
+            
+        cow_text = cowsay.get_output_string("cow", sanitized_text)
+        await ctx.send(content=f"```{cow_text}```")
+
</code_context>

<issue_to_address>
Potential for cowsay output to exceed Discord's message length limit.

Check cow_text length before sending, and truncate or split if it exceeds Discord's 2000 character limit.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
        cow_text = cowsay.get_output_string("cow", sanitized_text)
        await ctx.send(content=f"```{cow_text}```")
=======
        cow_text = cowsay.get_output_string("cow", sanitized_text)
        # Discord message limit is 2000 characters
        max_discord_length = 2000
        cow_text_wrapped = f"```{cow_text}```"
        if len(cow_text_wrapped) <= max_discord_length:
            await ctx.send(content=cow_text_wrapped)
        else:
            # Split cow_text into chunks that fit within the Discord limit, accounting for code block markers
            code_block_start = "```"
            code_block_end = "```"
            max_chunk_length = max_discord_length - len(code_block_start) - len(code_block_end)
            for i in range(0, len(cow_text), max_chunk_length):
                chunk = cow_text[i:i+max_chunk_length]
                await ctx.send(content=f"{code_block_start}{chunk}{code_block_end}")
>>>>>>> REPLACE

</suggested_fix>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 42 to 50
# Enforce character limit and sanitize input
max_length = 200
sanitized_text = ''.join(c for c in text if c.isprintable()).strip()
if len(sanitized_text) > max_length:
await ctx.send(content=f"❌ Input too long! Please limit your message to {max_length} characters.")
return

cow_text = cowsay.get_output_string("cow", sanitized_text)
await ctx.send(content=f"```{cow_text}```")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Sanitization may not prevent all unwanted input (e.g., backticks or code block escapes).

Consider escaping or removing backticks and other Markdown-sensitive characters to avoid Discord code block formatting issues.

Suggested change
# Enforce character limit and sanitize input
max_length = 200
sanitized_text = ''.join(c for c in text if c.isprintable()).strip()
if len(sanitized_text) > max_length:
await ctx.send(content=f"❌ Input too long! Please limit your message to {max_length} characters.")
return
cow_text = cowsay.get_output_string("cow", sanitized_text)
await ctx.send(content=f"```{cow_text}```")
# Enforce character limit and sanitize input
max_length = 200
sanitized_text = ''.join(c for c in text if c.isprintable()).strip()
# Remove Markdown-sensitive characters (backticks and triple backticks)
sanitized_text = sanitized_text.replace("`", "")
sanitized_text = sanitized_text.replace("```", "")
if len(sanitized_text) > max_length:
await ctx.send(content=f"❌ Input too long! Please limit your message to {max_length} characters.")
return
cow_text = cowsay.get_output_string("cow", sanitized_text)
await ctx.send(content=f"```{cow_text}```")

Comment on lines +49 to +50
cow_text = cowsay.get_output_string("cow", sanitized_text)
await ctx.send(content=f"```{cow_text}```")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Potential for cowsay output to exceed Discord's message length limit.

Check cow_text length before sending, and truncate or split if it exceeds Discord's 2000 character limit.

Suggested change
cow_text = cowsay.get_output_string("cow", sanitized_text)
await ctx.send(content=f"```{cow_text}```")
cow_text = cowsay.get_output_string("cow", sanitized_text)
# Discord message limit is 2000 characters
max_discord_length = 2000
cow_text_wrapped = f"```{cow_text}```"
if len(cow_text_wrapped) <= max_discord_length:
await ctx.send(content=cow_text_wrapped)
else:
# Split cow_text into chunks that fit within the Discord limit, accounting for code block markers
code_block_start = "```"
code_block_end = "```"
max_chunk_length = max_discord_length - len(code_block_start) - len(code_block_end)
for i in range(0, len(cow_text), max_chunk_length):
chunk = cow_text[i:i+max_chunk_length]
await ctx.send(content=f"{code_block_start}{chunk}{code_block_end}")

@cherryl1k
Copy link
Collaborator

it'll probably be a while till we get around look to this, just to let you know

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants