2020-07-06 00:18:40 +00:00
|
|
|
package segments
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/diamondburned/arikawa/discord"
|
|
|
|
"github.com/diamondburned/arikawa/state"
|
|
|
|
"github.com/diamondburned/cchat-discord/urlutils"
|
2020-07-08 21:23:14 +00:00
|
|
|
"github.com/diamondburned/cchat/text"
|
2020-07-06 00:18:40 +00:00
|
|
|
"github.com/diamondburned/ningen/md"
|
|
|
|
"github.com/dustin/go-humanize"
|
|
|
|
)
|
|
|
|
|
|
|
|
var imageExts = []string{".jpg", ".jpeg", ".png", ".webp", ".gif"}
|
|
|
|
|
|
|
|
func (r *TextRenderer) renderEmbeds(embeds []discord.Embed, m *discord.Message, s state.Store) {
|
|
|
|
for _, embed := range embeds {
|
|
|
|
r.startBlock()
|
2020-07-09 04:13:49 +00:00
|
|
|
r.buf.WriteString("---")
|
|
|
|
r.ensureBreak()
|
2020-07-06 00:18:40 +00:00
|
|
|
|
|
|
|
r.renderEmbed(embed, m, s)
|
|
|
|
|
2020-07-09 04:13:49 +00:00
|
|
|
r.ensureBreak()
|
2020-07-06 00:18:40 +00:00
|
|
|
r.buf.WriteString("---") // render prepends newline already
|
|
|
|
r.endBlock()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *TextRenderer) renderEmbed(embed discord.Embed, m *discord.Message, s state.Store) {
|
|
|
|
if a := embed.Author; a != nil && a.Name != "" {
|
|
|
|
if a.ProxyIcon != "" {
|
2020-07-08 08:35:30 +00:00
|
|
|
r.append(EmbedAuthor(r.buf.Len(), *a))
|
2020-07-06 00:18:40 +00:00
|
|
|
r.buf.WriteByte(' ')
|
|
|
|
}
|
|
|
|
|
|
|
|
start, end := r.writeString(a.Name)
|
2020-07-08 09:15:45 +00:00
|
|
|
r.ensureBreak()
|
2020-07-06 00:18:40 +00:00
|
|
|
|
|
|
|
if a.URL != "" {
|
|
|
|
r.append(LinkSegment{
|
|
|
|
start,
|
|
|
|
end,
|
|
|
|
a.URL,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if embed.Title != "" {
|
|
|
|
start, end := r.writeString(embed.Title)
|
2020-07-08 09:15:45 +00:00
|
|
|
r.ensureBreak()
|
2020-07-06 00:18:40 +00:00
|
|
|
|
2020-07-08 21:23:14 +00:00
|
|
|
// Make the title bold.
|
|
|
|
r.append(InlineSegment{
|
|
|
|
start: start,
|
|
|
|
end: end,
|
|
|
|
attributes: text.AttrBold,
|
|
|
|
})
|
|
|
|
|
2020-07-06 00:18:40 +00:00
|
|
|
if embed.URL != "" {
|
|
|
|
r.append(LinkSegment{
|
|
|
|
start,
|
|
|
|
end,
|
|
|
|
embed.URL,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-08 08:35:30 +00:00
|
|
|
// If we have a thumbnail, then write one.
|
|
|
|
if embed.Thumbnail != nil {
|
|
|
|
r.append(EmbedThumbnail(r.buf.Len(), *embed.Thumbnail))
|
2020-07-08 09:15:45 +00:00
|
|
|
r.ensureBreak()
|
2020-07-08 08:35:30 +00:00
|
|
|
}
|
|
|
|
|
2020-07-06 00:18:40 +00:00
|
|
|
if embed.Description != "" {
|
|
|
|
// Since Discord embeds' descriptions are technically Markdown, we can
|
|
|
|
// borrow our Markdown parser for this.
|
|
|
|
node := md.ParseWithMessage([]byte(embed.Description), s, m, false)
|
|
|
|
// Create a new renderer with inherited state and buffer but a new byte
|
|
|
|
// source.
|
|
|
|
desc := r.clone([]byte(embed.Description))
|
|
|
|
// Walk using the newly created state.
|
|
|
|
desc.walk(node)
|
|
|
|
// Join the created state.
|
|
|
|
r.join(desc)
|
|
|
|
// Write a new line.
|
2020-07-08 09:15:45 +00:00
|
|
|
r.ensureBreak()
|
2020-07-06 00:18:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(embed.Fields) > 0 {
|
2020-07-08 09:15:45 +00:00
|
|
|
// Pad two new lines.
|
|
|
|
r.startBlockN(2)
|
2020-07-06 00:18:40 +00:00
|
|
|
|
|
|
|
// Write fields indented once.
|
|
|
|
for _, field := range embed.Fields {
|
|
|
|
fmt.Fprintf(r.buf, "\t%s: %s\n", field.Name, field.Value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if f := embed.Footer; f != nil && f.Text != "" {
|
|
|
|
if f.ProxyIcon != "" {
|
2020-07-08 08:35:30 +00:00
|
|
|
r.append(EmbedFooter(r.buf.Len(), *f))
|
2020-07-06 00:18:40 +00:00
|
|
|
r.buf.WriteByte(' ')
|
|
|
|
}
|
|
|
|
|
|
|
|
r.buf.WriteString(f.Text)
|
2020-07-08 09:15:45 +00:00
|
|
|
r.ensureBreak()
|
2020-07-06 00:18:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if embed.Timestamp.Valid() {
|
|
|
|
if embed.Footer != nil {
|
|
|
|
r.buf.WriteString(" - ")
|
|
|
|
}
|
|
|
|
|
|
|
|
r.buf.WriteString(embed.Timestamp.Format(time.RFC1123))
|
2020-07-08 09:15:45 +00:00
|
|
|
r.ensureBreak()
|
2020-07-06 00:18:40 +00:00
|
|
|
}
|
2020-07-08 08:35:30 +00:00
|
|
|
|
|
|
|
// Write an image if there's one.
|
|
|
|
if embed.Image != nil {
|
|
|
|
r.append(EmbedImage(r.buf.Len(), *embed.Image))
|
2020-07-08 09:15:45 +00:00
|
|
|
r.ensureBreak()
|
2020-07-08 08:35:30 +00:00
|
|
|
}
|
2020-07-06 00:18:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *TextRenderer) renderAttachments(attachments []discord.Attachment) {
|
|
|
|
// Don't do anything if there are no attachments.
|
|
|
|
if len(attachments) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-07-08 09:15:45 +00:00
|
|
|
// Start a (small)new block before rendering attachments.
|
|
|
|
r.ensureBreak()
|
2020-07-06 00:18:40 +00:00
|
|
|
|
|
|
|
// Render all attachments. Newline delimited.
|
|
|
|
for i, attachment := range attachments {
|
|
|
|
r.renderAttachment(attachment)
|
|
|
|
|
|
|
|
if i != len(attachments) {
|
|
|
|
r.buf.WriteByte('\n')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *TextRenderer) renderAttachment(a discord.Attachment) {
|
|
|
|
if urlutils.ExtIs(a.Proxy, imageExts) {
|
2020-07-08 08:35:30 +00:00
|
|
|
r.append(EmbedAttachment(r.buf.Len(), a))
|
2020-07-06 00:18:40 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
start, end := r.writeStringf(
|
|
|
|
"File: %s (%s)",
|
|
|
|
a.Filename, humanize.Bytes(a.Size),
|
|
|
|
)
|
|
|
|
|
|
|
|
r.append(LinkSegment{
|
|
|
|
start,
|
|
|
|
end,
|
|
|
|
a.URL,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
type AvatarSegment struct {
|
|
|
|
start int
|
|
|
|
url string
|
|
|
|
text string
|
|
|
|
}
|
|
|
|
|
|
|
|
func EmbedAuthor(start int, a discord.EmbedAuthor) AvatarSegment {
|
|
|
|
return AvatarSegment{
|
|
|
|
start: start,
|
|
|
|
url: a.ProxyIcon,
|
|
|
|
text: "Avatar",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// EmbedFooter uses an avatar segment to comply with Discord.
|
2020-07-07 07:41:14 +00:00
|
|
|
func EmbedFooter(start int, f discord.EmbedFooter) AvatarSegment {
|
2020-07-06 00:18:40 +00:00
|
|
|
return AvatarSegment{
|
|
|
|
start: start,
|
2020-07-07 07:41:14 +00:00
|
|
|
url: f.ProxyIcon,
|
2020-07-06 00:18:40 +00:00
|
|
|
text: "Icon",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a AvatarSegment) Bounds() (int, int) {
|
|
|
|
return a.start, a.start
|
|
|
|
}
|
|
|
|
|
|
|
|
// Avatar returns the avatar URL.
|
|
|
|
func (a AvatarSegment) Avatar() (url string) {
|
|
|
|
return a.url
|
|
|
|
}
|
|
|
|
|
|
|
|
// AvatarSize returns the size of a small emoji.
|
|
|
|
func (a AvatarSegment) AvatarSize() int {
|
|
|
|
return InlineEmojiSize
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a AvatarSegment) AvatarText() string {
|
|
|
|
return a.text
|
|
|
|
}
|
|
|
|
|
|
|
|
type ImageSegment struct {
|
|
|
|
start int
|
|
|
|
url string
|
|
|
|
w, h int
|
|
|
|
text string
|
|
|
|
}
|
|
|
|
|
2020-07-08 08:35:30 +00:00
|
|
|
func EmbedImage(start int, i discord.EmbedImage) ImageSegment {
|
2020-07-06 00:18:40 +00:00
|
|
|
return ImageSegment{
|
|
|
|
start: start,
|
|
|
|
url: i.Proxy,
|
|
|
|
w: int(i.Width),
|
|
|
|
h: int(i.Height),
|
2020-07-08 08:35:30 +00:00
|
|
|
text: fmt.Sprintf("Image (%s)", urlutils.Name(i.URL)),
|
2020-07-06 00:18:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-08 08:35:30 +00:00
|
|
|
func EmbedThumbnail(start int, t discord.EmbedThumbnail) ImageSegment {
|
2020-07-06 00:18:40 +00:00
|
|
|
return ImageSegment{
|
|
|
|
start: start,
|
|
|
|
url: t.Proxy,
|
|
|
|
w: int(t.Width),
|
|
|
|
h: int(t.Height),
|
2020-07-08 08:35:30 +00:00
|
|
|
text: fmt.Sprintf("Thumbnail (%s)", urlutils.Name(t.URL)),
|
2020-07-06 00:18:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func EmbedAttachment(start int, a discord.Attachment) ImageSegment {
|
|
|
|
return ImageSegment{
|
|
|
|
start: start,
|
|
|
|
url: a.Proxy,
|
|
|
|
w: int(a.Width),
|
|
|
|
h: int(a.Height),
|
|
|
|
text: fmt.Sprintf("%s (%s)", a.Filename, humanize.Bytes(a.Size)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i ImageSegment) Bounds() (start, end int) {
|
|
|
|
return i.start, i.start
|
|
|
|
}
|
|
|
|
|
|
|
|
// Image returns the URL.
|
|
|
|
func (i ImageSegment) Image() string {
|
|
|
|
return i.url
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i ImageSegment) ImageSize() (w, h int) {
|
|
|
|
return i.w, i.h
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i ImageSegment) ImageText() string {
|
|
|
|
return i.text
|
|
|
|
}
|