mirror of
https://github.com/shazow/ssh-chat.git
synced 2025-04-12 15:17:16 +03:00
progress: porting over v2 manually
This commit is contained in:
parent
e72af3ad8e
commit
1dba5be9cc
@ -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"
|
||||||
|
@ -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
24
chat/member.go
Normal 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()
|
||||||
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
44
chat/room.go
44
chat/room.go
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
145
host.go
@ -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
43
member.go
Normal 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
48
multiscreen.go
Normal 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
41
whois.go
Normal 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()))
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user