Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions codec_misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ func (s *MessageEntity) Encode(e *jx.Encoder) {
e.Str(s.CustomEmojiID)
}

if s.UnixTime != 0 {
e.FieldStart("unix_time")
e.Int(s.UnixTime)
}

if s.DateTimeFormat != "" {
e.FieldStart("date_time_format")
e.Str(s.DateTimeFormat)
}

e.ObjEnd()
}

Expand Down Expand Up @@ -88,6 +98,20 @@ func (s *MessageEntity) Decode(d *jx.Decoder) error {
}

s.CustomEmojiID = v
case "unix_time":
v, err := d.Int()
if err != nil {
return err
}

s.UnixTime = v
case "date_time_format":
v, err := d.Str()
if err != nil {
return err
}

s.DateTimeFormat = v
default:
return d.Skip()
}
Expand Down
33 changes: 20 additions & 13 deletions codec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,23 @@ func fullMessage() *Message {
MediaGroupID: "mg",
AuthorSignature: "auth",
Text: "hello",
Entities: []MessageEntity{{Type: EntityBold, Offset: 0, Length: 5}, {Type: EntityTextMention, Offset: 1, Length: 2, User: user, URL: "u", Language: "go", CustomEmojiID: "e"}},
Caption: "cap",
CaptionEntities: []MessageEntity{{Type: EntityItalic, Offset: 0, Length: 3}},
Animation: &Animation{FileID: "a", FileUniqueID: "au", Width: 1, Height: 2, Duration: 3, Thumbnail: thumb, FileName: "a.gif", MIMEType: "image/gif", FileSize: 4},
Audio: &Audio{FileID: "b", FileUniqueID: "bu", Duration: 10, Performer: "p", Title: "t", FileName: "b.mp3", MIMEType: "audio/mpeg", FileSize: 5, Thumbnail: thumb},
Document: &Document{FileID: "c", FileUniqueID: "cu", Thumbnail: thumb, FileName: "c.pdf", MIMEType: "application/pdf", FileSize: 6},
Photo: []PhotoSize{{FileID: "p1", FileUniqueID: "p1u", Width: 100, Height: 200, FileSize: 7}},
Sticker: &Sticker{FileID: "s", FileUniqueID: "su", Type: StickerRegular, Width: 512, Height: 512, IsAnimated: true, IsVideo: true, Thumbnail: thumb, Emoji: "🙂", SetName: "set", FileSize: 8},
Video: &Video{FileID: "v", FileUniqueID: "vu", Width: 640, Height: 480, Duration: 12, Thumbnail: thumb, FileName: "v.mp4", MIMEType: "video/mp4", FileSize: 9},
VideoNote: &VideoNote{FileID: "vn", FileUniqueID: "vnu", Length: 240, Duration: 6, Thumbnail: thumb, FileSize: 11},
Voice: &Voice{FileID: "vo", FileUniqueID: "vou", Duration: 4, MIMEType: "audio/ogg", FileSize: 12},
Contact: &Contact{PhoneNumber: "+1", FirstName: "Ada", LastName: "L", UserID: 1, VCard: "vc"},
Dice: &Dice{Emoji: DiceDart, Value: 6},
Entities: []MessageEntity{
{Type: EntityBold, Offset: 0, Length: 5},
{Type: EntityTextMention, Offset: 1, Length: 2, User: user, URL: "u", Language: "go", CustomEmojiID: "e"},
{Type: EntityDateTime, Offset: 3, Length: 4, UnixTime: 1781027109, DateTimeFormat: "wdt"},
},
Caption: "cap",
CaptionEntities: []MessageEntity{{Type: EntityItalic, Offset: 0, Length: 3}},
Animation: &Animation{FileID: "a", FileUniqueID: "au", Width: 1, Height: 2, Duration: 3, Thumbnail: thumb, FileName: "a.gif", MIMEType: "image/gif", FileSize: 4},
Audio: &Audio{FileID: "b", FileUniqueID: "bu", Duration: 10, Performer: "p", Title: "t", FileName: "b.mp3", MIMEType: "audio/mpeg", FileSize: 5, Thumbnail: thumb},
Document: &Document{FileID: "c", FileUniqueID: "cu", Thumbnail: thumb, FileName: "c.pdf", MIMEType: "application/pdf", FileSize: 6},
Photo: []PhotoSize{{FileID: "p1", FileUniqueID: "p1u", Width: 100, Height: 200, FileSize: 7}},
Sticker: &Sticker{FileID: "s", FileUniqueID: "su", Type: StickerRegular, Width: 512, Height: 512, IsAnimated: true, IsVideo: true, Thumbnail: thumb, Emoji: "🙂", SetName: "set", FileSize: 8},
Video: &Video{FileID: "v", FileUniqueID: "vu", Width: 640, Height: 480, Duration: 12, Thumbnail: thumb, FileName: "v.mp4", MIMEType: "video/mp4", FileSize: 9},
VideoNote: &VideoNote{FileID: "vn", FileUniqueID: "vnu", Length: 240, Duration: 6, Thumbnail: thumb, FileSize: 11},
Voice: &Voice{FileID: "vo", FileUniqueID: "vou", Duration: 4, MIMEType: "audio/ogg", FileSize: 12},
Contact: &Contact{PhoneNumber: "+1", FirstName: "Ada", LastName: "L", UserID: 1, VCard: "vc"},
Dice: &Dice{Emoji: DiceDart, Value: 6},
Poll: &Poll{
ID: "poll1",
Question: "q?",
Expand Down Expand Up @@ -164,6 +168,7 @@ func TestLeafEntitiesJSON(t *testing.T) {
jsonRoundTrip(t, Voice{FileID: "vo", FileUniqueID: "vou", Duration: 3, MIMEType: "m", FileSize: 10})
jsonRoundTrip(t, Sticker{FileID: "s", FileUniqueID: "su", Type: StickerMask, Width: 1, Height: 2, IsAnimated: true, IsVideo: true, Thumbnail: thumb, Emoji: "x", SetName: "set", FileSize: 11})
jsonRoundTrip(t, MessageEntity{Type: EntityTextLink, Offset: 1, Length: 2, URL: "u", User: &User{ID: 1, FirstName: "A"}, Language: "go", CustomEmojiID: "e"})
jsonRoundTrip(t, MessageEntity{Type: EntityDateTime, Offset: 0, Length: 10, UnixTime: 1781027109, DateTimeFormat: "wdt"})
jsonRoundTrip(t, Contact{PhoneNumber: "+1", FirstName: "A", LastName: "B", UserID: 1, VCard: "v"})
jsonRoundTrip(t, Dice{Emoji: DiceBasketball, Value: 5})
jsonRoundTrip(t, Location{Longitude: 1.5, Latitude: 2.25, HorizontalAccuracy: 0.5, LivePeriod: 60, Heading: 90, ProximityAlertRadius: 10})
Expand Down Expand Up @@ -219,6 +224,8 @@ func TestDecodeTypeMismatch(t *testing.T) {
`{"photo":5}`, // array field, number value
`{"reply_markup":{"inline_keyboard":7}}`, // nested array, number value
`{"forward_origin":{"type":"user","date":"x"}}`, // union variant, bad field
`{"entities":[{"unix_time":"string"}]}`, // int field, string value
`{"entities":[{"date_time_format":12345}]}`, // string field, number value
}
for _, c := range cases {
var m Message
Expand Down
28 changes: 28 additions & 0 deletions entities.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package botapi

import (
"strconv"
"strings"

"github.com/gotd/td/tg"
)
Expand Down Expand Up @@ -130,6 +131,33 @@ func entitiesFromTg(entities []tg.MessageEntityClass) []MessageEntity {
case *tg.MessageEntityCustomEmoji:
me.Type = EntityCustomEmoji
me.CustomEmojiID = strconv.FormatInt(e.DocumentID, 10)
case *tg.MessageEntityFormattedDate:
me.Type = EntityDateTime
me.UnixTime = e.Date

if e.Relative {
me.DateTimeFormat = "r"
} else {
var dateTimeFormat strings.Builder

if e.DayOfWeek {
dateTimeFormat.WriteString("w")
}

if e.ShortDate {
dateTimeFormat.WriteString("d")
} else if e.LongDate {
dateTimeFormat.WriteString("D")
}

if e.ShortTime {
dateTimeFormat.WriteString("t")
} else if e.LongTime {
dateTimeFormat.WriteString("T")
}

me.DateTimeFormat = dateTimeFormat.String()
}
default:
// Unknown or bot-irrelevant entity (e.g. unknown future types): skip.
continue
Expand Down
68 changes: 68 additions & 0 deletions entities_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,71 @@ func TestEntitiesAllTypes(t *testing.T) {
t.Fatal("expandable blockquote lost")
}
}

func TestEntitiesFormattedDateFromTg(t *testing.T) {
cases := []struct {
name string
in tg.MessageEntityClass
want MessageEntity
}{
{
name: "RelativeTime",
in: &tg.MessageEntityFormattedDate{
Offset: 0,
Length: 5,
Date: 1781027109,
Relative: true,
},
want: MessageEntity{
Type: EntityDateTime,
UnixTime: 1781027109,
DateTimeFormat: "r",
},
},
{
name: "Absolute-DayOfWeek-ShortDate-ShortTime",
in: &tg.MessageEntityFormattedDate{
Offset: 5,
Length: 10,
Date: 1781027109,
DayOfWeek: true,
ShortDate: true,
ShortTime: true,
},
want: MessageEntity{
Type: EntityDateTime,
UnixTime: 1781027109,
DateTimeFormat: "wdt",
},
},
{
name: "Absolute-LongDate-LongTime",
in: &tg.MessageEntityFormattedDate{
Offset: 10,
Length: 8,
Date: 1781027109,
LongDate: true,
LongTime: true,
},
want: MessageEntity{
Type: EntityDateTime,
UnixTime: 1781027109,
DateTimeFormat: "DT",
},
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
out := entitiesFromTg([]tg.MessageEntityClass{tc.in})
if len(out) != 1 {
t.Fatalf("expected 1 entity, got %d", len(out))
}

got := out[0]
if got.Type != tc.want.Type || got.UnixTime != tc.want.UnixTime || got.DateTimeFormat != tc.want.DateTimeFormat {
t.Errorf("got %+v, want %+v", got, tc.want)
}
})
}
}
1 change: 1 addition & 0 deletions enums.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const (
EntityTextLink MessageEntityType = "text_link"
EntityTextMention MessageEntityType = "text_mention"
EntityCustomEmoji MessageEntityType = "custom_emoji"
EntityDateTime MessageEntityType = "date_time"
)

// ChatMemberStatus is a member's status in a chat.
Expand Down
16 changes: 9 additions & 7 deletions types_message.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import "github.com/gotd/td/tg"
// MessageEntity represents one special entity in a text message (e.g. a
// hashtag, link, or formatted run).
type MessageEntity struct {
Type MessageEntityType `json:"type"`
Offset int `json:"offset"`
Length int `json:"length"`
URL string `json:"url,omitempty"`
User *User `json:"user,omitempty"`
Language string `json:"language,omitempty"`
CustomEmojiID string `json:"custom_emoji_id,omitempty"`
Type MessageEntityType `json:"type"`
Offset int `json:"offset"`
Length int `json:"length"`
URL string `json:"url,omitempty"`
User *User `json:"user,omitempty"`
Language string `json:"language,omitempty"`
CustomEmojiID string `json:"custom_emoji_id,omitempty"`
UnixTime int `json:"unix_time,omitempty"`
DateTimeFormat string `json:"date_time_format,omitempty"`
}

// Contact represents a phone contact.
Expand Down