mirror of
https://github.com/shazow/ssh-chat.git
synced 2025-06-07 19:03:17 +03:00
broken: Use Member rather than message.User when possible; chat tests are broken.
This commit is contained in:
parent
9ecd2a6fa2
commit
33a76bb7f4
@ -5,6 +5,7 @@ package chat
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/shazow/ssh-chat/chat/message"
|
"github.com/shazow/ssh-chat/chat/message"
|
||||||
@ -110,7 +111,7 @@ func InitCommands(c *Commands) {
|
|||||||
c.Add(Command{
|
c.Add(Command{
|
||||||
Prefix: "/help",
|
Prefix: "/help",
|
||||||
Handler: func(room *Room, msg message.CommandMsg) error {
|
Handler: func(room *Room, msg message.CommandMsg) error {
|
||||||
op := room.IsOp(msg.From())
|
op := room.IsOp(msg.From().(Member))
|
||||||
room.Send(message.NewSystemMsg(room.commands.Help(op), msg.From()))
|
room.Send(message.NewSystemMsg(room.commands.Help(op), msg.From()))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@ -135,8 +136,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")
|
||||||
@ -150,7 +150,7 @@ func InitCommands(c *Commands) {
|
|||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return ErrMissingArg
|
return ErrMissingArg
|
||||||
}
|
}
|
||||||
member, ok := room.MemberByID(msg.From().ID())
|
member, ok := room.MemberByID(msg.From().(Member).ID())
|
||||||
if !ok {
|
if !ok {
|
||||||
return ErrMissingMember
|
return ErrMissingMember
|
||||||
}
|
}
|
||||||
@ -184,7 +184,7 @@ func InitCommands(c *Commands) {
|
|||||||
PrefixHelp: "[colors|...]",
|
PrefixHelp: "[colors|...]",
|
||||||
Help: "Set your color theme. (More themes: solarized, mono, hacker)",
|
Help: "Set your color theme. (More themes: solarized, mono, hacker)",
|
||||||
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 {
|
||||||
@ -215,7 +215,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)
|
||||||
@ -253,7 +253,7 @@ 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())
|
from, ok := room.Member(msg.From().(Member))
|
||||||
if !ok {
|
if !ok {
|
||||||
return ErrMissingMember
|
return ErrMissingMember
|
||||||
}
|
}
|
||||||
@ -278,7 +278,7 @@ func InitCommands(c *Commands) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if id == msg.From().ID() {
|
if id == msg.From().(Member).ID() {
|
||||||
return errors.New("cannot ignore self")
|
return errors.New("cannot ignore self")
|
||||||
}
|
}
|
||||||
target, ok := room.MemberByID(id)
|
target, ok := room.MemberByID(id)
|
||||||
@ -302,7 +302,7 @@ func InitCommands(c *Commands) {
|
|||||||
Prefix: "/unignore",
|
Prefix: "/unignore",
|
||||||
PrefixHelp: "USER",
|
PrefixHelp: "USER",
|
||||||
Handler: func(room *Room, msg message.CommandMsg) error {
|
Handler: func(room *Room, msg message.CommandMsg) error {
|
||||||
from, ok := room.Member(msg.From())
|
from, ok := room.Member(msg.From().(Member))
|
||||||
if !ok {
|
if !ok {
|
||||||
return ErrMissingMember
|
return ErrMissingMember
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,9 @@ type roomMember struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Member interface {
|
type Member interface {
|
||||||
ID() string
|
message.Author
|
||||||
|
|
||||||
Name() string
|
ID() string
|
||||||
SetName(string)
|
SetName(string)
|
||||||
|
|
||||||
Config() message.UserConfig
|
Config() message.UserConfig
|
||||||
|
@ -6,6 +6,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Author interface {
|
||||||
|
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 +21,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 +74,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 +87,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 +142,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 +166,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 +196,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 +220,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,12 +127,12 @@ func (t Theme) ID() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Colorize name string given some index
|
// Colorize name string given some index
|
||||||
func (t Theme) ColorName(u *User) string {
|
func (t Theme) ColorName(u Author) string {
|
||||||
if t.names == nil {
|
if t.names == nil {
|
||||||
return u.Name()
|
return u.Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
return t.names.Get(u.colorIdx).Format(u.Name())
|
return t.names.Get(u.Color()).Format(u.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Colorize the PM string
|
// Colorize the PM string
|
||||||
|
@ -16,10 +16,53 @@ const reHighlight = `\b(%s)\b`
|
|||||||
|
|
||||||
var ErrUserClosed = errors.New("user closed")
|
var ErrUserClosed = errors.New("user closed")
|
||||||
|
|
||||||
|
// User container that knows about writing to an IO screen.
|
||||||
|
type UserScreen struct {
|
||||||
|
*User
|
||||||
|
io.WriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserScreen) Close() error {
|
||||||
|
u.User.Close()
|
||||||
|
return u.WriteCloser.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleMsg will render the message to the screen, blocking.
|
||||||
|
func (u *UserScreen) HandleMsg(m Message) error {
|
||||||
|
r := u.render(m)
|
||||||
|
_, err := u.Write([]byte(r))
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("Write failed to %s, closing: %s", u.Name(), err)
|
||||||
|
u.User.Close()
|
||||||
|
u.WriteCloser.Close()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume message buffer into the handler. Will block, should be called in a
|
||||||
|
// goroutine.
|
||||||
|
func (u *UserScreen) Consume() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-u.done:
|
||||||
|
return
|
||||||
|
case m, ok := <-u.msg:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
u.HandleMsg(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume one message and stop, mostly for testing
|
||||||
|
// TODO: Stop using it and remove it.
|
||||||
|
func (u *UserScreen) ConsumeOne() Message {
|
||||||
|
return <-u.msg
|
||||||
|
}
|
||||||
|
|
||||||
// User definition, implemented set Item interface and io.Writer
|
// User definition, implemented set Item interface and io.Writer
|
||||||
type User struct {
|
type User struct {
|
||||||
io.WriteCloser
|
|
||||||
|
|
||||||
colorIdx int
|
colorIdx int
|
||||||
joined time.Time
|
joined time.Time
|
||||||
closeOnce sync.Once
|
closeOnce sync.Once
|
||||||
@ -29,7 +72,7 @@ type User struct {
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
name string
|
name string
|
||||||
config UserConfig
|
config UserConfig
|
||||||
replyTo *User // Set when user gets a /msg, for replying.
|
replyTo Author // Set when user gets a /msg, for replying.
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUser(name string) *User {
|
func NewUser(name string) *User {
|
||||||
@ -45,11 +88,11 @@ func NewUser(name string) *User {
|
|||||||
return &u
|
return &u
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUserScreen(name string, screen io.WriteCloser) *User {
|
func NewUserScreen(name string, screen io.WriteCloser) *UserScreen {
|
||||||
u := NewUser(name)
|
return &UserScreen{
|
||||||
u.WriteCloser = screen
|
User: NewUser(name),
|
||||||
|
WriteCloser: screen,
|
||||||
return u
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *User) Config() UserConfig {
|
func (u *User) Config() UserConfig {
|
||||||
@ -70,6 +113,10 @@ func (u *User) ID() string {
|
|||||||
return SanitizeName(u.name)
|
return SanitizeName(u.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *User) Color() int {
|
||||||
|
return u.colorIdx
|
||||||
|
}
|
||||||
|
|
||||||
func (u *User) Name() string {
|
func (u *User) Name() string {
|
||||||
u.mu.Lock()
|
u.mu.Lock()
|
||||||
defer u.mu.Unlock()
|
defer u.mu.Unlock()
|
||||||
@ -89,14 +136,14 @@ func (u *User) SetName(name string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReplyTo returns the last user that messaged this user.
|
// ReplyTo returns the last user that messaged this user.
|
||||||
func (u *User) ReplyTo() *User {
|
func (u *User) ReplyTo() Author {
|
||||||
u.mu.Lock()
|
u.mu.Lock()
|
||||||
defer u.mu.Unlock()
|
defer u.mu.Unlock()
|
||||||
return u.replyTo
|
return u.replyTo
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetReplyTo sets the last user to message this user.
|
// SetReplyTo sets the last user to message this user.
|
||||||
func (u *User) SetReplyTo(user *User) {
|
func (u *User) SetReplyTo(user Author) {
|
||||||
// TODO: Use UserConfig.ReplyTo string
|
// TODO: Use UserConfig.ReplyTo string
|
||||||
u.mu.Lock()
|
u.mu.Lock()
|
||||||
defer u.mu.Unlock()
|
defer u.mu.Unlock()
|
||||||
@ -112,46 +159,11 @@ func (u *User) setColorIdx(idx int) {
|
|||||||
// Disconnect user, stop accepting messages
|
// Disconnect user, stop accepting messages
|
||||||
func (u *User) Close() {
|
func (u *User) Close() {
|
||||||
u.closeOnce.Do(func() {
|
u.closeOnce.Do(func() {
|
||||||
u.WriteCloser.Close()
|
|
||||||
// close(u.msg) TODO: Close?
|
// close(u.msg) TODO: Close?
|
||||||
close(u.done)
|
close(u.done)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Consume message buffer into the handler. Will block, should be called in a
|
|
||||||
// goroutine.
|
|
||||||
func (u *User) Consume() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-u.done:
|
|
||||||
return
|
|
||||||
case m, ok := <-u.msg:
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
u.HandleMsg(m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consume one message and stop, mostly for testing
|
|
||||||
// TODO: Stop using it and remove it.
|
|
||||||
func (u *User) ConsumeOne() Message {
|
|
||||||
return <-u.msg
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if there are pending messages, used for testing
|
|
||||||
// TODO: Stop using it and remove it.
|
|
||||||
func (u *User) HasMessages() bool {
|
|
||||||
select {
|
|
||||||
case msg := <-u.msg:
|
|
||||||
u.msg <- msg
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHighlight sets the highlighting regular expression to match string.
|
// SetHighlight sets the highlighting regular expression to match string.
|
||||||
func (u *User) SetHighlight(s string) error {
|
func (u *User) SetHighlight(s string) error {
|
||||||
re, err := regexp.Compile(fmt.Sprintf(reHighlight, s))
|
re, err := regexp.Compile(fmt.Sprintf(reHighlight, s))
|
||||||
@ -177,17 +189,6 @@ func (u *User) render(m Message) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleMsg will render the message to the screen, blocking.
|
|
||||||
func (u *User) HandleMsg(m Message) error {
|
|
||||||
r := u.render(m)
|
|
||||||
_, err := u.Write([]byte(r))
|
|
||||||
if err != nil {
|
|
||||||
logger.Printf("Write failed to %s, closing: %s", u.Name(), err)
|
|
||||||
u.Close()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add message to consume by user
|
// Add message to consume by user
|
||||||
func (u *User) Send(m Message) error {
|
func (u *User) Send(m Message) error {
|
||||||
select {
|
select {
|
||||||
|
@ -78,21 +78,22 @@ func (r *Room) HandleMsg(m message.Message) {
|
|||||||
go r.HandleMsg(m)
|
go r.HandleMsg(m)
|
||||||
}
|
}
|
||||||
case message.MessageTo:
|
case message.MessageTo:
|
||||||
user := m.To()
|
user := m.To().(Member)
|
||||||
user.Send(m)
|
user.Send(m)
|
||||||
default:
|
default:
|
||||||
fromMsg, skip := m.(message.MessageFrom)
|
fromMsg, skip := m.(message.MessageFrom)
|
||||||
var skipUser Member
|
var skipUser Member
|
||||||
if skip {
|
if skip {
|
||||||
skipUser = fromMsg.From()
|
skipUser = fromMsg.From().(Member)
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
roomMember := item.Value().(*roomMember)
|
roomMember := item.Value().(*roomMember)
|
||||||
user := roomMember.Member
|
user := roomMember.Member
|
||||||
|
from := fromMsg.From().(Member)
|
||||||
|
|
||||||
if fromMsg != nil && roomMember.Ignored.In(fromMsg.From().ID()) {
|
if fromMsg != nil && roomMember.Ignored.In(from.ID()) {
|
||||||
// Skip because ignored
|
// Skip because ignored
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package chat
|
package chat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -42,7 +41,7 @@ func TestRoomServe(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ScreenedUser struct {
|
type ScreenedUser struct {
|
||||||
user *message.User
|
*message.User
|
||||||
screen *MockScreen
|
screen *MockScreen
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,19 +158,6 @@ func expectOutput(t *testing.T, buffer []byte, expected string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendCommand(cmd string, mock ScreenedUser, room *Room, buffer *[]byte) error {
|
|
||||||
msg, ok := message.NewPublicMsg(cmd, mock.user).ParseCommand()
|
|
||||||
if !ok {
|
|
||||||
return errors.New("cannot parse command message")
|
|
||||||
}
|
|
||||||
|
|
||||||
room.Send(msg)
|
|
||||||
mock.user.HandleMsg(mock.user.ConsumeOne())
|
|
||||||
mock.screen.Read(buffer)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRoomJoin(t *testing.T) {
|
func TestRoomJoin(t *testing.T) {
|
||||||
var expected, actual []byte
|
var expected, actual []byte
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user