github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/app/slashcommands/command_share.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package slashcommands
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"strings"
    10  
    11  	"github.com/masterhung0112/hk_server/v5/app"
    12  	"github.com/masterhung0112/hk_server/v5/app/request"
    13  	"github.com/masterhung0112/hk_server/v5/model"
    14  	"github.com/masterhung0112/hk_server/v5/shared/i18n"
    15  )
    16  
    17  type ShareProvider struct {
    18  }
    19  
    20  const (
    21  	CommandTriggerShare   = "share-channel"
    22  	AvailableShareActions = "invite, uninvite, unshare, status"
    23  )
    24  
    25  func init() {
    26  	app.RegisterCommandProvider(&ShareProvider{})
    27  }
    28  
    29  func (sp *ShareProvider) GetTrigger() string {
    30  	return CommandTriggerShare
    31  }
    32  
    33  func (sp *ShareProvider) GetCommand(a *app.App, T i18n.TranslateFunc) *model.Command {
    34  	share := model.NewAutocompleteData(CommandTriggerShare, "[action]", T("api.command_share.available_actions", map[string]interface{}{"Actions": AvailableShareActions}))
    35  
    36  	inviteRemote := model.NewAutocompleteData("invite", "", T("api.command_share.invite_remote.help"))
    37  	inviteRemote.AddNamedDynamicListArgument("connectionID", T("api.command_share.remote_id.help"), "builtin:"+CommandTriggerShare, true)
    38  	inviteRemote.AddNamedTextArgument("readonly", T("api.command_share.share_read_only.help"), T("api.command_share.share_read_only.hint"), "Y|N|y|n", false)
    39  
    40  	unInviteRemote := model.NewAutocompleteData("uninvite", "", T("api.command_share.uninvite_remote.help"))
    41  	unInviteRemote.AddNamedDynamicListArgument("connectionID", T("api.command_share.uninvite_remote_id.help"), "builtin:"+CommandTriggerShare, true)
    42  
    43  	unshareChannel := model.NewAutocompleteData("unshare", "", T("api.command_share.unshare_channel.help"))
    44  
    45  	status := model.NewAutocompleteData("status", "", T("api.command_share.channel_status.help"))
    46  
    47  	share.AddCommand(inviteRemote)
    48  	share.AddCommand(unInviteRemote)
    49  	share.AddCommand(unshareChannel)
    50  	share.AddCommand(status)
    51  
    52  	return &model.Command{
    53  		Trigger:          CommandTriggerShare,
    54  		AutoComplete:     true,
    55  		AutoCompleteDesc: T("api.command_share.desc"),
    56  		AutoCompleteHint: T("api.command_share.hint"),
    57  		DisplayName:      T("api.command_share.name"),
    58  		AutocompleteData: share,
    59  	}
    60  }
    61  
    62  func (sp *ShareProvider) GetAutoCompleteListItems(a *app.App, commandArgs *model.CommandArgs, arg *model.AutocompleteArg, parsed, toBeParsed string) ([]model.AutocompleteListItem, error) {
    63  	switch {
    64  	case strings.Contains(parsed, " share "):
    65  
    66  		return sp.getAutoCompleteShareChannel(a, commandArgs, arg)
    67  
    68  	case strings.Contains(parsed, " invite "):
    69  
    70  		return sp.getAutoCompleteInviteRemote(a, commandArgs, arg)
    71  
    72  	case strings.Contains(parsed, " uninvite "):
    73  
    74  		return sp.getAutoCompleteUnInviteRemote(a, commandArgs, arg)
    75  
    76  	}
    77  	return nil, errors.New("invalid action")
    78  }
    79  
    80  func (sp *ShareProvider) getAutoCompleteShareChannel(a *app.App, commandArgs *model.CommandArgs, arg *model.AutocompleteArg) ([]model.AutocompleteListItem, error) {
    81  	channel, err := a.GetChannel(commandArgs.ChannelId)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  
    86  	var item model.AutocompleteListItem
    87  
    88  	switch arg.Name {
    89  	case "name":
    90  		item = model.AutocompleteListItem{
    91  			Item:     channel.Name,
    92  			HelpText: channel.DisplayName,
    93  		}
    94  	case "displayname":
    95  		item = model.AutocompleteListItem{
    96  			Item:     channel.DisplayName,
    97  			HelpText: channel.Name,
    98  		}
    99  	default:
   100  		return nil, fmt.Errorf("%s not a dynamic argument", arg.Name)
   101  	}
   102  	return []model.AutocompleteListItem{item}, nil
   103  }
   104  
   105  func (sp *ShareProvider) getAutoCompleteInviteRemote(a *app.App, commandArgs *model.CommandArgs, arg *model.AutocompleteArg) ([]model.AutocompleteListItem, error) {
   106  	switch arg.Name {
   107  	case "connectionID":
   108  		return getRemoteClusterAutocompleteListItemsNotInChannel(a, commandArgs.ChannelId, true)
   109  	default:
   110  		return nil, fmt.Errorf("%s not a dynamic argument", arg.Name)
   111  	}
   112  }
   113  
   114  func (sp *ShareProvider) getAutoCompleteUnInviteRemote(a *app.App, _ *model.CommandArgs, arg *model.AutocompleteArg) ([]model.AutocompleteListItem, error) {
   115  	switch arg.Name {
   116  	case "connectionID":
   117  		return getRemoteClusterAutocompleteListItems(a, true)
   118  	default:
   119  		return nil, fmt.Errorf("%s not a dynamic argument", arg.Name)
   120  	}
   121  }
   122  
   123  func (sp *ShareProvider) DoCommand(a *app.App, c *request.Context, args *model.CommandArgs, message string) *model.CommandResponse {
   124  	if !a.HasPermissionTo(args.UserId, model.PERMISSION_MANAGE_SHARED_CHANNELS) {
   125  		return responsef(args.T("api.command_share.permission_required", map[string]interface{}{"Permission": "manage_shared_channels"}))
   126  	}
   127  
   128  	if a.Srv().GetSharedChannelSyncService() == nil {
   129  		return responsef(args.T("api.command_share.service_disabled"))
   130  	}
   131  
   132  	if a.Srv().GetRemoteClusterService() == nil {
   133  		return responsef(args.T("api.command_remote.service_disabled"))
   134  	}
   135  
   136  	margs := parseNamedArgs(args.Command)
   137  	action, ok := margs[ActionKey]
   138  	if !ok {
   139  		return responsef(args.T("api.command_share.missing_action", map[string]interface{}{"Actions": AvailableShareActions}))
   140  	}
   141  
   142  	switch action {
   143  	case "share":
   144  		return sp.doShareChannel(a, args, margs)
   145  	case "unshare":
   146  		return sp.doUnshareChannel(a, args, margs)
   147  	case "invite":
   148  		return sp.doInviteRemote(a, args, margs)
   149  	case "uninvite":
   150  		return sp.doUninviteRemote(a, args, margs)
   151  	case "status":
   152  		return sp.doStatus(a, args, margs)
   153  	}
   154  	return responsef(args.T("api.command_share.unknown_action", map[string]interface{}{"Action": action, "Actions": AvailableShareActions}))
   155  }
   156  
   157  func (sp *ShareProvider) doShareChannel(a *app.App, args *model.CommandArgs, margs map[string]string) *model.CommandResponse {
   158  	// check that channel exists.
   159  	channel, errApp := a.GetChannel(args.ChannelId)
   160  	if errApp != nil {
   161  		return responsef(args.T("api.command_share.share_channel.error", map[string]interface{}{"Error": errApp.Error()}))
   162  	}
   163  
   164  	if name := margs["name"]; name == "" {
   165  		margs["name"] = channel.Name
   166  	}
   167  	if name := margs["displayname"]; name == "" {
   168  		margs["displayname"] = channel.DisplayName
   169  	}
   170  	if name := margs["purpose"]; name == "" {
   171  		margs["purpose"] = channel.Purpose
   172  	}
   173  	if name := margs["header"]; name == "" {
   174  		margs["header"] = channel.Header
   175  	}
   176  	if _, ok := margs["readonly"]; !ok {
   177  		margs["readonly"] = "N"
   178  	}
   179  
   180  	readonly, err := parseBool(margs["readonly"])
   181  	if err != nil {
   182  		return responsef(args.T("api.command_share.invalid_value.error", map[string]interface{}{"Arg": "readonly", "Error": err.Error()}))
   183  	}
   184  
   185  	sc := &model.SharedChannel{
   186  		ChannelId:        args.ChannelId,
   187  		TeamId:           args.TeamId,
   188  		Home:             true,
   189  		ReadOnly:         readonly,
   190  		ShareName:        margs["name"],
   191  		ShareDisplayName: margs["displayname"],
   192  		SharePurpose:     margs["purpose"],
   193  		ShareHeader:      margs["header"],
   194  		CreatorId:        args.UserId,
   195  	}
   196  
   197  	if _, err := a.SaveSharedChannel(sc); err != nil {
   198  		return responsef(args.T("api.command_share.share_channel.error", map[string]interface{}{"Error": err.Error()}))
   199  	}
   200  
   201  	notifyClientsForChannelUpdate(a, sc)
   202  
   203  	return responsef("##### " + args.T("api.command_share.channel_shared"))
   204  }
   205  
   206  func (sp *ShareProvider) doUnshareChannel(a *app.App, args *model.CommandArgs, margs map[string]string) *model.CommandResponse {
   207  	sc, appErr := a.GetSharedChannel(args.ChannelId)
   208  	if appErr != nil {
   209  		return responsef(args.T("api.command_share.shared_channel_unshare.error", map[string]interface{}{"Error": appErr.Error()}))
   210  	}
   211  
   212  	deleted, err := a.DeleteSharedChannel(args.ChannelId)
   213  	if err != nil {
   214  		return responsef(args.T("api.command_share.shared_channel_unshare.error", map[string]interface{}{"Error": err.Error()}))
   215  	}
   216  	if !deleted {
   217  		return responsef(args.T("api.command_share.not_shared_channel_unshare"))
   218  	}
   219  
   220  	notifyClientsForChannelUpdate(a, sc)
   221  
   222  	return responsef("##### " + args.T("api.command_share.shared_channel_unavailable"))
   223  }
   224  
   225  func (sp *ShareProvider) doInviteRemote(a *app.App, args *model.CommandArgs, margs map[string]string) (resp *model.CommandResponse) {
   226  	remoteId, ok := margs["connectionID"]
   227  	if !ok || remoteId == "" {
   228  		return responsef(args.T("api.command_share.must_specify_valid_remote"))
   229  	}
   230  
   231  	hasRemote, err := a.HasRemote(args.ChannelId, remoteId)
   232  	if err != nil {
   233  		return responsef(args.T("api.command_share.fetch_remote.error", map[string]interface{}{"Error": err.Error()}))
   234  	}
   235  	if hasRemote {
   236  		return responsef(args.T("api.command_share.remote_already_invited"))
   237  	}
   238  
   239  	// Check if channel is shared or not.
   240  	hasChan, err := a.HasSharedChannel(args.ChannelId)
   241  	if err != nil {
   242  		return responsef(args.T("api.command_share.check_channel_exist.error", map[string]interface{}{"Error": err.Error()}))
   243  	}
   244  	if !hasChan {
   245  		// If it doesn't exist, then create it.
   246  		resp2 := sp.doShareChannel(a, args, margs)
   247  		// We modify the outgoing response by prepending the text
   248  		// from the shareChannel response.
   249  		defer func() {
   250  			resp.Text = resp2.Text + "\n" + resp.Text
   251  		}()
   252  	}
   253  
   254  	// don't allow invitation to shared channel originating from remote.
   255  	// (also blocks cyclic invitations)
   256  	if err := a.CheckCanInviteToSharedChannel(args.ChannelId); err != nil {
   257  		return responsef(args.T("api.command_share.channel_invite_not_home.error"))
   258  	}
   259  
   260  	rc, appErr := a.GetRemoteCluster(remoteId)
   261  	if appErr != nil {
   262  		return responsef(args.T("api.command_share.remote_id_invalid.error", map[string]interface{}{"Error": appErr.Error()}))
   263  	}
   264  
   265  	channel, errApp := a.GetChannel(args.ChannelId)
   266  	if errApp != nil {
   267  		return responsef(args.T("api.command_share.channel_invite.error", map[string]interface{}{"Name": rc.DisplayName, "Error": errApp.Error()}))
   268  	}
   269  	// send channel invite to remote cluster
   270  	if err := a.Srv().GetSharedChannelSyncService().SendChannelInvite(channel, args.UserId, rc); err != nil {
   271  		return responsef(args.T("api.command_share.channel_invite.error", map[string]interface{}{"Name": rc.DisplayName, "Error": err.Error()}))
   272  	}
   273  
   274  	return responsef("##### " + args.T("api.command_share.invitation_sent", map[string]interface{}{"Name": rc.DisplayName, "SiteURL": rc.SiteURL}))
   275  }
   276  
   277  func (sp *ShareProvider) doUninviteRemote(a *app.App, args *model.CommandArgs, margs map[string]string) *model.CommandResponse {
   278  	remoteId, ok := margs["connectionID"]
   279  	if !ok || remoteId == "" {
   280  		return responsef(args.T("api.command_share.remote_not_valid"))
   281  	}
   282  
   283  	scr, err := a.GetSharedChannelRemoteByIds(args.ChannelId, remoteId)
   284  	if err != nil || scr.ChannelId != args.ChannelId {
   285  		return responsef(args.T("api.command_share.channel_remote_id_not_exists", map[string]interface{}{"RemoteId": remoteId}))
   286  	}
   287  
   288  	deleted, err := a.DeleteSharedChannelRemote(scr.Id)
   289  	if err != nil || !deleted {
   290  		return responsef(args.T("api.command_share.could_not_uninvite.error", map[string]interface{}{"RemoteId": remoteId, "Error": err.Error()}))
   291  	}
   292  	return responsef("##### " + args.T("api.command_share.remote_uninvited", map[string]interface{}{"RemoteId": remoteId}))
   293  }
   294  
   295  func (sp *ShareProvider) doStatus(a *app.App, args *model.CommandArgs, _ map[string]string) *model.CommandResponse {
   296  	statuses, err := a.GetSharedChannelRemotesStatus(args.ChannelId)
   297  	if err != nil {
   298  		return responsef(args.T("api.command_share.fetch_remote_status.error", map[string]interface{}{"Error": err.Error()}))
   299  	}
   300  	if len(statuses) == 0 {
   301  		return responsef(args.T("api.command_share.no_remote_invited"))
   302  	}
   303  
   304  	var sb strings.Builder
   305  
   306  	fmt.Fprintf(&sb, args.T("api.command_share.channel_status_id", map[string]interface{}{"ChannelId": statuses[0].ChannelId})+"\n\n")
   307  
   308  	fmt.Fprintf(&sb, args.T("api.command_share.remote_table_header")+" \n")
   309  	// "| Secure Connection | SiteURL | ReadOnly | InviteAccepted | Online | Last Sync |"
   310  	fmt.Fprintf(&sb, "| ---- | ---- | ---- | ---- | ---- | ---- | \n")
   311  
   312  	for _, status := range statuses {
   313  		readonly := formatBool(args.T, status.ReadOnly)
   314  		accepted := formatBool(args.T, status.IsInviteAccepted)
   315  		online := formatBool(args.T, isOnline(status.LastPingAt))
   316  
   317  		lastSync := formatTimestamp(status.NextSyncAt)
   318  
   319  		fmt.Fprintf(&sb, "| %s | %s | %s | %s | %s | %s |\n",
   320  			status.DisplayName, status.SiteURL, readonly, accepted, online, lastSync)
   321  	}
   322  	return responsef(sb.String())
   323  }
   324  
   325  func notifyClientsForChannelUpdate(a *app.App, sharedChannel *model.SharedChannel) {
   326  	messageWs := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_CONVERTED, sharedChannel.TeamId, "", "", nil)
   327  	messageWs.Add("channel_id", sharedChannel.ChannelId)
   328  	a.Publish(messageWs)
   329  }