]> Cypherpunks.ru repositories - goircd.git/blob - room.go
Many fixes and additions
[goircd.git] / room.go
1 /*
2 goircd -- minimalistic simple Internet Relay Chat (IRC) server
3 Copyright (C) 2014-2020 Sergey Matveev <stargrave@stargrave.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, version 3 of the License.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 package main
19
20 import (
21         "fmt"
22         "log"
23         "regexp"
24         "sort"
25         "strings"
26         "sync"
27 )
28
29 type Room struct {
30         name    string
31         topic   string
32         key     string
33         members map[*Client]struct{}
34         events  chan ClientEvent
35         sync.RWMutex
36 }
37
38 var (
39         RERoom = regexp.MustCompile("^#[^\x00\x07\x0a\x0d ,:/]{1,200}$")
40
41         rooms     map[string]*Room = make(map[string]*Room)
42         roomsLock sync.RWMutex
43         roomsWG   sync.WaitGroup
44 )
45
46 func (r *Room) SendTopic(c *Client) {
47         t := r.topic
48         if t == "" {
49                 c.ReplyNicknamed("331", r.name, "No topic is set")
50         } else {
51                 c.ReplyNicknamed("332", r.name, t)
52         }
53 }
54
55 func (r *Room) SendNames(c *Client) {
56         allowed := false
57         if r.key == "" {
58                 allowed = true
59         } else if _, isMember := r.members[c]; isMember {
60                 allowed = true
61         }
62         if !allowed {
63                 c.ReplyNicknamed("475", r.name, "Cannot join channel (+k)")
64                 return
65         }
66         r.RLock()
67         nicknames := make([]string, 0, len(r.members))
68         for member := range r.members {
69                 nicknames = append(nicknames, member.nickname)
70         }
71         r.RUnlock()
72         sort.Strings(nicknames)
73         maxLen := 512 - len(*hostname) - 2 - 2
74
75 MoreNicknames:
76         lenAll := 0
77         lenName := 0
78         for i, n := range nicknames {
79                 lenName = len(n) + 1
80                 if lenAll+lenName >= maxLen {
81                         c.ReplyNicknamed("353", "=", r.name, strings.Join(nicknames[:i-1], " "))
82                         nicknames = nicknames[i:]
83                         goto MoreNicknames
84                 }
85                 lenAll += lenName
86         }
87         if len(nicknames) > 0 {
88                 c.ReplyNicknamed("353", "=", r.name, strings.Join(nicknames, " "))
89         }
90         c.ReplyNicknamed("366", r.name, "End of NAMES list")
91 }
92
93 func (r *Room) Broadcast(msg string, excludes ...*Client) {
94         var exclude *Client
95         if len(excludes) > 0 {
96                 exclude = excludes[0]
97         }
98         r.RLock()
99         for member := range r.members {
100                 if member == exclude {
101                         continue
102                 }
103                 member.Msg(msg)
104         }
105         r.RUnlock()
106 }
107
108 func (r *Room) StateSave() {
109         stateSink <- StateEvent{r.name, r.topic, r.key}
110 }
111
112 func (r *Room) Processor(events <-chan ClientEvent) {
113         for e := range events {
114                 c := e.client
115                 switch e.eventType {
116                 case EventTerm:
117                         roomsWG.Done()
118                         return
119                 case EventNew:
120                         r.Lock()
121                         r.members[c] = struct{}{}
122                         r.Unlock()
123                         if *verbose {
124                                 log.Println(c, "joined", r.name)
125                         }
126                         r.SendTopic(c)
127                         r.Broadcast(fmt.Sprintf(":%s JOIN %s", c, r.name))
128                         logSink <- LogEvent{r.name, c.nickname, "joined", true}
129                         r.SendNames(c)
130                 case EventDel:
131                         if _, subscribed := r.members[c]; !subscribed {
132                                 c.ReplyNicknamed("442", r.name, "You are not on that channel")
133                                 continue
134                         }
135                         msg := fmt.Sprintf(":%s PART %s :%s", c, r.name, c.nickname)
136                         r.Broadcast(msg)
137                         r.Lock()
138                         delete(r.members, c)
139                         r.Unlock()
140                         logSink <- LogEvent{r.name, c.nickname, "left", true}
141                         if *verbose {
142                                 log.Println(c, "left", r.name)
143                         }
144                 case EventTopic:
145                         if _, subscribed := r.members[c]; !subscribed {
146                                 c.ReplyParts("442", r.name, "You are not on that channel")
147                                 continue
148                         }
149                         if e.text == "" {
150                                 r.SendTopic(c)
151                                 continue
152                         }
153                         topic := strings.TrimLeft(e.text, ":")
154                         r.topic = topic
155                         msg := fmt.Sprintf(":%s TOPIC %s :%s", c, r.name, r.topic)
156                         r.Broadcast(msg)
157                         logSink <- LogEvent{r.name, c.nickname, "set topic to " + r.topic, true}
158                         r.StateSave()
159                 case EventWho:
160                         r.RLock()
161                         for m := range r.members {
162                                 c.ReplyNicknamed(
163                                         "352",
164                                         r.name,
165                                         m.username,
166                                         m.Host(),
167                                         *hostname,
168                                         m.nickname,
169                                         "H",
170                                         "0 "+m.realname,
171                                 )
172                         }
173                         c.ReplyNicknamed("315", r.name, "End of /WHO list")
174                         r.RUnlock()
175                 case EventMode:
176                         if e.text == "" {
177                                 mode := "+n"
178                                 if r.key != "" {
179                                         mode = mode + "k"
180                                 }
181                                 c.Msg(fmt.Sprintf("324 %s %s %s", c.nickname, r.name, mode))
182                                 continue
183                         }
184                         if strings.HasPrefix(e.text, "b") {
185                                 c.ReplyNicknamed("368", r.name, "End of channel ban list")
186                                 continue
187                         }
188                         if strings.HasPrefix(e.text, "-k") || strings.HasPrefix(e.text, "+k") {
189                                 if _, subscribed := r.members[c]; !subscribed {
190                                         c.ReplyParts("442", r.name, "You are not on that channel")
191                                         continue
192                                 }
193                         } else {
194                                 c.ReplyNicknamed("472", e.text, "Unknown MODE flag")
195                                 continue
196                         }
197                         var msg string
198                         var msgLog string
199                         if strings.HasPrefix(e.text, "+k") {
200                                 cols := strings.Split(e.text, " ")
201                                 if len(cols) == 1 {
202                                         c.ReplyNotEnoughParameters("MODE")
203                                         continue
204                                 }
205                                 r.key = cols[1]
206                                 msg = fmt.Sprintf(":%s MODE %s +k %s", c, r.name, r.key)
207                                 msgLog = "set channel key"
208                         } else {
209                                 r.key = ""
210                                 msg = fmt.Sprintf(":%s MODE %s -k", c, r.name)
211                                 msgLog = "removed channel key"
212                         }
213                         r.Broadcast(msg)
214                         logSink <- LogEvent{r.name, c.nickname, msgLog, true}
215                         r.StateSave()
216                 case EventMsg:
217                         sep := strings.Index(e.text, " ")
218                         r.Broadcast(fmt.Sprintf(
219                                 ":%s %s %s :%s", c, e.text[:sep], r.name, e.text[sep+1:],
220                         ), c)
221                         logSink <- LogEvent{r.name, c.nickname, e.text[sep+1:], false}
222                 }
223         }
224 }
225
226 func RoomRegister(name string) *Room {
227         r := &Room{
228                 name:    name,
229                 members: make(map[*Client]struct{}),
230                 events:  make(chan ClientEvent),
231         }
232         roomsLock.Lock()
233         roomsWG.Add(1)
234         rooms[name] = r
235         roomsLock.Unlock()
236         go r.Processor(r.events)
237         return r
238 }