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