1// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
2// released under the MIT license
3
4package irc
5
6import (
7	"fmt"
8	"strings"
9
10	"github.com/ergochat/ergo/irc/history"
11	"github.com/ergochat/ergo/irc/modes"
12	"github.com/ergochat/ergo/irc/utils"
13)
14
15const (
16	npcNickMask   = "*%s*!%s@npc.fakeuser.invalid"
17	sceneNickMask = "=Scene=!%s@npc.fakeuser.invalid"
18)
19
20func sendRoleplayMessage(server *Server, client *Client, source string, targetString string, isScene, isAction bool, messageParts []string, rb *ResponseBuffer) {
21	config := server.Config()
22	if !config.Roleplay.Enabled {
23		rb.Add(nil, client.server.name, ERR_CANNOTSENDRP, targetString, client.t("Roleplaying has been disabled by the server administrators"))
24		return
25	}
26	if config.Roleplay.RequireOper && !client.HasRoleCapabs("roleplay") {
27		rb.Add(nil, client.server.name, ERR_CANNOTSENDRP, targetString, client.t("Insufficient privileges"))
28		return
29	}
30
31	var sourceMask string
32	if isScene {
33		sourceMask = fmt.Sprintf(sceneNickMask, client.Nick())
34	} else {
35		cfSource, cfSourceErr := CasefoldName(source)
36		skelSource, skelErr := Skeleton(source)
37		if cfSourceErr != nil || skelErr != nil ||
38			restrictedCasefoldedNicks.Has(cfSource) || restrictedSkeletons.Has(skelSource) {
39			rb.Add(nil, client.server.name, ERR_CANNOTSENDRP, targetString, client.t("Invalid roleplay name"))
40			return
41		}
42		sourceMask = fmt.Sprintf(npcNickMask, source, client.Nick())
43	}
44
45	// block attempts to send CTCP messages to Tor clients
46	if len(messageParts) > 0 && len(messageParts[0]) > 0 && messageParts[0][0] == '\x01' {
47		return
48	}
49
50	var buf strings.Builder
51	if isAction {
52		buf.WriteString("\x01ACTION ")
53	}
54	for i, part := range messageParts {
55		buf.WriteString(part)
56		if i != len(messageParts)-1 {
57			buf.WriteByte(' ')
58		}
59	}
60	if config.Roleplay.addSuffix {
61		buf.WriteString(" (")
62		buf.WriteString(client.Nick())
63		buf.WriteString(")")
64	}
65	if isAction {
66		buf.WriteString("\x01")
67	}
68
69	splitMessage := utils.MakeMessage(buf.String())
70
71	target, cerr := CasefoldChannel(targetString)
72	if cerr == nil {
73		channel := server.channels.Get(target)
74		if channel == nil {
75			rb.Add(nil, server.name, ERR_NOSUCHCHANNEL, client.nick, targetString, client.t("No such channel"))
76			return
77		}
78
79		targetString = channel.Name()
80		if canSpeak, mode := channel.CanSpeak(client); !canSpeak {
81			rb.Add(nil, client.server.name, ERR_CANNOTSENDTOCHAN, targetString, fmt.Sprintf(client.t("Cannot send to channel (+%s)"), mode))
82			return
83		}
84
85		if !channel.flags.HasMode(modes.ChanRoleplaying) {
86			rb.Add(nil, client.server.name, ERR_CANNOTSENDRP, targetString, client.t("Channel doesn't have roleplaying mode available"))
87			return
88		}
89
90		if config.Roleplay.RequireChanops && !channel.ClientIsAtLeast(client, modes.ChannelOperator) {
91			rb.Add(nil, client.server.name, ERR_CANNOTSENDRP, targetString, client.t("Insufficient privileges"))
92			return
93		}
94
95		isBot := client.HasMode(modes.Bot)
96		for _, member := range channel.Members() {
97			for _, session := range member.Sessions() {
98				// see discussion on #865: clients do not understand how to do local echo
99				// of roleplay commands, so send them a copy whether they have echo-message
100				// or not
101				if rb.session == session {
102					rb.AddSplitMessageFromClient(sourceMask, "*", isBot, nil, "PRIVMSG", targetString, splitMessage)
103				} else {
104					session.sendSplitMsgFromClientInternal(false, sourceMask, "*", isBot, nil, "PRIVMSG", targetString, splitMessage)
105				}
106			}
107		}
108
109		channel.AddHistoryItem(history.Item{
110			Type:    history.Privmsg,
111			Message: splitMessage,
112			Nick:    sourceMask,
113		}, client.Account())
114	} else {
115		target, err := CasefoldName(targetString)
116		user := server.clients.Get(target)
117		if err != nil || user == nil {
118			rb.Add(nil, server.name, ERR_NOSUCHNICK, client.nick, target, client.t("No such nick"))
119			return
120		}
121
122		if !user.HasMode(modes.UserRoleplaying) {
123			rb.Add(nil, client.server.name, ERR_CANNOTSENDRP, user.nick, client.t("User doesn't have roleplaying mode enabled"))
124			return
125		}
126
127		cnick := client.Nick()
128		tnick := user.Nick()
129		isBot := client.HasMode(modes.Bot)
130		for _, session := range user.Sessions() {
131			session.sendSplitMsgFromClientInternal(false, sourceMask, "*", isBot, nil, "PRIVMSG", tnick, splitMessage)
132		}
133		if away, awayMessage := user.Away(); away {
134			//TODO(dan): possibly implement cooldown of away notifications to users
135			rb.Add(nil, server.name, RPL_AWAY, cnick, tnick, awayMessage)
136		}
137	}
138}
139