Skip to content

feat: add self-timeout - #49

Merged
Vuks69 merged 1 commit into
Neon-Genesis-Linux:mainfrom
Uniolo1:main
Oct 4, 2026
Merged

Vuks69 merged 1 commit into
Neon-Genesis-Linux:mainfrom
Uniolo1:main

Conversation

@Uniolo1

@Uniolo1 Uniolo1 commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

This PR adds a self-timeout feature, fixing #26. Thanks to @dearfenane for helping me with this!

AI Usage disclosure

Stack overflow was down while I was working on part of this, so I used it to ask how to check if the duration is between a specific window and how to format a string. I just used ChatGPT.

Tests and stuff

CONTRIBUTING.md says to run a bunch of commands and to copy the output, so here it is!

user@the-puter:~/code/pen-bot-uniolo1$ test -z "$(gofmt -l .)"
go mod tidy && git diff --exit-code go.mod go.sum
go vet ./...
go test ./...
./custom-gcl run --timeout=5m ./...
?   	github.com/Neon-Genesis-Linux/pen-bot/cmd/pen-fun	[no test files]
?   	github.com/Neon-Genesis-Linux/pen-bot/internal/community	[no test files]
?   	github.com/Neon-Genesis-Linux/pen-bot/internal/core	[no test files]
ok  	github.com/Neon-Genesis-Linux/pen-bot/internal/db	(cached)
?   	github.com/Neon-Genesis-Linux/pen-bot/internal/logger	[no test files]
?   	github.com/Neon-Genesis-Linux/pen-bot/internal/moderation	[no test files]
bash: ./custom-gcl: No such file or directory
user@the-puter:~/code/pen-bot-uniolo1$ 

FYI: CONTRIBUTING.md says to Read DOCUMENTATION.md first, but DOCUMENTATION.md does not seem to exist

fixes #26

@dearfenane dearfenane left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

some typos i noticed

Comment thread internal/moderation/self_timeout.go Outdated
GuildID := e.GuildID()
if GuildID == nil {
// NOTE: should have been caught earlier
embed := selfTimeoutCommandEmbed("Command unavalible!", "This command can only be used in servers!", true)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

typo error

Comment thread internal/moderation/self_timeout.go Outdated
return updateEmbed(e, embed)
}

embed := selfTimeoutCommandEmbed("Sucessfully timed out", "See you later!", false)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

typo

Comment thread internal/moderation/self_timeout.go Outdated
}

