From 3120bebc4fb0c130a1c953ee52df29be31485860 Mon Sep 17 00:00:00 2001 From: nkoji21 <133028205+nkoji21@users.noreply.github.com> Date: Sat, 1 Aug 2026 12:01:58 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20=E3=83=97=E3=83=AD=E3=83=B3?= =?UTF-8?q?=E3=83=97=E3=83=88=E3=81=AE=E5=8F=8E=E9=9B=86=E6=9C=9F=E9=96=93?= =?UTF-8?q?=E3=82=92=E8=A8=AD=E5=AE=9A=E3=81=A8=E9=80=A3=E5=8B=95=E3=81=95?= =?UTF-8?q?=E3=81=9B=E3=80=81embed=20URL=20=E3=82=92=20discord.com=20?= =?UTF-8?q?=E3=81=AB=E9=99=90=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/summary/presenter.go | 14 +++++++++++--- internal/summary/presenter_test.go | 11 +++++++++++ internal/summary/prompt.go | 9 +++++++-- internal/summary/prompt_test.go | 9 ++++++--- internal/summary/service.go | 2 +- internal/summary/types.go | 2 ++ main.go | 1 + 7 files changed, 39 insertions(+), 9 deletions(-) diff --git a/internal/summary/presenter.go b/internal/summary/presenter.go index 662d469..285daf9 100644 --- a/internal/summary/presenter.go +++ b/internal/summary/presenter.go @@ -80,11 +80,14 @@ func BuildSummaryMessage(header string, highlights []Highlight, digests []Channe embed := &discordgo.MessageEmbed{ Title: title, Description: description, - URL: link, Color: color, } - if msg := findMessageByURL(digests, link); msg != nil { - embed.Author = &discordgo.MessageEmbedAuthor{Name: msg.Author, IconURL: msg.AvatarURL} + // モデルが幻覚した Discord 外 URL をリンク化しない + if isDiscordMessageLink(link) { + embed.URL = link + if msg := findMessageByURL(digests, link); msg != nil { + embed.Author = &discordgo.MessageEmbedAuthor{Name: msg.Author, IconURL: msg.AvatarURL} + } } embeds = append(embeds, embed) } @@ -96,6 +99,11 @@ func BuildSummaryMessage(header string, highlights []Highlight, digests []Channe return header, embeds } +func isDiscordMessageLink(link string) bool { + return strings.HasPrefix(link, "https://discord.com/channels/") || + strings.HasPrefix(link, "https://discordapp.com/channels/") +} + func findMessageByURL(digests []ChannelDigest, url string) *MessageDigest { for i := range digests { for j := range digests[i].Messages { diff --git a/internal/summary/presenter_test.go b/internal/summary/presenter_test.go index 2aba3af..4acf20d 100644 --- a/internal/summary/presenter_test.go +++ b/internal/summary/presenter_test.go @@ -139,6 +139,17 @@ func TestBuildSummaryMessage(t *testing.T) { } }) + t.Run("non-discord link is not linked", func(t *testing.T) { + highlights := []Highlight{{Title: "t", Description: "d", Link: "https://evil.example.com/x"}} + _, embeds := BuildSummaryMessage("h", highlights, digests) + if len(embeds) != 1 { + t.Fatalf("highlight should be kept, embeds = %d", len(embeds)) + } + if embeds[0].URL != "" || embeds[0].Author != nil { + t.Errorf("external URL should not be linked: %+v", embeds[0]) + } + }) + t.Run("incomplete highlights are dropped", func(t *testing.T) { highlights := []Highlight{{Title: "", Description: "d", Link: "l"}} _, embeds := BuildSummaryMessage("h", highlights, nil) diff --git a/internal/summary/prompt.go b/internal/summary/prompt.go index 56c3966..049ed6f 100644 --- a/internal/summary/prompt.go +++ b/internal/summary/prompt.go @@ -3,18 +3,23 @@ package summary import ( "fmt" "strings" + "time" ) -func buildSummaryPrompt(digests []ChannelDigest, maxHighlights int) string { +func buildSummaryPrompt(digests []ChannelDigest, maxHighlights int, lookback time.Duration) string { if len(digests) == 0 { return "" } if maxHighlights <= 0 { maxHighlights = 3 } + lookbackHours := int(lookback.Hours()) + if lookbackHours <= 0 { + lookbackHours = 24 + } var builder strings.Builder - builder.WriteString("あなたはDiscordサーバーの編集者です。以下は過去24時間に投稿されたメッセージ一覧です。\n") + fmt.Fprintf(&builder, "あなたはDiscordサーバーの編集者です。以下は過去%d時間に投稿されたメッセージ一覧です。\n", lookbackHours) fmt.Fprintf(&builder, "最も注目すべき出来事を最大%d件選び、日本語の夕刊ハイライトを作成してください。\n", maxHighlights) builder.WriteString("出力は必ず次のJSON配列のみとし、整形や説明文を入れないでください。\n") builder.WriteString("例: [{\"title\":\"見出し\",\"emoji\":\"\",\"description\":\"本文\",\"link\":\"https://discord.com/channels/...\",\"color\":\"#b0b0b0\"}]\n") diff --git a/internal/summary/prompt_test.go b/internal/summary/prompt_test.go index bc55227..b5e9266 100644 --- a/internal/summary/prompt_test.go +++ b/internal/summary/prompt_test.go @@ -17,11 +17,14 @@ func TestBuildSummaryPrompt(t *testing.T) { }, }} - prompt := buildSummaryPrompt(digests, 3) + prompt := buildSummaryPrompt(digests, 3, 24*time.Hour) if !strings.Contains(prompt, "最大3件") { t.Errorf("prompt should mention max highlights: %q", prompt) } + if !strings.Contains(prompt, "過去24時間") { + t.Errorf("prompt should mention lookback hours") + } if !strings.Contains(prompt, "# general") { t.Errorf("prompt should contain channel header") } @@ -36,11 +39,11 @@ func TestBuildSummaryPrompt(t *testing.T) { } func TestBuildSummaryPromptEdgeCases(t *testing.T) { - if got := buildSummaryPrompt(nil, 3); got != "" { + if got := buildSummaryPrompt(nil, 3, 24*time.Hour); got != "" { t.Errorf("empty digests should return empty prompt, got %q", got) } digests := []ChannelDigest{{Name: "c", Messages: []MessageDigest{{Author: "a", Content: "x"}}}} - if got := buildSummaryPrompt(digests, 0); !strings.Contains(got, "最大3件") { + if got := buildSummaryPrompt(digests, 0, 0); !strings.Contains(got, "最大3件") { t.Errorf("non-positive maxHighlights should fall back to 3") } } diff --git a/internal/summary/service.go b/internal/summary/service.go index 57de690..5017d38 100644 --- a/internal/summary/service.go +++ b/internal/summary/service.go @@ -126,7 +126,7 @@ func (s *Service) Generate(ctx context.Context, req Request) (Result, error) { summaryCtx = context.Background() } - prompt := buildSummaryPrompt(collectorResult.Digests, s.Config.MaxHighlights) + prompt := buildSummaryPrompt(collectorResult.Digests, s.Config.MaxHighlights, s.Config.Lookback) if prompt == "" { s.info(ctx, "Gemini呼び出しスキップ (プロンプトなし)") } else { diff --git a/internal/summary/types.go b/internal/summary/types.go index 98bad35..ea70a28 100644 --- a/internal/summary/types.go +++ b/internal/summary/types.go @@ -65,6 +65,8 @@ type Config struct { MessageCharBudget int MaxHighlights int MaxAttempts int + // Lookback はプロンプトの「過去N時間」表記に使う (収集期間と揃える) + Lookback time.Duration } // Summarizer は収集メッセージの要約を生成し、構造化ハイライトの生 JSON を返す。 diff --git a/main.go b/main.go index b25a74f..e401d38 100644 --- a/main.go +++ b/main.go @@ -86,6 +86,7 @@ func run() error { MessageCharBudget: cfg.Summary.MessageCharBudget, MaxHighlights: cfg.Summary.MaxHighlights, MaxAttempts: cfg.Summary.MaxAttempts, + Lookback: cfg.Summary.Lookback, }, }