// 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
}
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
}
// 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
},
})
joined time.Time
msg chan Message
done chan struct{}
+ replyTo *User // Set when user gets a /msg, for replying.
closed bool
closeOnce sync.Once
}
--- /dev/null
+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
+ },
+ })
+}
package main
import (
+ "errors"
"fmt"
"io"
"strings"
"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 {
// Make our own commands registry instance.
commands := chat.Commands{}
chat.InitCommands(&commands)
+ h.InitCommands(&commands)
ch.SetCommands(commands)
go ch.Serve()
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
+ },
+ })
}
+++ /dev/null
-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
-}
-// 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) {