1// Copyright (c) 2012-2014 Jeremy Latt 2// Copyright (c) 2014-2015 Edmund Huber 3// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net> 4// released under the MIT license 5 6package modes 7 8import ( 9 "fmt" 10 "sort" 11 "strings" 12 13 "github.com/ergochat/ergo/irc/utils" 14) 15 16var ( 17 // SupportedUserModes are the user modes that we actually support (modifying). 18 SupportedUserModes = Modes{ 19 Bot, Invisible, Operator, RegisteredOnly, ServerNotice, UserRoleplaying, 20 UserNoCTCP, 21 } 22 23 // SupportedChannelModes are the channel modes that we support. 24 SupportedChannelModes = Modes{ 25 BanMask, ChanRoleplaying, ExceptMask, InviteMask, InviteOnly, Key, 26 Moderated, NoOutside, OpOnlyTopic, RegisteredOnly, RegisteredOnlySpeak, 27 Secret, UserLimit, NoCTCP, Auditorium, OpModerated, Forward, 28 } 29) 30 31// ModeOp is an operation performed with modes 32type ModeOp rune 33 34const ( 35 // Add is used when adding the given key. 36 Add ModeOp = '+' 37 // List is used when listing modes (for instance, listing the current bans on a channel). 38 List ModeOp = '=' 39 // Remove is used when taking away the given key. 40 Remove ModeOp = '-' 41) 42 43// Mode represents a user/channel/server mode 44type Mode rune 45 46func (mode Mode) String() string { 47 return string(mode) 48} 49 50// ModeChange is a single mode changing 51type ModeChange struct { 52 Mode Mode 53 Op ModeOp 54 Arg string 55} 56 57// ModeChanges are a collection of 'ModeChange's 58type ModeChanges []ModeChange 59 60func (changes ModeChanges) Strings() (result []string) { 61 if len(changes) == 0 { 62 return 63 } 64 65 var builder strings.Builder 66 67 op := changes[0].Op 68 builder.WriteRune(rune(op)) 69 70 for _, change := range changes { 71 if change.Op != op { 72 op = change.Op 73 builder.WriteRune(rune(op)) 74 } 75 builder.WriteRune(rune(change.Mode)) 76 } 77 78 result = append(result, builder.String()) 79 80 for _, change := range changes { 81 if change.Arg == "" { 82 continue 83 } 84 result = append(result, change.Arg) 85 } 86 return 87} 88 89// Modes is just a raw list of modes 90type Modes []Mode 91 92func (modes Modes) String() string { 93 var builder strings.Builder 94 for _, m := range modes { 95 builder.WriteRune(rune(m)) 96 } 97 return builder.String() 98} 99 100// User Modes 101const ( 102 Bot Mode = 'B' 103 Invisible Mode = 'i' 104 Operator Mode = 'o' 105 Restricted Mode = 'r' 106 RegisteredOnly Mode = 'R' 107 ServerNotice Mode = 's' 108 TLS Mode = 'Z' 109 UserNoCTCP Mode = 'T' 110 UserRoleplaying Mode = 'E' 111 WallOps Mode = 'w' 112) 113 114// Channel Modes 115const ( 116 Auditorium Mode = 'u' // flag 117 BanMask Mode = 'b' // arg 118 ChanRoleplaying Mode = 'E' // flag 119 ExceptMask Mode = 'e' // arg 120 InviteMask Mode = 'I' // arg 121 InviteOnly Mode = 'i' // flag 122 Key Mode = 'k' // flag arg 123 Moderated Mode = 'm' // flag 124 NoOutside Mode = 'n' // flag 125 OpOnlyTopic Mode = 't' // flag 126 // RegisteredOnly mode is reused here from umode definition 127 RegisteredOnlySpeak Mode = 'M' // flag 128 Secret Mode = 's' // flag 129 UserLimit Mode = 'l' // flag arg 130 NoCTCP Mode = 'C' // flag 131 OpModerated Mode = 'U' // flag 132 Forward Mode = 'f' // flag arg 133) 134 135var ( 136 ChannelFounder Mode = 'q' // arg 137 ChannelAdmin Mode = 'a' // arg 138 ChannelOperator Mode = 'o' // arg 139 Halfop Mode = 'h' // arg 140 Voice Mode = 'v' // arg 141 142 // ChannelUserModes holds the list of all modes that can be applied to a user in a channel, 143 // including Voice, in descending order of precedence 144 ChannelUserModes = Modes{ 145 ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice, 146 } 147 148 ChannelModePrefixes = map[Mode]string{ 149 ChannelFounder: "~", 150 ChannelAdmin: "&", 151 ChannelOperator: "@", 152 Halfop: "%", 153 Voice: "+", 154 } 155) 156 157// 158// channel membership prefixes 159// 160 161// SplitChannelMembershipPrefixes takes a target and returns the prefixes on it, then the name. 162func SplitChannelMembershipPrefixes(target string) (prefixes string, name string) { 163 name = target 164 for i := 0; i < len(name); i++ { 165 switch name[i] { 166 case '~', '&', '@', '%', '+': 167 prefixes = target[:i+1] 168 name = target[i+1:] 169 default: 170 return 171 } 172 } 173 174 return 175} 176 177// GetLowestChannelModePrefix returns the lowest channel prefix mode out of the given prefixes. 178func GetLowestChannelModePrefix(prefixes string) (lowest Mode) { 179 for i, mode := range ChannelUserModes { 180 if strings.Contains(prefixes, ChannelModePrefixes[mode]) { 181 lowest = ChannelUserModes[i] 182 } 183 } 184 return 185} 186 187// 188// commands 189// 190 191// ParseUserModeChanges returns the valid changes, and the list of unknown chars. 192func ParseUserModeChanges(params ...string) (ModeChanges, map[rune]bool) { 193 changes := make(ModeChanges, 0) 194 unknown := make(map[rune]bool) 195 196 op := List 197 198 if 0 < len(params) { 199 modeArg := params[0] 200 skipArgs := 1 201 202 for _, mode := range modeArg { 203 if mode == '-' || mode == '+' { 204 op = ModeOp(mode) 205 continue 206 } 207 change := ModeChange{ 208 Mode: Mode(mode), 209 Op: op, 210 } 211 212 // put arg into modechange if needed 213 switch Mode(mode) { 214 case ServerNotice: 215 // arg is optional for ServerNotice (we accept bare `-s`) 216 if len(params) > skipArgs { 217 change.Arg = params[skipArgs] 218 skipArgs++ 219 } 220 } 221 222 var isKnown bool 223 for _, supportedMode := range SupportedUserModes { 224 if rune(supportedMode) == mode { 225 isKnown = true 226 break 227 } 228 } 229 if !isKnown { 230 unknown[mode] = true 231 continue 232 } 233 234 changes = append(changes, change) 235 } 236 } 237 238 return changes, unknown 239} 240 241// ParseChannelModeChanges returns the valid changes, and the list of unknown chars. 242func ParseChannelModeChanges(params ...string) (ModeChanges, map[rune]bool) { 243 changes := make(ModeChanges, 0) 244 unknown := make(map[rune]bool) 245 246 op := List 247 248 if 0 < len(params) { 249 modeArg := params[0] 250 skipArgs := 1 251 252 for _, mode := range modeArg { 253 if mode == '-' || mode == '+' { 254 op = ModeOp(mode) 255 continue 256 } 257 change := ModeChange{ 258 Mode: Mode(mode), 259 Op: op, 260 } 261 262 // put arg into modechange if needed 263 switch Mode(mode) { 264 case BanMask, ExceptMask, InviteMask: 265 if len(params) > skipArgs { 266 change.Arg = params[skipArgs] 267 skipArgs++ 268 } else { 269 change.Op = List 270 } 271 case ChannelFounder, ChannelAdmin, ChannelOperator, Halfop, Voice: 272 if len(params) > skipArgs { 273 change.Arg = params[skipArgs] 274 skipArgs++ 275 } else { 276 continue 277 } 278 case UserLimit, Forward: 279 // don't require value when removing 280 if change.Op == Add { 281 if len(params) > skipArgs { 282 change.Arg = params[skipArgs] 283 skipArgs++ 284 } else { 285 continue 286 } 287 } 288 case Key: 289 // #874: +k is technically a type B mode, requiring a parameter 290 // both for add and remove. so attempt to consume a parameter, 291 // but allow remove (but not add) even if no parameter is available. 292 // however, the remove parameter should always display as "*", matching 293 // the freenode behavior. 294 if len(params) > skipArgs { 295 if change.Op == Add { 296 change.Arg = params[skipArgs] 297 } 298 skipArgs++ 299 } else if change.Op == Add { 300 continue 301 } 302 if change.Op == Remove { 303 change.Arg = "*" 304 } 305 } 306 307 var isKnown bool 308 for _, supportedMode := range SupportedChannelModes { 309 if rune(supportedMode) == mode { 310 isKnown = true 311 break 312 } 313 } 314 for _, supportedMode := range ChannelUserModes { 315 if rune(supportedMode) == mode { 316 isKnown = true 317 break 318 } 319 } 320 if !isKnown { 321 unknown[mode] = true 322 continue 323 } 324 325 changes = append(changes, change) 326 } 327 } 328 329 return changes, unknown 330} 331 332// ModeSet holds a set of modes. 333type ModeSet [2]uint32 334 335// valid modes go from 65 ('A') to 122 ('z'), making at most 58 possible values; 336// subtract 65 from the mode value and use that bit of the uint32 to represent it 337const ( 338 minMode = 65 // 'A' 339 maxMode = 122 // 'z' 340) 341 342// returns a pointer to a new ModeSet 343func NewModeSet() *ModeSet { 344 var set ModeSet 345 return &set 346} 347 348// test whether `mode` is set 349func (set *ModeSet) HasMode(mode Mode) bool { 350 if set == nil { 351 return false 352 } 353 354 return utils.BitsetGet(set[:], uint(mode)-minMode) 355} 356 357// set `mode` to be on or off, return whether the value actually changed 358func (set *ModeSet) SetMode(mode Mode, on bool) (applied bool) { 359 return utils.BitsetSet(set[:], uint(mode)-minMode, on) 360} 361 362// copy the contents of another modeset on top of this one 363func (set *ModeSet) Copy(other *ModeSet) { 364 utils.BitsetCopy(set[:], other[:]) 365} 366 367// return the modes in the set as a slice 368func (set *ModeSet) AllModes() (result []Mode) { 369 if set == nil { 370 return 371 } 372 373 var i Mode 374 for i = minMode; i <= maxMode; i++ { 375 if set.HasMode(i) { 376 result = append(result, i) 377 } 378 } 379 return 380} 381 382// String returns the modes in this set. 383func (set *ModeSet) String() (result string) { 384 if set == nil { 385 return 386 } 387 388 var buf strings.Builder 389 for _, mode := range set.AllModes() { 390 buf.WriteRune(rune(mode)) 391 } 392 return buf.String() 393} 394 395// Prefixes returns a list of prefixes for the given set of channel modes. 396func (set *ModeSet) Prefixes(isMultiPrefix bool) (prefixes string) { 397 if set == nil { 398 return 399 } 400 401 // add prefixes in order from highest to lowest privs 402 for _, mode := range ChannelUserModes { 403 if set.HasMode(mode) { 404 prefixes += ChannelModePrefixes[mode] 405 } 406 } 407 408 if !isMultiPrefix && len(prefixes) > 1 { 409 prefixes = string(prefixes[0]) 410 } 411 412 return prefixes 413} 414 415// HighestChannelUserMode returns the most privileged channel-user mode 416// (e.g., ChannelFounder, Halfop, Voice) present in the ModeSet. 417// If no such modes are present, or `set` is nil, returns the zero mode. 418func (set *ModeSet) HighestChannelUserMode() (result Mode) { 419 for _, mode := range ChannelUserModes { 420 if set.HasMode(mode) { 421 return mode 422 } 423 } 424 return 425} 426 427type ByCodepoint Modes 428 429func (a ByCodepoint) Len() int { return len(a) } 430func (a ByCodepoint) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 431func (a ByCodepoint) Less(i, j int) bool { return a[i] < a[j] } 432 433func RplMyInfo() (param1, param2, param3 string) { 434 userModes := make(Modes, len(SupportedUserModes), len(SupportedUserModes)+1) 435 copy(userModes, SupportedUserModes) 436 // TLS is not in SupportedUserModes because it can't be modified 437 userModes = append(userModes, TLS) 438 sort.Sort(ByCodepoint(userModes)) 439 440 channelModes := make(Modes, len(SupportedChannelModes)+len(ChannelUserModes)) 441 copy(channelModes, SupportedChannelModes) 442 copy(channelModes[len(SupportedChannelModes):], ChannelUserModes) 443 sort.Sort(ByCodepoint(channelModes)) 444 445 // XXX enumerate these by hand, i can't see any way to DRY this 446 channelParametrizedModes := Modes{BanMask, ExceptMask, InviteMask, Key, UserLimit, Forward} 447 channelParametrizedModes = append(channelParametrizedModes, ChannelUserModes...) 448 sort.Sort(ByCodepoint(channelParametrizedModes)) 449 450 return userModes.String(), channelModes.String(), channelParametrizedModes.String() 451} 452 453func ChanmodesToken() (result string) { 454 // https://modern.ircdocs.horse#chanmodes-parameter 455 // type A: listable modes with parameters 456 A := Modes{BanMask, ExceptMask, InviteMask} 457 // type B: modes with parameters 458 B := Modes{Key} 459 // type C: modes that take a parameter only when set, never when unset 460 C := Modes{UserLimit, Forward} 461 // type D: modes without parameters 462 D := Modes{InviteOnly, Moderated, NoOutside, OpOnlyTopic, ChanRoleplaying, Secret, NoCTCP, RegisteredOnly, RegisteredOnlySpeak, Auditorium, OpModerated} 463 464 sort.Sort(ByCodepoint(A)) 465 sort.Sort(ByCodepoint(B)) 466 sort.Sort(ByCodepoint(C)) 467 sort.Sort(ByCodepoint(D)) 468 469 return fmt.Sprintf("%s,%s,%s,%s", A.String(), B.String(), C.String(), D.String()) 470} 471