12 "github.com/shazow/ssh-chat/set"
15 const messageBuffer = 5
16 const messageTimeout = 5 * time.Second
17 const reHighlight = `\b(%s)\b`
18 const timestampTimeout = 30 * time.Minute
20 var ErrUserClosed = errors.New("user closed")
22 // User definition, implemented set Item interface and io.Writer
36 replyTo *User // Set when user gets a /msg, for replying.
37 lastMsg time.Time // When the last message was rendered
40 func NewUser(identity Identifier) *User {
43 config: DefaultUserConfig,
45 msg: make(chan Message, messageBuffer),
46 done: make(chan struct{}),
49 u.setColorIdx(rand.Int())
54 func NewUserScreen(identity Identifier, screen io.WriteCloser) *User {
55 u := NewUser(identity)
61 func (u *User) Joined() time.Time {
65 func (u *User) Config() UserConfig {
71 func (u *User) SetConfig(cfg UserConfig) {
77 // Rename the user with a new Identifier.
78 func (u *User) SetID(id string) {
79 u.Identifier.SetID(id)
80 u.setColorIdx(rand.Int())
83 // ReplyTo returns the last user that messaged this user.
84 func (u *User) ReplyTo() *User {
90 // SetReplyTo sets the last user to message this user.
91 func (u *User) SetReplyTo(user *User) {
97 // setColorIdx will set the colorIdx to a specific value, primarily used for
99 func (u *User) setColorIdx(idx int) {
103 // Disconnect user, stop accepting messages
104 func (u *User) Close() {
105 u.closeOnce.Do(func() {
109 // close(u.msg) TODO: Close?
114 // Consume message buffer into the handler. Will block, should be called in a
116 func (u *User) Consume() {
121 case m, ok := <-u.msg:
130 // Consume one message and stop, mostly for testing
131 func (u *User) ConsumeOne() Message {
135 // Check if there are pending messages, used for testing
136 func (u *User) HasMessages() bool {
146 // SetHighlight sets the highlighting regular expression to match string.
147 func (u *User) SetHighlight(s string) error {
148 re, err := regexp.Compile(fmt.Sprintf(reHighlight, s))
153 u.config.Highlight = re
158 func (u *User) render(m Message) string {
161 switch m := m.(type) {
167 out += m.RenderSelf(cfg)
169 out += m.RenderFor(cfg)
172 out += m.Render(cfg.Theme)
177 out += m.RenderSelf(cfg)
179 out += m.Render(cfg.Theme)
181 if cfg.Timeformat != nil {
183 if cfg.Timezone != nil {
184 ts = ts.In(cfg.Timezone)
188 return cfg.Theme.Timestamp(ts.Format(*cfg.Timeformat)) + " " + out + Newline
193 // writeMsg renders the message and attempts to write it, will Close the user
195 func (u *User) writeMsg(m Message) error {
197 _, err := u.screen.Write([]byte(r))
199 logger.Printf("Write failed to %s, closing: %s", u.Name(), err)
205 // HandleMsg will render the message to the screen, blocking.
206 func (u *User) HandleMsg(m Message) error {
208 u.lastMsg = m.Timestamp()
213 // Add message to consume by user
214 func (u *User) Send(m Message) error {
219 case <-time.After(messageTimeout):
220 logger.Printf("Message buffer full, closing: %s", u.Name())
227 // Container for per-user configurations.
228 type UserConfig struct {
229 Highlight *regexp.Regexp
232 Echo bool // Echo shows your own messages after sending, disabled for bots
234 Timezone *time.Location
238 // Default user configuration to use
239 var DefaultUserConfig UserConfig
242 DefaultUserConfig = UserConfig{
248 // TODO: Seed random?
251 // RecentActiveUsers is a slice of *Users that knows how to be sorted by the time of the last message.
252 type RecentActiveUsers []*User
254 func (a RecentActiveUsers) Len() int { return len(a) }
255 func (a RecentActiveUsers) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
256 func (a RecentActiveUsers) Less(i, j int) bool {
258 defer a[i].mu.Unlock()
260 defer a[j].mu.Unlock()
262 if a[i].lastMsg.IsZero() {
263 return a[i].joined.Before(a[j].joined)
265 return a[i].lastMsg.Before(a[j].lastMsg)