if e.GuildID() == nil {
embed = selfTimeoutCommandEmbed("Command unavalible!", "This command can only be used in servers!", true)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

typo

Comment thread internal/moderation/self_timeout.go Outdated

// confirmation message
embed = selfTimeoutCommandEmbed("Confirm timeout", fmt.Sprintf(
"You are about to time yourself out, **this action is irreversal**, "+

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

shouldve been irreversible, not irreversal

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

shouldve been irreversible, not irreversal

Fixed typos you noticed (x2)

@Uniolo1

Uniolo1 commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

some typos i noticed

Fixed the typos you noticed.

@dearfenane dearfenane left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

also it seems like github didnt recognize the fixing thing, maybe do it "fixes #26" at the bottom of the pr

Comment thread internal/moderation/self_timeout.go Outdated
Comment thread internal/moderation/self_timeout.go Outdated
Comment thread internal/moderation/self_timeout.go Outdated
Comment thread internal/moderation/self_timeout.go Outdated
Comment thread internal/moderation/self_timeout.go Outdated
Comment thread internal/moderation/self_timeout.go Outdated
Comment thread internal/moderation/self_timeout.go Outdated
Comment thread internal/moderation/self_timeout.go Outdated
Comment thread internal/moderation/self_timeout.go
Comment thread internal/moderation/commands.go Outdated
@dearfenane

Copy link
Copy Markdown
Contributor

also i have just noticed

./custom-gcl run --timeout=5m ./...
?   	github.com/Neon-Genesis-Linux/pen-bot/cmd/pen-fun	[no test files]
?   	github.com/Neon-Genesis-Linux/pen-bot/internal/community	[no test files]
?   	github.com/Neon-Genesis-Linux/pen-bot/internal/core	[no test files]
ok  	github.com/Neon-Genesis-Linux/pen-bot/internal/db	(cached)
?   	github.com/Neon-Genesis-Linux/pen-bot/internal/logger	[no test files]
?   	github.com/Neon-Genesis-Linux/pen-bot/internal/moderation	[no test files]
bash: ./custom-gcl: No such file or directory
user@the-puter:~/code/pen-bot-uniolo1$ 

you need to run golangci-lint custom first...

@Uniolo1

Uniolo1 commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

Amended commit (again) to fix issues noted.

@dearfenane dearfenane left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

change this and ill approve your pr (though you will need your pr reviewed by vuks as well)

Comment thread internal/moderation/self_timeout.go Outdated

@dearfenane dearfenane left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@Uniolo1
Uniolo1 marked this pull request as draft October 4, 2026 02:16
@Uniolo1

Uniolo1 commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

Converting to draft: realized it should be in pen-mod not pen-fun, will amend after I figure out how I should go about dealing with docker and the README (since that would require adding a new binary)

@Uniolo1
Uniolo1 marked this pull request as ready for review October 4, 2026 02:27

@Vuks69 Vuks69 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Overall pretty well executed, just some inconsistencies.

Comment on lines +89 to +92
if err != nil {
embed := selfTimeoutCommandEmbed("Invalid permissions!", fmt.Sprintf("The bot lacks permission to timeout <@%s>", e.Member().User.ID), true)
return updateEmbed(e, embed)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The error is discarded and misdiagnosed. Every UpdateMember failure renders as "Invalid permissions!". A 404 unknown-member, a 429, a 5xx, or a network blip all tell the user to check their permissions.

And because you return updateEmbed's error rather than the REST error, disgo's default error handler logs only the edit failure; the timeout failure itself leaves no trace. Use rest.IsJSONErrorCode(err, rest.JSONErrorCodeMissingAccess) / errors.As for the 403 branch and slog.Error the rest, the way tldr does.

Comment thread internal/moderation/self_timeout.go Outdated

// buttons for embed
var buttons = [2]discord.ButtonComponent{
discord.NewDangerButton("I understand", fmt.Sprintf("/"+selfTimeoutCommand+"/Confirm/%s", duration.String())),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AI raised a potential problem with no expiry, so someone could click the button a few days later and still get timed out. Not sure how much of a problem this really would be. Can be fixed by including an expiry timestamp in the ID and checking it on click.

@Uniolo1 Uniolo1 Oct 4, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not sure how much of a problem this really would be

I don't think it is that big of an issue. The message is ephemeral so it will go away after some time

Comment thread internal/moderation/self_timeout.go Outdated
Comment on lines +116 to +133
duration, err := time.ParseDuration(durationString)
if err != nil {
embed = selfTimeoutCommandEmbed("Invalid duration", fmt.Sprintf("'%s' is not a valid duration!", durationString), true)
return sendEmbed(e, embed, nil)
}

// Ensure ms/ns times are not accepted
duration = duration.Round(time.Second)

// ensure duration is not too long or too short
// NOTE: "duration.String()" is more verbose as it includes values not specified by the user (as '0')
if duration < 10*time.Second {
embed = selfTimeoutCommandEmbed("Invalid duration", fmt.Sprintf("`%s` is too short", durationString), true)
return sendEmbed(e, embed, nil)
} else if duration > 3*24*time.Hour {
embed = selfTimeoutCommandEmbed("Invalid duration", fmt.Sprintf("`%s` is longer than 3 days!", durationString), true)
return sendEmbed(e, embed, nil)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd separate this into a validateDuration function, and use that function as well in handleSelfTimeoutCommandConfirm instead of L68-74 there.

Comment thread internal/moderation/self_timeout.go Outdated
Comment on lines +105 to +108
if e.GuildID() == nil {
embed = selfTimeoutCommandEmbed("Command unavailable!", "This command can only be used in servers!", true)
return sendEmbed(e, embed, nil)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This could be a separate requiresGuild function somewhere in Core, many commands will need that check as well.

Comment thread internal/moderation/self_timeout.go Outdated
Comment on lines +122 to +123
// Ensure ms/ns times are not accepted
duration = duration.Round(time.Second)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The comment says "Ensure ms/ns times are not accepted", but nothing rejects them; Round(time.Second) silently truncates. The confirm prompt echoes the raw 1h0m0.4s while the button carries 1h0m0s. Sub-second input is rejected only incidentally, by the 10s floor.

validateDuration could return the normalized/truncated duration so we don't have that discrepancy too. durationString needs to be overwritten here.

Comment thread internal/moderation/self_timeout.go Outdated
WithColor(color)
}

// helper to send discord message with the image

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not seeing an image anywhere (copypaste?), also doc comments should be full sentences (uppercase start, end with dot).

Comment thread internal/moderation/self_timeout.go Outdated
return err
}

// same as sendEmbed but for button handlers

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

describes the wrong difference, the real one is that it edits a message and force-clears components

Comment thread internal/moderation/self_timeout.go Outdated
Comment on lines +17 to +29
func selfTimeoutCommandEmbed(title string, desc string, isError bool) discord.Embed {
var color int
if isError {
color = 0xff0000 // red
} else {
color = 0xffc34d // yellow-ish orange
}

return discord.NewEmbed().
WithTitle(title).
WithDescription(desc).
WithColor(color)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think separating this out into selfTimeoutEmbed/selfTimeoutErrorEmbed like tldr would be more readable on invocations.

Comment thread internal/moderation/self_timeout.go Outdated

durationString, durationProvided := interaction.OptString("duration")
if !durationProvided {
embed = selfTimeoutCommandEmbed("Unset duration", "duration was not set!", true)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

error message typo (capitalization)

Comment thread internal/moderation/self_timeout.go Outdated
Comment on lines +61 to +67
func handleSelfTimeoutCommandGoback(data discord.ButtonInteractionData, e *handler.ComponentEvent) error {
// overwrite the original message
embed := selfTimeoutCommandEmbed("Timeout Cancelled", "See you around!", false)
return updateEmbed(e, embed)
}

func handleSelfTimeoutCommandConfirm(data discord.ButtonInteractionData, e *handler.ComponentEvent) error {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

$ pre-commit run --all
(snip...)
custom-gcl (golangci-lint + NilAway).....................................Failed
- hook id: custom-gcl
- exit code: 1

internal/moderation/self_timeout.go:61:37: unused-parameter: parameter 'data' seems to be unused, consider removing or renaming it as _ (revive)
func handleSelfTimeoutCommandGoback(data discord.ButtonInteractionData, e *handler.ComponentEvent) error {
                                    ^
internal/moderation/self_timeout.go:67:38: unused-parameter: parameter 'data' seems to be unused, consider removing or renaming it as _ (revive)
func handleSelfTimeoutCommandConfirm(data discord.ButtonInteractionData, e *handler.ComponentEvent) error {
                                     ^
2 issues:
* revive: 2
Suggested change
func handleSelfTimeoutCommandGoback(data discord.ButtonInteractionData, e *handler.ComponentEvent) error {
// overwrite the original message
embed := selfTimeoutCommandEmbed("Timeout Cancelled", "See you around!", false)
return updateEmbed(e, embed)
}
func handleSelfTimeoutCommandConfirm(data discord.ButtonInteractionData, e *handler.ComponentEvent) error {
func handleSelfTimeoutCommandGoback(_ discord.ButtonInteractionData, e *handler.ComponentEvent) error {
// overwrite the original message
embed := selfTimeoutCommandEmbed("Timeout Cancelled", "See you around!", false)
return updateEmbed(e, embed)
}
func handleSelfTimeoutCommandConfirm(_ discord.ButtonInteractionData, e *handler.ComponentEvent) error {

@Uniolo1

Uniolo1 commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

Overall pretty well executed, just some inconsistencies.

I amended my PR, should have addressed your issues (hopefully I didn't mess up anything when rebasing)

Fixes Neon-Genesis-Linux#26

AI Usage disclosure: Stack overflow was down while I was working on part
of this, so I used it to **ask** how to check if the duration is between
a specific window and how to format a string. I just used chatgpt.

AI Usage disclosure (Vuks edition): Vuks used opencode's big pickle
model inorder to fix some issues with this commit (all the long comments
are from the AI)

Assisted-by: chatgpt
Assisted-by: opencode's big pickle model
Co-authored-by: dearfenane <302284256+dearfenane@users.noreply.github.com>
Co-authored-by: Vuks69 <51289041+Vuks69@users.noreply.github.com>
Signed-off-by: Uniolo1 <89284448+Uniolo1@users.noreply.github.com>
@Vuks69
Vuks69 merged commit 18ead09 into Neon-Genesis-Linux:main Oct 4, 2026
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.

feat: self-timeout

3 participants