source: code/trunk/service.go@ 206

Last change on this file since 206 was 202, checked in by contact, 5 years ago

Add "network delete" service command

And add all the infrastructure required to stop and delete networks.

References: https://todo.sr.ht/~emersion/soju/17

File size: 5.4 KB
Line 
1package soju
2
3import (
4 "flag"
5 "fmt"
6 "io/ioutil"
7 "strings"
8
9 "github.com/google/shlex"
10 "gopkg.in/irc.v3"
11)
12
13const serviceNick = "BouncerServ"
14
15type serviceCommandSet map[string]*serviceCommand
16
17type serviceCommand struct {
18 usage string
19 desc string
20 handle func(dc *downstreamConn, params []string) error
21 children serviceCommandSet
22}
23
24func sendServicePRIVMSG(dc *downstreamConn, text string) {
25 dc.SendMessage(&irc.Message{
26 Prefix: &irc.Prefix{Name: serviceNick},
27 Command: "PRIVMSG",
28 Params: []string{dc.nick, text},
29 })
30}
31
32func handleServicePRIVMSG(dc *downstreamConn, text string) {
33 words, err := shlex.Split(text)
34 if err != nil {
35 sendServicePRIVMSG(dc, fmt.Sprintf("error: failed to parse command: %v", err))
36 return
37 }
38
39 cmd, params, err := serviceCommands.Get(words)
40 if err != nil {
41 sendServicePRIVMSG(dc, fmt.Sprintf(`error: %v (type "help" for a list of commands)`, err))
42 return
43 }
44
45 if err := cmd.handle(dc, params); err != nil {
46 sendServicePRIVMSG(dc, fmt.Sprintf("error: %v", err))
47 }
48}
49
50func (cmds serviceCommandSet) Get(params []string) (*serviceCommand, []string, error) {
51 if len(params) == 0 {
52 return nil, nil, fmt.Errorf("no command specified")
53 }
54
55 name := params[0]
56 params = params[1:]
57
58 cmd, ok := cmds[name]
59 if !ok {
60 for k := range cmds {
61 if !strings.HasPrefix(k, name) {
62 continue
63 }
64 if cmd != nil {
65 return nil, params, fmt.Errorf("command %q is ambiguous", name)
66 }
67 cmd = cmds[k]
68 }
69 }
70 if cmd == nil {
71 return nil, params, fmt.Errorf("command %q not found", name)
72 }
73
74 if len(params) == 0 || len(cmd.children) == 0 {
75 return cmd, params, nil
76 }
77 return cmd.children.Get(params)
78}
79
80var serviceCommands serviceCommandSet
81
82func init() {
83 serviceCommands = serviceCommandSet{
84 "help": {
85 usage: "[command]",
86 desc: "print help message",
87 handle: handleServiceHelp,
88 },
89 "network": {
90 children: serviceCommandSet{
91 "create": {
92 usage: "-addr <addr> [-name name] [-username username] [-pass pass] [-realname realname] [-nick nick]",
93 desc: "add a new network",
94 handle: handleServiceCreateNetwork,
95 },
96 "status": {
97 desc: "show a list of saved networks and their current status",
98 handle: handleServiceNetworkStatus,
99 },
100 "delete": {
101 usage: "<name>",
102 desc: "delete a network",
103 handle: handleServiceNetworkDelete,
104 },
105 },
106 },
107 }
108}
109
110func appendServiceCommandSetHelp(cmds serviceCommandSet, prefix []string, l *[]string) {
111 for name, cmd := range cmds {
112 words := append(prefix, name)
113 if len(cmd.children) == 0 {
114 s := strings.Join(words, " ")
115 *l = append(*l, s)
116 } else {
117 appendServiceCommandSetHelp(cmd.children, words, l)
118 }
119 }
120}
121
122func handleServiceHelp(dc *downstreamConn, params []string) error {
123 if len(params) > 0 {
124 cmd, rest, err := serviceCommands.Get(params)
125 if err != nil {
126 return err
127 }
128 words := params[:len(params)-len(rest)]
129
130 if len(cmd.children) > 0 {
131 var l []string
132 appendServiceCommandSetHelp(cmd.children, words, &l)
133 sendServicePRIVMSG(dc, "available commands: "+strings.Join(l, ", "))
134 } else {
135 text := strings.Join(words, " ")
136 if cmd.usage != "" {
137 text += " " + cmd.usage
138 }
139 text += ": " + cmd.desc
140
141 sendServicePRIVMSG(dc, text)
142 }
143 } else {
144 var l []string
145 appendServiceCommandSetHelp(serviceCommands, nil, &l)
146 sendServicePRIVMSG(dc, "available commands: "+strings.Join(l, ", "))
147 }
148 return nil
149}
150
151func newFlagSet() *flag.FlagSet {
152 fs := flag.NewFlagSet("", flag.ContinueOnError)
153 fs.SetOutput(ioutil.Discard)
154 return fs
155}
156
157func handleServiceCreateNetwork(dc *downstreamConn, params []string) error {
158 fs := newFlagSet()
159 addr := fs.String("addr", "", "")
160 name := fs.String("name", "", "")
161 username := fs.String("username", "", "")
162 pass := fs.String("pass", "", "")
163 realname := fs.String("realname", "", "")
164 nick := fs.String("nick", "", "")
165
166 if err := fs.Parse(params); err != nil {
167 return err
168 }
169 if *addr == "" {
170 return fmt.Errorf("flag -addr is required")
171 }
172
173 if *nick == "" {
174 *nick = dc.nick
175 }
176
177 var err error
178 network, err := dc.user.createNetwork(&Network{
179 Addr: *addr,
180 Name: *name,
181 Username: *username,
182 Pass: *pass,
183 Realname: *realname,
184 Nick: *nick,
185 })
186 if err != nil {
187 return fmt.Errorf("could not create network: %v", err)
188 }
189
190 sendServicePRIVMSG(dc, fmt.Sprintf("created network %q", network.GetName()))
191 return nil
192}
193
194func handleServiceNetworkStatus(dc *downstreamConn, params []string) error {
195 dc.user.forEachNetwork(func(net *network) {
196 var statuses []string
197 var details string
198 if uc := net.upstream(); uc != nil {
199 statuses = append(statuses, "connected as "+uc.nick)
200 details = fmt.Sprintf("%v channels", len(uc.channels))
201 } else {
202 statuses = append(statuses, "disconnected")
203 }
204
205 if net == dc.network {
206 statuses = append(statuses, "current")
207 }
208
209 s := fmt.Sprintf("%v (%v) [%v]", net.GetName(), net.Addr, strings.Join(statuses, ", "))
210 if details != "" {
211 s += ": " + details
212 }
213 sendServicePRIVMSG(dc, s)
214 })
215 return nil
216}
217
218func handleServiceNetworkDelete(dc *downstreamConn, params []string) error {
219 if len(params) != 1 {
220 return fmt.Errorf("expected exactly one argument")
221 }
222
223 net := dc.user.getNetwork(params[0])
224 if net == nil {
225 return fmt.Errorf("unknown network %q", params[0])
226 }
227
228 if err := dc.user.deleteNetwork(net.ID); err != nil {
229 return err
230 }
231
232 sendServicePRIVMSG(dc, fmt.Sprintf("deleted network %q", net.GetName()))
233 return nil
234}
Note: See TracBrowser for help on using the repository browser.