package main import ( "fmt" "html" "regexp" "strings" "time" "unicode" "github.com/zorchenhimer/MovieNight/common" ) var ( regexSpoiler = regexp.MustCompile(`\|\|(.*?)\|\|`) spoilerStart = `` spoilerEnd = `` ) type Client struct { name string // Display name conn *chatConnection belongsTo *ChatRoom color string CmdLevel common.CommandLevel IsColorForced bool IsNameForced bool regexName *regexp.Regexp // Times since last event. use time.Duration.Since() nextChat time.Time // rate limit chat messages nextNick time.Time // rate limit nickname changes nextColor time.Time // rate limit color changes nextAuth time.Time // rate limit failed auth attempts. Sould prolly have a backoff policy. authTries int // number of failed auth attempts nextDuplicate time.Time lastMsg string } func NewClient(connection *chatConnection, room *ChatRoom, name, color string) (*Client, error) { c := &Client{ conn: connection, belongsTo: room, color: color, } if err := c.setName(name); err != nil { return nil, fmt.Errorf("could not set client name to %#v: %v", name, err) } // Set initial vaules to their rate limit duration in the past. c.nextChat = time.Now() c.nextNick = time.Now() c.nextColor = time.Now() c.nextAuth = time.Now() return c, nil } //Client has a new message to broadcast func (cl *Client) NewMsg(data common.ClientData) { return } func (cl *Client) SendChatData(data common.ChatData) error { // Don't send chat or event data to clients that have not fully joined the // chatroom (ie, they have not set a name). if cl.name == "" && (data.Type == common.DTChat || data.Type == common.DTEvent) { return nil } // Colorize name on chat messages if data.Type == common.DTChat { var err error data = cl.replaceColorizedName(data) if err != nil { return fmt.Errorf("could not colorize name: %v", err) } } cd, err := data.ToJSON() if err != nil { return fmt.Errorf("could not create ChatDataJSON of type %d: %v", data.Type, err) } return cl.Send(cd) } func (cl *Client) Send(data common.ChatDataJSON) error { err := cl.conn.WriteData(data) if err != nil { return fmt.Errorf("could not send message: %v", err) } return nil } func (cl *Client) SendServerMessage(s string) error { err := cl.SendChatData(common.NewChatMessage("", ColorServerMessage, s, common.CmdlUser, common.MsgServer)) if err != nil { return fmt.Errorf("could send server message to %s: message - %#v: %v", cl.name, s, err) } return nil } // Make links clickable func formatLinks(input string) string { newMsg := []string{} for _, word := range strings.Split(input, " ") { if strings.HasPrefix(word, "http://") || strings.HasPrefix(word, "https://") { word = html.UnescapeString(word) word = fmt.Sprintf(`%s`, word, word) } newMsg = append(newMsg, word) } return strings.Join(newMsg, " ") } //Exiting out func (cl *Client) Exit() { cl.belongsTo.Leave(cl.name, cl.color) } // Outgoing messages func (cl *Client) Message(msg string) { msg = common.ParseEmotes(msg) cl.belongsTo.AddMsg(cl, false, false, msg) } // Outgoing /me command func (cl *Client) Me(msg string) { msg = common.ParseEmotes(msg) cl.belongsTo.AddMsg(cl, true, false, msg) } func (cl *Client) Mod() { if cl.CmdLevel < common.CmdlMod { cl.CmdLevel = common.CmdlMod } } func (cl *Client) Unmod() { cl.CmdLevel = common.CmdlUser } func (cl *Client) Host() string { return cl.conn.Host() } func (cl *Client) setName(s string) error { cl.name = s if cl.conn != nil { cl.conn.clientName = s } return nil } func (cl *Client) setColor(s string) error { cl.color = s return cl.SendChatData(common.NewChatHiddenMessage(common.CdColor, cl.color)) } func (cl *Client) replaceColorizedName(chatData common.ChatData) common.ChatData { data := chatData.Data.(common.DataMessage) words := strings.Split(data.Message, " ") newWords := []string{} for _, word := range words { if strings.ToLower(word) == strings.ToLower(cl.name) || strings.ToLower(word) == strings.ToLower("@"+cl.name) { newWords = append(newWords, ``+word+``) } else { newWords = append(newWords, word) } } data.Message = strings.Join(newWords, " ") chatData.Data = data return chatData } var dumbSpaces = []string{ "\n", "\t", "\r", "\u200b", } func removeDumbSpaces(msg string) string { for _, ds := range dumbSpaces { msg = strings.ReplaceAll(msg, ds, " ") } newMsg := "" for _, r := range msg { if unicode.IsSpace(r) { newMsg += " " } else { newMsg += string(r) } } return newMsg } func addSpoilerTags(msg string) string { return regexSpoiler.ReplaceAllString(msg, fmt.Sprintf(`%s$1%s`, spoilerStart, spoilerEnd)) }