source: code/trunk/irc.go@ 228

Last change on this file since 228 was 193, checked in by contact, 5 years ago

Request server-time cap

If the server didn't populate the time tag, do it ourselves.

File size: 4.7 KB
RevLine 
[98]1package soju
[20]2
3import (
4 "fmt"
5 "strings"
[43]6
7 "gopkg.in/irc.v3"
[20]8)
9
10const (
[108]11 rpl_statsping = "246"
12 rpl_localusers = "265"
13 rpl_globalusers = "266"
[162]14 rpl_creationtime = "329"
[108]15 rpl_topicwhotime = "333"
16 err_invalidcapcmd = "410"
[20]17)
18
[139]19type userModes string
[20]20
[139]21func (ms userModes) Has(c byte) bool {
[20]22 return strings.IndexByte(string(ms), c) >= 0
23}
24
[139]25func (ms *userModes) Add(c byte) {
[20]26 if !ms.Has(c) {
[139]27 *ms += userModes(c)
[20]28 }
29}
30
[139]31func (ms *userModes) Del(c byte) {
[20]32 i := strings.IndexByte(string(*ms), c)
33 if i >= 0 {
34 *ms = (*ms)[:i] + (*ms)[i+1:]
35 }
36}
37
[139]38func (ms *userModes) Apply(s string) error {
[20]39 var plusMinus byte
40 for i := 0; i < len(s); i++ {
41 switch c := s[i]; c {
42 case '+', '-':
43 plusMinus = c
44 default:
45 switch plusMinus {
46 case '+':
47 ms.Add(c)
48 case '-':
49 ms.Del(c)
50 default:
51 return fmt.Errorf("malformed modestring %q: missing plus/minus", s)
52 }
53 }
54 }
55 return nil
56}
57
[139]58type channelModeType byte
59
60// standard channel mode types, as explained in https://modern.ircdocs.horse/#mode-message
61const (
62 // modes that add or remove an address to or from a list
63 modeTypeA channelModeType = iota
64 // modes that change a setting on a channel, and must always have a parameter
65 modeTypeB
66 // modes that change a setting on a channel, and must have a parameter when being set, and no parameter when being unset
67 modeTypeC
68 // modes that change a setting on a channel, and must not have a parameter
69 modeTypeD
70)
71
72var stdChannelModes = map[byte]channelModeType{
73 'b': modeTypeA, // ban list
74 'e': modeTypeA, // ban exception list
75 'I': modeTypeA, // invite exception list
76 'k': modeTypeB, // channel key
77 'l': modeTypeC, // channel user limit
78 'i': modeTypeD, // channel is invite-only
79 'm': modeTypeD, // channel is moderated
80 'n': modeTypeD, // channel has no external messages
81 's': modeTypeD, // channel is secret
82 't': modeTypeD, // channel has protected topic
83}
84
85type channelModes map[byte]string
86
87func (cm channelModes) Apply(modeTypes map[byte]channelModeType, modeStr string, arguments ...string) error {
88 nextArgument := 0
89 var plusMinus byte
90 for i := 0; i < len(modeStr); i++ {
91 mode := modeStr[i]
92 if mode == '+' || mode == '-' {
93 plusMinus = mode
94 continue
95 }
96 if plusMinus != '+' && plusMinus != '-' {
97 return fmt.Errorf("malformed modestring %q: missing plus/minus", modeStr)
98 }
99
100 mt, ok := modeTypes[mode]
101 if !ok {
102 continue
103 }
104 if mt == modeTypeB || (mt == modeTypeC && plusMinus == '+') {
105 if plusMinus == '+' {
106 var argument string
107 // some sentitive arguments (such as channel keys) can be omitted for privacy
108 // (this will only happen for RPL_CHANNELMODEIS, never for MODE messages)
109 if nextArgument < len(arguments) {
110 argument = arguments[nextArgument]
111 }
112 cm[mode] = argument
113 } else {
114 delete(cm, mode)
115 }
116 nextArgument++
117 } else if mt == modeTypeC || mt == modeTypeD {
118 if plusMinus == '+' {
119 cm[mode] = ""
120 } else {
121 delete(cm, mode)
122 }
123 }
124 }
125 return nil
126}
127
128func (cm channelModes) Format() (modeString string, parameters []string) {
129 var modesWithValues strings.Builder
130 var modesWithoutValues strings.Builder
131 parameters = make([]string, 0, 16)
132 for mode, value := range cm {
133 if value != "" {
134 modesWithValues.WriteString(string(mode))
135 parameters = append(parameters, value)
136 } else {
137 modesWithoutValues.WriteString(string(mode))
138 }
139 }
140 modeString = "+" + modesWithValues.String() + modesWithoutValues.String()
141 return
142}
143
144const stdChannelTypes = "#&+!"
145
[20]146type channelStatus byte
147
148const (
149 channelPublic channelStatus = '='
150 channelSecret channelStatus = '@'
151 channelPrivate channelStatus = '*'
152)
153
154func parseChannelStatus(s string) (channelStatus, error) {
155 if len(s) > 1 {
156 return 0, fmt.Errorf("invalid channel status %q: more than one character", s)
157 }
158 switch cs := channelStatus(s[0]); cs {
159 case channelPublic, channelSecret, channelPrivate:
160 return cs, nil
161 default:
162 return 0, fmt.Errorf("invalid channel status %q: unknown status", s)
163 }
164}
165
[139]166type membership struct {
167 Mode byte
168 Prefix byte
169}
[20]170
[139]171var stdMemberships = []membership{
172 {'q', '~'}, // founder
173 {'a', '&'}, // protected
174 {'o', '@'}, // operator
175 {'h', '%'}, // halfop
176 {'v', '+'}, // voice
177}
[20]178
[139]179func (m *membership) String() string {
180 if m == nil {
[128]181 return ""
182 }
[139]183 return string(m.Prefix)
[128]184}
185
[43]186func parseMessageParams(msg *irc.Message, out ...*string) error {
187 if len(msg.Params) < len(out) {
188 return newNeedMoreParamsError(msg.Command)
189 }
190 for i := range out {
191 if out[i] != nil {
192 *out[i] = msg.Params[i]
193 }
194 }
195 return nil
196}
[153]197
198type batch struct {
199 Type string
200 Params []string
201 Outer *batch // if not-nil, this batch is nested in Outer
[155]202 Label string
[153]203}
[193]204
205// The server-time layout, as defined in the IRCv3 spec.
206const serverTimeLayout = "2006-01-02T15:04:05.000Z"
Note: See TracBrowser for help on using the repository browser.