]> Cypherpunks.ru repositories - goircd.git/blob - room.go
Forbid any later GNU GPL versions autousage
[goircd.git] / room.go
1 /*
2 goircd -- minimalistic simple Internet Relay Chat (IRC) server
3 Copyright (C) 2014-2018 | wn 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 var (
30         RERoom = regexp.MustCompile("^#[^\x00\x07\x0a\x0d ,:/]{1,200}$")
31 )
32
33 // Sanitize room's name. It can consist of 1 to 50 ASCII symbols
34 // with some exclusions. All room names will have "#" prefix.
35 func RoomNameValid(name string) bool {
36         return RERoom.MatchString(name)
37 }
38
39 type Room struct {
40         name    *string
41         topic   *string
42         key     *string
43         members map[*Client]struct{}
44         sync.RWMutex
45 }
46
47 func (room *Room) String() (name string) {
48         room.RLock()
49         name = *room.name
50         room.RUnlock()
51         return
52 }
53
54 func NewRoom(name string) *Room {
55         topic := ""
56         key := ""
57         return &Room{
58                 name:    &name,
59                 topic:   &topic,
60                 key:     &key,
61                 members: make(map[*Client]struct{}),
62         }
63 }
64
65 func (room *Room) SendTopic(client *Client) {
66         room.RLock()
67         if *room.topic == "" {
68                 client.ReplyNicknamed("331", room.String(), "No topic is set")
69         } else {
70                 client.ReplyNicknamed("332", room.String(), *room.topic)
71         }
72         room.RUnlock()
73 }
74
75 // Send message to all room's subscribers, possibly excluding someone.
76 func (room *Room) Broadcast(msg string, clientToIgnore ...*Client) {
77         room.RLock()
78         for member := range room.members {
79                 if (len(clientToIgnore) > 0) && member == clientToIgnore[0] {
80                         continue
81                 }
82                 member.Msg(msg)
83         }
84         room.RUnlock()
85 }
86
87 func (room *Room) StateSave() {
88         room.RLock()
89         stateSink <- StateEvent{room.String(), *room.topic, *room.key}
90         room.RUnlock()
91 }
92
93 func (room *Room) Processor(events <-chan ClientEvent) {
94         var client *Client
95         for event := range events {
96                 client = event.client
97                 switch event.eventType {
98                 case EventTerm:
99                         roomsGroup.Done()
100                         return
101                 case EventNew:
102                         room.Lock()
103                         room.members[client] = struct{}{}
104                         if *verbose {
105                                 log.Println(client, "joined", room.name)
106                         }
107                         room.Unlock()
108                         room.SendTopic(client)
109                         room.Broadcast(fmt.Sprintf(":%s JOIN %s", client, room.String()))
110                         logSink <- LogEvent{room.String(), *client.nickname, "joined", true}
111                         nicknames := make([]string, 0)
112                         room.RLock()
113                         for member := range room.members {
114                                 nicknames = append(nicknames, *member.nickname)
115                         }
116                         room.RUnlock()
117                         sort.Strings(nicknames)
118                         client.ReplyNicknamed("353", "=", room.String(), strings.Join(nicknames, " "))
119                         client.ReplyNicknamed("366", room.String(), "End of NAMES list")
120                 case EventDel:
121                         room.RLock()
122                         if _, subscribed := room.members[client]; !subscribed {
123                                 client.ReplyNicknamed("442", room.String(), "You are not on that channel")
124                                 room.RUnlock()
125                                 continue
126                         }
127                         room.RUnlock()
128                         room.Lock()
129                         delete(room.members, client)
130                         room.Unlock()
131                         room.RLock()
132                         msg := fmt.Sprintf(":%s PART %s :%s", client, room.String(), *client.nickname)
133                         room.Broadcast(msg)
134                         logSink <- LogEvent{room.String(), *client.nickname, "left", true}
135                         room.RUnlock()
136                 case EventTopic:
137                         room.RLock()
138                         if _, subscribed := room.members[client]; !subscribed {
139                                 client.ReplyParts("442", room.String(), "You are not on that channel")
140                                 room.RUnlock()
141                                 continue
142                         }
143                         if event.text == "" {
144                                 room.SendTopic(client)
145                                 room.RUnlock()
146                                 continue
147                         }
148                         room.RUnlock()
149                         topic := strings.TrimLeft(event.text, ":")
150                         room.Lock()
151                         room.topic = &topic
152                         room.Unlock()
153                         room.RLock()
154                         msg := fmt.Sprintf(":%s TOPIC %s :%s", client, room.String(), *room.topic)
155                         room.Broadcast(msg)
156                         logSink <- LogEvent{
157                                 room.String(),
158                                 *client.nickname,
159                                 "set topic to " + *room.topic,
160                                 true,
161                         }
162                         room.RUnlock()
163                         room.StateSave()
164                 case EventWho:
165                         room.RLock()
166                         for m := range room.members {
167                                 client.ReplyNicknamed(
168                                         "352",
169                                         room.String(),
170                                         *m.username,
171                                         m.Host(),
172                                         *hostname,
173                                         *m.nickname,
174                                         "H",
175                                         "0 "+*m.realname,
176                                 )
177                         }
178                         client.ReplyNicknamed("315", room.String(), "End of /WHO list")
179                         room.RUnlock()
180                 case EventMode:
181                         room.RLock()
182                         if event.text == "" {
183                                 mode := "+"
184                                 if *room.key != "" {
185                                         mode = mode + "k"
186                                 }
187                                 client.Msg(fmt.Sprintf("324 %s %s %s", *client.nickname, room.String(), mode))
188                                 room.RUnlock()
189                                 continue
190                         }
191                         if strings.HasPrefix(event.text, "b") {
192                                 client.ReplyNicknamed("368", room.String(), "End of channel ban list")
193                                 room.RUnlock()
194                                 continue
195                         }
196                         if strings.HasPrefix(event.text, "-k") || strings.HasPrefix(event.text, "+k") {
197                                 if _, subscribed := room.members[client]; !subscribed {
198                                         client.ReplyParts("442", room.String(), "You are not on that channel")
199                                         room.RUnlock()
200                                         continue
201                                 }
202                         } else {
203                                 client.ReplyNicknamed("472", event.text, "Unknown MODE flag")
204                                 room.RUnlock()
205                                 continue
206                         }
207                         room.RUnlock()
208                         var msg string
209                         var msgLog string
210                         if strings.HasPrefix(event.text, "+k") {
211                                 cols := strings.Split(event.text, " ")
212                                 if len(cols) == 1 {
213                                         client.ReplyNotEnoughParameters("MODE")
214                                         continue
215                                 }
216                                 room.Lock()
217                                 room.key = &cols[1]
218                                 msg = fmt.Sprintf(":%s MODE %s +k %s", client, *room.name, *room.key)
219                                 msgLog = "set channel key to " + *room.key
220                                 room.Unlock()
221                         } else {
222                                 key := ""
223                                 room.Lock()
224                                 room.key = &key
225                                 msg = fmt.Sprintf(":%s MODE %s -k", client, *room.name)
226                                 room.Unlock()
227                                 msgLog = "removed channel key"
228                         }
229                         room.Broadcast(msg)
230                         logSink <- LogEvent{room.String(), *client.nickname, msgLog, true}
231                         room.StateSave()
232                 case EventMsg:
233                         sep := strings.Index(event.text, " ")
234                         room.Broadcast(fmt.Sprintf(
235                                 ":%s %s %s :%s",
236                                 client,
237                                 event.text[:sep],
238                                 room.String(),
239                                 event.text[sep+1:]),
240                                 client,
241                         )
242                         logSink <- LogEvent{
243                                 room.String(),
244                                 *client.nickname,
245                                 event.text[sep+1:],
246                                 false,
247                         }
248                 }
249         }
250 }