Skip to content

feat: 引用メッセージを展開するように#57

Merged
kaitoyama merged 10 commits intostagingfrom
feat/expand-quotes
Feb 23, 2026
Merged

feat: 引用メッセージを展開するように#57
kaitoyama merged 10 commits intostagingfrom
feat/expand-quotes

Conversation

@cp-20
Copy link
Contributor

@cp-20 cp-20 commented Feb 21, 2026

User description

BOT_GPT が呼び出された時にメッセージを引用していた場合、それを展開して LLM に渡すようにした


例えば

↓について教えて
https://q.trap.jp/...

↓について教えて

> cp20:
> ここのログが怪しそう
> ```
> some logs here
> ```

のように展開されるはず


ただし展開されるには次の条件のいずれかを満たす必要がある

  • 引用メッセージの投稿者が自分
  • 引用メッセージの投稿者がBot
  • 引用メッセージのチャンネルが特定のチャンネル
    • #event / #random / #general / #services / #team/SysAd 以下全てのチャンネル

さらに1つの引用メッセージで10000文字を超えた分は省略される


PR Type

enhancement, bug_fix


Description

  • 引用メッセージを展開する機能を追加

  • キャッシュ機能を用いたチャンネルとユーザー情報の取得

  • 引用メッセージのフォーマットと条件を修正

  • 新しい依存関係の追加


Diagram Walkthrough

flowchart LR
  A["DirectMessageReceived"] --> B["FormatQuotedMessage"]
  B --> C["isChannelAllowingQuotes"]
  B --> D["isUserAllowingQuotes"]
  C --> E["GetChannelPath"]
  D --> F["GetUser"]
  G["MessageReceived"] --> B
Loading

File Walkthrough

Relevant files
Enhancement
5 files
channel.go
チャンネルパス取得のキャッシュ機能を追加                                                                         
+24/-0   
user.go
ユーザー情報取得のキャッシュ機能を追加                                                                           
+25/-0   
OnDirectMessageCreated.go
引用メッセージのフォーマット処理を追加                                                                           
+9/-2     
OnMessageCreated.go
引用メッセージのフォーマット処理を追加                                                                           
+9/-2     
quotes.go
引用メッセージのフォーマットと条件を実装                                                                         
+103/-0 
Dependencies
2 files
go.mod
新しい依存関係を追加                                                                                             
+1/-0     
go.sum
新しい依存関係のチェックサムを追加                                                                               
+2/-0     

Summary by CodeRabbit

リリースノート

  • 新機能

    • メッセージのクォート表示を導入しました。メッセージリンクを自動検出して引用ブロックとして表示します。
    • 引用表示はアクセス制御を尊重し、許可されたチャネル/ユーザーのみ内容を表示します。
    • 長文の引用は自動で省略されます。
    • DM含む受信メッセージで自動フォーマットを適用します。
  • パフォーマンス改善

    • チャネル情報とユーザー情報のキャッシュを追加しました。

@coderabbitai
Copy link

coderabbitai bot commented Feb 21, 2026

📝 Walkthrough

ウォークスルー

メッセージ内の trap.jp 参照を検出して元メッセージを取得・検証し、マークダウンの引用ブロックとして組み立てるフォーマッターを追加しました。チャンネルパスとユーザー情報の取得に1時間TTLのキャッシュ層を導入し、ハンドラーでフォーマット結果を使用します。

変更内容

Cohort / File(s) Summary
依存関係の追加
go.mod
間接依存として github.com/motoki317/sc v1.8.2 を追加
キャッシュ層(ボットAPI)
internal/bot/channel.go, internal/bot/user.go
チャンネルパスおよびユーザー詳細を1時間TTLでキャッシュする公開ラッパー GetChannelPath / GetUser を追加。内部で bot API を呼び出す関数を使用
メッセージハンドラーの更新
internal/handler/OnDirectMessageCreated.go, internal/handler/OnMessageCreated.go
受信メッセージを formatter.FormatQuotedMessage に渡す処理を追加。フォーマット失敗時はログ出力してフォールバック動作
クォート整形ロジック追加
internal/pkg/formatter/quotes.go
FormatQuotedMessage(userID, content) を実装。trap.jp URL 抽出、引用先メッセージ取得、チャンネル/ユーザー単位のアクセス判定、最大10,000ルーンでの切り詰め、Markdown引用ブロック生成を行う。エラーは呼び出し元に伝播

