<commit_before>// Copyright (c) 2018 <NAME> <<EMAIL>>
// released under the MIT license

package irc

import (
	"fmt"
	"sort"
	"strings"

	"github.com/goshuirc/irc-go/ircfmt"
	"github.com/goshuirc/irc-go/ircmsg"

	"github.com/oragono/oragono/irc/utils"
)

// defines an IRC service, e.g., NICKSERV
type ircService struct {
	Name           string
	ShortName      string
	CommandAliases []string
	Commands       map[string]*serviceCommand
	HelpBanner     string
}

// defines a command associated with a service, e.g., NICKSERV IDENTIFY
type serviceCommand struct {
	capabs       []string // oper capabs the given user has to have to access this command
	handler      func(server *Server, client *Client, command, params string, rb *ResponseBuffer)
	help         string
	helpShort    string
	authRequired bool
	enabled      func(*Server) bool // is this command enabled in the server config?
}

// all services, by lowercase name
var OragonoServices = map[string]*ircService{
	"nickserv": {
		Name:           "NickServ",
		ShortName:      "NS",
		CommandAliases: []string{"NICKSERV", "NS"},
		Commands:       nickservCommands,
		HelpBanner:     nickservHelp,
	},
	"chanserv": {
		Name:           "ChanServ",
		ShortName:      "CS",
		CommandAliases: []string{"CHANSERV", "CS"},
		Commands:       chanservCommands,
		HelpBanner:     chanservHelp,
	},
	"hostserv": {
		Name:           "HostServ",
		ShortName:      "HS",
		CommandAliases: []string{"HOSTSERV", "HS"},
		Commands:       hostservCommands,
		HelpBanner:     hostservHelp,
	},
}

// all service commands at the protocol level, by uppercase command name
// e.g., NICKSERV, NS
var oragonoServicesByCommandAlias map[string]*ircService

// special-cased command shared by all services
var servHelpCmd serviceCommand = serviceCommand{
	help: `Syntax: $bHELP [command]$b

HELP returns information on the given command.`,
	helpShort: `$bHELP$b shows in-depth information about commands.`,
}

// this handles IRC commands like `/NICKSERV INFO`, translating into `/MSG NICKSERV INFO`
func serviceCmdHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
	service, ok := oragonoServicesByCommandAlias[msg.Command]
	if !ok {
		server.logger.Warning("internal", "can't handle unrecognized service", msg.Command)
		return false
	}

	fakePrivmsg := strings.Join(msg.Params, " ")
	servicePrivmsgHandler(service, server, client, fakePrivmsg, rb)
	return false
}

// generic handler for service PRIVMSG
func servicePrivmsgHandler(service *ircService, server *Server, client *Client, message string, rb *ResponseBuffer) {
	commandName, params := utils.ExtractParam(message)
	commandName = strings.ToLower(commandName)

	nick := rb.target.Nick()
	sendNotice := func(notice string) {
		rb.Add(nil, service.Name, "NOTICE", nick, notice)
	}

	cmd := service.Commands[commandName]
	if cmd == nil {
		sendNotice(fmt.Sprintf("%s /%s HELP", client.t("Unknown command. To see available commands, run"), service.ShortName))
		return
	}

	if cmd.enabled != nil && !cmd.enabled(server) {
		sendNotice(client.t("This command has been disabled by the server administrators"))
		return
	}

	if 0 < len(cmd.capabs) && !client.HasRoleCapabs(cmd.capabs...) {
		sendNotice(client.t("Command restricted"))
		return
	}

	if cmd.authRequired && client.Account() == "" {
		sendNotice(client.t("You're not logged into an account"))
		return
	}

	server.logger.Debug("services", fmt.Sprintf("Client %s ran %s command %s", client.Nick(), service.Name, commandName))
	if commandName == "help" {
		serviceHelpHandler(service, server, client, params, rb)
	} else {
		cmd.handler(server, client, commandName, params, rb)
	}
}

