goircd/room.go

239 lines
5.6 KiB
Go

/*
goircd -- minimalistic simple Internet Relay Chat (IRC) server
Copyright (C) 2014-2021 Sergey Matveev <stargrave@stargrave.org>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
import (
"fmt"
"log"
"regexp"
"sort"
"strings"
"sync"
)
type Room struct {
name string
topic string
key string
members map[*Client]struct{}
events chan ClientEvent
sync.RWMutex
}
var (
RERoom = regexp.MustCompile("^#[^\x00\x07\x0a\x0d ,:/]{1,200}$")
rooms map[string]*Room = make(map[string]*Room)
roomsLock sync.RWMutex
roomsWG sync.WaitGroup
)
func (r *Room) SendTopic(c *Client) {
t := r.topic
if t == "" {
c.ReplyNicknamed("331", r.name, "No topic is set")
} else {
c.ReplyNicknamed("332", r.name, t)
}
}
func (r *Room) SendNames(c *Client) {
allowed := false
if r.key == "" {
allowed = true
} else if _, isMember := r.members[c]; isMember {
allowed = true
}
if !allowed {
c.ReplyNicknamed("475", r.name, "Cannot join channel (+k)")
return
}
r.RLock()
nicknames := make([]string, 0, len(r.members))
for member := range r.members {
nicknames = append(nicknames, member.nickname)
}
r.RUnlock()
sort.Strings(nicknames)
maxLen := 512 - len(*hostname) - 2 - 2
MoreNicknames:
lenAll := 0
lenName := 0
for i, n := range nicknames {
lenName = len(n) + 1
if lenAll+lenName >= maxLen {
c.ReplyNicknamed("353", "=", r.name, strings.Join(nicknames[:i-1], " "))
nicknames = nicknames[i:]
goto MoreNicknames
}
lenAll += lenName
}
if len(nicknames) > 0 {
c.ReplyNicknamed("353", "=", r.name, strings.Join(nicknames, " "))
}
c.ReplyNicknamed("366", r.name, "End of NAMES list")
}
func (r *Room) Broadcast(msg string, excludes ...*Client) {
var exclude *Client
if len(excludes) > 0 {
exclude = excludes[0]
}
r.RLock()
for member := range r.members {
if member == exclude {
continue
}
member.Msg(msg)
}
r.RUnlock()
}
func (r *Room) StateSave() {
stateSink <- StateEvent{r.name, r.topic, r.key}
}
func (r *Room) Processor(events <-chan ClientEvent) {
for e := range events {
c := e.client
switch e.eventType {
case EventTerm:
roomsWG.Done()
return
case EventNew:
r.Lock()
r.members[c] = struct{}{}
r.Unlock()
if *verbose {
log.Println(c, "joined", r.name)
}
r.SendTopic(c)
r.Broadcast(fmt.Sprintf(":%s JOIN %s", c, r.name))
logSink <- LogEvent{r.name, c.nickname, "joined", true}
r.SendNames(c)
case EventDel:
if _, subscribed := r.members[c]; !subscribed {
c.ReplyNicknamed("442", r.name, "You are not on that channel")
continue
}
msg := fmt.Sprintf(":%s PART %s :%s", c, r.name, c.nickname)
r.Broadcast(msg)
r.Lock()
delete(r.members, c)
r.Unlock()
logSink <- LogEvent{r.name, c.nickname, "left", true}
if *verbose {
log.Println(c, "left", r.name)
}
case EventTopic:
if _, subscribed := r.members[c]; !subscribed {
c.ReplyParts("442", r.name, "You are not on that channel")
continue
}
if e.text == "" {
r.SendTopic(c)
continue
}
topic := strings.TrimLeft(e.text, ":")
r.topic = topic
msg := fmt.Sprintf(":%s TOPIC %s :%s", c, r.name, r.topic)
r.Broadcast(msg)
logSink <- LogEvent{r.name, c.nickname, "set topic to " + r.topic, true}
r.StateSave()
case EventWho:
r.RLock()
for m := range r.members {
c.ReplyNicknamed(
"352",
r.name,
m.username,
m.Host(),
*hostname,
m.nickname,
"H",
"0 "+m.realname,
)
}
c.ReplyNicknamed("315", r.name, "End of /WHO list")
r.RUnlock()
case EventMode:
if e.text == "" {
mode := "+n"
if r.key != "" {
mode = mode + "k"
}
c.Msg(fmt.Sprintf("324 %s %s %s", c.nickname, r.name, mode))
continue
}
if strings.HasPrefix(e.text, "b") {
c.ReplyNicknamed("368", r.name, "End of channel ban list")
continue
}
if strings.HasPrefix(e.text, "-k") || strings.HasPrefix(e.text, "+k") {
if _, subscribed := r.members[c]; !subscribed {
c.ReplyParts("442", r.name, "You are not on that channel")
continue
}
} else {
c.ReplyNicknamed("472", e.text, "Unknown MODE flag")
continue
}
var msg string
var msgLog string
if strings.HasPrefix(e.text, "+k") {
cols := strings.Split(e.text, " ")
if len(cols) == 1 {
c.ReplyNotEnoughParameters("MODE")
continue
}
r.key = cols[1]
msg = fmt.Sprintf(":%s MODE %s +k %s", c, r.name, r.key)
msgLog = "set channel key"
} else {
r.key = ""
msg = fmt.Sprintf(":%s MODE %s -k", c, r.name)
msgLog = "removed channel key"
}
r.Broadcast(msg)
logSink <- LogEvent{r.name, c.nickname, msgLog, true}
r.StateSave()
case EventMsg:
sep := strings.Index(e.text, " ")
r.Broadcast(fmt.Sprintf(
":%s %s %s :%s", c, e.text[:sep], r.name, e.text[sep+1:],
), c)
logSink <- LogEvent{r.name, c.nickname, e.text[sep+1:], false}
}
}
}
func RoomRegister(name string) *Room {
r := &Room{
name: name,
members: make(map[*Client]struct{}),
events: make(chan ClientEvent),
}
roomsLock.Lock()
roomsWG.Add(1)
rooms[name] = r
roomsLock.Unlock()
go r.Processor(r.events)
return r
}