Browse Source

a few small things.

master
Gisle Aune 4 years ago
parent
commit
f7d53f4c63
  1. 4
      channel.go
  2. 71
      client.go
  3. 33
      cmd/ircrepl/main.go
  4. 24
      handlers/input.go
  5. 4
      isupport/isupport.go
  6. 4
      query.go
  7. 27
      state.go
  8. 4
      status.go
  9. 2
      target.go

4
channel.go

@ -23,8 +23,8 @@ func (channel *Channel) Name() string {
return channel.name
}
func (channel *Channel) State() TargetState {
return TargetState{
func (channel *Channel) State() ClientStateTarget {
return ClientStateTarget{
Kind: "channel",
Name: channel.name,
Users: channel.userlist.Users(),

71
client.go

@ -109,10 +109,10 @@ func New(ctx context.Context, config Config) *Client {
status: &Status{},
}
_, _ = client.AddTarget(client.status)
client.ctx, client.cancel = context.WithCancel(ctx)
_, _ = client.AddTarget(client.status)
go client.handleEventLoop()
go client.handleSendLoop()
@ -187,6 +187,14 @@ func (client *Client) Ready() bool {
return client.ready
}
// HasQuit returns true if the client had manually quit.
func (client *Client) HasQuit() bool {
client.mutex.RLock()
defer client.mutex.RUnlock()
return client.ready
}
func (client *Client) State() ClientState {
client.mutex.RLock()
@ -196,9 +204,10 @@ func (client *Client) State() ClientState {
Host: client.host,
Connected: client.conn != nil,
Ready: client.ready,
Quit: client.quit,
ISupport: client.isupport.State(),
Caps: make([]string, 0, len(client.capEnabled)),
Targets: make([]TargetState, 0, len(client.targets)),
Targets: make([]ClientStateTarget, 0, len(client.targets)),
}
for key, enabled := range client.capEnabled {
@ -266,13 +275,21 @@ func (client *Client) Connect(addr string, ssl bool) (err error) {
event, err := ParsePacket(line)
if err != nil {
client.EmitNonBlocking(NewErrorEvent("parse", "Read failed: "+err.Error()))
client.mutex.RLock()
hasQuit := client.quit
client.mutex.RUnlock()
if !hasQuit {
client.EmitNonBlocking(NewErrorEvent("parse", "Read failed: "+err.Error()))
}
continue
}
client.EmitNonBlocking(event)
}
_ = client.conn.Close()
client.mutex.Lock()
client.conn = nil
client.ready = false
@ -461,6 +478,16 @@ func (client *Client) EmitSync(ctx context.Context, event Event) (err error) {
func (client *Client) EmitInput(line string, target Target) context.Context {
event := ParseInput(line)
client.mutex.RLock()
if target != nil && client.targetIds[target] == "" {
client.EmitNonBlocking(NewErrorEvent("invalid_target", "Target does not exist."))
ctx, cancel := context.WithCancel(context.Background())
cancel()
return ctx
}
client.mutex.RUnlock()
if target != nil {
client.mutex.RLock()
event.targets = append(event.targets, target)
@ -535,13 +562,23 @@ func (client *Client) Part(channels ...string) {
client.SendQueuedf("PART %s", strings.Join(channels, ","))
}
// Quit sends a quit message and marks the client as having quit, which
// means HasQuit() will return true.
func (client *Client) Quit(reason string) {
client.mutex.Lock()
client.quit = true
client.mutex.Unlock()
client.SendQueuedf("QUIT :%s", reason)
}
// Target gets a target by kind and name
func (client *Client) Target(kind string, name string) Target {
client.mutex.RLock()
defer client.mutex.RUnlock()
for _, target := range client.targets {
if target.Kind() == kind && target.Name() == name {
if target.Kind() == kind && strings.EqualFold(name, target.Name()) {
return target
}
}
@ -631,6 +668,12 @@ func (client *Client) AddTarget(target Target) (id string, err error) {
client.targets = append(client.targets, target)
client.targetIds[target] = id
event := NewEvent("hook", "add_target")
event.Args = []string{client.targetIds[target], target.Kind(), target.Name()}
event.targets = []Target{target}
event.targetIds[target] = id
client.EmitNonBlocking(event)
return
}
@ -647,6 +690,10 @@ func (client *Client) RemoveTarget(target Target) (id string, err error) {
if target == client.targets[i] {
id = client.targetIds[target]
event := NewEvent("hook", "remove_target")
event.Args = []string{client.targetIds[target], target.Kind(), target.Name()}
client.EmitNonBlocking(event)
client.targets[i] = client.targets[len(client.targets)-1]
client.targets = client.targets[:len(client.targets)-1]
delete(client.targetIds, target)
@ -834,6 +881,20 @@ func (client *Client) handleEvent(event *Event) {
}
client.mutex.RUnlock()
// Clear connection-specific data
client.mutex.Lock()
client.nick = ""
client.user = ""
client.host = ""
client.capsRequested = client.capsRequested[:0]
for key := range client.capData {
delete(client.capData, key)
}
for key := range client.capEnabled {
delete(client.capEnabled, key)
}
client.mutex.Unlock()
// Start registration.
_ = client.Sendf("NICK %s", nick)
_ = client.Sendf("USER %s 8 * :%s", client.config.User, client.config.RealName)

33
cmd/ircrepl/main.go

@ -20,6 +20,7 @@ var flagUser = flag.String("user", "test", "The client user/ident")
var flagPass = flag.String("pass", "", "The server password")
var flagServer = flag.String("server", "localhost:6667", "The server to connect to")
var flagSsl = flag.Bool("ssl", false, "Whether to connect securely")
var flagSkipVerify = flag.Bool("skip-verify", false, "Skip SSL verification")
func main() {
ctx, cancel := context.WithCancel(context.Background())
@ -28,11 +29,12 @@ func main() {
flag.Parse()
client := irc.New(ctx, irc.Config{
Nick: *flagNick,
User: *flagUser,
Alternatives: strings.Split(*flagAlts, ","),
Password: *flagPass,
Languages: []string{"no_NB", "no", "en_US", "en"},
Nick: *flagNick,
User: *flagUser,
Alternatives: strings.Split(*flagAlts, ","),
Password: *flagPass,
Languages: []string{"no_NB", "no", "en_US", "en"},
SkipSSLVerification: *flagSkipVerify,
})
client.AddHandler(handlers.Input)
@ -41,6 +43,7 @@ func main() {
err := client.Connect(*flagServer, *flagSsl)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to connect: %s", err)
os.Exit(1)
}
var target irc.Target
@ -50,18 +53,18 @@ func main() {
if client.ISupport().IsChannel(name) {
log.Println("Set target channel", name)
target = client.Channel(name)
target = client.Target("target", name)
} else if len(name) > 0 {
log.Println("Set target query", name)
target = client.Query(name)
target = client.Target("query", name)
} else {
log.Println("Set target status")
target = client.Status()
target = client.Target("status", "status")
}
if target == nil {
log.Println("Target does not exist, set to status")
target = client.Status()
target = client.Target("status", "status")
}
event.PreventDefault()
@ -80,6 +83,18 @@ func main() {
return
}
if event.Name() == "hook.remove_target" {
if target != nil && target.Name() == event.Arg(2) && target.Kind() == event.Arg(1) {
log.Println("Unset target ", event.Arg(1), event.Arg(2))
target = nil
}
}
if event.Name() == "hook.add_target" {
log.Println("Set target ", event.Arg(1), event.Arg(2))
target = client.Target(event.Arg(1), event.Arg(2))
}
j, err := json.MarshalIndent(event, "", " ")
if err != nil {
return

24
handlers/input.go

@ -122,17 +122,29 @@ func Input(event *irc.Event, client *irc.Client) {
event.PreventDefault()
if event.Text == "" {
client.EmitNonBlocking(irc.NewErrorEvent("input", "Usage: /m <text...>"))
client.EmitNonBlocking(irc.NewErrorEvent("input", "Usage: /m <modes and args...>"))
break
}
channel := event.ChannelTarget()
if channel == nil {
client.EmitNonBlocking(irc.NewErrorEvent("input", "Target is not a channel"))
break
if channel := event.ChannelTarget(); channel != nil {
client.SendQueuedf("MODE %s %s", channel.Name(), event.Text)
} else if status := event.StatusTarget(); status != nil {
client.SendQueuedf("MODE %s %s", client.Nick(), event.Text)
} else {
client.EmitNonBlocking(irc.NewErrorEvent("input", "Target is not a channel or status"))
}
}
case "input.quit", "input.disconnect":
{
event.PreventDefault()
reason := event.Text
if reason == "" {
reason = "Client Quit"
}
client.SendQueuedf("MODE %s %s", channel.Name(), event.Text)
client.Quit(reason)
}
}
}

4
isupport/isupport.go

@ -199,6 +199,10 @@ func (isupport *ISupport) Prefixes(modes string) string {
// IsChannel returns whether the target name is a channel.
func (isupport *ISupport) IsChannel(targetName string) bool {
if len(targetName) < 1 {
return false
}
isupport.lock.RLock()
defer isupport.lock.RUnlock()

4
query.go

@ -19,8 +19,8 @@ func (query *Query) Name() string {
return query.user.Nick
}
func (query *Query) State() TargetState {
return TargetState{
func (query *Query) State() ClientStateTarget {
return ClientStateTarget{
Kind: "query",
Name: query.user.Nick,
Users: []list.User{query.user},

27
state.go

@ -5,25 +5,24 @@ import (
"github.com/gissleh/irc/list"
)
// ClientState is a serializable snapshot of the client's state.
type ClientState struct {
ID string `json:"id"`
Nick string `json:"nick"`
User string `json:"user"`
Host string `json:"host"`
Connected bool `json:"connected"`
Ready bool `json:"quit"`
ISupport *isupport.State `json:"isupport"`
Caps []string `json:"caps"`
Targets []TargetState `json:"targets"`
ID string `json:"id"`
Nick string `json:"nick"`
User string `json:"user"`
Host string `json:"host"`
Connected bool `json:"connected"`
Ready bool `json:"ready"`
Quit bool `json:"quit"`
ISupport *isupport.State `json:"isupport"`
Caps []string `json:"caps"`
Targets []ClientStateTarget `json:"targets"`
}
type TargetState struct {
// ClientStateTarget is a part of the ClientState representing a target's state at the time of snapshot.
type ClientStateTarget struct {
ID string `json:"id"`
Kind string `json:"kind"`
Name string `json:"name"`
Users []list.User `json:"users,omitempty"`
}
type EventData struct {
}

4
status.go

@ -14,8 +14,8 @@ func (status *Status) Name() string {
return "Status"
}
func (status *Status) State() TargetState {
return TargetState{
func (status *Status) State() ClientStateTarget {
return ClientStateTarget{
Kind: "status",
Name: "Status",
Users: nil,

2
target.go

@ -6,5 +6,5 @@ type Target interface {
Kind() string
Name() string
Handle(event *Event, client *Client)
State() TargetState
State() ClientStateTarget
}
Loading…
Cancel
Save