feat: add truncateText and truncateURL helpers for Discord field limits
Discord silently rejects presence updates when text fields exceed 128 characters or URL fields exceed 256 characters. Fixes #16
This commit is contained in:
@@ -55,6 +55,30 @@ const (
|
|||||||
|
|
||||||
const heartbeatInterval = 41 // Heartbeat interval in seconds
|
const heartbeatInterval = 41 // Heartbeat interval in seconds
|
||||||
|
|
||||||
|
// Discord API field length limits
|
||||||
|
const (
|
||||||
|
maxTextLength = 128 // Max characters for text fields (details, state, name, large_text)
|
||||||
|
maxURLLength = 256 // Max characters for URL fields (details_url, state_url, etc.)
|
||||||
|
)
|
||||||
|
|
||||||
|
// truncateText truncates s to maxTextLength runes, appending "…" if truncated.
|
||||||
|
func truncateText(s string) string {
|
||||||
|
runes := []rune(s)
|
||||||
|
if len(runes) <= maxTextLength {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return string(runes[:maxTextLength-1]) + "…"
|
||||||
|
}
|
||||||
|
|
||||||
|
// truncateURL returns s unchanged if within maxURLLength, otherwise returns ""
|
||||||
|
// (a truncated URL would be broken, so we omit it entirely).
|
||||||
|
func truncateURL(s string) string {
|
||||||
|
if len(s) <= maxURLLength {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// activity represents a Discord activity sent via Gateway opcode 3.
|
// activity represents a Discord activity sent via Gateway opcode 3.
|
||||||
type activity struct {
|
type activity struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|||||||
+51
@@ -448,4 +448,55 @@ var _ = Describe("discordRPC", func() {
|
|||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Describe("truncateText", func() {
|
||||||
|
It("returns short strings unchanged", func() {
|
||||||
|
Expect(truncateText("hello")).To(Equal("hello"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns exactly 128-char strings unchanged", func() {
|
||||||
|
s := strings.Repeat("a", 128)
|
||||||
|
Expect(truncateText(s)).To(Equal(s))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("truncates strings over 128 chars to 127 + ellipsis", func() {
|
||||||
|
s := strings.Repeat("a", 200)
|
||||||
|
result := truncateText(s)
|
||||||
|
Expect([]rune(result)).To(HaveLen(128))
|
||||||
|
Expect(result).To(HaveSuffix("…"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("handles multi-byte characters correctly", func() {
|
||||||
|
// 130 Japanese characters — each is one rune but 3 bytes
|
||||||
|
s := strings.Repeat("あ", 130)
|
||||||
|
result := truncateText(s)
|
||||||
|
runes := []rune(result)
|
||||||
|
Expect(runes).To(HaveLen(128))
|
||||||
|
Expect(string(runes[127])).To(Equal("…"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns empty string unchanged", func() {
|
||||||
|
Expect(truncateText("")).To(Equal(""))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("truncateURL", func() {
|
||||||
|
It("returns short URLs unchanged", func() {
|
||||||
|
Expect(truncateURL("https://example.com")).To(Equal("https://example.com"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns exactly 256-char URLs unchanged", func() {
|
||||||
|
u := "https://example.com/" + strings.Repeat("a", 236)
|
||||||
|
Expect(truncateURL(u)).To(Equal(u))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns empty string for URLs over 256 chars", func() {
|
||||||
|
u := "https://example.com/" + strings.Repeat("a", 237)
|
||||||
|
Expect(truncateURL(u)).To(Equal(""))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns empty string unchanged", func() {
|
||||||
|
Expect(truncateURL("")).To(Equal(""))
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user