1// Discordgo - Discord bindings for Go
2// Available at https://github.com/bwmarrin/discordgo
3
4// Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>.  All rights reserved.
5// Use of this source code is governed by a BSD-style
6// license that can be found in the LICENSE file.
7
8// This file contains code related to the Message struct
9
10package discordgo
11
12import (
13	"io"
14	"regexp"
15	"strings"
16)
17
18// MessageType is the type of Message
19// https://discord.com/developers/docs/resources/channel#message-object-message-types
20type MessageType int
21
22// Block contains the valid known MessageType values
23const (
24	MessageTypeDefault MessageType = iota
25	MessageTypeRecipientAdd
26	MessageTypeRecipientRemove
27	MessageTypeCall
28	MessageTypeChannelNameChange
29	MessageTypeChannelIconChange
30	MessageTypeChannelPinnedMessage
31	MessageTypeGuildMemberJoin
32	MessageTypeUserPremiumGuildSubscription
33	MessageTypeUserPremiumGuildSubscriptionTierOne
34	MessageTypeUserPremiumGuildSubscriptionTierTwo
35	MessageTypeUserPremiumGuildSubscriptionTierThree
36	MessageTypeChannelFollowAdd
37	MessageTypeGuildDiscoveryDisqualified = iota + 1
38	MessageTypeGuildDiscoveryRequalified
39	MessageTypeReply = iota + 4
40	MessageTypeApplicationCommand
41)
42
43// A Message stores all data related to a specific Discord message.
44type Message struct {
45	// The ID of the message.
46	ID string `json:"id"`
47
48	// The ID of the channel in which the message was sent.
49	ChannelID string `json:"channel_id"`
50
51	// The ID of the guild in which the message was sent.
52	GuildID string `json:"guild_id,omitempty"`
53
54	// The content of the message.
55	Content string `json:"content"`
56
57	// The time at which the messsage was sent.
58	// CAUTION: this field may be removed in a
59	// future API version; it is safer to calculate
60	// the creation time via the ID.
61	Timestamp Timestamp `json:"timestamp"`
62
63	// The time at which the last edit of the message
64	// occurred, if it has been edited.
65	EditedTimestamp Timestamp `json:"edited_timestamp"`
66
67	// The roles mentioned in the message.
68	MentionRoles []string `json:"mention_roles"`
69
70	// Whether the message is text-to-speech.
71	TTS bool `json:"tts"`
72
73	// Whether the message mentions everyone.
74	MentionEveryone bool `json:"mention_everyone"`
75
76	// The author of the message. This is not guaranteed to be a
77	// valid user (webhook-sent messages do not possess a full author).
78	Author *User `json:"author"`
79
80	// A list of attachments present in the message.
81	Attachments []*MessageAttachment `json:"attachments"`
82
83	// A list of embeds present in the message. Multiple
84	// embeds can currently only be sent by webhooks.
85	Embeds []*MessageEmbed `json:"embeds"`
86
87	// A list of users mentioned in the message.
88	Mentions []*User `json:"mentions"`
89
90	// A list of reactions to the message.
91	Reactions []*MessageReactions `json:"reactions"`
92
93	// Whether the message is pinned or not.
94	Pinned bool `json:"pinned"`
95
96	// The type of the message.
97	Type MessageType `json:"type"`
98
99	// The webhook ID of the message, if it was generated by a webhook
100	WebhookID string `json:"webhook_id"`
101
102	// Member properties for this message's author,
103	// contains only partial information
104	Member *Member `json:"member"`
105
106	// Channels specifically mentioned in this message
107	// Not all channel mentions in a message will appear in mention_channels.
108	// Only textual channels that are visible to everyone in a lurkable guild will ever be included.
109	// Only crossposted messages (via Channel Following) currently include mention_channels at all.
110	// If no mentions in the message meet these requirements, this field will not be sent.
111	MentionChannels []*Channel `json:"mention_channels"`
112
113	// Is sent with Rich Presence-related chat embeds
114	Activity *MessageActivity `json:"activity"`
115
116	// Is sent with Rich Presence-related chat embeds
117	Application *MessageApplication `json:"application"`
118
119	// MessageReference contains reference data sent with crossposted messages
120	MessageReference *MessageReference `json:"message_reference"`
121
122	// The flags of the message, which describe extra features of a message.
123	// This is a combination of bit masks; the presence of a certain permission can
124	// be checked by performing a bitwise AND between this int and the flag.
125	Flags MessageFlags `json:"flags"`
126}
127
128// MessageFlags is the flags of "message" (see MessageFlags* consts)
129// https://discord.com/developers/docs/resources/channel#message-object-message-flags
130type MessageFlags int
131
132// Valid MessageFlags values
133const (
134	MessageFlagsCrossPosted MessageFlags = 1 << iota
135	MessageFlagsIsCrossPosted
136	MessageFlagsSupressEmbeds
137	MessageFlagsSourceMessageDeleted
138	MessageFlagsUrgent
139)
140
141// File stores info about files you e.g. send in messages.
142type File struct {
143	Name        string
144	ContentType string
145	Reader      io.Reader
146}
147
148// MessageSend stores all parameters you can send with ChannelMessageSendComplex.
149type MessageSend struct {
150	Content         string                  `json:"content,omitempty"`
151	Embed           *MessageEmbed           `json:"embed,omitempty"`
152	TTS             bool                    `json:"tts"`
153	Files           []*File                 `json:"-"`
154	AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"`
155	Reference       *MessageReference       `json:"message_reference,omitempty"`
156
157	// TODO: Remove this when compatibility is not required.
158	File *File `json:"-"`
159}
160
161// MessageEdit is used to chain parameters via ChannelMessageEditComplex, which
162// is also where you should get the instance from.
163type MessageEdit struct {
164	Content         *string                 `json:"content,omitempty"`
165	Embed           *MessageEmbed           `json:"embed,omitempty"`
166	AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"`
167
168	ID      string
169	Channel string
170}
171
172// NewMessageEdit returns a MessageEdit struct, initialized
173// with the Channel and ID.
174func NewMessageEdit(channelID string, messageID string) *MessageEdit {
175	return &MessageEdit{
176		Channel: channelID,
177		ID:      messageID,
178	}
179}
180
181// SetContent is the same as setting the variable Content,
182// except it doesn't take a pointer.
183func (m *MessageEdit) SetContent(str string) *MessageEdit {
184	m.Content = &str
185	return m
186}
187
188// SetEmbed is a convenience function for setting the embed,
189// so you can chain commands.
190func (m *MessageEdit) SetEmbed(embed *MessageEmbed) *MessageEdit {
191	m.Embed = embed
192	return m
193}
194
195// AllowedMentionType describes the types of mentions used
196// in the MessageAllowedMentions type.
197type AllowedMentionType string
198
199// The types of mentions used in MessageAllowedMentions.
200const (
201	AllowedMentionTypeRoles    AllowedMentionType = "roles"
202	AllowedMentionTypeUsers    AllowedMentionType = "users"
203	AllowedMentionTypeEveryone AllowedMentionType = "everyone"
204)
205
206// MessageAllowedMentions allows the user to specify which mentions
207// Discord is allowed to parse in this message. This is useful when
208// sending user input as a message, as it prevents unwanted mentions.
209// If this type is used, all mentions must be explicitly whitelisted,
210// either by putting an AllowedMentionType in the Parse slice
211// (allowing all mentions of that type) or, in the case of roles and
212// users, explicitly allowing those mentions on an ID-by-ID basis.
213// For more information on this functionality, see:
214// https://discordapp.com/developers/docs/resources/channel#allowed-mentions-object-allowed-mentions-reference
215type MessageAllowedMentions struct {
216	// The mention types that are allowed to be parsed in this message.
217	// Please note that this is purposely **not** marked as omitempty,
218	// so if a zero-value MessageAllowedMentions object is provided no
219	// mentions will be allowed.
220	Parse []AllowedMentionType `json:"parse"`
221
222	// A list of role IDs to allow. This cannot be used when specifying
223	// AllowedMentionTypeRoles in the Parse slice.
224	Roles []string `json:"roles,omitempty"`
225
226	// A list of user IDs to allow. This cannot be used when specifying
227	// AllowedMentionTypeUsers in the Parse slice.
228	Users []string `json:"users,omitempty"`
229}
230
231// A MessageAttachment stores data for message attachments.
232type MessageAttachment struct {
233	ID       string `json:"id"`
234	URL      string `json:"url"`
235	ProxyURL string `json:"proxy_url"`
236	Filename string `json:"filename"`
237	Width    int    `json:"width"`
238	Height   int    `json:"height"`
239	Size     int    `json:"size"`
240}
241
242// MessageEmbedFooter is a part of a MessageEmbed struct.
243type MessageEmbedFooter struct {
244	Text         string `json:"text,omitempty"`
245	IconURL      string `json:"icon_url,omitempty"`
246	ProxyIconURL string `json:"proxy_icon_url,omitempty"`
247}
248
249// MessageEmbedImage is a part of a MessageEmbed struct.
250type MessageEmbedImage struct {
251	URL      string `json:"url,omitempty"`
252	ProxyURL string `json:"proxy_url,omitempty"`
253	Width    int    `json:"width,omitempty"`
254	Height   int    `json:"height,omitempty"`
255}
256
257// MessageEmbedThumbnail is a part of a MessageEmbed struct.
258type MessageEmbedThumbnail struct {
259	URL      string `json:"url,omitempty"`
260	ProxyURL string `json:"proxy_url,omitempty"`
261	Width    int    `json:"width,omitempty"`
262	Height   int    `json:"height,omitempty"`
263}
264
265// MessageEmbedVideo is a part of a MessageEmbed struct.
266type MessageEmbedVideo struct {
267	URL    string `json:"url,omitempty"`
268	Width  int    `json:"width,omitempty"`
269	Height int    `json:"height,omitempty"`
270}
271
272// MessageEmbedProvider is a part of a MessageEmbed struct.
273type MessageEmbedProvider struct {
274	URL  string `json:"url,omitempty"`
275	Name string `json:"name,omitempty"`
276}
277
278// MessageEmbedAuthor is a part of a MessageEmbed struct.
279type MessageEmbedAuthor struct {
280	URL          string `json:"url,omitempty"`
281	Name         string `json:"name,omitempty"`
282	IconURL      string `json:"icon_url,omitempty"`
283	ProxyIconURL string `json:"proxy_icon_url,omitempty"`
284}
285
286// MessageEmbedField is a part of a MessageEmbed struct.
287type MessageEmbedField struct {
288	Name   string `json:"name,omitempty"`
289	Value  string `json:"value,omitempty"`
290	Inline bool   `json:"inline,omitempty"`
291}
292
293// An MessageEmbed stores data for message embeds.
294type MessageEmbed struct {
295	URL         string                 `json:"url,omitempty"`
296	Type        EmbedType              `json:"type,omitempty"`
297	Title       string                 `json:"title,omitempty"`
298	Description string                 `json:"description,omitempty"`
299	Timestamp   string                 `json:"timestamp,omitempty"`
300	Color       int                    `json:"color,omitempty"`
301	Footer      *MessageEmbedFooter    `json:"footer,omitempty"`
302	Image       *MessageEmbedImage     `json:"image,omitempty"`
303	Thumbnail   *MessageEmbedThumbnail `json:"thumbnail,omitempty"`
304	Video       *MessageEmbedVideo     `json:"video,omitempty"`
305	Provider    *MessageEmbedProvider  `json:"provider,omitempty"`
306	Author      *MessageEmbedAuthor    `json:"author,omitempty"`
307	Fields      []*MessageEmbedField   `json:"fields,omitempty"`
308}
309
310// EmbedType is the type of embed
311// https://discord.com/developers/docs/resources/channel#embed-object-embed-types
312type EmbedType string
313
314// Block of valid EmbedTypes
315const (
316	EmbedTypeRich    EmbedType = "rich"
317	EmbedTypeImage   EmbedType = "image"
318	EmbedTypeVideo   EmbedType = "video"
319	EmbedTypeGifv    EmbedType = "gifv"
320	EmbedTypeArticle EmbedType = "article"
321	EmbedTypeLink    EmbedType = "link"
322)
323
324// MessageReactions holds a reactions object for a message.
325type MessageReactions struct {
326	Count int    `json:"count"`
327	Me    bool   `json:"me"`
328	Emoji *Emoji `json:"emoji"`
329}
330
331// MessageActivity is sent with Rich Presence-related chat embeds
332type MessageActivity struct {
333	Type    MessageActivityType `json:"type"`
334	PartyID string              `json:"party_id"`
335}
336
337// MessageActivityType is the type of message activity
338type MessageActivityType int
339
340// Constants for the different types of Message Activity
341const (
342	MessageActivityTypeJoin MessageActivityType = iota + 1
343	MessageActivityTypeSpectate
344	MessageActivityTypeListen
345	MessageActivityTypeJoinRequest
346)
347
348// MessageFlag describes an extra feature of the message
349type MessageFlag int
350
351// Constants for the different bit offsets of Message Flags
352const (
353	// This message has been published to subscribed channels (via Channel Following)
354	MessageFlagCrossposted MessageFlag = 1 << iota
355	// This message originated from a message in another channel (via Channel Following)
356	MessageFlagIsCrosspost
357	// Do not include any embeds when serializing this message
358	MessageFlagSuppressEmbeds
359)
360
361// MessageApplication is sent with Rich Presence-related chat embeds
362type MessageApplication struct {
363	ID          string `json:"id"`
364	CoverImage  string `json:"cover_image"`
365	Description string `json:"description"`
366	Icon        string `json:"icon"`
367	Name        string `json:"name"`
368}
369
370// MessageReference contains reference data sent with crossposted messages
371type MessageReference struct {
372	MessageID string `json:"message_id"`
373	ChannelID string `json:"channel_id"`
374	GuildID   string `json:"guild_id,omitempty"`
375}
376
377// Reference returns MessageReference of given message
378func (m *Message) Reference() *MessageReference {
379	return &MessageReference{
380		GuildID:   m.GuildID,
381		ChannelID: m.ChannelID,
382		MessageID: m.ID,
383	}
384}
385
386// ContentWithMentionsReplaced will replace all @<id> mentions with the
387// username of the mention.
388func (m *Message) ContentWithMentionsReplaced() (content string) {
389	content = m.Content
390
391	for _, user := range m.Mentions {
392		content = strings.NewReplacer(
393			"<@"+user.ID+">", "@"+user.Username,
394			"<@!"+user.ID+">", "@"+user.Username,
395		).Replace(content)
396	}
397	return
398}
399
400var patternChannels = regexp.MustCompile("<#[^>]*>")
401
402// ContentWithMoreMentionsReplaced will replace all @<id> mentions with the
403// username of the mention, but also role IDs and more.
404func (m *Message) ContentWithMoreMentionsReplaced(s *Session) (content string, err error) {
405	content = m.Content
406
407	if !s.StateEnabled {
408		content = m.ContentWithMentionsReplaced()
409		return
410	}
411
412	channel, err := s.State.Channel(m.ChannelID)
413	if err != nil {
414		content = m.ContentWithMentionsReplaced()
415		return
416	}
417
418	for _, user := range m.Mentions {
419		nick := user.Username
420
421		member, err := s.State.Member(channel.GuildID, user.ID)
422		if err == nil && member.Nick != "" {
423			nick = member.Nick
424		}
425
426		content = strings.NewReplacer(
427			"<@"+user.ID+">", "@"+user.Username,
428			"<@!"+user.ID+">", "@"+nick,
429		).Replace(content)
430	}
431	for _, roleID := range m.MentionRoles {
432		role, err := s.State.Role(channel.GuildID, roleID)
433		if err != nil || !role.Mentionable {
434			continue
435		}
436
437		content = strings.Replace(content, "<@&"+role.ID+">", "@"+role.Name, -1)
438	}
439
440	content = patternChannels.ReplaceAllStringFunc(content, func(mention string) string {
441		channel, err := s.State.Channel(mention[2 : len(mention)-1])
442		if err != nil || channel.Type == ChannelTypeGuildVoice {
443			return mention
444		}
445
446		return "#" + channel.Name
447	})
448	return
449}
450