Skip to content

Commit 1604b5e

Browse files
authored
Merge pull request #459 from fluffur/add-date-time-message-entity
feat: add date time message entity
2 parents 22e0a1e + fea4f81 commit 1604b5e

6 files changed

Lines changed: 150 additions & 20 deletions

File tree

codec_misc.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ func (s *MessageEntity) Encode(e *jx.Encoder) {
3232
e.Str(s.CustomEmojiID)
3333
}
3434

35+
if s.UnixTime != 0 {
36+
e.FieldStart("unix_time")
37+
e.Int(s.UnixTime)
38+
}
39+
40+
if s.DateTimeFormat != "" {
41+
e.FieldStart("date_time_format")
42+
e.Str(s.DateTimeFormat)
43+
}
44+
3545
e.ObjEnd()
3646
}
3747

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

90100
s.CustomEmojiID = v
101+
case "unix_time":
102+
v, err := d.Int()
103+
if err != nil {
104+
return err
105+
}
106+
107+
s.UnixTime = v
108+
case "date_time_format":
109+
v, err := d.Str()
110+
if err != nil {
111+
return err
112+
}
113+
114+
s.DateTimeFormat = v
91115
default:
92116
return d.Skip()
93117
}

codec_test.go

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,19 +56,23 @@ func fullMessage() *Message {
5656
MediaGroupID: "mg",
5757
AuthorSignature: "auth",
5858
Text: "hello",
59-
Entities: []MessageEntity{{Type: EntityBold, Offset: 0, Length: 5}, {Type: EntityTextMention, Offset: 1, Length: 2, User: user, URL: "u", Language: "go", CustomEmojiID: "e"}},
60-
Caption: "cap",
61-
CaptionEntities: []MessageEntity{{Type: EntityItalic, Offset: 0, Length: 3}},
62-
Animation: &Animation{FileID: "a", FileUniqueID: "au", Width: 1, Height: 2, Duration: 3, Thumbnail: thumb, FileName: "a.gif", MIMEType: "image/gif", FileSize: 4},
63-
Audio: &Audio{FileID: "b", FileUniqueID: "bu", Duration: 10, Performer: "p", Title: "t", FileName: "b.mp3", MIMEType: "audio/mpeg", FileSize: 5, Thumbnail: thumb},
64-
Document: &Document{FileID: "c", FileUniqueID: "cu", Thumbnail: thumb, FileName: "c.pdf", MIMEType: "application/pdf", FileSize: 6},
65-
Photo: []PhotoSize{{FileID: "p1", FileUniqueID: "p1u", Width: 100, Height: 200, FileSize: 7}},
66-
Sticker: &Sticker{FileID: "s", FileUniqueID: "su", Type: StickerRegular, Width: 512, Height: 512, IsAnimated: true, IsVideo: true, Thumbnail: thumb, Emoji: "🙂", SetName: "set", FileSize: 8},
67-
Video: &Video{FileID: "v", FileUniqueID: "vu", Width: 640, Height: 480, Duration: 12, Thumbnail: thumb, FileName: "v.mp4", MIMEType: "video/mp4", FileSize: 9},
68-
VideoNote: &VideoNote{FileID: "vn", FileUniqueID: "vnu", Length: 240, Duration: 6, Thumbnail: thumb, FileSize: 11},
69-
Voice: &Voice{FileID: "vo", FileUniqueID: "vou", Duration: 4, MIMEType: "audio/ogg", FileSize: 12},
70-
Contact: &Contact{PhoneNumber: "+1", FirstName: "Ada", LastName: "L", UserID: 1, VCard: "vc"},
71-
Dice: &Dice{Emoji: DiceDart, Value: 6},
59+
Entities: []MessageEntity{
60+
{Type: EntityBold, Offset: 0, Length: 5},
61+
{Type: EntityTextMention, Offset: 1, Length: 2, User: user, URL: "u", Language: "go", CustomEmojiID: "e"},
62+
{Type: EntityDateTime, Offset: 3, Length: 4, UnixTime: 1781027109, DateTimeFormat: "wdt"},
63+
},
64+
Caption: "cap",
65+
CaptionEntities: []MessageEntity{{Type: EntityItalic, Offset: 0, Length: 3}},
66+
Animation: &Animation{FileID: "a", FileUniqueID: "au", Width: 1, Height: 2, Duration: 3, Thumbnail: thumb, FileName: "a.gif", MIMEType: "image/gif", FileSize: 4},
67+
Audio: &Audio{FileID: "b", FileUniqueID: "bu", Duration: 10, Performer: "p", Title: "t", FileName: "b.mp3", MIMEType: "audio/mpeg", FileSize: 5, Thumbnail: thumb},
68+
Document: &Document{FileID: "c", FileUniqueID: "cu", Thumbnail: thumb, FileName: "c.pdf", MIMEType: "application/pdf", FileSize: 6},
69+
Photo: []PhotoSize{{FileID: "p1", FileUniqueID: "p1u", Width: 100, Height: 200, FileSize: 7}},
70+
Sticker: &Sticker{FileID: "s", FileUniqueID: "su", Type: StickerRegular, Width: 512, Height: 512, IsAnimated: true, IsVideo: true, Thumbnail: thumb, Emoji: "🙂", SetName: "set", FileSize: 8},
71+
Video: &Video{FileID: "v", FileUniqueID: "vu", Width: 640, Height: 480, Duration: 12, Thumbnail: thumb, FileName: "v.mp4", MIMEType: "video/mp4", FileSize: 9},
72+
VideoNote: &VideoNote{FileID: "vn", FileUniqueID: "vnu", Length: 240, Duration: 6, Thumbnail: thumb, FileSize: 11},
73+
Voice: &Voice{FileID: "vo", FileUniqueID: "vou", Duration: 4, MIMEType: "audio/ogg", FileSize: 12},
74+
Contact: &Contact{PhoneNumber: "+1", FirstName: "Ada", LastName: "L", UserID: 1, VCard: "vc"},
75+
Dice: &Dice{Emoji: DiceDart, Value: 6},
7276
Poll: &Poll{
7377
ID: "poll1",
7478
Question: "q?",
@@ -164,6 +168,7 @@ func TestLeafEntitiesJSON(t *testing.T) {
164168
jsonRoundTrip(t, Voice{FileID: "vo", FileUniqueID: "vou", Duration: 3, MIMEType: "m", FileSize: 10})
165169
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})
166170
jsonRoundTrip(t, MessageEntity{Type: EntityTextLink, Offset: 1, Length: 2, URL: "u", User: &User{ID: 1, FirstName: "A"}, Language: "go", CustomEmojiID: "e"})
171+
jsonRoundTrip(t, MessageEntity{Type: EntityDateTime, Offset: 0, Length: 10, UnixTime: 1781027109, DateTimeFormat: "wdt"})
167172
jsonRoundTrip(t, Contact{PhoneNumber: "+1", FirstName: "A", LastName: "B", UserID: 1, VCard: "v"})
168173
jsonRoundTrip(t, Dice{Emoji: DiceBasketball, Value: 5})
169174
jsonRoundTrip(t, Location{Longitude: 1.5, Latitude: 2.25, HorizontalAccuracy: 0.5, LivePeriod: 60, Heading: 90, ProximityAlertRadius: 10})
@@ -219,6 +224,8 @@ func TestDecodeTypeMismatch(t *testing.T) {
219224
`{"photo":5}`, // array field, number value
220225
`{"reply_markup":{"inline_keyboard":7}}`, // nested array, number value
221226
`{"forward_origin":{"type":"user","date":"x"}}`, // union variant, bad field
227+
`{"entities":[{"unix_time":"string"}]}`, // int field, string value
228+
`{"entities":[{"date_time_format":12345}]}`, // string field, number value
222229
}
223230
for _, c := range cases {
224231
var m Message

entities.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package botapi
22

33
import (
44
"strconv"
5+
"strings"
56

67
"github.com/gotd/td/tg"
78
)
@@ -130,6 +131,33 @@ func entitiesFromTg(entities []tg.MessageEntityClass) []MessageEntity {
130131
case *tg.MessageEntityCustomEmoji:
131132
me.Type = EntityCustomEmoji
132133
me.CustomEmojiID = strconv.FormatInt(e.DocumentID, 10)
134+
case *tg.MessageEntityFormattedDate:
135+
me.Type = EntityDateTime
136+
me.UnixTime = e.Date
137+
138+
if e.Relative {
139+
me.DateTimeFormat = "r"
140+
} else {
141+
var dateTimeFormat strings.Builder
142+
143+
if e.DayOfWeek {
144+
dateTimeFormat.WriteString("w")
145+
}
146+
147+
if e.ShortDate {
148+
dateTimeFormat.WriteString("d")
149+
} else if e.LongDate {
150+
dateTimeFormat.WriteString("D")
151+
}
152+
153+
if e.ShortTime {
154+
dateTimeFormat.WriteString("t")
155+
} else if e.LongTime {
156+
dateTimeFormat.WriteString("T")
157+
}
158+
159+
me.DateTimeFormat = dateTimeFormat.String()
160+
}
133161
default:
134162
// Unknown or bot-irrelevant entity (e.g. unknown future types): skip.
135163
continue

entities_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,71 @@ func TestEntitiesAllTypes(t *testing.T) {
117117
t.Fatal("expandable blockquote lost")
118118
}
119119
}
120+
121+
func TestEntitiesFormattedDateFromTg(t *testing.T) {
122+
cases := []struct {
123+
name string
124+
in tg.MessageEntityClass
125+
want MessageEntity
126+
}{
127+
{
128+
name: "RelativeTime",
129+
in: &tg.MessageEntityFormattedDate{
130+
Offset: 0,
131+
Length: 5,
132+
Date: 1781027109,
133+
Relative: true,
134+
},
135+
want: MessageEntity{
136+
Type: EntityDateTime,
137+
UnixTime: 1781027109,
138+
DateTimeFormat: "r",
139+
},
140+
},
141+
{
142+
name: "Absolute-DayOfWeek-ShortDate-ShortTime",
143+
in: &tg.MessageEntityFormattedDate{
144+
Offset: 5,
145+
Length: 10,
146+
Date: 1781027109,
147+
DayOfWeek: true,
148+
ShortDate: true,
149+
ShortTime: true,
150+
},
151+
want: MessageEntity{
152+
Type: EntityDateTime,
153+
UnixTime: 1781027109,
154+
DateTimeFormat: "wdt",
155+
},
156+
},
157+
{
158+
name: "Absolute-LongDate-LongTime",
159+
in: &tg.MessageEntityFormattedDate{
160+
Offset: 10,
161+
Length: 8,
162+
Date: 1781027109,
163+
LongDate: true,
164+
LongTime: true,
165+
},
166+
want: MessageEntity{
167+
Type: EntityDateTime,
168+
UnixTime: 1781027109,
169+
DateTimeFormat: "DT",
170+
},
171+
},
172+
}
173+
174+
for _, tc := range cases {
175+
t.Run(tc.name, func(t *testing.T) {
176+
out := entitiesFromTg([]tg.MessageEntityClass{tc.in})
177+
if len(out) != 1 {
178+
t.Fatalf("expected 1 entity, got %d", len(out))
179+
}
180+
181+
got := out[0]
182+
if got.Type != tc.want.Type || got.UnixTime != tc.want.UnixTime || got.DateTimeFormat != tc.want.DateTimeFormat {
183+
t.Errorf("got %+v, want %+v", got, tc.want)
184+
}
185+
})
186+
}
187+
}

enums.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ const (
7373
EntityTextLink MessageEntityType = "text_link"
7474
EntityTextMention MessageEntityType = "text_mention"
7575
EntityCustomEmoji MessageEntityType = "custom_emoji"
76+
EntityDateTime MessageEntityType = "date_time"
7677
)
7778

7879
// ChatMemberStatus is a member's status in a chat.

types_message.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import "github.com/gotd/td/tg"
55
// MessageEntity represents one special entity in a text message (e.g. a
66
// hashtag, link, or formatted run).
77
type MessageEntity struct {
8-
Type MessageEntityType `json:"type"`
9-
Offset int `json:"offset"`
10-
Length int `json:"length"`
11-
URL string `json:"url,omitempty"`
12-
User *User `json:"user,omitempty"`
13-
Language string `json:"language,omitempty"`
14-
CustomEmojiID string `json:"custom_emoji_id,omitempty"`
8+
Type MessageEntityType `json:"type"`
9+
Offset int `json:"offset"`
10+
Length int `json:"length"`
11+
URL string `json:"url,omitempty"`
12+
User *User `json:"user,omitempty"`
13+
Language string `json:"language,omitempty"`
14+
CustomEmojiID string `json:"custom_emoji_id,omitempty"`
15+
UnixTime int `json:"unix_time,omitempty"`
16+
DateTimeFormat string `json:"date_time_format,omitempty"`
1517
}
1618

1719
// Contact represents a phone contact.

0 commit comments

Comments
 (0)