progress: porting over v2 manually

This commit is contained in:
Andrey Petrov 2018-06-05 19:31:34 -04:00
parent e72af3ad8e
commit 1dba5be9cc
12 changed files with 319 additions and 117 deletions

View File

@ -25,10 +25,6 @@
branch = "master" branch = "master"
name = "github.com/alexcesaro/log" name = "github.com/alexcesaro/log"
[[constraint]]
branch = "master"
name = "github.com/dustin/go-humanize"
[[constraint]] [[constraint]]
branch = "master" branch = "master"
name = "github.com/howeyc/gopass" name = "github.com/howeyc/gopass"

View File

@ -6,6 +6,7 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"io"
"strings" "strings"
"github.com/shazow/ssh-chat/chat/message" "github.com/shazow/ssh-chat/chat/message"
@ -25,6 +26,10 @@ var ErrMissingArg = errors.New("missing argument")
// The error returned when a command is added without a prefix. // The error returned when a command is added without a prefix.
var ErrMissingPrefix = errors.New("command missing prefix") var ErrMissingPrefix = errors.New("command missing prefix")
// The error returned when we fail to find a corresponding userMember struct
// for an ID. This should not happen, probably a bug somewhere if encountered.
var ErrMissingMember = errors.New("failed to find member")
// Command is a definition of a handler for a command. // Command is a definition of a handler for a command.
type Command struct { type Command struct {
// The command's key, such as /foo // The command's key, such as /foo
@ -132,8 +137,7 @@ func InitCommands(c *Commands) {
Prefix: "/exit", Prefix: "/exit",
Help: "Exit the chat.", Help: "Exit the chat.",
Handler: func(room *Room, msg message.CommandMsg) error { Handler: func(room *Room, msg message.CommandMsg) error {
msg.From().Close() return msg.From().(io.Closer).Close()
return nil
}, },
}) })
c.Alias("/exit", "/quit") c.Alias("/exit", "/quit")
@ -173,14 +177,14 @@ func InitCommands(c *Commands) {
Prefix: "/names", Prefix: "/names",
Help: "List users who are connected.", Help: "List users who are connected.",
Handler: func(room *Room, msg message.CommandMsg) error { Handler: func(room *Room, msg message.CommandMsg) error {
theme := msg.From().Config().Theme theme := msg.From().(Member).Config().Theme
colorize := func(u *message.User) string { colorize := func(u Member) string {
return theme.ColorName(u) return theme.ColorName(u)
} }
if theme == nil { if theme == nil {
colorize = func(u *message.User) string { colorize = func(u Member) string {
return u.Name() return u.Name()
} }
} }
@ -188,7 +192,7 @@ func InitCommands(c *Commands) {
names := room.Members.ListPrefix("") names := room.Members.ListPrefix("")
colNames := make([]string, len(names)) colNames := make([]string, len(names))
for i, uname := range names { for i, uname := range names {
colNames[i] = colorize(uname.Value().(*Member).User) colNames[i] = colorize(uname.Value().(Member))
} }
body := fmt.Sprintf("%d connected: %s", len(colNames), strings.Join(colNames, ", ")) body := fmt.Sprintf("%d connected: %s", len(colNames), strings.Join(colNames, ", "))
@ -203,7 +207,7 @@ func InitCommands(c *Commands) {
PrefixHelp: "[colors|...]", PrefixHelp: "[colors|...]",
Help: "Set your color theme.", Help: "Set your color theme.",
Handler: func(room *Room, msg message.CommandMsg) error { Handler: func(room *Room, msg message.CommandMsg) error {
user := msg.From() user := msg.From().(Member)
args := msg.Args() args := msg.Args()
cfg := user.Config() cfg := user.Config()
if len(args) == 0 { if len(args) == 0 {
@ -242,7 +246,7 @@ func InitCommands(c *Commands) {
Prefix: "/quiet", Prefix: "/quiet",
Help: "Silence room announcements.", Help: "Silence room announcements.",
Handler: func(room *Room, msg message.CommandMsg) error { Handler: func(room *Room, msg message.CommandMsg) error {
u := msg.From() u := msg.From().(Member)
cfg := u.Config() cfg := u.Config()
cfg.Quiet = !cfg.Quiet cfg.Quiet = !cfg.Quiet
u.SetConfig(cfg) u.SetConfig(cfg)
@ -293,7 +297,7 @@ func InitCommands(c *Commands) {
Prefix: "/timestamp", Prefix: "/timestamp",
Help: "Timestamps after 30min of inactivity.", Help: "Timestamps after 30min of inactivity.",
Handler: func(room *Room, msg message.CommandMsg) error { Handler: func(room *Room, msg message.CommandMsg) error {
u := msg.From() u := msg.From().(Member)
cfg := u.Config() cfg := u.Config()
cfg.Timestamp = !cfg.Timestamp cfg.Timestamp = !cfg.Timestamp
u.SetConfig(cfg) u.SetConfig(cfg)
@ -314,11 +318,15 @@ func InitCommands(c *Commands) {
PrefixHelp: "[USER]", PrefixHelp: "[USER]",
Help: "Hide messages from USER, /unignore USER to stop hiding.", Help: "Hide messages from USER, /unignore USER to stop hiding.",
Handler: func(room *Room, msg message.CommandMsg) error { Handler: func(room *Room, msg message.CommandMsg) error {
from, ok := room.Member(msg.From())
if !ok {
return ErrMissingMember
}
id := strings.TrimSpace(strings.TrimLeft(msg.Body(), "/ignore")) id := strings.TrimSpace(strings.TrimLeft(msg.Body(), "/ignore"))
if id == "" { if id == "" {
// Print ignored names, if any. // Print ignored names, if any.
var names []string var names []string
msg.From().Ignored.Each(func(_ string, item set.Item) error { from.Ignored.Each(func(_ string, item set.Item) error {
names = append(names, item.Key()) names = append(names, item.Key())
return nil return nil
}) })
@ -342,7 +350,7 @@ func InitCommands(c *Commands) {
return fmt.Errorf("user not found: %s", id) return fmt.Errorf("user not found: %s", id)
} }
err := msg.From().Ignored.Add(set.Itemize(id, target)) err := from.Ignored.Add(set.Itemize(id, target))
if err == set.ErrCollision { if err == set.ErrCollision {
return fmt.Errorf("user already ignored: %s", id) return fmt.Errorf("user already ignored: %s", id)
} else if err != nil { } else if err != nil {
@ -363,7 +371,12 @@ func InitCommands(c *Commands) {
return errors.New("must specify user") return errors.New("must specify user")
} }
if err := msg.From().Ignored.Remove(id); err != nil { from, ok := room.Member(msg.From())
if !ok {
return ErrMissingMember
}
if err := from.Ignored.Remove(id); err != nil {
return err return err
} }

24
chat/member.go Normal file
View File

@ -0,0 +1,24 @@
package chat
import (
"github.com/shazow/ssh-chat/chat/message"
"github.com/shazow/ssh-chat/set"
)
// Member is a User with per-Room metadata attached to it.
type roomMember struct {
Member
Ignored *set.Set
}
type Member interface {
message.Author
Config() message.UserConfig
SetConfig(message.UserConfig)
Send(message.Message) error
SetID(string)
Close()
}

View File

@ -6,6 +6,12 @@ import (
"time" "time"
) )
type Author interface {
ID() string
Name() string
Color() int
}
// Message is an interface for messages. // Message is an interface for messages.
type Message interface { type Message interface {
Render(*Theme) string Render(*Theme) string
@ -16,15 +22,15 @@ type Message interface {
type MessageTo interface { type MessageTo interface {
Message Message
To() *User To() Author
} }
type MessageFrom interface { type MessageFrom interface {
Message Message
From() *User From() Author
} }
func ParseInput(body string, from *User) Message { func ParseInput(body string, from Author) Message {
m := NewPublicMsg(body, from) m := NewPublicMsg(body, from)
cmd, isCmd := m.ParseCommand() cmd, isCmd := m.ParseCommand()
if isCmd { if isCmd {
@ -69,10 +75,10 @@ func (m Msg) Timestamp() time.Time {
// PublicMsg is any message from a user sent to the room. // PublicMsg is any message from a user sent to the room.
type PublicMsg struct { type PublicMsg struct {
Msg Msg
from *User from Author
} }
func NewPublicMsg(body string, from *User) PublicMsg { func NewPublicMsg(body string, from Author) PublicMsg {
return PublicMsg{ return PublicMsg{
Msg: Msg{ Msg: Msg{
body: body, body: body,
@ -82,7 +88,7 @@ func NewPublicMsg(body string, from *User) PublicMsg {
} }
} }
func (m PublicMsg) From() *User { func (m PublicMsg) From() Author {
return m.from return m.from
} }
@ -137,10 +143,10 @@ func (m PublicMsg) String() string {
// sender to see the emote. // sender to see the emote.
type EmoteMsg struct { type EmoteMsg struct {
Msg Msg
from *User from Author
} }
func NewEmoteMsg(body string, from *User) *EmoteMsg { func NewEmoteMsg(body string, from Author) *EmoteMsg {
return &EmoteMsg{ return &EmoteMsg{
Msg: Msg{ Msg: Msg{
body: body, body: body,
@ -161,17 +167,17 @@ func (m EmoteMsg) String() string {
// PrivateMsg is a message sent to another user, not shown to anyone else. // PrivateMsg is a message sent to another user, not shown to anyone else.
type PrivateMsg struct { type PrivateMsg struct {
PublicMsg PublicMsg
to *User to Author
} }
func NewPrivateMsg(body string, from *User, to *User) PrivateMsg { func NewPrivateMsg(body string, from Author, to Author) PrivateMsg {
return PrivateMsg{ return PrivateMsg{
PublicMsg: NewPublicMsg(body, from), PublicMsg: NewPublicMsg(body, from),
to: to, to: to,
} }
} }
func (m PrivateMsg) To() *User { func (m PrivateMsg) To() Author {
return m.to return m.to
} }
@ -191,10 +197,10 @@ func (m PrivateMsg) String() string {
// to anyone else. Usually in response to something, like /help. // to anyone else. Usually in response to something, like /help.
type SystemMsg struct { type SystemMsg struct {
Msg Msg
to *User to Author
} }
func NewSystemMsg(body string, to *User) *SystemMsg { func NewSystemMsg(body string, to Author) *SystemMsg {
return &SystemMsg{ return &SystemMsg{
Msg: Msg{ Msg: Msg{
body: body, body: body,
@ -215,7 +221,7 @@ func (m *SystemMsg) String() string {
return fmt.Sprintf("-> %s", m.body) return fmt.Sprintf("-> %s", m.body)
} }
func (m *SystemMsg) To() *User { func (m *SystemMsg) To() Author {
return m.to return m.to
} }

View File

@ -126,13 +126,18 @@ func (t Theme) ID() string {
return t.id return t.id
} }
type coloredName interface {
Name() string
Color() int
}
// Colorize name string given some index // Colorize name string given some index
func (t Theme) ColorName(u *User) string { func (t Theme) ColorName(n coloredName) string {
if t.names == nil { if t.names == nil {
return u.Name() return n.Name()
} }
return t.names.Get(u.colorIdx).Format(u.Name()) return t.names.Get(n.Color()).Format(n.Name())
} }
// Colorize the PM string // Colorize the PM string

View File

@ -71,6 +71,10 @@ func (u *User) SetConfig(cfg UserConfig) {
u.mu.Unlock() u.mu.Unlock()
} }
func (u *User) Color() int {
return u.colorIdx
}
// Rename the user with a new Identifier. // Rename the user with a new Identifier.
func (u *User) SetID(id string) { func (u *User) SetID(id string) {
u.Identifier.SetID(id) u.Identifier.SetID(id)

View File

@ -21,11 +21,6 @@ var ErrRoomClosed = errors.New("room closed")
// as empty string. // as empty string.
var ErrInvalidName = errors.New("invalid name") var ErrInvalidName = errors.New("invalid name")
// Member is a User with per-Room metadata attached to it.
type Member struct {
*message.User
}
// Room definition, also a Set of User Items // Room definition, also a Set of User Items
type Room struct { type Room struct {
topic string topic string
@ -63,7 +58,7 @@ func (r *Room) Close() {
r.closeOnce.Do(func() { r.closeOnce.Do(func() {
r.closed = true r.closed = true
r.Members.Each(func(_ string, item set.Item) error { r.Members.Each(func(_ string, item set.Item) error {
item.Value().(*Member).Close() item.Value().(Member).Close()
return nil return nil
}) })
r.Members.Clear() r.Members.Clear()
@ -87,25 +82,28 @@ func (r *Room) HandleMsg(m message.Message) {
go r.HandleMsg(m) go r.HandleMsg(m)
} }
case message.MessageTo: case message.MessageTo:
user := m.To() if user, ok := r.Member(m.To()); ok {
user.Send(m) user.Send(m)
} else {
// Todo: Handle error?
}
default: default:
fromMsg, skip := m.(message.MessageFrom) fromMsg, skip := m.(message.MessageFrom)
var skipUser *message.User var skipAuthor message.Author
if skip { if skip {
skipUser = fromMsg.From() skipAuthor = fromMsg.From()
} }
r.history.Add(m) r.history.Add(m)
r.Members.Each(func(_ string, item set.Item) (err error) { r.Members.Each(func(_ string, item set.Item) (err error) {
user := item.Value().(*Member).User user := item.Value().(*roomMember)
if fromMsg != nil && user.Ignored.In(fromMsg.From().ID()) { if fromMsg != nil && user.Ignored.In(fromMsg.From().ID()) {
// Skip because ignored // Skip because ignored
return return
} }
if skip && skipUser == user { if skip && skipAuthor == user.Member.(message.Author) {
// Skip self // Skip self
return return
} }
@ -135,19 +133,22 @@ func (r *Room) Send(m message.Message) {
} }
// History feeds the room's recent message history to the user's handler. // History feeds the room's recent message history to the user's handler.
func (r *Room) History(u *message.User) { func (r *Room) History(u Member) {
for _, m := range r.history.Get(historyLen) { for _, m := range r.history.Get(historyLen) {
u.Send(m) u.Send(m)
} }
} }
// Join the room as a user, will announce. // Join the room as a user, will announce.
func (r *Room) Join(u *message.User) (*Member, error) { func (r *Room) Join(u Member) (*roomMember, error) {
// TODO: Check if closed // TODO: Check if closed
if u.ID() == "" { if u.ID() == "" {
return nil, ErrInvalidName return nil, ErrInvalidName
} }
member := &Member{u} member := &roomMember{
Member: u,
Ignored: set.New(),
}
err := r.Members.Add(set.Itemize(u.ID(), member)) err := r.Members.Add(set.Itemize(u.ID(), member))
if err != nil { if err != nil {
return nil, err return nil, err
@ -187,28 +188,29 @@ func (r *Room) Rename(oldID string, u message.Identifier) error {
// Member returns a corresponding Member object to a User if the Member is // Member returns a corresponding Member object to a User if the Member is
// present in this room. // present in this room.
func (r *Room) Member(u *message.User) (*Member, bool) { func (r *Room) Member(u message.Author) (*roomMember, bool) {
m, ok := r.MemberByID(u.ID()) m, ok := r.MemberByID(u.ID())
if !ok { if !ok {
return nil, false return nil, false
} }
// Check that it's the same user // Check that it's the same user
if m.User != u { if m.Member != u {
return nil, false return nil, false
} }
return m, true return m, true
} }
func (r *Room) MemberByID(id string) (*Member, bool) { func (r *Room) MemberByID(id string) (*roomMember, bool) {
m, err := r.Members.Get(id) m, err := r.Members.Get(id)
if err != nil { if err != nil {
return nil, false return nil, false
} }
return m.Value().(*Member), true rm, ok := m.Value().(*roomMember)
return rm, ok
} }
// IsOp returns whether a user is an operator in this room. // IsOp returns whether a user is an operator in this room.
func (r *Room) IsOp(u *message.User) bool { func (r *Room) IsOp(u message.Author) bool {
return r.Ops.In(u.ID()) return r.Ops.In(u.ID())
} }
@ -228,7 +230,7 @@ func (r *Room) NamesPrefix(prefix string) []string {
items := r.Members.ListPrefix(prefix) items := r.Members.ListPrefix(prefix)
names := make([]string, len(items)) names := make([]string, len(items))
for i, item := range items { for i, item := range items {
names[i] = item.Value().(*Member).User.Name() names[i] = item.Value().(*roomMember).Name()
} }
return names return names
} }

View File

@ -120,7 +120,6 @@ func main() {
fmt.Printf("Listening for connections on %v\n", s.Addr().String()) fmt.Printf("Listening for connections on %v\n", s.Addr().String())
host := sshchat.NewHost(s, auth) host := sshchat.NewHost(s, auth)
host.SetTheme(message.Themes[0])
host.Version = Version host.Version = Version
err = fromFile(options.Admin, func(line []byte) error { err = fromFile(options.Admin, func(line []byte) error {

145
host.go
View File

@ -38,12 +38,11 @@ type Host struct {
// Version string to print on /version // Version string to print on /version
Version string Version string
// Default theme
theme message.Theme
mu sync.Mutex mu sync.Mutex
motd string motd string
count int count int
oneScreen *multiScreen
} }
// NewHost creates a Host on top of an existing listener. // NewHost creates a Host on top of an existing listener.
@ -65,13 +64,6 @@ func NewHost(listener *sshd.SSHListener, auth *Auth) *Host {
return &h return &h
} }
// SetTheme sets the default theme for the host.
func (h *Host) SetTheme(theme message.Theme) {
h.mu.Lock()
h.theme = theme
h.mu.Unlock()
}
// SetMotd sets the host's message of the day. // SetMotd sets the host's message of the day.
func (h *Host) SetMotd(motd string) { func (h *Host) SetMotd(motd string) {
h.mu.Lock() h.mu.Lock()
@ -87,54 +79,31 @@ func (h *Host) isOp(conn sshd.Connection) bool {
return h.auth.IsOp(key) return h.auth.IsOp(key)
} }
// Connect a specific Terminal to this host and its room. func (h *Host) getScreen(term *sshd.Terminal) (screen *multiScreen, isNew bool) {
func (h *Host) Connect(term *sshd.Terminal) { if h.oneScreen != nil {
h.oneScreen.add(term)
return h.oneScreen, false
}
id := NewIdentity(term.Conn) id := NewIdentity(term.Conn)
user := message.NewUserScreen(id, term) user := message.NewUserScreen(id, term)
// TODO: Skip this if TERM is set to something dumb?
cfg := user.Config() cfg := user.Config()
cfg.Theme = &h.theme cfg.Theme = message.DefaultTheme
user.SetConfig(cfg) user.SetConfig(cfg)
go user.Consume()
// Close term once user is closed. h.oneScreen = &multiScreen{
defer user.Close() User: user,
defer term.Close()
h.mu.Lock()
motd := h.motd
count := h.count
h.count++
h.mu.Unlock()
// Send MOTD
if motd != "" {
user.Send(message.NewAnnounceMsg(motd))
} }
member, err := h.Join(user) return h.oneScreen, true
if err != nil { }
// Try again...
id.SetName(fmt.Sprintf("Guest%d", count))
member, err = h.Join(user)
}
if err != nil {
logger.Errorf("[%s] Failed to join: %s", term.Conn.RemoteAddr(), err)
return
}
// Successfully joined. func (h *Host) consumeScreen(term *sshd.Terminal, user *message.User) {
term.SetPrompt(GetPrompt(user)) term.SetPrompt(GetPrompt(user))
term.AutoCompleteCallback = h.AutoCompleteFunction(user) term.AutoCompleteCallback = h.AutoCompleteFunction(user)
user.SetHighlight(user.Name())
// Should the user be op'd on join?
if h.isOp(term.Conn) {
h.Room.Ops.Add(set.Itemize(member.ID(), member))
}
ratelimit := rateio.NewSimpleLimiter(3, time.Second*3) ratelimit := rateio.NewSimpleLimiter(3, time.Second*3)
logger.Debugf("[%s] Joined: %s", term.Conn.RemoteAddr(), user.Name())
for { for {
line, err := term.ReadLine() line, err := term.ReadLine()
if err == io.EOF { if err == io.EOF {
@ -175,7 +144,57 @@ func (h *Host) Connect(term *sshd.Terminal) {
user.SetHighlight(user.Name()) user.SetHighlight(user.Name())
} }
} }
}
// Connect a specific Terminal to this host and its room.
func (h *Host) Connect(term *sshd.Terminal) {
screen, isNew := h.getScreen(term)
user := screen.User
go user.Consume()
defer term.Close()
if !isNew {
h.consumeScreen(term, user)
return
}
// XXX: defer user.Close()
h.mu.Lock()
motd := h.motd
count := h.count
h.count++
h.mu.Unlock()
// Send MOTD
if motd != "" {
user.Send(message.NewAnnounceMsg(motd))
}
member, err := h.Join(user)
if err != nil {
// Try again...
user.SetID(fmt.Sprintf("Guest%d", count))
member, err = h.Join(user)
}
if err != nil {
logger.Errorf("[%s] Failed to join: %s", term.Conn.RemoteAddr(), err)
return
}
// Successfully joined.
user.SetHighlight(user.Name())
// Should the user be op'd on join?
if h.isOp(term.Conn) {
h.Room.Ops.Add(set.Itemize(member.ID(), member))
}
logger.Debugf("[%s] Joined: %s", term.Conn.RemoteAddr(), user.Name())
h.consumeScreen(term, user)
// XXX: Move this into a user close thing
err = h.Leave(user) err = h.Leave(user)
if err != nil { if err != nil {
logger.Errorf("[%s] Failed to leave: %s", term.Conn.RemoteAddr(), err) logger.Errorf("[%s] Failed to leave: %s", term.Conn.RemoteAddr(), err)
@ -267,12 +286,13 @@ func (h *Host) AutoCompleteFunction(u *message.User) func(line string, pos int,
} }
// GetUser returns a message.User based on a name. // GetUser returns a message.User based on a name.
func (h *Host) GetUser(name string) (*message.User, bool) { func (h *Host) GetUser(name string) (User, bool) {
m, ok := h.MemberByID(name) m, ok := h.MemberByID(name)
if !ok { if !ok {
return nil, false return nil, false
} }
return m.User, true u, ok := m.Member.(User)
return u, ok
} }
// InitCommands adds host-specific commands to a Commands container. These will // InitCommands adds host-specific commands to a Commands container. These will
@ -318,7 +338,7 @@ func (h *Host) InitCommands(c *chat.Commands) {
return errors.New("must specify message") return errors.New("must specify message")
} }
target := msg.From().ReplyTo() target := msg.From().(Member).ReplyTo()
if target == nil { if target == nil {
return errors.New("no message to reply to") return errors.New("no message to reply to")
} }
@ -354,13 +374,12 @@ func (h *Host) InitCommands(c *chat.Commands) {
return errors.New("user not found") return errors.New("user not found")
} }
id := target.Identifier.(*Identity)
var whois string var whois string
switch room.IsOp(msg.From()) { switch h.IsOp(msg.From()) {
case true: case true:
whois = id.WhoisAdmin() whois = whoisAdmin(target)
case false: case false:
whois = id.Whois() whois = whoisPublic(target)
} }
room.Send(message.NewSystemMsg(whois, msg.From())) room.Send(message.NewSystemMsg(whois, msg.From()))
@ -440,15 +459,16 @@ func (h *Host) InitCommands(c *chat.Commands) {
until, _ = time.ParseDuration(args[1]) until, _ = time.ParseDuration(args[1])
} }
id := target.Identifier.(*Identity) for _, conn := range target.Connections() {
h.auth.Ban(id.PublicKey(), until) h.auth.Ban(conn.PublicKey(), until)
h.auth.BanAddr(id.RemoteAddr(), until) h.auth.BanAddr(conn.RemoteAddr(), until)
}
body := fmt.Sprintf("%s was banned by %s.", target.Name(), msg.From().Name()) body := fmt.Sprintf("%s was banned by %s.", target.Name(), msg.From().Name())
room.Send(message.NewAnnounceMsg(body)) room.Send(message.NewAnnounceMsg(body))
target.Close() target.Close()
logger.Debugf("Banned: \n-> %s", id.Whois()) logger.Debugf("Banned: \n-> %s", whoisAdmin(target))
return nil return nil
}, },
@ -504,17 +524,18 @@ func (h *Host) InitCommands(c *chat.Commands) {
until, _ = time.ParseDuration(args[1]) until, _ = time.ParseDuration(args[1])
} }
member, ok := room.MemberByID(args[0]) user, ok := h.GetUser(args[0])
if !ok { if !ok {
return errors.New("user not found") return errors.New("user not found")
} }
room.Ops.Add(set.Itemize(member.ID(), member)) room.Ops.Add(set.Itemize(user.ID(), user))
id := member.Identifier.(*Identity) for _, conn := range user.Connections() {
h.auth.Op(id.PublicKey(), until) h.auth.Op(conn.PublicKey(), until)
}
body := fmt.Sprintf("Made op by %s.", msg.From().Name()) body := fmt.Sprintf("Made op by %s.", msg.From().Name())
room.Send(message.NewSystemMsg(body, member.User)) room.Send(message.NewSystemMsg(body, user))
return nil return nil
}, },

43
member.go Normal file
View File

@ -0,0 +1,43 @@
package sshchat
import (
"sync"
"time"
"github.com/shazow/ssh-chat/chat"
"github.com/shazow/ssh-chat/chat/message"
"github.com/shazow/ssh-chat/sshd"
)
type client struct {
Member
sync.Mutex
conns []sshd.Connection
}
func (cl *client) Connections() []sshd.Connection {
return cl.conns
}
func (cl *client) Close() {
// TODO: Stack errors?
for _, conn := range cl.conns {
conn.Close()
}
}
type Member interface {
chat.Member
Joined() time.Time
Prompt() string
ReplyTo() message.Author
SetHighlight(string) error
SetReplyTo(message.Author)
}
type User interface {
Member
Connections() []sshd.Connection
}

48
multiscreen.go Normal file
View File

@ -0,0 +1,48 @@
package sshchat
import (
"io"
"sync"
"github.com/shazow/ssh-chat/chat/message"
)
type multiScreen struct {
*message.User
mu sync.Mutex
writers []io.WriteCloser
}
func (s *multiScreen) add(w io.WriteCloser) {
s.mu.Lock()
s.writers = append(s.writers, w)
s.mu.Unlock()
}
func (s *multiScreen) Write(p []byte) (n int, err error) {
s.mu.Lock()
defer s.mu.Unlock()
for i, w := range s.writers {
n, err = w.Write(p)
if err == nil && n != len(p) {
err = io.ErrShortWrite
}
if err == nil {
continue
}
if err != nil && len(s.writers) == 1 {
// Once we're out of writers, fail.
return len(p), err
}
// Remove faulty writer
w.Close()
s.writers[i] = s.writers[len(s.writers)-1]
s.writers[len(s.writers)-1] = nil
s.writers = s.writers[:len(s.writers)-1]
// TODO: Emit error to a callback or something?
}
return len(p), nil
}

41
whois.go Normal file
View File

@ -0,0 +1,41 @@
package sshchat
import (
"net"
"time"
"github.com/shazow/ssh-chat/chat"
"github.com/shazow/ssh-chat/chat/message"
"github.com/shazow/ssh-chat/sshd"
)
// Helpers for printing whois messages
func whoisPublic(u User) string {
fingerprint := "(no public key)"
// FIXME: Use all connections?
conn := u.Connections()[0]
if conn.PublicKey() != nil {
fingerprint = sshd.Fingerprint(conn.PublicKey())
}
return "name: " + u.Name() + message.Newline +
" > fingerprint: " + fingerprint + message.Newline +
" > client: " + chat.SanitizeData(string(conn.ClientVersion())) + message.Newline +
" > joined: " + humanSince(time.Since(u.Joined())) + " ago"
}
func whoisAdmin(u User) string {
// FIXME: Use all connections?
conn := u.Connections()[0]
ip, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
fingerprint := "(no public key)"
if conn.PublicKey() != nil {
fingerprint = sshd.Fingerprint(conn.PublicKey())
}
return "name: " + u.Name() + message.Newline +
" > ip: " + ip + message.Newline +
" > fingerprint: " + fingerprint + message.Newline +
" > client: " + chat.SanitizeData(string(conn.ClientVersion())) + message.Newline +
" > joined: " + humanSince(time.Since(u.Joined()))
}