1 // goircd -- minimalistic simple Internet Relay Chat (IRC) server
2 // Copyright (C) 2014-2024 Sergey Matveev <stargrave@stargrave.org>
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.
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.
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/>.
31 members map[*Client]struct{}
32 events chan ClientEvent
37 RERoom = regexp.MustCompile("^#[^\x00\x07\x0a\x0d ,:/]{1,200}$")
39 rooms map[string]*Room = make(map[string]*Room)
40 roomsLock sync.RWMutex
41 roomsWG sync.WaitGroup
44 func (r *Room) SendTopic(c *Client) {
47 c.ReplyNicknamed("331", r.name, "No topic is set")
49 c.ReplyNicknamed("332", r.name, t)
53 func (r *Room) SendNames(c *Client) {
57 } else if _, isMember := r.members[c]; isMember {
61 c.ReplyNicknamed("475", r.name, "Cannot join channel (+k)")
65 nicknames := make([]string, 0, len(r.members))
66 for member := range r.members {
67 nicknames = append(nicknames, member.nickname)
70 sort.Strings(nicknames)
71 maxLen := 512 - len(*hostname) - 2 - 2
76 for i, n := range nicknames {
78 if lenAll+lenName >= maxLen {
79 c.ReplyNicknamed("353", "=", r.name, strings.Join(nicknames[:i-1], " "))
80 nicknames = nicknames[i:]
85 if len(nicknames) > 0 {
86 c.ReplyNicknamed("353", "=", r.name, strings.Join(nicknames, " "))
88 c.ReplyNicknamed("366", r.name, "End of NAMES list")
91 func (r *Room) Broadcast(msg string, excludes ...*Client) {
93 if len(excludes) > 0 {
97 for member := range r.members {
98 if member == exclude {
106 func (r *Room) StateSave() {
107 stateSink <- StateEvent{r.name, r.topic, r.key}
110 func (r *Room) Processor(events <-chan ClientEvent) {
111 for e := range events {
119 r.members[c] = struct{}{}
122 log.Println(c, "joined", r.name)
125 r.Broadcast(fmt.Sprintf(":%s JOIN %s", c, r.name))
126 logSink <- LogEvent{r.name, c.nickname, "joined", true}
129 if _, subscribed := r.members[c]; !subscribed {
130 c.ReplyNicknamed("442", r.name, "You are not on that channel")
133 msg := fmt.Sprintf(":%s PART %s :%s", c, r.name, c.nickname)
138 logSink <- LogEvent{r.name, c.nickname, "left", true}
140 log.Println(c, "left", r.name)
143 if _, subscribed := r.members[c]; !subscribed {
144 c.ReplyParts("442", r.name, "You are not on that channel")
151 topic := strings.TrimLeft(e.text, ":")
153 msg := fmt.Sprintf(":%s TOPIC %s :%s", c, r.name, r.topic)
155 logSink <- LogEvent{r.name, c.nickname, "set topic to " + r.topic, true}
159 for m := range r.members {
171 c.ReplyNicknamed("315", r.name, "End of /WHO list")
179 c.Msg(fmt.Sprintf("324 %s %s %s", c.nickname, r.name, mode))
182 if strings.HasPrefix(e.text, "b") {
183 c.ReplyNicknamed("368", r.name, "End of channel ban list")
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")
192 c.ReplyNicknamed("472", e.text, "Unknown MODE flag")
197 if strings.HasPrefix(e.text, "+k") {
198 cols := strings.Split(e.text, " ")
200 c.ReplyNotEnoughParameters("MODE")
204 msg = fmt.Sprintf(":%s MODE %s +k %s", c, r.name, r.key)
205 msgLog = "set channel key"
208 msg = fmt.Sprintf(":%s MODE %s -k", c, r.name)
209 msgLog = "removed channel key"
212 logSink <- LogEvent{r.name, c.nickname, msgLog, true}
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:],
219 logSink <- LogEvent{r.name, c.nickname, e.text[sep+1:], false}
224 func RoomRegister(name string) *Room {
227 members: make(map[*Client]struct{}),
228 events: make(chan ClientEvent),
234 go r.Processor(r.events)