diff --git a/Makefile b/Makefile index 036e97e79..36dafd599 100644 --- a/Makefile +++ b/Makefile @@ -92,6 +92,7 @@ bundle: rm -rf dist/ mkdir -p dist/$(PLUGIN_ID) cp $(MANIFEST_FILE) dist/$(PLUGIN_ID)/ + cp -r assets dist/$(PLUGIN_ID)/assets ifneq ($(HAS_SERVER),) mkdir -p dist/$(PLUGIN_ID)/server/dist; cp -r server/dist/* dist/$(PLUGIN_ID)/server/dist/; diff --git a/assets/profile.png b/assets/profile.png new file mode 100644 index 000000000..081af6c52 Binary files /dev/null and b/assets/profile.png differ diff --git a/server/api.go b/server/api.go index 8931b3ac9..e37904a41 100644 --- a/server/api.go +++ b/server/api.go @@ -4,7 +4,10 @@ import ( "context" "encoding/json" "fmt" + "io" "net/http" + "os" + "path/filepath" "strconv" "strings" "time" @@ -19,7 +22,6 @@ import ( const ( API_ERROR_ID_NOT_CONNECTED = "not_connected" - GITHUB_ICON_URL = "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png" GITHUB_USERNAME = "GitHub Plugin" ) @@ -48,6 +50,8 @@ func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Req switch path := r.URL.Path; path { case "/webhook": p.handleWebhook(w, r) + case "/assets/profile.png": + p.handleProfileImage(w, r) case "/oauth/connect": p.connectUserToGitHub(w, r) case "/oauth/complete": @@ -207,6 +211,21 @@ func (p *Plugin) completeConnectUserToGitHub(w http.ResponseWriter, r *http.Requ w.Write([]byte(html)) } +func (p *Plugin) handleProfileImage(w http.ResponseWriter, r *http.Request) { + config := p.getConfiguration() + + img, err := os.Open(filepath.Join(config.PluginsDirectory, manifest.Id, "assets", "profile.png")) + if err != nil { + http.NotFound(w, r) + mlog.Error("Unable to read github profile image, err=" + err.Error()) + return + } + defer img.Close() + + w.Header().Set("Content-Type", "image/png") + io.Copy(w, img) +} + type ConnectedResponse struct { Connected bool `json:"connected"` GitHubUsername string `json:"github_username"` diff --git a/server/command.go b/server/command.go index b3fe4d270..4f1b3f61d 100644 --- a/server/command.go +++ b/server/command.go @@ -45,12 +45,12 @@ func getCommand() *model.Command { } } -func getCommandResponse(responseType, text string) *model.CommandResponse { +func (p *Plugin) getCommandResponse(responseType, text string) *model.CommandResponse { return &model.CommandResponse{ ResponseType: responseType, Text: text, Username: GITHUB_USERNAME, - IconURL: GITHUB_ICON_URL, + IconURL: p.getConfiguration().ProfileImageURL, Type: model.POST_DEFAULT, } } @@ -74,10 +74,10 @@ func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*mo if action == "connect" { config := p.API.GetConfig() if config.ServiceSettings.SiteURL == nil { - return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, "Encountered an error connecting to GitHub."), nil + return p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, "Encountered an error connecting to GitHub."), nil } - resp := getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, fmt.Sprintf("[Click here to link your GitHub account.](%s/plugins/github/oauth/connect)", *config.ServiceSettings.SiteURL)) + resp := p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, fmt.Sprintf("[Click here to link your GitHub account.](%s/plugins/github/oauth/connect)", *config.ServiceSettings.SiteURL)) return resp, nil } @@ -90,7 +90,7 @@ func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*mo if apiErr.ID == API_ERROR_ID_NOT_CONNECTED { text = "You must connect your account to GitHub first. Either click on the GitHub logo in the bottom left of the screen or enter `/github connect`." } - return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, text), nil + return p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, text), nil } githubClient = p.githubConnect(*info.Token) @@ -102,11 +102,11 @@ func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*mo txt := "" if len(parameters) == 0 { - return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, "Please specify a repository or 'list' command."), nil + return p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, "Please specify a repository or 'list' command."), nil } else if len(parameters) == 1 && parameters[0] == "list" { subs, err := p.GetSubscriptionsByChannel(args.ChannelId) if err != nil { - return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, err.Error()), nil + return p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, err.Error()), nil } if len(subs) == 0 { @@ -117,7 +117,7 @@ func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*mo for _, sub := range subs { txt += fmt.Sprintf("* `%s` - %s\n", sub.Repository, sub.Features) } - return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, txt), nil + return p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, txt), nil } else if len(parameters) > 1 { features = strings.Join(parameters[1:], " ") } @@ -125,62 +125,62 @@ func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*mo _, owner, repo := parseOwnerAndRepo(parameters[0], config.EnterpriseBaseURL) if repo == "" { if err := p.SubscribeOrg(context.Background(), githubClient, args.UserId, owner, args.ChannelId, features); err != nil { - return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, err.Error()), nil + return p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, err.Error()), nil } - return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, fmt.Sprintf("Successfully subscribed to organization %s.", owner)), nil + return p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, fmt.Sprintf("Successfully subscribed to organization %s.", owner)), nil } if err := p.Subscribe(context.Background(), githubClient, args.UserId, owner, repo, args.ChannelId, features); err != nil { - return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, err.Error()), nil + return p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, err.Error()), nil } - return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, fmt.Sprintf("Successfully subscribed to %s.", repo)), nil + return p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, fmt.Sprintf("Successfully subscribed to %s.", repo)), nil case "unsubscribe": if len(parameters) == 0 { - return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, "Please specify a repository."), nil + return p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, "Please specify a repository."), nil } repo := parameters[0] if err := p.Unsubscribe(args.ChannelId, repo); err != nil { mlog.Error(err.Error()) - return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, "Encountered an error trying to unsubscribe. Please try again."), nil + return p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, "Encountered an error trying to unsubscribe. Please try again."), nil } - return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, fmt.Sprintf("Succesfully unsubscribed from %s.", repo)), nil + return p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, fmt.Sprintf("Succesfully unsubscribed from %s.", repo)), nil case "disconnect": p.disconnectGitHubAccount(args.UserId) - return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, "Disconnected your GitHub account."), nil + return p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, "Disconnected your GitHub account."), nil case "todo": text, err := p.GetToDo(ctx, info.GitHubUsername, githubClient) if err != nil { mlog.Error(err.Error()) - return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, "Encountered an error getting your to do items."), nil + return p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, "Encountered an error getting your to do items."), nil } - return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, text), nil + return p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, text), nil case "me": gitUser, _, err := githubClient.Users.Get(ctx, "") if err != nil { - return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, "Encountered an error getting your GitHub profile."), nil + return p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, "Encountered an error getting your GitHub profile."), nil } text := fmt.Sprintf("You are connected to GitHub as:\n# [![image](%s =40x40)](%s) [%s](%s)", gitUser.GetAvatarURL(), gitUser.GetHTMLURL(), gitUser.GetLogin(), gitUser.GetHTMLURL()) - return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, text), nil + return p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, text), nil case "help": text := "###### Mattermost GitHub Plugin - Slash Command Help\n" + strings.Replace(COMMAND_HELP, "|", "`", -1) - return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, text), nil + return p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, text), nil case "": text := "###### Mattermost GitHub Plugin - Slash Command Help\n" + strings.Replace(COMMAND_HELP, "|", "`", -1) - return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, text), nil + return p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, text), nil case "settings": if len(parameters) < 2 { - return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, "Please specify both a setting and value. Use `/github help` for more usage information."), nil + return p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, "Please specify both a setting and value. Use `/github help` for more usage information."), nil } setting := parameters[0] if setting != SETTING_NOTIFICATIONS && setting != SETTING_REMINDERS { - return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, "Unknown setting."), nil + return p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, "Unknown setting."), nil } strValue := parameters[1] @@ -188,7 +188,7 @@ func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*mo if strValue == SETTING_ON { value = true } else if strValue != SETTING_OFF { - return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, "Invalid value. Accepted values are: \"on\" or \"off\"."), nil + return p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, "Invalid value. Accepted values are: \"on\" or \"off\"."), nil } if setting == SETTING_NOTIFICATIONS { @@ -205,7 +205,7 @@ func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*mo p.storeGitHubUserInfo(info) - return getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, "Settings updated."), nil + return p.getCommandResponse(model.COMMAND_RESPONSE_TYPE_EPHEMERAL, "Settings updated."), nil } return &model.CommandResponse{}, nil diff --git a/server/configuration.go b/server/configuration.go index f2d4a0f16..434ce07ab 100644 --- a/server/configuration.go +++ b/server/configuration.go @@ -2,8 +2,10 @@ package main import ( "fmt" + "path" "reflect" + "github.com/mattermost/mattermost-server/model" "github.com/pkg/errors" ) @@ -28,6 +30,8 @@ type configuration struct { EncryptionKey string EnterpriseBaseURL string EnterpriseUploadURL string + PluginsDirectory string + ProfileImageURL string } // Clone shallow copies the configuration. Your implementation may require a deep copy if @@ -81,7 +85,7 @@ func (p *Plugin) getConfiguration() *configuration { // This method panics if setConfiguration is called with the existing configuration. This almost // certainly means that the configuration was modified without being cloned and may result in // an unsafe access. -func (p *Plugin) setConfiguration(configuration *configuration) { +func (p *Plugin) setConfiguration(configuration *configuration, serverConfiguration *model.Config) { p.configurationLock.Lock() defer p.configurationLock.Unlock() @@ -96,6 +100,14 @@ func (p *Plugin) setConfiguration(configuration *configuration) { panic("setConfiguration called with the existing configuration") } + // PluginDirectory & ProfileImageURL should be set based on server configuration and not the plugin configuration + if serverConfiguration.PluginSettings.Directory != nil { + configuration.PluginsDirectory = *serverConfiguration.PluginSettings.Directory + } + if serverConfiguration.ServiceSettings.SiteURL != nil { + configuration.ProfileImageURL = path.Join(*serverConfiguration.ServiceSettings.SiteURL, "plugins", manifest.Id, "assets", "profile.png") + } + p.configuration = configuration } @@ -108,7 +120,9 @@ func (p *Plugin) OnConfigurationChange() error { return errors.Wrap(err, "failed to load plugin configuration") } - p.setConfiguration(configuration) + serverConfiguration := p.API.GetConfig() + + p.setConfiguration(configuration, serverConfiguration) return nil } diff --git a/server/plugin.go b/server/plugin.go index 0666edd7e..f0084ec3a 100644 --- a/server/plugin.go +++ b/server/plugin.go @@ -88,6 +88,7 @@ func (p *Plugin) OnActivate() error { } p.BotUserID = user.Id + return nil } @@ -220,6 +221,8 @@ func (p *Plugin) CreateBotDMPost(userID, message, postType string) *model.AppErr return err } + config := p.getConfiguration() + post := &model.Post{ UserId: p.BotUserID, ChannelId: channel.Id, @@ -228,7 +231,7 @@ func (p *Plugin) CreateBotDMPost(userID, message, postType string) *model.AppErr Props: map[string]interface{}{ "from_webhook": "true", "override_username": GITHUB_USERNAME, - "override_icon_url": GITHUB_ICON_URL, + "override_icon_url": config.ProfileImageURL, }, } diff --git a/server/webhook.go b/server/webhook.go index a56350522..483a25a50 100644 --- a/server/webhook.go +++ b/server/webhook.go @@ -217,7 +217,7 @@ func (p *Plugin) postPullRequestEvent(event *github.PullRequestEvent) { Props: map[string]interface{}{ "from_webhook": "true", "override_username": GITHUB_USERNAME, - "override_icon_url": GITHUB_ICON_URL, + "override_icon_url": config.ProfileImageURL, }, } @@ -309,7 +309,7 @@ func (p *Plugin) postIssueEvent(event *github.IssuesEvent) { Props: map[string]interface{}{ "from_webhook": "true", "override_username": GITHUB_USERNAME, - "override_icon_url": GITHUB_ICON_URL, + "override_icon_url": config.ProfileImageURL, }, } @@ -400,7 +400,7 @@ func (p *Plugin) postPushEvent(event *github.PushEvent) { Props: map[string]interface{}{ "from_webhook": "true", "override_username": GITHUB_USERNAME, - "override_icon_url": GITHUB_ICON_URL, + "override_icon_url": config.ProfileImageURL, }, Message: newPushMessage, } @@ -452,7 +452,7 @@ func (p *Plugin) postCreateEvent(event *github.CreateEvent) { Props: map[string]interface{}{ "from_webhook": "true", "override_username": GITHUB_USERNAME, - "override_icon_url": GITHUB_ICON_URL, + "override_icon_url": config.ProfileImageURL, }, Message: newCreateMessage, } @@ -504,7 +504,7 @@ func (p *Plugin) postDeleteEvent(event *github.DeleteEvent) { Props: map[string]interface{}{ "from_webhook": "true", "override_username": GITHUB_USERNAME, - "override_icon_url": GITHUB_ICON_URL, + "override_icon_url": config.ProfileImageURL, }, Message: newDeleteMessage, } @@ -559,7 +559,7 @@ func (p *Plugin) postIssueCommentEvent(event *github.IssueCommentEvent) { Props: map[string]interface{}{ "from_webhook": "true", "override_username": GITHUB_USERNAME, - "override_icon_url": GITHUB_ICON_URL, + "override_icon_url": config.ProfileImageURL, }, } @@ -641,7 +641,7 @@ func (p *Plugin) postPullRequestReviewEvent(event *github.PullRequestReviewEvent Props: map[string]interface{}{ "from_webhook": "true", "override_username": GITHUB_USERNAME, - "override_icon_url": GITHUB_ICON_URL, + "override_icon_url": config.ProfileImageURL, }, } @@ -702,7 +702,7 @@ func (p *Plugin) postPullRequestReviewCommentEvent(event *github.PullRequestRevi Props: map[string]interface{}{ "from_webhook": "true", "override_username": GITHUB_USERNAME, - "override_icon_url": GITHUB_ICON_URL, + "override_icon_url": config.ProfileImageURL, }, } @@ -738,6 +738,7 @@ func (p *Plugin) postPullRequestReviewCommentEvent(event *github.PullRequestRevi func (p *Plugin) handleCommentMentionNotification(event *github.IssueCommentEvent) { body := event.GetComment().GetBody() + config := p.getConfiguration() // Try to parse out email footer junk if strings.Contains(body, "notifications@github.com") { @@ -755,7 +756,7 @@ func (p *Plugin) handleCommentMentionNotification(event *github.IssueCommentEven Props: map[string]interface{}{ "from_webhook": "true", "override_username": GITHUB_USERNAME, - "override_icon_url": GITHUB_ICON_URL, + "override_icon_url": config.ProfileImageURL, }, }