|
|
|
|
@ -14,6 +14,7 @@ type user struct {
|
|
|
|
|
Login string `json:"login"` |
|
|
|
|
Username string `json:"username"` |
|
|
|
|
Email string `json:"email"` |
|
|
|
|
Name string `json:"name"` // commit author/committer 표시 이름
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (u *user) login() string { |
|
|
|
|
@ -63,8 +64,10 @@ type review struct {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type commitInfo struct { |
|
|
|
|
ID string `json:"id"` |
|
|
|
|
Message string `json:"message"` |
|
|
|
|
URL string `json:"url"` |
|
|
|
|
Author *user `json:"author"` |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type payload struct { |
|
|
|
|
@ -72,6 +75,7 @@ type payload struct {
|
|
|
|
|
Number int `json:"number"` |
|
|
|
|
Repository struct { |
|
|
|
|
FullName string `json:"full_name"` |
|
|
|
|
HTMLURL string `json:"html_url"` |
|
|
|
|
} `json:"repository"` |
|
|
|
|
Sender *user `json:"sender"` |
|
|
|
|
PullRequest *pullRequest `json:"pull_request"` |
|
|
|
|
@ -146,28 +150,55 @@ func BuildChannelMessage(event string, body []byte) (notify.Notification, bool)
|
|
|
|
|
switch event { |
|
|
|
|
case "push": |
|
|
|
|
branch := strings.TrimPrefix(p.Ref, "refs/heads/") |
|
|
|
|
title := fmt.Sprintf("📤 [%s] %s에 커밋 %d개 push", repo, branch, len(p.Commits)) |
|
|
|
|
lines := make([]string, 0, len(p.Commits)) |
|
|
|
|
n := len(p.Commits) |
|
|
|
|
plural := "" |
|
|
|
|
if n != 1 { |
|
|
|
|
plural = "s" |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 헤더: [repo:branch] N new commit(s) pushed by {actor} (Gitea 기본 형식과 유사)
|
|
|
|
|
repoRef := fmt.Sprintf("%s:%s", repo, branch) |
|
|
|
|
if p.Repository.HTMLURL != "" { |
|
|
|
|
repoRef = fmt.Sprintf("<%s/src/branch/%s|%s:%s>", p.Repository.HTMLURL, branch, repo, branch) |
|
|
|
|
} |
|
|
|
|
head := fmt.Sprintf("[%s] %d new commit%s pushed by %s", repoRef, n, plural, actor) |
|
|
|
|
|
|
|
|
|
// 커밋 줄: <url|shortsha>: 메시지 - 작성자명
|
|
|
|
|
lines := make([]string, 0, n) |
|
|
|
|
for _, cm := range p.Commits { |
|
|
|
|
short := cm.ID |
|
|
|
|
if len(short) > 10 { |
|
|
|
|
short = short[:10] |
|
|
|
|
} |
|
|
|
|
msg := strings.SplitN(cm.Message, "\n", 2)[0] |
|
|
|
|
lines = append(lines, fmt.Sprintf("• <%s|%s>", cm.URL, msg)) |
|
|
|
|
// 링크는 sha에만 — 메시지의 '>' 등이 링크를 중간에 끊지 않도록 평문은 이스케이프.
|
|
|
|
|
line := fmt.Sprintf("<%s|%s>: %s", cm.URL, short, escapeMrkdwn(msg)) |
|
|
|
|
if cm.Author != nil && cm.Author.Name != "" { |
|
|
|
|
line += " - " + escapeMrkdwn(cm.Author.Name) |
|
|
|
|
} |
|
|
|
|
lines = append(lines, line) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
body := head |
|
|
|
|
if p.Repository.HTMLURL != "" { |
|
|
|
|
body += "\n" + p.Repository.HTMLURL |
|
|
|
|
} |
|
|
|
|
bodyText := strings.Join(lines, "\n") |
|
|
|
|
if bodyText == "" { |
|
|
|
|
bodyText = "(커밋 정보 없음)" |
|
|
|
|
if len(lines) > 0 { |
|
|
|
|
body += "\n" + strings.Join(lines, "\n") |
|
|
|
|
} |
|
|
|
|
return channelNote(title, bodyText, ctx), true |
|
|
|
|
text := fmt.Sprintf("[%s:%s] %d new commit%s pushed by %s", repo, branch, n, plural, actor) |
|
|
|
|
return pushNote(text, body), true |
|
|
|
|
|
|
|
|
|
case "pull_request": |
|
|
|
|
if pr := p.PullRequest; pr != nil { |
|
|
|
|
title := fmt.Sprintf("🔀 [%s] PR #%d %s", repo, pr.Number, withAction(p.Action)) |
|
|
|
|
return channelNote(title, fmt.Sprintf("<%s|%s>", pr.HTMLURL, pr.Title), ctx), true |
|
|
|
|
return channelNote(title, fmt.Sprintf("<%s|%s>", pr.HTMLURL, escapeMrkdwn(pr.Title)), ctx), true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
case "issues": |
|
|
|
|
if iss := p.Issue; iss != nil { |
|
|
|
|
title := fmt.Sprintf("📋 [%s] 이슈 #%d %s", repo, iss.Number, withAction(p.Action)) |
|
|
|
|
return channelNote(title, fmt.Sprintf("<%s|%s>", iss.HTMLURL, iss.Title), ctx), true |
|
|
|
|
return channelNote(title, fmt.Sprintf("<%s|%s>", iss.HTMLURL, escapeMrkdwn(iss.Title)), ctx), true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
case "issue_comment": |
|
|
|
|
@ -177,13 +208,13 @@ func BuildChannelMessage(event string, body []byte) (notify.Notification, bool)
|
|
|
|
|
url = p.Comment.HTMLURL |
|
|
|
|
} |
|
|
|
|
title := fmt.Sprintf("💬 [%s] #%d 새 댓글", repo, iss.Number) |
|
|
|
|
return channelNote(title, fmt.Sprintf("<%s|%s>", url, iss.Title), ctx), true |
|
|
|
|
return channelNote(title, fmt.Sprintf("<%s|%s>", url, escapeMrkdwn(iss.Title)), ctx), true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
case "pull_request_review": |
|
|
|
|
if pr := p.PullRequest; pr != nil { |
|
|
|
|
title := fmt.Sprintf("📝 [%s] PR #%d 리뷰", repo, pr.Number) |
|
|
|
|
return channelNote(title, fmt.Sprintf("<%s|%s>", pr.HTMLURL, pr.Title), ctx), true |
|
|
|
|
return channelNote(title, fmt.Sprintf("<%s|%s>", pr.HTMLURL, escapeMrkdwn(pr.Title)), ctx), true |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -196,6 +227,12 @@ func BuildChannelMessage(event string, body []byte) (notify.Notification, bool)
|
|
|
|
|
return channelNote(title, detail, ctx), true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// escapeMrkdwn은 Slack mrkdwn에서 의미를 갖는 문자를 이스케이프한다.
|
|
|
|
|
// 특히 '>'를 그대로 두면 <url|텍스트> 링크가 중간에서 끊긴다.
|
|
|
|
|
func escapeMrkdwn(s string) string { |
|
|
|
|
return strings.NewReplacer("&", "&", "<", "<", ">", ">").Replace(s) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// withAction은 액션이 있으면 "(action)" 형태로 덧붙인다.
|
|
|
|
|
func withAction(action string) string { |
|
|
|
|
if action == "" { |
|
|
|
|
@ -211,6 +248,16 @@ func channelNote(title, body, context string) notify.Notification {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// pushNote는 헤더+커밋 목록을 한 섹션에 담는다(Gitea 기본 메시지와 유사한 단순 형식).
|
|
|
|
|
func pushNote(text, body string) notify.Notification { |
|
|
|
|
return notify.Notification{ |
|
|
|
|
Text: text, |
|
|
|
|
Blocks: []notify.Block{ |
|
|
|
|
{"type": "section", "text": notify.Block{"type": "mrkdwn", "text": body}}, |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func handlePullRequest(p *payload, repo, sender string) []notify.Notification { |
|
|
|
|
pr := p.PullRequest |
|
|
|
|
if pr == nil { |
|
|
|
|
|