/kick and /msg
authorAndrey Petrov <andrey.petrov@shazow.net>
Sun, 11 Jan 2015 02:05:31 +0000 (18:05 -0800)
committerAndrey Petrov <andrey.petrov@shazow.net>
Sun, 11 Jan 2015 02:05:31 +0000 (18:05 -0800)
chat/channel.go
chat/command.go
chat/user.go
command.go [new file with mode: 0644]
host.go
sshd/multi.go [deleted file]
sshd/pty.go

index 34d981f3040eb7666714cba57a3bbe8e4ca41b61..4fadb69dd2e6a9f0d23a9f1da22675c2a40d5fe7 100644 (file)
@@ -142,12 +142,20 @@ func (ch *Channel) Leave(u *User) error {
 // Member returns a corresponding Member object to a User if the Member is
 // present in this channel.
 func (ch *Channel) Member(u *User) (*Member, bool) {
-       m, err := ch.members.Get(u.Id())
-       if err != nil {
+       m, ok := ch.MemberById(u.Id())
+       if !ok {
                return nil, false
        }
        // Check that it's the same user
-       if m.(*Member).User != u {
+       if m.User != u {
+               return nil, false
+       }
+       return m, true
+}
+
+func (ch *Channel) MemberById(id Id) (*Member, bool) {
+       m, err := ch.members.Get(id)
+       if err != nil {
                return nil, false
        }
        return m.(*Member), true
index 1328c4967a9a7940b6e4bc19de3cf9ef5e1238d4..351d090360237352368ef6516a183833096fe1a5 100644 (file)
@@ -86,7 +86,7 @@ func (c Commands) Help(showOp bool) string {
        }
        help := "Available commands:" + Newline + NewCommandsHelp(normal).String()
        if showOp {
-               help += Newline + "Operator commands:" + Newline + NewCommandsHelp(op).String()
+               help += Newline + "-> Operator commands:" + Newline + NewCommandsHelp(op).String()
        }
        return help
 }
@@ -231,12 +231,12 @@ func InitCommands(c *Commands) {
 
                        // TODO: Add support for fingerprint-based op'ing. This will
                        // probably need to live in host land.
-                       member, err := channel.members.Get(Id(args[0]))
-                       if err != nil {
+                       member, ok := channel.MemberById(Id(args[0]))
+                       if !ok {
                                return errors.New("user not found")
                        }
 
-                       member.(*Member).Op = true
+                       member.Op = true
                        return nil
                },
        })
index 203a48aaaa6551b55741a5b0e7e414b0f9cab88d..75ea3307fbcb18dc1c293abcc45d1a1d2eae29a1 100644 (file)
@@ -20,6 +20,7 @@ type User struct {
        joined    time.Time
        msg       chan Message
        done      chan struct{}
+       replyTo   *User // Set when user gets a /msg, for replying.
        closed    bool
        closeOnce sync.Once
 }
diff --git a/command.go b/command.go
new file mode 100644 (file)
index 0000000..4700054
--- /dev/null
@@ -0,0 +1,39 @@
+package main
+
+import (
+       "errors"
+
+       "github.com/shazow/ssh-chat/chat"
+)
+
+// InitCommands adds host-specific commands to a Commands container.
+func InitCommands(h *Host, c *chat.Commands) {
+       c.Add(chat.Command{
+               Op:         true,
+               Prefix:     "/msg",
+               PrefixHelp: "USER MESSAGE",
+               Help:       "Send MESSAGE to USER.",
+               Handler: func(channel *chat.Channel, msg chat.CommandMsg) error {
+                       if !channel.IsOp(msg.From()) {
+                               return errors.New("must be op")
+                       }
+
+                       args := msg.Args()
+                       switch len(args) {
+                       case 0:
+                               return errors.New("must specify user")
+                       case 1:
+                               return errors.New("must specify message")
+                       }
+
+                       member, ok := channel.MemberById(chat.Id(args[0]))
+                       if !ok {
+                               return errors.New("user not found")
+                       }
+
+                       m := chat.NewPrivateMsg("hello", msg.From(), member.User)
+                       channel.Send(m)
+                       return nil
+               },
+       })
+}
diff --git a/host.go b/host.go
index 51a1fb58ae120cbf7765f5fcf8e0695dc70b2ff7..d3510e712ec96b9e16371f7cc6debf1bed93761a 100644 (file)
--- a/host.go
+++ b/host.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+       "errors"
        "fmt"
        "io"
        "strings"
@@ -9,6 +10,15 @@ import (
        "github.com/shazow/ssh-chat/sshd"
 )
 
+// GetPrompt will render the terminal prompt string based on the user.
+func GetPrompt(user *chat.User) string {
+       name := user.Name()
+       if user.Config.Theme != nil {
+               name = user.Config.Theme.ColorName(user)
+       }
+       return fmt.Sprintf("[%s] ", name)
+}
+
 // Host is the bridge between sshd and chat modules
 // TODO: Should be easy to add support for multiple channels, if we want.
 type Host struct {
@@ -35,6 +45,7 @@ func NewHost(listener *sshd.SSHListener) *Host {
        // Make our own commands registry instance.
        commands := chat.Commands{}
        chat.InitCommands(&commands)
+       h.InitCommands(&commands)
        ch.SetCommands(commands)
 
        go ch.Serve()
@@ -159,11 +170,58 @@ func (h *Host) AutoCompleteFunction(line string, pos int, key rune) (newLine str
        return
 }
 
-// GetPrompt will render the terminal prompt string based on the user.
-func GetPrompt(user *chat.User) string {
-       name := user.Name()
-       if user.Config.Theme != nil {
-               name = user.Config.Theme.ColorName(user)
-       }
-       return fmt.Sprintf("[%s] ", name)
+// InitCommands adds host-specific commands to a Commands container. These will
+// override any existing commands.
+func (h *Host) InitCommands(c *chat.Commands) {
+       c.Add(chat.Command{
+               Prefix:     "/msg",
+               PrefixHelp: "USER MESSAGE",
+               Help:       "Send MESSAGE to USER.",
+               Handler: func(channel *chat.Channel, msg chat.CommandMsg) error {
+                       args := msg.Args()
+                       switch len(args) {
+                       case 0:
+                               return errors.New("must specify user")
+                       case 1:
+                               return errors.New("must specify message")
+                       }
+
+                       member, ok := channel.MemberById(chat.Id(args[0]))
+                       if !ok {
+                               return errors.New("user not found")
+                       }
+
+                       m := chat.NewPrivateMsg(strings.Join(args[2:], " "), msg.From(), member.User)
+                       channel.Send(m)
+                       return nil
+               },
+       })
+
+       // Op commands
+       c.Add(chat.Command{
+               Op:         true,
+               Prefix:     "/kick",
+               PrefixHelp: "USER",
+               Help:       "Kick USER from the server.",
+               Handler: func(channel *chat.Channel, msg chat.CommandMsg) error {
+                       if !channel.IsOp(msg.From()) {
+                               return errors.New("must be op")
+                       }
+
+                       args := msg.Args()
+                       if len(args) == 0 {
+                               return errors.New("must specify user")
+                       }
+
+                       member, ok := channel.MemberById(chat.Id(args[0]))
+                       if !ok {
+                               return errors.New("user not found")
+                       }
+
+                       body := fmt.Sprintf("%s was kicked by %s.", member.Name(), msg.From().Name())
+                       channel.Send(chat.NewAnnounceMsg(body))
+                       member.User.Close()
+                       return nil
+               },
+       })
 }
diff --git a/sshd/multi.go b/sshd/multi.go
deleted file mode 100644 (file)
index 62447a7..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-package sshd
-
-import (
-       "fmt"
-       "io"
-       "strings"
-)
-
-// Keep track of multiple errors and coerce them into one error
-type MultiError []error
-
-func (e MultiError) Error() string {
-       switch len(e) {
-       case 0:
-               return ""
-       case 1:
-               return e[0].Error()
-       default:
-               errs := []string{}
-               for _, err := range e {
-                       errs = append(errs, err.Error())
-               }
-               return fmt.Sprintf("%d errors: %s", strings.Join(errs, "; "))
-       }
-}
-
-// Keep track of multiple closers and close them all as one closer
-type MultiCloser []io.Closer
-
-func (c MultiCloser) Close() error {
-       errors := MultiError{}
-       for _, closer := range c {
-               err := closer.Close()
-               if err != nil {
-                       errors = append(errors, err)
-               }
-       }
-       if len(errors) == 0 {
-               return nil
-       }
-       return errors
-}
index 5aecc3eb075992bde706ab109f81d7029566f6d1..06d34f037683fc11a3f4856120da842548a8c30c 100644 (file)
@@ -1,8 +1,9 @@
-// Borrowed from go.crypto circa 2011
 package sshd
 
 import "encoding/binary"
 
+// Helpers below are borrowed from go.crypto circa 2011:
+
 // parsePtyRequest parses the payload of the pty-req message and extracts the
 // dimensions of the terminal. See RFC 4254, section 6.2.
 func parsePtyRequest(s []byte) (width, height int, ok bool) {