シーケンス図

sequenceDiagram
    participant Handler as Handler
    participant Formatter as Formatter
    participant BotCache as Bot API (Cache)
    participant TrapAPI as Trap API

    Handler->>Formatter: FormatQuotedMessage(userID, content)
    Formatter->>Formatter: 抽出: trap.jp URL を正規表現で検出
    loop 各 messageID
        Formatter->>BotCache: GetMessage(messageID)
        BotCache->>TrapAPI: Fetch message details
        TrapAPI-->>BotCache: Message data
        BotCache-->>Formatter: Message content

        Formatter->>BotCache: GetChannelPath(channelID)
        BotCache-->>Formatter: Channel path

        Formatter->>BotCache: GetUser(authorID)
        BotCache-->>Formatter: User details

        Formatter->>Formatter: isChannelAllowingQuotes / isUserAllowingQuotes
        Formatter->>Formatter: トランケートとMarkdown引用生成
    end
    Formatter->>Formatter: 元コンテンツからURL除去し引用を付加
    Formatter-->>Handler: フォーマット済みコンテンツ
Loading

推定レビュー難度

🎯 3 (Moderate) | ⏱️ ~22 分

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed プルリクエストのタイトルは「feat: 引用メッセージを展開するように」で、PR目的と完全に一致しており、主な変更内容(引用メッセージの展開機能追加)を明確に示しています。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/expand-quotes

Comment @coderabbitai help to get the list of available commands and usage tips.

@cp-20 cp-20 self-assigned this Feb 21, 2026
@github-actions
Copy link

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Error Handling

The function FormatQuotedMessage in quotes.go does not handle the case where bot.GetMessage(messageID) returns nil effectively. It simply continues without logging or handling this scenario, which might lead to silent failures.

