source: code/trunk/service.go@ 302

Last change on this file since 302 was 279, checked in by contact, 5 years ago

Remove network.upstream

This is an artifact from when we used locks. No need for this anymore.

File size: 7.3 KB
Line 
1package soju
2
3import (
4 "flag"
5 "fmt"
6 "io/ioutil"
7 "strings"
8
9 "github.com/google/shlex"
10 "golang.org/x/crypto/bcrypt"
11 "gopkg.in/irc.v3"
12)
13
14const serviceNick = "BouncerServ"
15
16var servicePrefix = &irc.Prefix{
17 Name: serviceNick,
18 User: serviceNick,
19 Host: serviceNick,
20}
21
22type serviceCommandSet map[string]*serviceCommand
23
24type serviceCommand struct {
25 usage string
26 desc string
27 handle func(dc *downstreamConn, params []string) error
28 children serviceCommandSet
29}
30
31func sendServiceNOTICE(dc *downstreamConn, text string) {
32 dc.SendMessage(&irc.Message{
33 Prefix: servicePrefix,
34 Command: "NOTICE",
35 Params: []string{dc.nick, text},
36 })
37}
38
39func sendServicePRIVMSG(dc *downstreamConn, text string) {
40 dc.SendMessage(&irc.Message{
41 Prefix: servicePrefix,
42 Command: "PRIVMSG",
43 Params: []string{dc.nick, text},
44 })
45}
46
47func handleServicePRIVMSG(dc *downstreamConn, text string) {
48 words, err := shlex.Split(text)
49 if err != nil {
50 sendServicePRIVMSG(dc, fmt.Sprintf("error: failed to parse command: %v", err))
51 return
52 }
53
54 cmd, params, err := serviceCommands.Get(words)
55 if err != nil {
56 sendServicePRIVMSG(dc, fmt.Sprintf(`error: %v (type "help" for a list of commands)`, err))
57 return
58 }
59
60 if err := cmd.handle(dc, params); err != nil {
61 sendServicePRIVMSG(dc, fmt.Sprintf("error: %v", err))
62 }
63}
64
65func (cmds serviceCommandSet) Get(params []string) (*serviceCommand, []string, error) {
66 if len(params) == 0 {
67 return nil, nil, fmt.Errorf("no command specified")
68 }
69
70 name := params[0]
71 params = params[1:]
72
73 cmd, ok := cmds[name]
74 if !ok {
75 for k := range cmds {
76 if !strings.HasPrefix(k, name) {
77 continue
78 }
79 if cmd != nil {
80 return nil, params, fmt.Errorf("command %q is ambiguous", name)
81 }
82 cmd = cmds[k]
83 }
84 }
85 if cmd == nil {
86 return nil, params, fmt.Errorf("command %q not found", name)
87 }
88
89 if len(params) == 0 || len(cmd.children) == 0 {
90 return cmd, params, nil
91 }
92 return cmd.children.Get(params)
93}
94
95var serviceCommands serviceCommandSet
96
97func init() {
98 serviceCommands = serviceCommandSet{
99 "help": {
100 usage: "[command]",
101 desc: "print help message",
102 handle: handleServiceHelp,
103 },
104 "network": {
105 children: serviceCommandSet{
106 "create": {
107 usage: "-addr <addr> [-name name] [-username username] [-pass pass] [-realname realname] [-nick nick] [[-connect-command command] ...]",
108 desc: "add a new network",
109 handle: handleServiceCreateNetwork,
110 },
111 "status": {
112 desc: "show a list of saved networks and their current status",
113 handle: handleServiceNetworkStatus,
114 },
115 "delete": {
116 usage: "<name>",
117 desc: "delete a network",
118 handle: handleServiceNetworkDelete,
119 },
120 },
121 },
122 "change-password": {
123 usage: "<new password>",
124 desc: "change your password",
125 handle: handlePasswordChange,
126 },
127 }
128}
129
130func appendServiceCommandSetHelp(cmds serviceCommandSet, prefix []string, l *[]string) {
131 for name, cmd := range cmds {
132 words := append(prefix, name)
133 if len(cmd.children) == 0 {
134 s := strings.Join(words, " ")
135 *l = append(*l, s)
136 } else {
137 appendServiceCommandSetHelp(cmd.children, words, l)
138 }
139 }
140}
141
142func handleServiceHelp(dc *downstreamConn, params []string) error {
143 if len(params) > 0 {
144 cmd, rest, err := serviceCommands.Get(params)
145 if err != nil {
146 return err
147 }
148 words := params[:len(params)-len(rest)]
149
150 if len(cmd.children) > 0 {
151 var l []string
152 appendServiceCommandSetHelp(cmd.children, words, &l)
153 sendServicePRIVMSG(dc, "available commands: "+strings.Join(l, ", "))
154 } else {
155 text := strings.Join(words, " ")
156 if cmd.usage != "" {
157 text += " " + cmd.usage
158 }
159 text += ": " + cmd.desc
160
161 sendServicePRIVMSG(dc, text)
162 }
163 } else {
164 var l []string
165 appendServiceCommandSetHelp(serviceCommands, nil, &l)
166 sendServicePRIVMSG(dc, "available commands: "+strings.Join(l, ", "))
167 }
168 return nil
169}
170
171func newFlagSet() *flag.FlagSet {
172 fs := flag.NewFlagSet("", flag.ContinueOnError)
173 fs.SetOutput(ioutil.Discard)
174 return fs
175}
176
177type stringSliceVar []string
178
179func (v *stringSliceVar) String() string {
180 return fmt.Sprint([]string(*v))
181}
182
183func (v *stringSliceVar) Set(s string) error {
184 *v = append(*v, s)
185 return nil
186}
187
188func handleServiceCreateNetwork(dc *downstreamConn, params []string) error {
189 fs := newFlagSet()
190 addr := fs.String("addr", "", "")
191 name := fs.String("name", "", "")
192 username := fs.String("username", "", "")
193 pass := fs.String("pass", "", "")
194 realname := fs.String("realname", "", "")
195 nick := fs.String("nick", "", "")
196 var connectCommands stringSliceVar
197 fs.Var(&connectCommands, "connect-command", "")
198
199 if err := fs.Parse(params); err != nil {
200 return err
201 }
202 if *addr == "" {
203 return fmt.Errorf("flag -addr is required")
204 }
205
206 if addrParts := strings.SplitN(*addr, "://", 2); len(addrParts) == 2 {
207 scheme := addrParts[0]
208 switch scheme {
209 case "ircs", "irc+insecure":
210 default:
211 return fmt.Errorf("unknown scheme %q (supported schemes: ircs, irc+insecure)", scheme)
212 }
213 }
214
215 for _, command := range connectCommands {
216 _, err := irc.ParseMessage(command)
217 if err != nil {
218 return fmt.Errorf("flag -connect-command must be a valid raw irc command string: %q: %v", command, err)
219 }
220 }
221
222 if *nick == "" {
223 *nick = dc.nick
224 }
225
226 var err error
227 network, err := dc.user.createNetwork(&Network{
228 Addr: *addr,
229 Name: *name,
230 Username: *username,
231 Pass: *pass,
232 Realname: *realname,
233 Nick: *nick,
234 ConnectCommands: connectCommands,
235 })
236 if err != nil {
237 return fmt.Errorf("could not create network: %v", err)
238 }
239
240 sendServicePRIVMSG(dc, fmt.Sprintf("created network %q", network.GetName()))
241 return nil
242}
243
244func handleServiceNetworkStatus(dc *downstreamConn, params []string) error {
245 dc.user.forEachNetwork(func(net *network) {
246 var statuses []string
247 var details string
248 if uc := net.conn; uc != nil {
249 if dc.nick != uc.nick {
250 statuses = append(statuses, "connected as "+uc.nick)
251 } else {
252 statuses = append(statuses, "connected")
253 }
254 details = fmt.Sprintf("%v channels", len(uc.channels))
255 } else {
256 statuses = append(statuses, "disconnected")
257 if net.lastError != nil {
258 details = net.lastError.Error()
259 }
260 }
261
262 if net == dc.network {
263 statuses = append(statuses, "current")
264 }
265
266 name := net.GetName()
267 if name != net.Addr {
268 name = fmt.Sprintf("%v (%v)", name, net.Addr)
269 }
270
271 s := fmt.Sprintf("%v [%v]", name, strings.Join(statuses, ", "))
272 if details != "" {
273 s += ": " + details
274 }
275 sendServicePRIVMSG(dc, s)
276 })
277 return nil
278}
279
280func handleServiceNetworkDelete(dc *downstreamConn, params []string) error {
281 if len(params) != 1 {
282 return fmt.Errorf("expected exactly one argument")
283 }
284
285 net := dc.user.getNetwork(params[0])
286 if net == nil {
287 return fmt.Errorf("unknown network %q", params[0])
288 }
289
290 if err := dc.user.deleteNetwork(net.ID); err != nil {
291 return err
292 }
293
294 sendServicePRIVMSG(dc, fmt.Sprintf("deleted network %q", net.GetName()))
295 return nil
296}
297
298func handlePasswordChange(dc *downstreamConn, params []string) error {
299 if len(params) != 1 {
300 return fmt.Errorf("expected exactly one argument")
301 }
302
303 hashed, err := bcrypt.GenerateFromPassword([]byte(params[0]), bcrypt.DefaultCost)
304 if err != nil {
305 return fmt.Errorf("failed to hash password: %v", err)
306 }
307 if err := dc.user.updatePassword(string(hashed)); err != nil {
308 return err
309 }
310
311 sendServicePRIVMSG(dc, "password updated")
312 return nil
313}
Note: See TracBrowser for help on using the repository browser.