837 lines
18 KiB
Go
837 lines
18 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type Channel struct {
|
|
name string
|
|
topic string
|
|
topicBy string
|
|
topicTime time.Time
|
|
clients map[string]*Client
|
|
operators map[string]*Client
|
|
halfops map[string]*Client
|
|
voices map[string]*Client
|
|
owners map[string]*Client
|
|
admins map[string]*Client
|
|
modes map[rune]bool
|
|
key string
|
|
limit int
|
|
banList []string
|
|
quietList []string // Users who can join but not speak (+q)
|
|
exceptList []string // Users exempt from bans (+e)
|
|
inviteList []string // Users who can join invite-only channels (+I)
|
|
created time.Time
|
|
|
|
// Advanced mode settings
|
|
floodSettings string // Flood protection settings (e.g., "10:5")
|
|
joinThrottle string // Join throttling settings (e.g., "3:10")
|
|
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
func NewChannel(name string) *Channel {
|
|
return &Channel{
|
|
name: name,
|
|
clients: make(map[string]*Client),
|
|
operators: make(map[string]*Client),
|
|
halfops: make(map[string]*Client),
|
|
voices: make(map[string]*Client),
|
|
owners: make(map[string]*Client),
|
|
admins: make(map[string]*Client),
|
|
modes: make(map[rune]bool),
|
|
banList: make([]string, 0),
|
|
quietList: make([]string, 0),
|
|
exceptList: make([]string, 0),
|
|
inviteList: make([]string, 0),
|
|
created: time.Now(),
|
|
}
|
|
}
|
|
|
|
func (ch *Channel) Name() string {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
return ch.name
|
|
}
|
|
|
|
func (ch *Channel) Topic() string {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
return ch.topic
|
|
}
|
|
|
|
func (ch *Channel) TopicBy() string {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
return ch.topicBy
|
|
}
|
|
|
|
func (ch *Channel) TopicTime() time.Time {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
return ch.topicTime
|
|
}
|
|
|
|
func (ch *Channel) SetTopic(topic, by string) {
|
|
ch.mu.Lock()
|
|
defer ch.mu.Unlock()
|
|
ch.topic = topic
|
|
ch.topicBy = by
|
|
ch.topicTime = time.Now()
|
|
}
|
|
|
|
func (ch *Channel) AddClient(client *Client) {
|
|
ch.mu.Lock()
|
|
defer ch.mu.Unlock()
|
|
|
|
nick := strings.ToLower(client.Nick())
|
|
|
|
// Check if client is already in the channel
|
|
if _, exists := ch.clients[nick]; exists {
|
|
// Client already in channel, don't add again
|
|
return
|
|
}
|
|
|
|
ch.clients[nick] = client
|
|
client.AddChannel(ch)
|
|
|
|
// First user gets configured founder mode (default: operator)
|
|
if len(ch.clients) == 1 {
|
|
// Get the configured founder mode from server config
|
|
founderMode := "o" // Default fallback
|
|
if client.server != nil && client.server.config != nil {
|
|
founderMode = client.server.config.Channels.FounderMode
|
|
}
|
|
|
|
// Apply the appropriate mode based on configuration
|
|
switch founderMode {
|
|
case "q":
|
|
ch.owners[nick] = client
|
|
case "a":
|
|
ch.admins[nick] = client
|
|
case "o":
|
|
ch.operators[nick] = client
|
|
case "h":
|
|
ch.halfops[nick] = client
|
|
case "v":
|
|
ch.voices[nick] = client
|
|
default:
|
|
// Invalid config, fallback to operator
|
|
ch.operators[nick] = client
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ch *Channel) RemoveClient(client *Client) {
|
|
ch.mu.Lock()
|
|
defer ch.mu.Unlock()
|
|
|
|
nick := strings.ToLower(client.Nick())
|
|
delete(ch.clients, nick)
|
|
delete(ch.operators, nick)
|
|
delete(ch.halfops, nick)
|
|
delete(ch.voices, nick)
|
|
delete(ch.owners, nick)
|
|
delete(ch.admins, nick)
|
|
client.RemoveChannel(ch.name)
|
|
}
|
|
|
|
func (ch *Channel) HasClient(client *Client) bool {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
_, exists := ch.clients[strings.ToLower(client.Nick())]
|
|
return exists
|
|
}
|
|
|
|
func (ch *Channel) IsOperator(client *Client) bool {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
_, exists := ch.operators[strings.ToLower(client.Nick())]
|
|
return exists
|
|
}
|
|
|
|
func (ch *Channel) IsVoice(client *Client) bool {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
_, exists := ch.voices[strings.ToLower(client.Nick())]
|
|
return exists
|
|
}
|
|
|
|
func (ch *Channel) IsHalfop(client *Client) bool {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
_, exists := ch.halfops[strings.ToLower(client.Nick())]
|
|
return exists
|
|
}
|
|
|
|
func (ch *Channel) IsOwner(client *Client) bool {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
_, exists := ch.owners[strings.ToLower(client.Nick())]
|
|
return exists
|
|
}
|
|
|
|
func (ch *Channel) IsQuieted(client *Client) bool {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
return ch.isQuietedUnsafe(client)
|
|
}
|
|
|
|
func (ch *Channel) isQuietedUnsafe(client *Client) bool {
|
|
hostmask := fmt.Sprintf("%s!%s@%s", client.Nick(), client.User(), client.Host())
|
|
|
|
for _, quiet := range ch.quietList {
|
|
if ch.matchesBanMask(client, quiet, hostmask) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (ch *Channel) SetOperator(client *Client, isOp bool) {
|
|
ch.mu.Lock()
|
|
defer ch.mu.Unlock()
|
|
|
|
nick := strings.ToLower(client.Nick())
|
|
if isOp {
|
|
ch.operators[nick] = client
|
|
} else {
|
|
delete(ch.operators, nick)
|
|
}
|
|
}
|
|
|
|
func (ch *Channel) SetVoice(client *Client, hasVoice bool) {
|
|
ch.mu.Lock()
|
|
defer ch.mu.Unlock()
|
|
|
|
nick := strings.ToLower(client.Nick())
|
|
if hasVoice {
|
|
ch.voices[nick] = client
|
|
} else {
|
|
delete(ch.voices, nick)
|
|
}
|
|
}
|
|
|
|
func (ch *Channel) SetHalfop(client *Client, isHalfop bool) {
|
|
ch.mu.Lock()
|
|
defer ch.mu.Unlock()
|
|
|
|
nick := strings.ToLower(client.Nick())
|
|
if isHalfop {
|
|
ch.halfops[nick] = client
|
|
} else {
|
|
delete(ch.halfops, nick)
|
|
}
|
|
}
|
|
|
|
func (ch *Channel) SetOwner(client *Client, isOwner bool) {
|
|
ch.mu.Lock()
|
|
defer ch.mu.Unlock()
|
|
|
|
nick := strings.ToLower(client.Nick())
|
|
if isOwner {
|
|
ch.owners[nick] = client
|
|
} else {
|
|
delete(ch.owners, nick)
|
|
}
|
|
}
|
|
|
|
func (ch *Channel) IsAdmin(client *Client) bool {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
_, exists := ch.admins[strings.ToLower(client.Nick())]
|
|
return exists
|
|
}
|
|
|
|
func (ch *Channel) SetAdmin(client *Client, isAdmin bool) {
|
|
ch.mu.Lock()
|
|
defer ch.mu.Unlock()
|
|
|
|
nick := strings.ToLower(client.Nick())
|
|
if isAdmin {
|
|
ch.admins[nick] = client
|
|
} else {
|
|
delete(ch.admins, nick)
|
|
}
|
|
}
|
|
|
|
func (ch *Channel) GetClients() []*Client {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
|
|
clients := make([]*Client, 0, len(ch.clients))
|
|
for _, client := range ch.clients {
|
|
clients = append(clients, client)
|
|
}
|
|
return clients
|
|
}
|
|
|
|
func (ch *Channel) GetClientCount() int {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
return len(ch.clients)
|
|
}
|
|
|
|
func (ch *Channel) UserCount() int {
|
|
return ch.GetClientCount()
|
|
}
|
|
|
|
func (ch *Channel) Broadcast(message string, exclude *Client) {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
|
|
for _, client := range ch.clients {
|
|
if exclude != nil && client.Nick() == exclude.Nick() {
|
|
continue
|
|
}
|
|
client.SendMessage(message)
|
|
}
|
|
}
|
|
|
|
func (ch *Channel) BroadcastFrom(source, message string, exclude *Client) {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
|
|
for _, client := range ch.clients {
|
|
if exclude != nil && client.Nick() == exclude.Nick() {
|
|
continue
|
|
}
|
|
client.SendFrom(source, message)
|
|
}
|
|
}
|
|
|
|
func (ch *Channel) HasMode(mode rune) bool {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
return ch.modes[mode]
|
|
}
|
|
|
|
func (ch *Channel) SetMode(mode rune, set bool) {
|
|
ch.mu.Lock()
|
|
defer ch.mu.Unlock()
|
|
|
|
if set {
|
|
ch.modes[mode] = true
|
|
} else {
|
|
delete(ch.modes, mode)
|
|
}
|
|
}
|
|
|
|
func (ch *Channel) GetModes() string {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
|
|
var modes []rune
|
|
for mode := range ch.modes {
|
|
modes = append(modes, mode)
|
|
}
|
|
|
|
if len(modes) == 0 {
|
|
return ""
|
|
}
|
|
|
|
return "+" + string(modes)
|
|
}
|
|
|
|
func (ch *Channel) CanSendMessage(client *Client) bool {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
|
|
// Check if user is quieted first
|
|
if ch.isQuietedUnsafe(client) {
|
|
// Only owners, operators, and halfops can speak when quieted
|
|
nick := strings.ToLower(client.Nick())
|
|
_, isOwner := ch.owners[nick]
|
|
_, isOp := ch.operators[nick]
|
|
_, isHalfop := ch.halfops[nick]
|
|
|
|
if !isOwner && !isOp && !isHalfop {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// If channel is not moderated, anyone in the channel can send
|
|
if !ch.modes['m'] {
|
|
return true
|
|
}
|
|
|
|
// In moderated channels, only owners, operators, halfops and voiced users can send messages
|
|
nick := strings.ToLower(client.Nick())
|
|
_, isOwner := ch.owners[nick]
|
|
_, isOp := ch.operators[nick]
|
|
_, isHalfop := ch.halfops[nick]
|
|
_, hasVoice := ch.voices[nick]
|
|
|
|
return isOwner || isOp || isHalfop || hasVoice
|
|
}
|
|
|
|
func (ch *Channel) Key() string {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
return ch.key
|
|
}
|
|
|
|
func (ch *Channel) SetKey(key string) {
|
|
ch.mu.Lock()
|
|
defer ch.mu.Unlock()
|
|
ch.key = key
|
|
}
|
|
|
|
func (ch *Channel) Limit() int {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
return ch.limit
|
|
}
|
|
|
|
func (ch *Channel) SetLimit(limit int) {
|
|
ch.mu.Lock()
|
|
defer ch.mu.Unlock()
|
|
ch.limit = limit
|
|
}
|
|
|
|
func (ch *Channel) GetNamesReply() string {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
|
|
var names []string
|
|
for _, client := range ch.clients {
|
|
prefix := ""
|
|
if ch.IsOwner(client) {
|
|
prefix = "~"
|
|
} else if ch.IsOperator(client) {
|
|
prefix = "@"
|
|
} else if ch.IsHalfop(client) {
|
|
prefix = "%"
|
|
} else if ch.IsVoice(client) {
|
|
prefix = "+"
|
|
}
|
|
names = append(names, prefix+client.Nick())
|
|
}
|
|
|
|
return strings.Join(names, " ")
|
|
}
|
|
|
|
func (ch *Channel) CanSpeak(client *Client) bool {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
|
|
// If channel is not moderated, anyone can speak
|
|
if !ch.modes['m'] {
|
|
return true
|
|
}
|
|
|
|
// Operators and voiced users can always speak
|
|
return ch.IsOperator(client) || ch.IsVoice(client)
|
|
}
|
|
|
|
func (ch *Channel) CanJoin(client *Client, key string) bool {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
|
|
// Check if invite-only
|
|
if ch.modes['i'] {
|
|
// Check invite list
|
|
for _, mask := range ch.inviteList {
|
|
if ch.matchesMask(client.Prefix(), mask) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Check key
|
|
if ch.modes['k'] && ch.key != key {
|
|
return false
|
|
}
|
|
|
|
// Check limit
|
|
if ch.modes['l'] && len(ch.clients) >= ch.limit {
|
|
return false
|
|
}
|
|
|
|
// Check ban list
|
|
for _, mask := range ch.banList {
|
|
if ch.matchesMask(client.Prefix(), mask) {
|
|
// Check exception list
|
|
for _, exceptMask := range ch.exceptList {
|
|
if ch.matchesMask(client.Prefix(), exceptMask) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (ch *Channel) matchesMask(target, mask string) bool {
|
|
// Simple mask matching - should be enhanced for production
|
|
return strings.Contains(strings.ToLower(target), strings.ToLower(mask))
|
|
}
|
|
|
|
func (ch *Channel) AddBan(mask string) {
|
|
ch.mu.Lock()
|
|
defer ch.mu.Unlock()
|
|
ch.banList = append(ch.banList, mask)
|
|
}
|
|
|
|
func (ch *Channel) RemoveBan(mask string) {
|
|
ch.mu.Lock()
|
|
defer ch.mu.Unlock()
|
|
|
|
for i, ban := range ch.banList {
|
|
if ban == mask {
|
|
ch.banList = append(ch.banList[:i], ch.banList[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ch *Channel) GetBans() []string {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
|
|
bans := make([]string, len(ch.banList))
|
|
copy(bans, ch.banList)
|
|
return bans
|
|
}
|
|
|
|
// Extended ban list management
|
|
func (ch *Channel) AddQuiet(mask string) {
|
|
ch.mu.Lock()
|
|
defer ch.mu.Unlock()
|
|
ch.quietList = append(ch.quietList, mask)
|
|
}
|
|
|
|
func (ch *Channel) RemoveQuiet(mask string) {
|
|
ch.mu.Lock()
|
|
defer ch.mu.Unlock()
|
|
|
|
for i, quiet := range ch.quietList {
|
|
if quiet == mask {
|
|
ch.quietList = append(ch.quietList[:i], ch.quietList[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ch *Channel) GetQuiets() []string {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
|
|
quiets := make([]string, len(ch.quietList))
|
|
copy(quiets, ch.quietList)
|
|
return quiets
|
|
}
|
|
|
|
func (ch *Channel) AddExcept(mask string) {
|
|
ch.mu.Lock()
|
|
defer ch.mu.Unlock()
|
|
ch.exceptList = append(ch.exceptList, mask)
|
|
}
|
|
|
|
func (ch *Channel) RemoveExcept(mask string) {
|
|
ch.mu.Lock()
|
|
defer ch.mu.Unlock()
|
|
|
|
for i, except := range ch.exceptList {
|
|
if except == mask {
|
|
ch.exceptList = append(ch.exceptList[:i], ch.exceptList[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ch *Channel) GetExcepts() []string {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
|
|
excepts := make([]string, len(ch.exceptList))
|
|
copy(excepts, ch.exceptList)
|
|
return excepts
|
|
}
|
|
|
|
func (ch *Channel) AddInviteException(mask string) {
|
|
ch.mu.Lock()
|
|
defer ch.mu.Unlock()
|
|
ch.inviteList = append(ch.inviteList, mask)
|
|
}
|
|
|
|
func (ch *Channel) RemoveInviteException(mask string) {
|
|
ch.mu.Lock()
|
|
defer ch.mu.Unlock()
|
|
|
|
for i, invite := range ch.inviteList {
|
|
if invite == mask {
|
|
ch.inviteList = append(ch.inviteList[:i], ch.inviteList[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ch *Channel) GetInviteExceptions() []string {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
|
|
invites := make([]string, len(ch.inviteList))
|
|
copy(invites, ch.inviteList)
|
|
return invites
|
|
}
|
|
|
|
// IsExempt checks if a client is exempt from bans
|
|
func (ch *Channel) IsExempt(client *Client) bool {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
|
|
hostmask := fmt.Sprintf("%s!%s@%s", client.Nick(), client.User(), client.Host())
|
|
|
|
for _, except := range ch.exceptList {
|
|
if ch.matchesBanMask(client, except, hostmask) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// CanJoinInviteOnly checks if a client can join an invite-only channel
|
|
func (ch *Channel) CanJoinInviteOnly(client *Client) bool {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
|
|
hostmask := fmt.Sprintf("%s!%s@%s", client.Nick(), client.User(), client.Host())
|
|
|
|
for _, invite := range ch.inviteList {
|
|
if ch.matchesBanMask(client, invite, hostmask) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (ch *Channel) Created() time.Time {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
return ch.created
|
|
}
|
|
|
|
// IsBanned checks if a client matches any ban mask in the channel
|
|
func (ch *Channel) IsBanned(client *Client) bool {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
|
|
// Check exemptions first - if exempt, not banned
|
|
if ch.isExemptUnsafe(client) {
|
|
return false
|
|
}
|
|
|
|
hostmask := fmt.Sprintf("%s!%s@%s", client.Nick(), client.User(), client.Host())
|
|
|
|
for _, ban := range ch.banList {
|
|
if ch.matchesBanMask(client, ban, hostmask) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// isExemptUnsafe checks exemptions without locking (internal use)
|
|
func (ch *Channel) isExemptUnsafe(client *Client) bool {
|
|
hostmask := fmt.Sprintf("%s!%s@%s", client.Nick(), client.User(), client.Host())
|
|
|
|
for _, except := range ch.exceptList {
|
|
if ch.matchesBanMask(client, except, hostmask) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// matchesBanMask checks if a client matches a ban mask (supports extended bans)
|
|
func (ch *Channel) matchesBanMask(client *Client, banMask, hostmask string) bool {
|
|
// Check for extended ban format: ~type:parameter or ~type parameter
|
|
if strings.HasPrefix(banMask, "~") {
|
|
return ch.matchesExtendedBan(client, banMask)
|
|
}
|
|
|
|
// Traditional hostmask ban
|
|
return matchWildcard(banMask, hostmask)
|
|
}
|
|
|
|
// matchesExtendedBan handles extended ban types
|
|
func (ch *Channel) matchesExtendedBan(client *Client, extban string) bool {
|
|
if len(extban) < 2 || extban[0] != '~' {
|
|
return false
|
|
}
|
|
|
|
// Parse ~type:parameter or ~type parameter format
|
|
var banType string
|
|
var parameter string
|
|
|
|
if strings.Contains(extban, ":") {
|
|
// Format: ~type:parameter
|
|
parts := strings.SplitN(extban[1:], ":", 2)
|
|
banType = parts[0]
|
|
if len(parts) > 1 {
|
|
parameter = parts[1]
|
|
}
|
|
} else {
|
|
// Format: ~type parameter (space separated)
|
|
parts := strings.Fields(extban[1:])
|
|
if len(parts) > 0 {
|
|
banType = parts[0]
|
|
if len(parts) > 1 {
|
|
parameter = strings.Join(parts[1:], " ")
|
|
}
|
|
}
|
|
}
|
|
|
|
switch banType {
|
|
case "a": // Account ban: ~a:accountname or ~a accountname
|
|
if parameter == "" {
|
|
// ~a with no parameter bans unregistered users
|
|
return client.account == ""
|
|
}
|
|
// ~a:account bans specific account
|
|
return client.account == parameter
|
|
|
|
case "c": // Channel ban: ~c:#channel - ban users in another channel
|
|
if parameter == "" || !strings.HasPrefix(parameter, "#") {
|
|
return false
|
|
}
|
|
targetChannel := client.server.GetChannel(parameter)
|
|
return targetChannel != nil && targetChannel.HasClient(client)
|
|
|
|
case "j": // Join prevent: ~j:#channel - prevent joining if in another channel
|
|
if parameter == "" || !strings.HasPrefix(parameter, "#") {
|
|
return false
|
|
}
|
|
targetChannel := client.server.GetChannel(parameter)
|
|
return targetChannel != nil && targetChannel.HasClient(client)
|
|
|
|
case "n": // Nick pattern ban: ~n:pattern
|
|
if parameter == "" {
|
|
return false
|
|
}
|
|
return matchWildcard(parameter, client.Nick())
|
|
|
|
case "q": // Quiet ban: ~q:mask - this is a special case for quiet functionality
|
|
// When used in regular ban list, ~q acts as a quiet
|
|
if parameter == "" {
|
|
// ~q with no parameter quiets everyone
|
|
return true
|
|
}
|
|
// ~q:mask quiets matching users - check against hostmask pattern
|
|
hostmask := fmt.Sprintf("%s!%s@%s", client.Nick(), client.User(), client.Host())
|
|
return matchWildcard(parameter, hostmask)
|
|
|
|
case "r": // Real name ban: ~r:pattern
|
|
if parameter == "" {
|
|
return false
|
|
}
|
|
return matchWildcard(parameter, client.realname)
|
|
|
|
case "s": // Server ban: ~s:servername
|
|
if parameter == "" {
|
|
return false
|
|
}
|
|
return matchWildcard(parameter, client.server.config.Server.Name)
|
|
|
|
case "o": // Operator ban: ~o (bans all opers)
|
|
return client.IsOper()
|
|
|
|
case "z": // Non-SSL ban: ~z (bans non-SSL users)
|
|
return !client.ssl
|
|
|
|
case "Z": // SSL-only ban: ~Z (bans SSL users)
|
|
return client.ssl
|
|
|
|
case "u": // Username pattern ban: ~u:pattern
|
|
if parameter == "" {
|
|
return false
|
|
}
|
|
return matchWildcard(parameter, client.User())
|
|
|
|
case "h": // Hostname pattern ban: ~h:pattern
|
|
if parameter == "" {
|
|
return false
|
|
}
|
|
return matchWildcard(parameter, client.Host())
|
|
|
|
case "i": // IP ban: ~i:ip/cidr
|
|
if parameter == "" {
|
|
return false
|
|
}
|
|
// Simple IP matching for now (could be enhanced with CIDR)
|
|
return matchWildcard(parameter, client.Host())
|
|
|
|
case "R": // Registered only: ~R (bans unregistered users)
|
|
return client.account == ""
|
|
|
|
case "m": // Mute ban: ~m:mask - similar to quiet
|
|
if parameter == "" {
|
|
return true
|
|
}
|
|
hostmask := fmt.Sprintf("%s!%s@%s", client.Nick(), client.User(), client.Host())
|
|
return matchWildcard(parameter, hostmask)
|
|
|
|
default:
|
|
// Unknown extended ban type
|
|
return false
|
|
}
|
|
}
|
|
|
|
// IsInvited checks if a client is on the invite list for the channel
|
|
func (ch *Channel) IsInvited(client *Client) bool {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
|
|
hostmask := fmt.Sprintf("%s!%s@%s", client.Nick(), client.User(), client.Host())
|
|
|
|
for _, invite := range ch.inviteList {
|
|
if matchWildcard(invite, hostmask) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// matchWildcard checks if a pattern with wildcards (* and ?) matches a string
|
|
func matchWildcard(pattern, str string) bool {
|
|
matched, _ := filepath.Match(strings.ToLower(pattern), strings.ToLower(str))
|
|
return matched
|
|
}
|
|
|
|
// SetFloodSettings sets the flood protection settings
|
|
func (ch *Channel) SetFloodSettings(settings string) {
|
|
ch.mu.Lock()
|
|
defer ch.mu.Unlock()
|
|
ch.floodSettings = settings
|
|
}
|
|
|
|
// GetFloodSettings returns the flood protection settings
|
|
func (ch *Channel) GetFloodSettings() string {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
return ch.floodSettings
|
|
}
|
|
|
|
// SetJoinThrottle sets the join throttling settings
|
|
func (ch *Channel) SetJoinThrottle(settings string) {
|
|
ch.mu.Lock()
|
|
defer ch.mu.Unlock()
|
|
ch.joinThrottle = settings
|
|
}
|
|
|
|
// GetJoinThrottle returns the join throttling settings
|
|
func (ch *Channel) GetJoinThrottle() string {
|
|
ch.mu.RLock()
|
|
defer ch.mu.RUnlock()
|
|
return ch.joinThrottle
|
|
}
|