code.gitea.io/gitea@v1.22.3/services/webhook/msteams.go (about)

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package webhook
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"net/http"
    10  	"net/url"
    11  	"strings"
    12  
    13  	webhook_model "code.gitea.io/gitea/models/webhook"
    14  	"code.gitea.io/gitea/modules/git"
    15  	api "code.gitea.io/gitea/modules/structs"
    16  	"code.gitea.io/gitea/modules/util"
    17  	webhook_module "code.gitea.io/gitea/modules/webhook"
    18  )
    19  
    20  type (
    21  	// MSTeamsFact for Fact Structure
    22  	MSTeamsFact struct {
    23  		Name  string `json:"name"`
    24  		Value string `json:"value"`
    25  	}
    26  
    27  	// MSTeamsSection is a MessageCard section
    28  	MSTeamsSection struct {
    29  		ActivityTitle    string        `json:"activityTitle"`
    30  		ActivitySubtitle string        `json:"activitySubtitle"`
    31  		ActivityImage    string        `json:"activityImage"`
    32  		Facts            []MSTeamsFact `json:"facts"`
    33  		Text             string        `json:"text"`
    34  	}
    35  
    36  	// MSTeamsAction is an action (creates buttons, links etc)
    37  	MSTeamsAction struct {
    38  		Type    string                `json:"@type"`
    39  		Name    string                `json:"name"`
    40  		Targets []MSTeamsActionTarget `json:"targets,omitempty"`
    41  	}
    42  
    43  	// MSTeamsActionTarget is the actual link to follow, etc
    44  	MSTeamsActionTarget struct {
    45  		Os  string `json:"os"`
    46  		URI string `json:"uri"`
    47  	}
    48  
    49  	// MSTeamsPayload is the parent object
    50  	MSTeamsPayload struct {
    51  		Type            string           `json:"@type"`
    52  		Context         string           `json:"@context"`
    53  		ThemeColor      string           `json:"themeColor"`
    54  		Title           string           `json:"title"`
    55  		Summary         string           `json:"summary"`
    56  		Sections        []MSTeamsSection `json:"sections"`
    57  		PotentialAction []MSTeamsAction  `json:"potentialAction"`
    58  	}
    59  )
    60  
    61  // Create implements PayloadConvertor Create method
    62  func (m msteamsConvertor) Create(p *api.CreatePayload) (MSTeamsPayload, error) {
    63  	// created tag/branch
    64  	refName := git.RefName(p.Ref).ShortName()
    65  	title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName)
    66  
    67  	return createMSTeamsPayload(
    68  		p.Repo,
    69  		p.Sender,
    70  		title,
    71  		"",
    72  		p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName),
    73  		greenColor,
    74  		&MSTeamsFact{fmt.Sprintf("%s:", p.RefType), refName},
    75  	), nil
    76  }
    77  
    78  // Delete implements PayloadConvertor Delete method
    79  func (m msteamsConvertor) Delete(p *api.DeletePayload) (MSTeamsPayload, error) {
    80  	// deleted tag/branch
    81  	refName := git.RefName(p.Ref).ShortName()
    82  	title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName)
    83  
    84  	return createMSTeamsPayload(
    85  		p.Repo,
    86  		p.Sender,
    87  		title,
    88  		"",
    89  		p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName),
    90  		yellowColor,
    91  		&MSTeamsFact{fmt.Sprintf("%s:", p.RefType), refName},
    92  	), nil
    93  }
    94  
    95  // Fork implements PayloadConvertor Fork method
    96  func (m msteamsConvertor) Fork(p *api.ForkPayload) (MSTeamsPayload, error) {
    97  	title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName)
    98  
    99  	return createMSTeamsPayload(
   100  		p.Repo,
   101  		p.Sender,
   102  		title,
   103  		"",
   104  		p.Repo.HTMLURL,
   105  		greenColor,
   106  		&MSTeamsFact{"Forkee:", p.Forkee.FullName},
   107  	), nil
   108  }
   109  
   110  // Push implements PayloadConvertor Push method
   111  func (m msteamsConvertor) Push(p *api.PushPayload) (MSTeamsPayload, error) {
   112  	var (
   113  		branchName = git.RefName(p.Ref).ShortName()
   114  		commitDesc string
   115  	)
   116  
   117  	var titleLink string
   118  	if p.TotalCommits == 1 {
   119  		commitDesc = "1 new commit"
   120  		titleLink = p.Commits[0].URL
   121  	} else {
   122  		commitDesc = fmt.Sprintf("%d new commits", p.TotalCommits)
   123  		titleLink = p.CompareURL
   124  	}
   125  	if titleLink == "" {
   126  		titleLink = p.Repo.HTMLURL + "/src/" + util.PathEscapeSegments(branchName)
   127  	}
   128  
   129  	title := fmt.Sprintf("[%s:%s] %s", p.Repo.FullName, branchName, commitDesc)
   130  
   131  	var text string
   132  	// for each commit, generate attachment text
   133  	for i, commit := range p.Commits {
   134  		text += fmt.Sprintf("[%s](%s) %s - %s", commit.ID[:7], commit.URL,
   135  			strings.TrimRight(commit.Message, "\r\n"), commit.Author.Name)
   136  		// add linebreak to each commit but the last
   137  		if i < len(p.Commits)-1 {
   138  			text += "\n\n"
   139  		}
   140  	}
   141  
   142  	return createMSTeamsPayload(
   143  		p.Repo,
   144  		p.Sender,
   145  		title,
   146  		text,
   147  		titleLink,
   148  		greenColor,
   149  		&MSTeamsFact{"Commit count:", fmt.Sprintf("%d", p.TotalCommits)},
   150  	), nil
   151  }
   152  
   153  // Issue implements PayloadConvertor Issue method
   154  func (m msteamsConvertor) Issue(p *api.IssuePayload) (MSTeamsPayload, error) {
   155  	title, _, attachmentText, color := getIssuesPayloadInfo(p, noneLinkFormatter, false)
   156  
   157  	return createMSTeamsPayload(
   158  		p.Repository,
   159  		p.Sender,
   160  		title,
   161  		attachmentText,
   162  		p.Issue.HTMLURL,
   163  		color,
   164  		&MSTeamsFact{"Issue #:", fmt.Sprintf("%d", p.Issue.ID)},
   165  	), nil
   166  }
   167  
   168  // IssueComment implements PayloadConvertor IssueComment method
   169  func (m msteamsConvertor) IssueComment(p *api.IssueCommentPayload) (MSTeamsPayload, error) {
   170  	title, _, color := getIssueCommentPayloadInfo(p, noneLinkFormatter, false)
   171  
   172  	return createMSTeamsPayload(
   173  		p.Repository,
   174  		p.Sender,
   175  		title,
   176  		p.Comment.Body,
   177  		p.Comment.HTMLURL,
   178  		color,
   179  		&MSTeamsFact{"Issue #:", fmt.Sprintf("%d", p.Issue.ID)},
   180  	), nil
   181  }
   182  
   183  // PullRequest implements PayloadConvertor PullRequest method
   184  func (m msteamsConvertor) PullRequest(p *api.PullRequestPayload) (MSTeamsPayload, error) {
   185  	title, _, attachmentText, color := getPullRequestPayloadInfo(p, noneLinkFormatter, false)
   186  
   187  	return createMSTeamsPayload(
   188  		p.Repository,
   189  		p.Sender,
   190  		title,
   191  		attachmentText,
   192  		p.PullRequest.HTMLURL,
   193  		color,
   194  		&MSTeamsFact{"Pull request #:", fmt.Sprintf("%d", p.PullRequest.ID)},
   195  	), nil
   196  }
   197  
   198  // Review implements PayloadConvertor Review method
   199  func (m msteamsConvertor) Review(p *api.PullRequestPayload, event webhook_module.HookEventType) (MSTeamsPayload, error) {
   200  	var text, title string
   201  	var color int
   202  	switch p.Action {
   203  	case api.HookIssueReviewed:
   204  		action, err := parseHookPullRequestEventType(event)
   205  		if err != nil {
   206  			return MSTeamsPayload{}, err
   207  		}
   208  
   209  		title = fmt.Sprintf("[%s] Pull request review %s: #%d %s", p.Repository.FullName, action, p.Index, p.PullRequest.Title)
   210  		text = p.Review.Content
   211  
   212  		switch event {
   213  		case webhook_module.HookEventPullRequestReviewApproved:
   214  			color = greenColor
   215  		case webhook_module.HookEventPullRequestReviewRejected:
   216  			color = redColor
   217  		case webhook_module.HookEventPullRequestReviewComment:
   218  			color = greyColor
   219  		default:
   220  			color = yellowColor
   221  		}
   222  	}
   223  
   224  	return createMSTeamsPayload(
   225  		p.Repository,
   226  		p.Sender,
   227  		title,
   228  		text,
   229  		p.PullRequest.HTMLURL,
   230  		color,
   231  		&MSTeamsFact{"Pull request #:", fmt.Sprintf("%d", p.PullRequest.ID)},
   232  	), nil
   233  }
   234  
   235  // Repository implements PayloadConvertor Repository method
   236  func (m msteamsConvertor) Repository(p *api.RepositoryPayload) (MSTeamsPayload, error) {
   237  	var title, url string
   238  	var color int
   239  	switch p.Action {
   240  	case api.HookRepoCreated:
   241  		title = fmt.Sprintf("[%s] Repository created", p.Repository.FullName)
   242  		url = p.Repository.HTMLURL
   243  		color = greenColor
   244  	case api.HookRepoDeleted:
   245  		title = fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName)
   246  		color = yellowColor
   247  	}
   248  
   249  	return createMSTeamsPayload(
   250  		p.Repository,
   251  		p.Sender,
   252  		title,
   253  		"",
   254  		url,
   255  		color,
   256  		nil,
   257  	), nil
   258  }
   259  
   260  // Wiki implements PayloadConvertor Wiki method
   261  func (m msteamsConvertor) Wiki(p *api.WikiPayload) (MSTeamsPayload, error) {
   262  	title, color, _ := getWikiPayloadInfo(p, noneLinkFormatter, false)
   263  
   264  	return createMSTeamsPayload(
   265  		p.Repository,
   266  		p.Sender,
   267  		title,
   268  		"",
   269  		p.Repository.HTMLURL+"/wiki/"+url.PathEscape(p.Page),
   270  		color,
   271  		&MSTeamsFact{"Repository:", p.Repository.FullName},
   272  	), nil
   273  }
   274  
   275  // Release implements PayloadConvertor Release method
   276  func (m msteamsConvertor) Release(p *api.ReleasePayload) (MSTeamsPayload, error) {
   277  	title, color := getReleasePayloadInfo(p, noneLinkFormatter, false)
   278  
   279  	return createMSTeamsPayload(
   280  		p.Repository,
   281  		p.Sender,
   282  		title,
   283  		"",
   284  		p.Release.HTMLURL,
   285  		color,
   286  		&MSTeamsFact{"Tag:", p.Release.TagName},
   287  	), nil
   288  }
   289  
   290  func (m msteamsConvertor) Package(p *api.PackagePayload) (MSTeamsPayload, error) {
   291  	title, color := getPackagePayloadInfo(p, noneLinkFormatter, false)
   292  
   293  	return createMSTeamsPayload(
   294  		p.Repository,
   295  		p.Sender,
   296  		title,
   297  		"",
   298  		p.Package.HTMLURL,
   299  		color,
   300  		&MSTeamsFact{"Package:", p.Package.Name},
   301  	), nil
   302  }
   303  
   304  func createMSTeamsPayload(r *api.Repository, s *api.User, title, text, actionTarget string, color int, fact *MSTeamsFact) MSTeamsPayload {
   305  	facts := make([]MSTeamsFact, 0, 2)
   306  	if r != nil {
   307  		facts = append(facts, MSTeamsFact{
   308  			Name:  "Repository:",
   309  			Value: r.FullName,
   310  		})
   311  	}
   312  	if fact != nil {
   313  		facts = append(facts, *fact)
   314  	}
   315  
   316  	return MSTeamsPayload{
   317  		Type:       "MessageCard",
   318  		Context:    "https://schema.org/extensions",
   319  		ThemeColor: fmt.Sprintf("%x", color),
   320  		Title:      title,
   321  		Summary:    title,
   322  		Sections: []MSTeamsSection{
   323  			{
   324  				ActivityTitle:    s.FullName,
   325  				ActivitySubtitle: s.UserName,
   326  				ActivityImage:    s.AvatarURL,
   327  				Text:             text,
   328  				Facts:            facts,
   329  			},
   330  		},
   331  		PotentialAction: []MSTeamsAction{
   332  			{
   333  				Type: "OpenUri",
   334  				Name: "View in Gitea",
   335  				Targets: []MSTeamsActionTarget{
   336  					{
   337  						Os:  "default",
   338  						URI: actionTarget,
   339  					},
   340  				},
   341  			},
   342  		},
   343  	}
   344  }
   345  
   346  type msteamsConvertor struct{}
   347  
   348  var _ payloadConvertor[MSTeamsPayload] = msteamsConvertor{}
   349  
   350  func newMSTeamsRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook_model.HookTask) (*http.Request, []byte, error) {
   351  	return newJSONRequest(msteamsConvertor{}, w, t, true)
   352  }