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
38 replyTo *User // Set when user gets a /msg, for replying.
39 lastMsg time.Time // When the last message was rendered.
40 awayReason string // Away reason, "" when not away.
41 awaySince time.Time // When away was set, 0 when not away.
44 func NewUser(identity Identifier) *User {
47 config: DefaultUserConfig,
49 msg: make(chan Message, messageBuffer),
50 done: make(chan struct{}),
54 u.setColorIdx(rand.Int())
59 func NewUserScreen(identity Identifier, screen io.WriteCloser) *User {
60 u := NewUser(identity)
66 func (u *User) Joined() time.Time {
70 func (u *User) LastMsg() time.Time {
76 // SetAway sets the users away reason and state.
77 func (u *User) SetAway(msg string) {
82 u.awaySince = time.Time{}
84 // Reset away timer even if already away
85 u.awaySince = time.Now()
89 // GetAway returns if the user is away, when they went away, and the reason.
90 func (u *User) GetAway() (bool, time.Time, string) {
93 return u.awayReason != "", u.awaySince, u.awayReason
96 func (u *User) Config() UserConfig {
102 func (u *User) SetConfig(cfg UserConfig) {
107 if u.OnChange != nil {
112 // Rename the user with a new Identifier.
113 func (u *User) SetID(id string) {
114 u.Identifier.SetID(id)
115 u.setColorIdx(rand.Int())
117 if u.OnChange != nil {
122 // ReplyTo returns the last user that messaged this user.
123 func (u *User) ReplyTo() *User {
129 // SetReplyTo sets the last user to message this user.
130 func (u *User) SetReplyTo(user *User) {
136 // setColorIdx will set the colorIdx to a specific value, primarily used for
138 func (u *User) setColorIdx(idx int) {
142 // Disconnect user, stop accepting messages
143 func (u *User) Close() {
144 u.closeOnce.Do(func() {
146 if err := u.screen.Close(); err != nil {
147 logger.Printf("Failed to close user %q screen: %s", u.ID(), err)
150 // close(u.msg) TODO: Close?
155 // Consume message buffer into the handler. Will block, should be called in a
157 func (u *User) Consume() {
162 case m, ok := <-u.msg:
171 // Consume one message and stop, mostly for testing
172 func (u *User) ConsumeOne() Message {
176 // Check if there are pending messages, used for testing
177 func (u *User) HasMessages() bool {
187 // SetHighlight sets the highlighting regular expression to match string.
188 func (u *User) SetHighlight(s string) error {
189 re, err := regexp.Compile(fmt.Sprintf(reHighlight, s))
194 u.config.Highlight = re
199 func (u *User) render(m Message) string {
202 switch m := m.(type) {
206 u.lastMsg = m.Timestamp()
212 out += m.RenderSelf(cfg)
213 } else if u.Focused.Len() > 0 && !u.Focused.In(m.From().ID()) {
214 // Skip message during focus
217 out += m.RenderFor(cfg)
220 out += m.Render(cfg.Theme)
225 out += m.RenderSelf(cfg)
227 out += m.Render(cfg.Theme)
229 if cfg.Timeformat != nil {
231 if cfg.Timezone != nil {
232 ts = ts.In(cfg.Timezone)
236 return ts.Format(*cfg.Timeformat) + " " + out + Newline
241 // writeMsg renders the message and attempts to write it, will Close the user
243 func (u *User) writeMsg(m Message) error {
245 _, err := u.screen.Write([]byte(r))
247 logger.Printf("Write failed to %s, closing: %s", u.ID(), err)
253 // HandleMsg will render the message to the screen, blocking.
254 func (u *User) HandleMsg(m Message) error {
258 // Add message to consume by user
259 func (u *User) Send(m Message) error {
264 case <-time.After(messageTimeout):
265 logger.Printf("Message buffer full, closing: %s", u.ID())
272 // Container for per-user configurations.
273 type UserConfig struct {
274 Highlight *regexp.Regexp
277 Echo bool // Echo shows your own messages after sending, disabled for bots
279 Timezone *time.Location
283 // Default user configuration to use
284 var DefaultUserConfig UserConfig
287 tfmt := "2006-01-02 15:04:05"
288 DefaultUserConfig = UserConfig{
295 // TODO: Seed random?
298 // RecentActiveUsers is a slice of *Users that knows how to be sorted by the
299 // time of the last message. If no message has been sent, then fall back to the
300 // time joined instead.
301 type RecentActiveUsers []*User
303 func (a RecentActiveUsers) Len() int { return len(a) }
304 func (a RecentActiveUsers) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
305 func (a RecentActiveUsers) Less(i, j int) bool {
307 defer a[i].mu.Unlock()
309 defer a[j].mu.Unlock()