Skip to content

Conversation

@Aqua-218
Copy link

Overview

Pin機能の実装

Issue

#213

@Aqua-218 Aqua-218 requested a review from a team as a code owner January 28, 2026 12:34
Copilot AI review requested due to automatic review settings January 28, 2026 12:34
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements a Pin feature for a Discord bot, allowing users to pin messages that will be automatically reposted when new messages arrive in the channel. The implementation adds three commands: /pin (via modal), /unpin, and a message context menu option to pin selected messages.

Changes:

  • Added pin command infrastructure with modal-based and context menu-based pinning
  • Implemented automatic message resending when new messages arrive in channels with pinned content
  • Added database operations for managing pin settings per channel

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
src/internal/repository/pin_setting.go Adds DeleteByChannelID method to support unpinning by channel
src/internal/bot/handler/messageCreate.go Implements automatic resending of pinned messages on new message events
src/internal/bot/handler/interaction.go Adds modal submit handling for pin feature
src/internal/bot/command/registry.go Registers pin, unpin, and pin select commands
src/internal/bot/command/general/pin.go Implements /pin command with modal interface and permission checks
src/internal/bot/command/general/unpin.go Implements /unpin command to remove pinned messages
src/internal/bot/command/general/pin_select.go Implements message context menu for pinning selected messages
src/internal/bot/command/general/pin_modal.go Handles modal submission for pin command
src/internal/bot/command/commands.go Registers command contexts for pin-related commands

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +90 to +97
// 自分のピン留めメッセージの場合は無視
if r.Author != nil && r.Author.ID == s.State.User.ID {
if len(r.Embeds) == 0 {
return
}
if r.Embeds[0].Footer != nil && strings.Contains(r.Embeds[0].Footer.Text, "Pinned Message") {
return
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The check for bot's own messages is incomplete. When r.Author is nil (which can happen in certain edge cases), the code will skip the embed check and proceed to resend the pinned message. This could cause the bot to enter an infinite loop where it keeps deleting and resending its own pinned messages. Move the bot check to be a top-level check before line 91, similar to the pattern at lines 26-29.

Suggested change
// 自分のピン留めメッセージの場合は無視
if r.Author != nil && r.Author.ID == s.State.User.ID {
if len(r.Embeds) == 0 {
return
}
if r.Embeds[0].Footer != nil && strings.Contains(r.Embeds[0].Footer.Text, "Pinned Message") {
return
}
// 自分のメッセージや Author が存在しないメッセージの場合は無視
if r.Author == nil || r.Author.ID == s.State.User.ID {
return

Copilot uses AI. Check for mistakes.
Comment on lines 54 to 69
repo := repository.NewPinSettingRepository(ctx.DB)
setting := &model.PinSetting{
ID: i.ChannelID,
URL: sentMessage.ID,
Title: "Pinned Message",
Content: message,
GuildID: i.GuildID,
ChannelID: i.ChannelID,
}

if err := repo.Update(setting); err != nil {
if err := repo.Create(setting); err != nil {
replyPinError(s, i, config, "エラー", "ピン留めの保存に失敗しました。")
return true
}
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The pin modal handler does not check if a pinned message already exists in the channel, unlike the PinSelect function which properly validates this at lines 61-64 in pin_select.go. This inconsistency means that using the modal via /pin command could overwrite existing pins without warning, while using the message context menu will reject the operation. Consider adding the same check here: query for existing settings and either reject with an error message or explicitly handle the overwrite case.

Copilot uses AI. Check for mistakes.
Comment on lines +74 to +93
sentMessage, err := s.ChannelMessageSendEmbed(i.ChannelID, embed)
if err != nil {
replyPinError(s, i, config, "エラー", "メッセージの送信に失敗しました。")
return
}

setting := &model.PinSetting{
ID: i.ChannelID,
URL: sentMessage.ID,
Title: "Pinned Message",
Content: targetMsg.Content,
GuildID: i.GuildID,
ChannelID: i.ChannelID,
}

err = repo.Create(setting)
if err != nil {
replyPinError(s, i, config, "エラー", "ピン留めの保存に失敗しました。")
return
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

In the pin message context menu handler, after successfully pinning a message, there is no cleanup of the old pinned message if one exists. Unlike the modal handler and resendPinnedMessage function, this code doesn't check for or delete existing pinned messages before creating a new one. This is inconsistent with the error check at lines 61-64 which prevents creating a second pin. If that check were ever removed or if there's a race condition, this could result in multiple pinned messages accumulating in the channel. Consider deleting the old pinned message if settings already exist before the check at line 61.

Copilot uses AI. Check for mistakes.
Comment on lines 116 to 131

func replyPinSuccess(s *discordgo.Session, i *discordgo.InteractionCreate, config *internal.Config, title string) {
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
{
Title: title,
Color: config.Colors.Success,
Timestamp: time.Now().Format(time.RFC3339),
},
},
Flags: discordgo.MessageFlagsEphemeral,
},
})
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The replyPinSuccess function is defined but never used anywhere in the codebase. Consider removing this unused function to reduce code clutter and maintenance burden.

Suggested change
func replyPinSuccess(s *discordgo.Session, i *discordgo.InteractionCreate, config *internal.Config, title string) {
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
{
Title: title,
Color: config.Colors.Success,
Timestamp: time.Now().Format(time.RFC3339),
},
},
Flags: discordgo.MessageFlagsEphemeral,
},
})
}

