mirror of
https://github.com/gissleh/irc.git
synced 2026-09-28 23:25:35 +00:00
+2
-2
@@ -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(),
|
||||
|
||||
@@ -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)
|
||||
|
||||
+24
-9
@@ -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
|
||||
|
||||
+18
-6
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user