- Remove CAS, as it no longer works for us - Remove other checks - Implement 15 minute greet timeout check - Fix chat logos - Ditch thirdparty logger, use stdlib log facility wrapper for logging
249 lines
6.0 KiB
Go
249 lines
6.0 KiB
Go
package elog
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/NicoNex/echotron/v3"
|
|
)
|
|
|
|
// renderLine assembles one log line:
|
|
//
|
|
// 21:33:07.123 in chat -1001 (title=…) user 42 (first_name=…) says …
|
|
//
|
|
// Empty field groups are dropped entirely rather than printed as empty parens.
|
|
func renderLine(chat echotron.Chat, user *echotron.User, body string) string {
|
|
var b strings.Builder
|
|
|
|
b.WriteString(nowUTC().Format(stampLayout))
|
|
b.WriteString(" in chat ")
|
|
b.WriteString(itoa(chat.ID))
|
|
|
|
if desc := describeChat(&chat); desc != "" {
|
|
b.WriteString(" (")
|
|
b.WriteString(desc)
|
|
b.WriteString(")")
|
|
}
|
|
|
|
if user != nil {
|
|
b.WriteString(" user ")
|
|
b.WriteString(itoa(user.ID))
|
|
|
|
if desc := describeUser(user); desc != "" {
|
|
b.WriteString(" (")
|
|
b.WriteString(desc)
|
|
b.WriteString(")")
|
|
}
|
|
}
|
|
|
|
if body != "" {
|
|
b.WriteString(" ")
|
|
b.WriteString(body)
|
|
}
|
|
|
|
return b.String()
|
|
}
|
|
|
|
// describeChat renders a chat's identity fields. Type is deliberately omitted:
|
|
// the user-specified format has no slot for it, and which of title / first_name
|
|
// is present already distinguishes group from private chat.
|
|
func describeChat(c *echotron.Chat) string {
|
|
if c == nil {
|
|
return ""
|
|
}
|
|
|
|
return joinFields([]string{
|
|
field("title", c.Title),
|
|
field("username", atUsername(c.Username)),
|
|
field("first_name", c.FirstName),
|
|
field("last_name", c.LastName),
|
|
})
|
|
}
|
|
|
|
// describeUser renders a user's identity fields.
|
|
func describeUser(u *echotron.User) string {
|
|
if u == nil {
|
|
return ""
|
|
}
|
|
|
|
return joinFields([]string{
|
|
field("first_name", u.FirstName),
|
|
field("last_name", u.LastName),
|
|
field("username", atUsername(u.Username)),
|
|
})
|
|
}
|
|
|
|
// describePayload renders what a message carries: media first, then a caption,
|
|
// because "post photo (…) caption" reads better than the reverse.
|
|
func describePayload(m *echotron.Message) string {
|
|
kind, details := describeMedia(m)
|
|
|
|
var b strings.Builder
|
|
|
|
if kind != "" {
|
|
b.WriteString("post ")
|
|
b.WriteString(kind)
|
|
|
|
if details != "" {
|
|
b.WriteString(" (")
|
|
b.WriteString(details)
|
|
b.WriteString(")")
|
|
}
|
|
}
|
|
|
|
// A caption accompanies the media rather than replacing it: "post photo (…)"
|
|
// alone loses what the user actually said.
|
|
if m.Text != "" {
|
|
if b.Len() > 0 {
|
|
b.WriteString(" ")
|
|
}
|
|
|
|
b.WriteString("says ")
|
|
b.WriteString(m.Text)
|
|
}
|
|
|
|
return b.String()
|
|
}
|
|
|
|
// describeMedia returns the media kind and its key info. The first media field
|
|
// set on the message wins, in the order listed in the SPEC.
|
|
func describeMedia(m *echotron.Message) (string, string) {
|
|
switch {
|
|
case len(m.Photo) > 0:
|
|
// The array is ordered smallest first, so the last entry is the largest.
|
|
best := m.Photo[len(m.Photo)-1]
|
|
|
|
return "photo", joinFields([]string{
|
|
dims(best.Width, best.Height),
|
|
sizeField(int64(best.FileSize)),
|
|
})
|
|
|
|
case m.Video != nil:
|
|
return "video", joinFields([]string{
|
|
field("file_name", m.Video.FileName),
|
|
field("mime", m.Video.MimeType),
|
|
dims(m.Video.Width, m.Video.Height),
|
|
field("duration", seconds(m.Video.Duration)),
|
|
sizeField(m.Video.FileSize),
|
|
})
|
|
|
|
case m.Animation != nil:
|
|
return "animation", joinFields([]string{
|
|
field("file_name", m.Animation.FileName),
|
|
field("mime", m.Animation.MimeType),
|
|
dims(m.Animation.Width, m.Animation.Height),
|
|
field("duration", seconds(m.Animation.Duration)),
|
|
sizeField(m.Animation.FileSize),
|
|
})
|
|
|
|
case m.Audio != nil:
|
|
return "audio", joinFields([]string{
|
|
field("title", m.Audio.Title),
|
|
field("file_name", m.Audio.FileName),
|
|
field("mime", m.Audio.MimeType),
|
|
field("duration", seconds(m.Audio.Duration)),
|
|
sizeField(m.Audio.FileSize),
|
|
})
|
|
|
|
case m.Voice != nil:
|
|
return "voice", joinFields([]string{
|
|
field("mime", m.Voice.MimeType),
|
|
field("duration", seconds(m.Voice.Duration)),
|
|
sizeField(m.Voice.FileSize),
|
|
})
|
|
|
|
case m.VideoNote != nil:
|
|
return "video_note", joinFields([]string{
|
|
dims(m.VideoNote.Length, m.VideoNote.Length),
|
|
field("duration", seconds(m.VideoNote.Duration)),
|
|
sizeField(int64(m.VideoNote.FileSize)),
|
|
})
|
|
|
|
case m.Sticker != nil:
|
|
return "sticker", joinFields([]string{
|
|
field("emoji", m.Sticker.Emoji),
|
|
field("set", m.Sticker.SetName),
|
|
dims(m.Sticker.Width, m.Sticker.Height),
|
|
field("video", itoaBool(m.Sticker.IsVideo)),
|
|
field("animated", itoaBool(m.Sticker.IsAnimated)),
|
|
})
|
|
|
|
case m.Document != nil:
|
|
return "document", joinFields([]string{
|
|
field("file_name", m.Document.FileName),
|
|
field("mime", m.Document.MimeType),
|
|
sizeField(m.Document.FileSize),
|
|
})
|
|
|
|
case m.Contact != nil:
|
|
return "contact", joinFields([]string{
|
|
field("phone", m.Contact.PhoneNumber),
|
|
field("first_name", m.Contact.FirstName),
|
|
field("last_name", m.Contact.LastName),
|
|
field("user_id", itoaInt(m.Contact.UserID)),
|
|
})
|
|
|
|
case m.Location != nil:
|
|
return "location", joinFields([]string{
|
|
field("lat", itoaFloat(m.Location.Latitude)),
|
|
field("lon", itoaFloat(m.Location.Longitude)),
|
|
})
|
|
|
|
case m.Venue != nil:
|
|
parts := []string{
|
|
field("title", m.Venue.Title),
|
|
field("address", m.Venue.Address),
|
|
}
|
|
|
|
if m.Venue.Location != nil {
|
|
parts = append(parts,
|
|
field("lat", itoaFloat(m.Venue.Location.Latitude)),
|
|
field("lon", itoaFloat(m.Venue.Location.Longitude)))
|
|
}
|
|
|
|
return "venue", joinFields(parts)
|
|
|
|
case m.Dice != nil:
|
|
return "dice", joinFields([]string{
|
|
field("emoji", m.Dice.Emoji),
|
|
field("value", itoaInt(m.Dice.Value)),
|
|
})
|
|
}
|
|
|
|
return "", ""
|
|
}
|
|
|
|
// dims renders "WxH", or empty when the dimensions are unknown.
|
|
func dims(w int, h int) string {
|
|
if w <= 0 || h <= 0 {
|
|
return ""
|
|
}
|
|
|
|
return itoaInt(w) + "x" + itoaInt(h)
|
|
}
|
|
|
|
// itoaBool renders a boolean for an inline field.
|
|
func itoaBool(b bool) string {
|
|
if b {
|
|
return "true"
|
|
}
|
|
|
|
return "false"
|
|
}
|
|
|
|
// describeReference renders "user 42 (first_name=Ann)", used for the newcomer in
|
|
// a service message and for the member in a status change.
|
|
func describeReference(u *echotron.User) string {
|
|
if u == nil {
|
|
return "user unknown"
|
|
}
|
|
|
|
if desc := describeUser(u); desc != "" {
|
|
return fmt.Sprintf("user %d (%s)", u.ID, desc)
|
|
}
|
|
|
|
return fmt.Sprintf("user %d", u.ID)
|
|
}
|
|
|
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|