func FormatQuotedMessage(userID string, content string) (string, error) {
	matches := quoteRegex.FindAllSubmatch([]byte(content), len(content))
	messageIDs := make([]string, 0, len(matches))
	for _, match := range matches {
		if len(match) < 2 {
			continue
		}
		messageID := string(match[1])
		messageIDs = append(messageIDs, messageID)
	}

	var formattedContent strings.Builder
	formattedContent.WriteString(quoteRegex.ReplaceAllString(content, ""))

	for _, messageID := range messageIDs {
		message := bot.GetMessage(messageID)
		if message == nil {
			continue
		}

		if len(message.Content) > maxQuoteLength {
			message.Content = message.Content[:maxQuoteLength] + "(以下略)"
		}

		channelAllowed, err := isChannelAllowingQuotes(message.ChannelId)
		if err != nil {
			return "", err
		}
		userAllowed, err := isUserAllowingQuotes(userID, message.UserId)
		if err != nil {
			return "", err
		}
		if !channelAllowed && !userAllowed {
			continue
		}

		quote, err := getQuoteMarkdown(message)
		if err != nil {
			return "", err
		}
		formattedContent.WriteString("\n\n" + quote)
	}
	return formattedContent.String(), nil
Logging

In OnDirectMessageCreated.go, the error when formatting quoted messages is logged, but the function continues with the unformatted message. Consider whether this behavior is appropriate or if additional handling is needed.

formattedMessage, err := formatter.FormatQuotedMessage(p.Message.User.ID, plainTextWithoutMention)
if err != nil {
	log.Printf("Error formatting quoted message: %v\n", err)
	formattedMessage = plainTextWithoutMention
}

if p.Message.User.Name != "pikachu" {
	_ = bot.PostMessage(p.Message.ChannelID, "DMではあんまり沢山使わないでね。定期的な`/reset`を忘れない事。")
}

messageReceived(p.Message.Text, formattedMessage, p.Message.ChannelID)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/bot/channel.go`:
- Around line 13-17: The linter fails due to a missing blank line between the
if-block and the following return in the code that calls
bot.API().ChannelAPI.GetChannelPath(ctx, channelID).Execute(); update the
function containing that snippet to insert a single empty line between the
closing brace of the "if err != nil { return "", err }" block and "return
path.Path, nil" so it conforms to the nlreturn rule (locate the call to
GetChannelPath and the variables path and err to find the exact spot).

In `@internal/bot/user.go`:
- Around line 14-18: The linter fails due to missing blank line before the final
return; update the function that calls bot.API().UserAPI.GetUser (the block that
checks err) to add a single empty line between the closing `if err != nil {
return nil, err }` block and the subsequent `return user, nil` so there is a
blank line separating the error-handling if and the final return.

In `@internal/pkg/formatter/quotes.go`:
- Around line 100-102: for ループの終了直後に return formattedContent.String(), nil
が続いており nlreturn によって CI が落ちているので、quotes.go
内の該当ブロック(formattedContent.WriteString("\n\n" + quote) を含むループ)と return
formattedContent.String(), nil の間に空行を挿入して改行ルールに従わせてください。対象の識別子:
formattedContent.WriteString と return formattedContent.String(), nil
を見つけて、その間に1行の空行を追加してください。
- Around line 32-46: isUserAllowingQuotes currently fetches the wrong user and
compares IDs in the wrong direction; treat the first arg as the requester ID and
the second as the quoted-message author ID, call bot.GetUser(messageUserID) (not
bot.GetUser(userID)), then allow when the quoted-message author is a bot
(messageUser.Bot) or when the quoted-message author ID equals the requester ID;
update the comparisons and the bot.GetUser call inside isUserAllowingQuotes
accordingly (use messageUserID for lookup and compare messageUser.ID to userID).
- Around line 80-82: The current truncation of message.Content uses byte-based
slicing which can cut multibyte characters mid-rune; change the logic in the
quotes.go truncation branch to operate on runes: compute rune length of
message.Content, check against maxQuoteLength as a rune count, convert
message.Content to []rune and slice to maxQuoteLength runes, then reassign the
string (e.g., using string([]rune(...)) ) and append "(以下略)"; update any other
uses of maxQuoteLength in this function to be rune-aware as well.
- Around line 11-13: quoteRegexStr currently contains `\\.` inside a raw string
which makes the regex look for a literal backslash before dots and thus never
matches real URLs; update the pattern in quoteRegexStr (used to build quoteRegex
via regexp.MustCompile) to use `\.` for the literal dots in the URL (e.g.,
change `https://q\\.trap\\.jp/messages/...` to
`https://q\.trap\.jp/messages/...`) so the regex matches actual URLs, then run
tests/usage paths that exercise quoteRegex to confirm quotes are detected.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@internal/pkg/formatter/quotes.go`:
- Around line 33-48: The isUserAllowingQuotes function correctly implements the
approval logic (checks userID vs messageUserID, calls bot.GetUser and checks
messageUser.Bot) so no code changes are required; leave isUserAllowingQuotes and
its bot.GetUser/messageUser.Bot checks as-is.
- Around line 61-106: No change required: the FormatQuotedMessage function
already correctly enforces rune-based trimming (maxQuoteLength), checks channel
and user permissions (isChannelAllowingQuotes, isUserAllowingQuotes), and
handles errors and newline formatting; leave the implementation of
FormatQuotedMessage as-is and proceed with approving/merging the changes.

@cp-20 cp-20 requested a review from kaitoyama February 21, 2026 06:21
@kaitoyama kaitoyama changed the base branch from main to staging February 21, 2026 06:23
Copy link
Contributor

@kaitoyama kaitoyama left a comment

Choose a reason for hiding this comment

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

よさそうです!
stgで試します!

@kaitoyama kaitoyama merged commit ee7a615 into staging Feb 23, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants