3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-22 20:09:41 +01:00

Merge pull request #25 from edmund-huber/theater_mode

WIP: adding theater-mode, fixes #15
This commit is contained in:
Jeremy Latt 2014-03-20 11:13:40 -07:00
commit 6403e79a5b
10 changed files with 212 additions and 31 deletions

View File

@ -9,3 +9,6 @@ password = "JDJhJDA0JHJzVFFlNXdOUXNhLmtkSGRUQVVEVHVYWXRKUmdNQ3FKVTRrczRSMTlSWGRP
[operator "root"] [operator "root"]
password = "JDJhJDA0JEhkcm10UlNFRkRXb25iOHZuSDVLZXVBWlpyY0xyNkQ4dlBVc1VMWVk1LlFjWFpQbGxZNUtl" ; 'toor' password = "JDJhJDA0JEhkcm10UlNFRkRXb25iOHZuSDVLZXVBWlpyY0xyNkQ4dlBVc1VMWVk1LlFjWFpQbGxZNUtl" ; 'toor'
[theater "#ghostbusters"]
password = "JDJhJDA0JG0yY1h4cTRFUHhkcjIzN2p1M2Nvb2VEYjAzSHh4eTB3YkZ0VFRLV1ZPVXdqeFBSRUtmRlBT" ; 'venkman'

View File

@ -6,14 +6,15 @@ import (
) )
type Channel struct { type Channel struct {
flags ChannelModeSet flags ChannelModeSet
lists map[ChannelMode]*UserMaskSet lists map[ChannelMode]*UserMaskSet
key Text key Text
members MemberSet members MemberSet
name Name name Name
server *Server server *Server
topic Text topic Text
userLimit uint64 userLimit uint64
theaterUser *Client
} }
// NewChannel creates a new channel from a `Server` and a `name` // NewChannel creates a new channel from a `Server` and a `name`
@ -405,9 +406,11 @@ func (channel *Channel) applyMode(client *Client, change *ChannelModeChange) boo
return channel.applyModeMember(client, change.mode, change.op, return channel.applyModeMember(client, change.mode, change.op,
NewName(change.arg)) NewName(change.arg))
case Theater:
client.ErrConfiguredMode(change.mode)
default: default:
client.ErrUnknownMode(change.mode, channel) client.ErrUnknownMode(change.mode, channel)
return false
} }
return false return false
} }

View File

@ -13,27 +13,28 @@ const (
) )
type Client struct { type Client struct {
atime time.Time atime time.Time
authorized bool authorized bool
awayMessage Text awayMessage Text
capabilities CapabilitySet capabilities CapabilitySet
capState CapState capState CapState
channels ChannelSet channels ChannelSet
commands chan Command commands chan Command
ctime time.Time ctime time.Time
flags map[UserMode]bool flags map[UserMode]bool
hasQuit bool hasQuit bool
hops uint hops uint
hostname Name hostname Name
idleTimer *time.Timer idleTimer *time.Timer
loginTimer *time.Timer loginTimer *time.Timer
nick Name nick Name
quitTimer *time.Timer quitTimer *time.Timer
realname Text realname Text
registered bool registered bool
server *Server server *Server
socket *Socket socket *Socket
username Name username Name
theaterChannels []*Channel
} }
func NewClient(server *Server, conn net.Conn) *Client { func NewClient(server *Server, conn net.Conn) *Client {
@ -259,6 +260,10 @@ func (client *Client) Quit(message Text) {
return return
} }
for _, channel := range client.theaterChannels {
delete(channel.flags, Theater)
}
client.Reply(RplError("connection closed")) client.Reply(RplError("connection closed"))
client.hasQuit = true client.hasQuit = true
client.server.whoWas.Append(client) client.server.whoWas.Append(client)

View File

@ -49,6 +49,7 @@ var (
PRIVMSG: NewPrivMsgCommand, PRIVMSG: NewPrivMsgCommand,
PROXY: NewProxyCommand, PROXY: NewProxyCommand,
QUIT: NewQuitCommand, QUIT: NewQuitCommand,
THEATER: NewTheaterCommand, // nonstandard
TIME: NewTimeCommand, TIME: NewTimeCommand,
TOPIC: NewTopicCommand, TOPIC: NewTopicCommand,
USER: NewUserCommand, USER: NewUserCommand,
@ -947,6 +948,31 @@ func NewInviteCommand(args []string) (Command, error) {
}, nil }, nil
} }
func NewTheaterCommand(args []string) (Command, error) {
if len(args) < 1 {
return nil, NotEnoughArgsError
} else if upperSubCmd := strings.ToUpper(args[0]); upperSubCmd == "IDENTIFY" && len(args) == 3 {
return &TheaterIdentifyCommand{
channel: NewName(args[1]),
PassCommand: PassCommand{password: []byte(args[2])},
}, nil
} else if upperSubCmd == "PRIVMSG" && len(args) == 4 {
return &TheaterPrivMsgCommand{
channel: NewName(args[1]),
asNick: NewName(args[2]),
message: NewText(args[3]),
}, nil
} else if upperSubCmd == "ACTION" && len(args) == 4 {
return &TheaterActionCommand{
channel: NewName(args[1]),
asNick: NewName(args[2]),
action: NewText(args[3]),
}, nil
} else {
return nil, ErrParseCommand
}
}
type TimeCommand struct { type TimeCommand struct {
BaseCommand BaseCommand
target Name target Name

View File

@ -29,6 +29,8 @@ type Config struct {
} }
Operator map[string]*PassConfig Operator map[string]*PassConfig
Theater map[string]*PassConfig
} }
func (conf *Config) Operators() map[Name][]byte { func (conf *Config) Operators() map[Name][]byte {
@ -39,6 +41,18 @@ func (conf *Config) Operators() map[Name][]byte {
return operators return operators
} }
func (conf *Config) Theaters() map[Name][]byte {
theaters := make(map[Name][]byte)
for s, theaterConf := range conf.Theater {
name := NewName(s)
if !name.IsChannel() {
log.Fatal("config uses a non-channel for a theater!")
}
theaters[name] = theaterConf.PasswordBytes()
}
return theaters
}
func LoadConfig(filename string) (config *Config, err error) { func LoadConfig(filename string) (config *Config, err error) {
config = &Config{} config = &Config{}
err = gcfg.ReadFileInto(config, filename) err = gcfg.ReadFileInto(config, filename)

View File

@ -30,6 +30,7 @@ const (
PRIVMSG StringCode = "PRIVMSG" PRIVMSG StringCode = "PRIVMSG"
PROXY StringCode = "PROXY" PROXY StringCode = "PROXY"
QUIT StringCode = "QUIT" QUIT StringCode = "QUIT"
THEATER StringCode = "THEATER" // nonstandard
TIME StringCode = "TIME" TIME StringCode = "TIME"
TOPIC StringCode = "TOPIC" TOPIC StringCode = "TOPIC"
USER StringCode = "USER" USER StringCode = "USER"

View File

@ -83,6 +83,7 @@ const (
Quiet ChannelMode = 'q' // flag Quiet ChannelMode = 'q' // flag
ReOp ChannelMode = 'r' // flag ReOp ChannelMode = 'r' // flag
Secret ChannelMode = 's' // flag, deprecated Secret ChannelMode = 's' // flag, deprecated
Theater ChannelMode = 'T' // flag arg, nonstandard
UserLimit ChannelMode = 'l' // flag arg UserLimit ChannelMode = 'l' // flag arg
Voice ChannelMode = 'v' // arg Voice ChannelMode = 'v' // arg
) )
@ -90,7 +91,7 @@ const (
var ( var (
SupportedChannelModes = ChannelModes{ SupportedChannelModes = ChannelModes{
BanMask, ExceptMask, InviteMask, InviteOnly, Key, NoOutside, BanMask, ExceptMask, InviteMask, InviteOnly, Key, NoOutside,
OpOnlyTopic, Persistent, Private, UserLimit, OpOnlyTopic, Persistent, Private, Theater, UserLimit,
} }
) )

View File

@ -548,6 +548,11 @@ func (target *Client) ErrUnknownMode(mode ChannelMode, channel *Channel) {
"%s :is unknown mode char to me for %s", mode, channel) "%s :is unknown mode char to me for %s", mode, channel)
} }
func (target *Client) ErrConfiguredMode(mode ChannelMode) {
target.NumericReply(ERR_UNKNOWNMODE,
"%s :can only change this mode in daemon configuration", mode)
}
func (target *Client) ErrChannelIsFull(channel *Channel) { func (target *Client) ErrChannelIsFull(channel *Channel) {
target.NumericReply(ERR_CHANNELISFULL, target.NumericReply(ERR_CHANNELISFULL,
"%s :Cannot join channel (+l)", channel) "%s :Cannot join channel (+l)", channel)

View File

@ -40,6 +40,7 @@ type Server struct {
password []byte password []byte
signals chan os.Signal signals chan os.Signal
whoWas *WhoWasList whoWas *WhoWasList
theaters map[Name][]byte
} }
var ( var (
@ -61,6 +62,7 @@ func NewServer(config *Config) *Server {
operators: config.Operators(), operators: config.Operators(),
signals: make(chan os.Signal, len(SERVER_SIGNALS)), signals: make(chan os.Signal, len(SERVER_SIGNALS)),
whoWas: NewWhoWasList(100), whoWas: NewWhoWasList(100),
theaters: config.Theaters(),
} }
if config.Server.Password != "" { if config.Server.Password != "" {

121
irc/theater.go Normal file
View File

@ -0,0 +1,121 @@
package irc
import (
"fmt"
)
type TheaterClient Name
func (c TheaterClient) Id() Name {
return Name(c)
}
func (c TheaterClient) Nick() Name {
return Name(c)
}
type TheaterSubCommand string
type theaterSubCommand interface {
String() string
}
type TheaterIdentifyCommand struct {
PassCommand
channel Name
}
func (m *TheaterIdentifyCommand) LoadPassword(s *Server) {
m.hash = s.theaters[m.channel]
}
func (cmd *TheaterIdentifyCommand) String() string {
return fmt.Sprintf("THEATER_IDENTIFY(channel=%s)", cmd.channel)
}
func (m *TheaterIdentifyCommand) HandleServer(s *Server) {
client := m.Client()
if !m.channel.IsChannel() {
client.ErrNoSuchChannel(m.channel)
return
}
channel := s.channels.Get(m.channel)
if channel == nil {
client.ErrNoSuchChannel(m.channel)
return
}
if (m.hash == nil) || (m.err != nil) {
client.ErrPasswdMismatch()
return
}
if channel.theaterUser == nil {
client.theaterChannels = append(client.theaterChannels, channel)
channel.flags[Theater] = true
channel.theaterUser = client
}
}
type TheaterPrivMsgCommand struct {
BaseCommand
channel Name
asNick Name
message Text
}
func (cmd *TheaterPrivMsgCommand) String() string {
return fmt.Sprintf("THEATER_PRIVMSG(channel=%s, asNick=%s, message=%s)", cmd.channel, cmd.asNick, cmd.message)
}
func (m *TheaterPrivMsgCommand) HandleServer(s *Server) {
client := m.Client()
if !m.channel.IsChannel() {
client.ErrNoSuchChannel(m.channel)
return
}
channel := s.channels.Get(m.channel)
if channel == nil {
client.ErrNoSuchChannel(m.channel)
return
}
if channel.theaterUser == client {
for member := range channel.members {
member.Reply(RplPrivMsg(TheaterClient(m.asNick), channel, m.message))
}
}
}
type TheaterActionCommand struct {
BaseCommand
channel Name
asNick Name
action Text
}
func (cmd *TheaterActionCommand) String() string {
return fmt.Sprintf("THEATER_ACTION(channel=%s, asNick=%s, action=%s)", cmd.channel, cmd.asNick, cmd.action)
}
func (m *TheaterActionCommand) HandleServer(s *Server) {
client := m.Client()
if m.channel.IsChannel() {
client.ErrNoSuchChannel(m.channel)
return
}
channel := s.channels.Get(m.channel)
if channel == nil {
client.ErrNoSuchChannel(m.channel)
return
}
if channel.theaterUser == client {
for member := range channel.members {
member.Reply(RplPrivMsg(TheaterClient(m.asNick), channel, NewText(fmt.Sprintf("\001ACTION %s\001", m.action))))
}
}
}