// generic handler that displays help for service commands
func serviceHelpHandler(service *ircService, server *Server, client *Client, params string, rb *ResponseBuffer) {
	nick := rb.target.Nick()
	sendNotice := func(notice string) {
		rb.Add(nil, service.Name, "NOTICE", nick, notice)
	}

	sendNotice(ircfmt.Unescape(fmt.Sprintf("*** $b%s HELP$b ***", service.Name)))

	if params == "" {
		// show general help
		var shownHelpLines sort.StringSlice
		for _, commandInfo := range service.Commands {
			// skip commands user can't access
			if 0 < len(commandInfo.capabs) && !client.HasRoleCapabs(commandInfo.capabs...) {
				continue
			}
			if commandInfo.enabled != nil && !commandInfo.enabled(server) {
				continue
			}

			shownHelpLines = append(shownHelpLines, "    "+client.t(commandInfo.helpShort))
		}

		// sort help lines
		sort.Sort(shownHelpLines)

		// assemble help text
		assembledHelpLines := strings.Join(shownHelpLines, "\n")
		fullHelp := ircfmt.Unescape(fmt.Sprintf(client.t(service.HelpBanner), assembledHelpLines))

		// push out help text
		for _, line := range strings.Split(fullHelp, "\n") {
			sendNotice(line)
		}
	} else {
		commandInfo := service.Commands[strings.ToLower(strings.TrimSpace(params))]
		if commandInfo == nil {
			sendNotice(client.t(fmt.Sprintf("Unknown command. To see available commands, run /%s HELP", service.ShortName)))
		} else {
			for _, line := range strings.Split(ircfmt.Unescape(client.t(commandInfo.help)), "\n") {
				sendNotice(line)<commit_msg>services: Explicitly show when commands have been disabled in the HELP output<commit_after>// Copyright (c) 2018 <NAME> <<EMAIL>>
// released under the MIT license

package irc

import (
	"fmt"
	"sort"
	"strings"

	"github.com/goshuirc/irc-go/ircfmt"
	"github.com/goshuirc/irc-go/ircmsg"

	"github.com/oragono/oragono/irc/utils"
)

// defines an IRC service, e.g., NICKSERV
type ircService struct {
	Name           string
	ShortName      string
	CommandAliases []string
	Commands       map[string]*serviceCommand
	HelpBanner     string
}

// defines a command associated with a service, e.g., NICKSERV IDENTIFY
type serviceCommand struct {
	capabs       []string // oper capabs the given user has to have to access this command
	handler      func(server *Server, client *Client, command, params string, rb *ResponseBuffer)
	help         string
	helpShort    string
	authRequired bool
	enabled      func(*Server) bool // is this command enabled in the server config?
}

// all services, by lowercase name
var OragonoServices = map[string]*ircService{
	"nickserv": {
		Name:           "NickServ",
		ShortName:      "NS",
		CommandAliases: []string{"NICKSERV", "NS"},
		Commands:       nickservCommands,
		HelpBanner:     nickservHelp,
	},
	"chanserv": {
		Name:           "ChanServ",
		ShortName:      "CS",
		CommandAliases: []string{"CHANSERV", "CS"},
		Commands:       chanservCommands,
		HelpBanner:     chanservHelp,
	},
	"hostserv": {
		Name:           "HostServ",
		ShortName:      "HS",
		CommandAliases: []string{"HOSTSERV", "HS"},
		Commands:       hostservCommands,
		HelpBanner:     hostservHelp,
	},
}

// all service commands at the protocol level, by uppercase command name
// e.g., NICKSERV, NS
var oragonoServicesByCommandAlias map[string]*ircService

// special-cased command shared by all services
var servHelpCmd serviceCommand = serviceCommand{
	help: `Syntax: $bHELP [command]$b

HELP returns information on the given command.`,
	helpShort: `$bHELP$b shows in-depth information about commands.`,
}

// this handles IRC commands like `/NICKSERV INFO`, translating into `/MSG NICKSERV INFO`
func serviceCmdHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
	service, ok := oragonoServicesByCommandAlias[msg.Command]
	if !ok {
		server.logger.Warning("internal", "can't handle unrecognized service", msg.Command)
		return false
	}

	fakePrivmsg := strings.Join(msg.Params, " ")
	servicePrivmsgHandler(service, server, client, fakePrivmsg, rb)
	return false
}

// generic handler for service PRIVMSG
func servicePrivmsgHandler(service *ircService, server *Server, client *Client, message string, rb *ResponseBuffer) {
	commandName, params := utils.ExtractParam(message)
	commandName = strings.ToLower(commandName)

	nick := rb.target.Nick()
	sendNotice := func(notice string) {
		rb.Add(nil, service.Name, "NOTICE", nick, notice)
	}

	cmd := service.Commands[commandName]
	if cmd == nil {
		sendNotice(fmt.Sprintf("%s /%s HELP", client.t("Unknown command. To see available commands, run"), service.ShortName))
		return
	}

	if cmd.enabled != nil && !cmd.enabled(server) {
		sendNotice(client.t("This command has been disabled by the server administrators"))
		return
	}

	if 0 < len(cmd.capabs) && !client.HasRoleCapabs(cmd.capabs...) {
		sendNotice(client.t("Command restricted"))
		return
	}

	if cmd.authRequired && client.Account() == "" {
		sendNotice(client.t("You're not logged into an account"))
		return
	}

	server.logger.Debug("services", fmt.Sprintf("Client %s ran %s command %s", client.Nick(), service.Name, commandName))
	if commandName == "help" {
		serviceHelpHandler(service, server, client, params, rb)
	} else {
		cmd.handler(server, client, commandName, params, rb)
	}
}

// generic handler that displays help for service commands
func serviceHelpHandler(service *ircService, server *Server, client *Client, params string, rb *ResponseBuffer) {
	nick := rb.target.Nick()
	sendNotice := func(notice string) {
		rb.Add(nil, service.Name, "NOTICE", nick, notice)
	}

	sendNotice(ircfmt.Unescape(fmt.Sprintf("*** $b%s HELP$b ***", service.Name)))

	if params == "" {
		// show general help
		var shownHelpLines sort.StringSlice
		var disabledCommands bool
		for _, commandInfo := range service.Commands {
			// skip commands user can't access
			if 0 < len(commandInfo.capabs) && !client.HasRoleCapabs(commandInfo.capabs...) {
				continue
			}
			if commandInfo.enabled != nil && !commandInfo.enabled(server) {
				disabledCommands = true
				continue
			}

			shownHelpLines = append(shownHelpLines, "    "+client.t(commandInfo.helpShort))
		}

		if disabledCommands {
			shownHelpLines = append(shownHelpLines, "    "+client.t("... and other commands which have been disabled"))
		}

		// sort help lines
		sort.Sort(shownHelpLines)

		// assemble help text
		assembledHelpLines := strings.Join(shownHelpLines, "\n")
		fullHelp := ircfmt.Unescape(fmt.Sprintf(client.t(service.HelpBanner), assembledHelpLines))

		// push out help text
		for _, line := range strings.Split(fullHelp, "\n") {
			sendNotice(line)
		}
	} else {
		commandInfo := service.Commands[strings.ToLower(strings.TrimSpace(params))]
		if commandInfo == nil {
			sendNotice(client.t(fmt.Sprintf("Unknown command. To see available commands, run /%s HELP", service.ShortName)))
		} else {
			for _, line := range strings.Split(ircfmt.Unescape(client.t(commandInfo.help)), "\n") {
				sendNotice(line)
{'fraction_non_alphanumeric': 0.12624741103370363, 'fraction_numerical': 0.001129730747505178, 'mean_word_length': 4.0369843527738265, 'pattern_counts': {'":': 6, '<': 13, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '604006', 'n_tokens_mistral': 3573, 'n_tokens_neox': 3316, 'n_words': 903}