|
@@ -5,16 +5,62 @@ package irc
|
5
|
5
|
|
6
|
6
|
import (
|
7
|
7
|
"fmt"
|
|
8
|
+ "sort"
|
8
|
9
|
"strings"
|
9
|
10
|
|
10
|
11
|
"github.com/goshuirc/irc-go/ircfmt"
|
11
|
12
|
"github.com/oragono/oragono/irc/modes"
|
12
|
13
|
"github.com/oragono/oragono/irc/sno"
|
|
14
|
+ "github.com/oragono/oragono/irc/utils"
|
13
|
15
|
)
|
14
|
16
|
|
15
|
|
-// ChanServNotice sends the client a notice from ChanServ.
|
16
|
|
-func (rb *ResponseBuffer) ChanServNotice(text string) {
|
17
|
|
- rb.Add(nil, fmt.Sprintf("ChanServ!services@%s", rb.target.server.name), "NOTICE", rb.target.nick, text)
|
|
17
|
+const chanservHelp = `ChanServ lets you register and manage channels.
|
|
18
|
+
|
|
19
|
+To see in-depth help for a specific ChanServ command, try:
|
|
20
|
+ $b/CS HELP <command>$b
|
|
21
|
+
|
|
22
|
+Here are the commands you can use:
|
|
23
|
+%s`
|
|
24
|
+
|
|
25
|
+type csCommand struct {
|
|
26
|
+ capabs []string // oper capabs the given user has to have to access this command
|
|
27
|
+ handler func(server *Server, client *Client, command, params string, rb *ResponseBuffer)
|
|
28
|
+ help string
|
|
29
|
+ helpShort string
|
|
30
|
+ oper bool // true if the user has to be an oper to use this command
|
|
31
|
+}
|
|
32
|
+
|
|
33
|
+var (
|
|
34
|
+ chanservCommands = map[string]*csCommand{
|
|
35
|
+ "help": {
|
|
36
|
+ help: `Syntax: $bHELP [command]$b
|
|
37
|
+
|
|
38
|
+HELP returns information on the given command.`,
|
|
39
|
+ helpShort: `$bHELP$b shows in-depth information about commands.`,
|
|
40
|
+ },
|
|
41
|
+ "op": {
|
|
42
|
+ handler: csOpHandler,
|
|
43
|
+ help: `Syntax: $bOP #channel [nickname]$b
|
|
44
|
+
|
|
45
|
+OP makes the given nickname, or yourself, a channel admin. You can only use
|
|
46
|
+this command if you're the founder of the channel.`,
|
|
47
|
+ helpShort: `$bOP$b makes the given user (or yourself) a channel admin.`,
|
|
48
|
+ },
|
|
49
|
+ "register": {
|
|
50
|
+ handler: csRegisterHandler,
|
|
51
|
+ help: `Syntax: $bREGISTER #channel$b
|
|
52
|
+
|
|
53
|
+REGISTER lets you own the given channel. If you rejoin this channel, you'll be
|
|
54
|
+given admin privs on it. Modes set on the channel and the topic will also be
|
|
55
|
+remembered.`,
|
|
56
|
+ helpShort: `$bREGISTER$b lets you own a given channel.`,
|
|
57
|
+ },
|
|
58
|
+ }
|
|
59
|
+)
|
|
60
|
+
|
|
61
|
+// csNotice sends the client a notice from ChanServ
|
|
62
|
+func csNotice(rb *ResponseBuffer, text string) {
|
|
63
|
+ rb.Add(nil, "ChanServ", "NOTICE", rb.target.Nick(), text)
|
18
|
64
|
}
|
19
|
65
|
|
20
|
66
|
// chanservReceiveNotice handles NOTICEs that ChanServ receives.
|
|
@@ -24,68 +70,115 @@ func (server *Server) chanservNoticeHandler(client *Client, message string, rb *
|
24
|
70
|
|
25
|
71
|
// chanservReceiveNotice handles NOTICEs that ChanServ receives.
|
26
|
72
|
func (server *Server) chanservPrivmsgHandler(client *Client, message string, rb *ResponseBuffer) {
|
27
|
|
- var params []string
|
28
|
|
- for _, p := range strings.Split(message, " ") {
|
29
|
|
- if len(p) > 0 {
|
30
|
|
- params = append(params, p)
|
31
|
|
- }
|
|
73
|
+ commandName, params := utils.ExtractParam(message)
|
|
74
|
+ commandName = strings.ToLower(commandName)
|
|
75
|
+
|
|
76
|
+ commandInfo := chanservCommands[commandName]
|
|
77
|
+ if commandInfo == nil {
|
|
78
|
+ csNotice(rb, client.t("Unknown command. To see available commands, run /CS HELP"))
|
|
79
|
+ return
|
32
|
80
|
}
|
33
|
|
- if len(params) < 1 {
|
34
|
|
- rb.ChanServNotice(client.t("You need to run a command"))
|
35
|
|
- //TODO(dan): dump CS help here
|
|
81
|
+
|
|
82
|
+ if commandInfo.oper && !client.HasMode(modes.Operator) {
|
|
83
|
+ csNotice(rb, client.t("Command restricted"))
|
36
|
84
|
return
|
37
|
85
|
}
|
38
|
86
|
|
39
|
|
- command := strings.ToLower(params[0])
|
40
|
|
- server.logger.Debug("chanserv", fmt.Sprintf("Client %s ran command %s", client.nick, command))
|
|
87
|
+ if 0 < len(commandInfo.capabs) && !client.HasRoleCapabs(commandInfo.capabs...) {
|
|
88
|
+ csNotice(rb, client.t("Command restricted"))
|
|
89
|
+ return
|
|
90
|
+ }
|
41
|
91
|
|
42
|
|
- if command == "register" {
|
43
|
|
- if len(params) < 2 {
|
44
|
|
- rb.ChanServNotice(client.t("Syntax: REGISTER <channel>"))
|
45
|
|
- return
|
46
|
|
- }
|
|
92
|
+ // custom help handling here to prevent recursive init loop
|
|
93
|
+ if commandName == "help" {
|
|
94
|
+ csHelpHandler(server, client, commandName, params, rb)
|
|
95
|
+ return
|
|
96
|
+ }
|
47
|
97
|
|
48
|
|
- server.chanservRegisterHandler(client, params[1], rb)
|
49
|
|
- } else if command == "op" {
|
50
|
|
- if len(params) < 2 {
|
51
|
|
- rb.ChanServNotice(client.t("Syntax: OP <channel> [<nick>]"))
|
52
|
|
- return
|
53
|
|
- }
|
|
98
|
+ if commandInfo.handler == nil {
|
|
99
|
+ csNotice(rb, client.t("Command error. Please report this to the developers"))
|
|
100
|
+ return
|
|
101
|
+ }
|
|
102
|
+
|
|
103
|
+ server.logger.Debug("chanserv", fmt.Sprintf("Client %s ran command %s", client.Nick(), commandName))
|
|
104
|
+
|
|
105
|
+ commandInfo.handler(server, client, commandName, params, rb)
|
|
106
|
+}
|
54
|
107
|
|
55
|
|
- var clientToOp string
|
56
|
|
- if 2 < len(params) {
|
57
|
|
- clientToOp = params[2]
|
|
108
|
+func csHelpHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
|
|
109
|
+ csNotice(rb, ircfmt.Unescape(client.t("*** $bChanServ HELP$b ***")))
|
|
110
|
+
|
|
111
|
+ if params == "" {
|
|
112
|
+ // show general help
|
|
113
|
+ var shownHelpLines sort.StringSlice
|
|
114
|
+ for _, commandInfo := range chanservCommands {
|
|
115
|
+ // skip commands user can't access
|
|
116
|
+ if commandInfo.oper && !client.HasMode(modes.Operator) {
|
|
117
|
+ continue
|
|
118
|
+ }
|
|
119
|
+ if 0 < len(commandInfo.capabs) && !client.HasRoleCapabs(commandInfo.capabs...) {
|
|
120
|
+ continue
|
|
121
|
+ }
|
|
122
|
+
|
|
123
|
+ shownHelpLines = append(shownHelpLines, " "+client.t(commandInfo.helpShort))
|
58
|
124
|
}
|
59
|
125
|
|
60
|
|
- server.chanservOpHandler(client, params[1], clientToOp, rb)
|
|
126
|
+ // sort help lines
|
|
127
|
+ sort.Sort(shownHelpLines)
|
|
128
|
+
|
|
129
|
+ // assemble help text
|
|
130
|
+ assembledHelpLines := strings.Join(shownHelpLines, "\n")
|
|
131
|
+ fullHelp := ircfmt.Unescape(fmt.Sprintf(client.t(chanservHelp), assembledHelpLines))
|
|
132
|
+
|
|
133
|
+ // push out help text
|
|
134
|
+ for _, line := range strings.Split(fullHelp, "\n") {
|
|
135
|
+ csNotice(rb, line)
|
|
136
|
+ }
|
61
|
137
|
} else {
|
62
|
|
- rb.ChanServNotice(client.t("Sorry, I don't know that command"))
|
|
138
|
+ commandInfo := chanservCommands[strings.ToLower(strings.TrimSpace(params))]
|
|
139
|
+ if commandInfo == nil {
|
|
140
|
+ csNotice(rb, client.t("Unknown command. To see available commands, run /cs HELP"))
|
|
141
|
+ } else {
|
|
142
|
+ for _, line := range strings.Split(ircfmt.Unescape(client.t(commandInfo.help)), "\n") {
|
|
143
|
+ csNotice(rb, line)
|
|
144
|
+ }
|
|
145
|
+ }
|
63
|
146
|
}
|
|
147
|
+
|
|
148
|
+ csNotice(rb, ircfmt.Unescape(client.t("*** $bEnd of ChanServ HELP$b ***")))
|
64
|
149
|
}
|
65
|
150
|
|
66
|
|
-// chanservOpHandler handles the ChanServ OP subcommand.
|
67
|
|
-func (server *Server) chanservOpHandler(client *Client, channelName, clientToOp string, rb *ResponseBuffer) {
|
|
151
|
+func csOpHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
|
|
152
|
+ channelName, clientToOp := utils.ExtractParam(params)
|
|
153
|
+
|
|
154
|
+ if channelName == "" {
|
|
155
|
+ csNotice(rb, ircfmt.Unescape(client.t("Syntax: $bOP #channel [nickname]$b")))
|
|
156
|
+ return
|
|
157
|
+ }
|
|
158
|
+
|
|
159
|
+ clientToOp = strings.TrimSpace(clientToOp)
|
|
160
|
+
|
68
|
161
|
channelKey, err := CasefoldChannel(channelName)
|
69
|
162
|
if err != nil {
|
70
|
|
- rb.ChanServNotice(client.t("Channel name is not valid"))
|
|
163
|
+ csNotice(rb, client.t("Channel name is not valid"))
|
71
|
164
|
return
|
72
|
165
|
}
|
73
|
166
|
|
74
|
167
|
channelInfo := server.channels.Get(channelKey)
|
75
|
168
|
if channelInfo == nil {
|
76
|
|
- rb.ChanServNotice(client.t("Channel does not exist"))
|
|
169
|
+ csNotice(rb, client.t("Channel does not exist"))
|
77
|
170
|
return
|
78
|
171
|
}
|
79
|
172
|
|
80
|
173
|
clientAccount := client.Account()
|
81
|
174
|
|
82
|
175
|
if clientAccount == "" {
|
83
|
|
- rb.ChanServNotice(client.t("You must be logged in to op on a channel"))
|
|
176
|
+ csNotice(rb, client.t("You must be logged in to op on a channel"))
|
84
|
177
|
return
|
85
|
178
|
}
|
86
|
179
|
|
87
|
180
|
if clientAccount != channelInfo.Founder() {
|
88
|
|
- rb.ChanServNotice(client.t("You must be the channel founder to op"))
|
|
181
|
+ csNotice(rb, client.t("You must be the channel founder to op"))
|
89
|
182
|
return
|
90
|
183
|
}
|
91
|
184
|
|
|
@@ -94,7 +187,7 @@ func (server *Server) chanservOpHandler(client *Client, channelName, clientToOp
|
94
|
187
|
casefoldedNickname, err := CasefoldName(clientToOp)
|
95
|
188
|
target = server.clients.Get(casefoldedNickname)
|
96
|
189
|
if err != nil || target == nil {
|
97
|
|
- rb.ChanServNotice(client.t("Could not find given client"))
|
|
190
|
+ csNotice(rb, client.t("Could not find given client"))
|
98
|
191
|
return
|
99
|
192
|
}
|
100
|
193
|
} else {
|
|
@@ -116,47 +209,52 @@ func (server *Server) chanservOpHandler(client *Client, channelName, clientToOp
|
116
|
209
|
}
|
117
|
210
|
}
|
118
|
211
|
|
119
|
|
- rb.ChanServNotice(fmt.Sprintf(client.t("Successfully op'd in channel %s"), channelName))
|
|
212
|
+ csNotice(rb, fmt.Sprintf(client.t("Successfully op'd in channel %s"), channelName))
|
120
|
213
|
|
121
|
214
|
server.logger.Info("chanserv", fmt.Sprintf("Client %s op'd [%s] in channel %s", client.nick, clientToOp, channelName))
|
122
|
215
|
server.snomasks.Send(sno.LocalChannels, fmt.Sprintf(ircfmt.Unescape("Client $c[grey][$r%s$c[grey]] CS OP'd $c[grey][$r%s$c[grey]] in channel $c[grey][$r%s$c[grey]]"), client.nickMaskString, clientToOp, channelName))
|
123
|
216
|
}
|
124
|
217
|
|
125
|
|
-// chanservRegisterHandler handles the ChanServ REGISTER subcommand.
|
126
|
|
-func (server *Server) chanservRegisterHandler(client *Client, channelName string, rb *ResponseBuffer) {
|
|
218
|
+func csRegisterHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
|
127
|
219
|
if !server.channelRegistrationEnabled {
|
128
|
|
- rb.ChanServNotice(client.t("Channel registration is not enabled"))
|
|
220
|
+ csNotice(rb, client.t("Channel registration is not enabled"))
|
|
221
|
+ return
|
|
222
|
+ }
|
|
223
|
+
|
|
224
|
+ channelName := strings.TrimSpace(params)
|
|
225
|
+ if channelName == "" {
|
|
226
|
+ csNotice(rb, ircfmt.Unescape(client.t("Syntax: $bREGISTER #channel$b")))
|
129
|
227
|
return
|
130
|
228
|
}
|
131
|
229
|
|
132
|
230
|
channelKey, err := CasefoldChannel(channelName)
|
133
|
231
|
if err != nil {
|
134
|
|
- rb.ChanServNotice(client.t("Channel name is not valid"))
|
|
232
|
+ csNotice(rb, client.t("Channel name is not valid"))
|
135
|
233
|
return
|
136
|
234
|
}
|
137
|
235
|
|
138
|
236
|
channelInfo := server.channels.Get(channelKey)
|
139
|
237
|
if channelInfo == nil || !channelInfo.ClientIsAtLeast(client, modes.ChannelOperator) {
|
140
|
|
- rb.ChanServNotice(client.t("You must be an oper on the channel to register it"))
|
|
238
|
+ csNotice(rb, client.t("You must be an oper on the channel to register it"))
|
141
|
239
|
return
|
142
|
240
|
}
|
143
|
241
|
|
144
|
242
|
if client.Account() == "" {
|
145
|
|
- rb.ChanServNotice(client.t("You must be logged in to register a channel"))
|
|
243
|
+ csNotice(rb, client.t("You must be logged in to register a channel"))
|
146
|
244
|
return
|
147
|
245
|
}
|
148
|
246
|
|
149
|
247
|
// this provides the synchronization that allows exactly one registration of the channel:
|
150
|
248
|
err = channelInfo.SetRegistered(client.Account())
|
151
|
249
|
if err != nil {
|
152
|
|
- rb.ChanServNotice(err.Error())
|
|
250
|
+ csNotice(rb, err.Error())
|
153
|
251
|
return
|
154
|
252
|
}
|
155
|
253
|
|
156
|
254
|
// registration was successful: make the database reflect it
|
157
|
255
|
go server.channelRegistry.StoreChannel(channelInfo, true)
|
158
|
256
|
|
159
|
|
- rb.ChanServNotice(fmt.Sprintf(client.t("Channel %s successfully registered"), channelName))
|
|
257
|
+ csNotice(rb, fmt.Sprintf(client.t("Channel %s successfully registered"), channelName))
|
160
|
258
|
|
161
|
259
|
server.logger.Info("chanserv", fmt.Sprintf("Client %s registered channel %s", client.nick, channelName))
|
162
|
260
|
server.snomasks.Send(sno.LocalChannels, fmt.Sprintf(ircfmt.Unescape("Channel registered $c[grey][$r%s$c[grey]] by $c[grey][$r%s$c[grey]]"), channelName, client.nickMaskString))
|