* Move pluginCommands into Channels We move pluginCommands, pluginCommandsLock into Channels. We also move the plugin related route handlers under Channels and move the init code under NewChannels. To achieve this, the router initialization is bumped up. Along with it, we clean up some App methods which were just wrappers over Channel methods. Instead, we call the Channel method directly to make things more readable and easy to understand. ```release-note NONE ``` * fix tests ```release-note NONE ```
169 строки
5.4 KiB
Go
169 строки
5.4 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/mattermost/mattermost-server/v6/app/request"
|
|
"github.com/mattermost/mattermost-server/v6/model"
|
|
)
|
|
|
|
type PluginCommand struct {
|
|
Command *model.Command
|
|
PluginId string
|
|
}
|
|
|
|
func (a *App) RegisterPluginCommand(pluginID string, command *model.Command) error {
|
|
if command.Trigger == "" {
|
|
return errors.New("invalid command")
|
|
}
|
|
if command.AutocompleteData != nil {
|
|
if err := command.AutocompleteData.IsValid(); err != nil {
|
|
return errors.Wrap(err, "invalid autocomplete data in command")
|
|
}
|
|
}
|
|
|
|
if command.AutocompleteData == nil {
|
|
command.AutocompleteData = model.NewAutocompleteData(command.Trigger, command.AutoCompleteHint, command.AutoCompleteDesc)
|
|
} else {
|
|
baseURL, err := url.Parse("/plugins/" + pluginID)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "Can't parse url %s", "/plugins/"+pluginID)
|
|
}
|
|
err = command.AutocompleteData.UpdateRelativeURLsForPluginCommands(baseURL)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Can't update relative urls for plugin commands")
|
|
}
|
|
}
|
|
|
|
command = &model.Command{
|
|
Trigger: strings.ToLower(command.Trigger),
|
|
TeamId: command.TeamId,
|
|
AutoComplete: command.AutoComplete,
|
|
AutoCompleteDesc: command.AutoCompleteDesc,
|
|
AutoCompleteHint: command.AutoCompleteHint,
|
|
DisplayName: command.DisplayName,
|
|
AutocompleteData: command.AutocompleteData,
|
|
AutocompleteIconData: command.AutocompleteIconData,
|
|
}
|
|
|
|
a.ch.pluginCommandsLock.Lock()
|
|
defer a.ch.pluginCommandsLock.Unlock()
|
|
|
|
for _, pc := range a.ch.pluginCommands {
|
|
if pc.Command.Trigger == command.Trigger && pc.Command.TeamId == command.TeamId {
|
|
if pc.PluginId == pluginID {
|
|
pc.Command = command
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
a.ch.pluginCommands = append(a.ch.pluginCommands, &PluginCommand{
|
|
Command: command,
|
|
PluginId: pluginID,
|
|
})
|
|
return nil
|
|
}
|
|
|
|
func (a *App) UnregisterPluginCommand(pluginID, teamID, trigger string) {
|
|
trigger = strings.ToLower(trigger)
|
|
|
|
a.ch.pluginCommandsLock.Lock()
|
|
defer a.ch.pluginCommandsLock.Unlock()
|
|
|
|
var remaining []*PluginCommand
|
|
for _, pc := range a.ch.pluginCommands {
|
|
if pc.Command.TeamId != teamID || pc.Command.Trigger != trigger {
|
|
remaining = append(remaining, pc)
|
|
}
|
|
}
|
|
a.ch.pluginCommands = remaining
|
|
}
|
|
|
|
func (ch *Channels) unregisterPluginCommands(pluginID string) {
|
|
ch.pluginCommandsLock.Lock()
|
|
defer ch.pluginCommandsLock.Unlock()
|
|
|
|
var remaining []*PluginCommand
|
|
for _, pc := range ch.pluginCommands {
|
|
if pc.PluginId != pluginID {
|
|
remaining = append(remaining, pc)
|
|
}
|
|
}
|
|
ch.pluginCommands = remaining
|
|
}
|
|
|
|
func (a *App) PluginCommandsForTeam(teamID string) []*model.Command {
|
|
a.ch.pluginCommandsLock.RLock()
|
|
defer a.ch.pluginCommandsLock.RUnlock()
|
|
|
|
var commands []*model.Command
|
|
for _, pc := range a.ch.pluginCommands {
|
|
if pc.Command.TeamId == "" || pc.Command.TeamId == teamID {
|
|
commands = append(commands, pc.Command)
|
|
}
|
|
}
|
|
return commands
|
|
}
|
|
|
|
// tryExecutePluginCommand attempts to run a command provided by a plugin based on the given arguments. If no such
|
|
// command can be found, returns nil for all arguments.
|
|
func (a *App) tryExecutePluginCommand(c *request.Context, args *model.CommandArgs) (*model.Command, *model.CommandResponse, *model.AppError) {
|
|
parts := strings.Split(args.Command, " ")
|
|
trigger := parts[0][1:]
|
|
trigger = strings.ToLower(trigger)
|
|
|
|
var matched *PluginCommand
|
|
a.ch.pluginCommandsLock.RLock()
|
|
for _, pc := range a.ch.pluginCommands {
|
|
if (pc.Command.TeamId == "" || pc.Command.TeamId == args.TeamId) && pc.Command.Trigger == trigger {
|
|
matched = pc
|
|
break
|
|
}
|
|
}
|
|
a.ch.pluginCommandsLock.RUnlock()
|
|
if matched == nil {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
pluginsEnvironment := a.GetPluginsEnvironment()
|
|
if pluginsEnvironment == nil {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
// Checking if plugin is working or not
|
|
if err := pluginsEnvironment.PerformHealthCheck(matched.PluginId); err != nil {
|
|
return matched.Command, nil, model.NewAppError("ExecutePluginCommand", "model.plugin_command_error.error.app_error", map[string]interface{}{"Command": trigger}, "err= Plugin has recently crashed: "+matched.PluginId, http.StatusInternalServerError)
|
|
}
|
|
|
|
pluginHooks, err := pluginsEnvironment.HooksForPlugin(matched.PluginId)
|
|
if err != nil {
|
|
return matched.Command, nil, model.NewAppError("ExecutePluginCommand", "model.plugin_command.error.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
|
|
}
|
|
|
|
for username, userID := range a.MentionsToTeamMembers(args.Command, args.TeamId) {
|
|
args.AddUserMention(username, userID)
|
|
}
|
|
|
|
for channelName, channelID := range a.MentionsToPublicChannels(args.Command, args.TeamId) {
|
|
args.AddChannelMention(channelName, channelID)
|
|
}
|
|
|
|
response, appErr := pluginHooks.ExecuteCommand(pluginContext(c), args)
|
|
|
|
// Checking if plugin crashed after running the command
|
|
if err := pluginsEnvironment.PerformHealthCheck(matched.PluginId); err != nil {
|
|
errMessage := fmt.Sprintf("err= Plugin %s crashed due to /%s command", matched.PluginId, trigger)
|
|
return matched.Command, nil, model.NewAppError("ExecutePluginCommand", "model.plugin_command_crash.error.app_error", map[string]interface{}{"Command": trigger, "PluginId": matched.PluginId}, errMessage, http.StatusInternalServerError)
|
|
}
|
|
return matched.Command, response, appErr
|
|
}
|