Copilot uses AI. Check for mistakes.
CustomID: "message",
Label: "投稿内容",
Style: discordgo.TextInputParagraph,
Placeholder: "投稿内容を入力してください。すでにPinされたメッセージがある場合は上書きされます。",
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The modal placeholder text states "すでにPinされたメッセージがある場合は上書きされます" (existing pinned messages will be overwritten), but this contradicts the actual behavior in pin_select.go (lines 61-64) which prevents pinning when a message already exists. This discrepancy in the user-facing message creates confusion about the expected behavior. Ensure the placeholder text accurately reflects the actual implementation, or align the implementation with this stated behavior by allowing overwrites.

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +53
err = repo.DeleteByChannelID(i.ChannelID)
if err != nil {
replyPinError(s, i, config, "エラーが発生しました", "ピン留めの解除中にエラーが発生しました。")
return
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The unpin command deletes the pin setting from the database but does not delete the actual pinned message from the Discord channel. Users will see the pinned message remain in the channel even after unpinning. Before deleting from the database (line 49), iterate through the settings and delete each message using s.ChannelMessageDelete(i.ChannelID, setting.URL) to clean up the Discord channel as well.

Copilot uses AI. Check for mistakes.
Comment on lines +75 to +77
func (r *PinSettingRepository) DeleteByChannelID(channelID string) error {
return r.db.Delete(&model.PinSetting{}, "channel_id = ?", channelID).Error
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The newly added DeleteByChannelID method lacks test coverage. The repository test file (pin_setting_test.go) contains comprehensive tests for other methods but does not test this new functionality. Add test cases to verify that DeleteByChannelID correctly deletes all pin settings for a given channel ID, preserves settings for other channels, and handles non-existent channel IDs appropriately.

Copilot uses AI. Check for mistakes.
@yuito-it yuito-it added the kind/feature 新機能のリクエスト label Jan 28, 2026
@yuito-it yuito-it added this to the v9 - Go Edition milestone Jan 28, 2026
@yuito-it yuito-it linked an issue Jan 28, 2026 that may be closed by this pull request
@yuito-it yuito-it moved this from Untouched to In Review in UniBot Feature Relase Jan 28, 2026
@yuito-it yuito-it changed the title Feat/go pin [v9] Pinメッセージ機能の実装 Jan 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/feature 新機能のリクエスト

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

[v9] Pin メッセージ

3 participants