Skip to content
phyce edited this page Aug 16, 2022 · 49 revisions

Contents

Gateway Intents

As of v0.21.0, DiscordGo supports the Gateway Intents feature, which allows bot developers to choose which events to receive from Discord on connection, preventing unnecessary events from taking up network and processing time. This feature also makes certain API features require extra authorization, such as receiving events around guild members and their presences. These are called Privileged Intents.

By default, DiscordGo enables all non-privileged intents. To enable all intents (including privileged intents), you can set your session's Identify.Intents field to discordgo.MakeIntent(discordgo.IntentsAll) before calling Open; note that this requires the extra step of enabling these privileged intents in your application portal; starting October 7, 2020 this will require an extra verification step through Discord. If this extra verification isn't obtained, your bot will not have access to those privileged intents, and therefore will not have access to the events that they provide.

As of the time of writing this, the intents system is optional. It can be disabled by setting the same Identify.Intents field on your session to nil. This will lead to the old behavior of all events being sent across the gateway. At some point in the future, intents will become mandatory, at which time disabling the system will (presumably) prevent your bot from connecting. We would suggest integrating the system into your bot as soon as possible to make that transition as smooth as can be.

Application commands

Application commands are commands that an application can register to Discord. They provide users a first-class way of interacting directly with your application that feels deeply integrated into Discord. More about them you can read here.

Application commands release notice

Quoting Carson (CarsonHoffman - maintainer of this library)

To be honest, we’re in a bit of a holding pattern to see if other changes happen. We still get breaking changes on Discord’s end every few weeks.

So, if you want to install/update library correctly - here is the instructions.

First of all you must use @master revision of discordgo's package (github.com/bwmarrin/discordgo@master).

To add the library to the dependencies you just do go get github.com/bwmarrin/discordgo@master. Or if you already have discordgo as a dependency you might want to execute go list -m github.com/bwmarrin/discordgo@master and use replace statement in go.mod.

Example:

replace github.com/bwmarrin/discordgo v0.23.2 => <output of go list>

Registering application commands

To register an application command you need to call ApplicationCommandCreate function.

Slash commands

Chat application commands (aka slash commands) are the commands user inputs in the chat using / prefix.

command := &discordgo.ApplicationCommand{
    Name: "command-name",
    Type: discordgo.ChatApplicationCommand,
    Description: "Slash commands are amazing",
}

Options

To register a command with options you specify them in the Options field.

command := &discordgo.ApplicationCommand{
    Name: "command-name",
    Type: discordgo.ChatApplicationCommand,
    Description: "Slash commands are amazing",
    Options: []*discordgo.ApplicationCommandOption {
        {
            Name: "rick-astley",
            Description: "The singer",
            Type: discordgo.ApplicationCommandOptionString,
            // Commands might have choices, think of them like of enum values
            Choices: []*discordgo.ApplicationCommandOptionChoice {
                {
                    Name: "never-gonna-give-you-up",
                    Value: "never gonna run around and desert you",
                },
                {
                    Name: "never-gonna-make-you-cry",
                    Value: "never gonna tell a lie and hurt you",
                },
            },
        },
    },
}

Subcommands

To make a subcommand (or subcommand group) you add the option with their name and set the type to discordgo.ApplicationCommandOptionSubCommand or discordgo.ApplicationCommandOptionSubCommandGroup. Also currently the command must not have any additional options than subcommands and cannot be executed without usage of them.

command := &discordgo.ApplicationCommand{
    Name: "command-name",
    Type: discordgo.ChatApplicationCommand,
    Description: "This is command description",
    Options: []*discordgo.ApplicationCommandOption {
        {
            Name: "subcommand",
            Type: discordgo.ApplicationCommandOptionSubCommand,
            Description: "This is subcommand description",
        },
        {
            Name: "subcommand-group",
            Type: discordgo.ApplicationCommandOptionSubCommandGroup,
            Description: "This is subcommand group description",
            Options: []*discordgo.ApplicationCommandOption {
                {
                    Name: "subcommand",
                    Type: discordgo.ApplicationCommandOptionSubCommand,
                    Description: "This is subcommand description",
                },
            },
        },
    },
}

To process subcommands in the interaction event handler/http endpoint you must look into options[0]. For subcommand groups you do that twice, second time with options (subcommand) of options[0] (subcommand group).

Context menu commands

Context menu commands are the commands embedded to right-click context menu. They might be of two types: user and message ones. The user could be performed by right-clicking on a user, the other one on a message.

command := discordgo.ApplicationCommand {
    Name: "User scope command", // Context menu commands might contain spaces and capitalized letters.
    Type: discordgo.UserApplicationCommand, // Or discordgo.MessageApplicationCommand for message context menu.
}

Handling application commands

To handle application command first of all you must convert the data to application command kind (use i.ApplicationCommandData() to convert).

func handler(s *discordgo.Session, i *discordgo.InteractionCreate) {
    if i.Type != discordgo.InteractionApplicationCommand {
        return
    }

    data := i.ApplicationCommandData()
    switch data.Options[0].Name {
    case "command":
        // Do something
    case "subcommand-group":
        data := data.Options[0]
        switch data.Options[0].Name {
        case "subcommand":
            // Do something
        }
    case "subcommand":
        data := data.Options[0]
        switch data.Options[0].Name {
        case "subcommand":
            // Do something
        }
    }
}

Responding to interactions

The interaction handler must respond to an interaction in (approximately) 3 seconds, otherwise the interaction would be marked failed. To respond to an interaction you call the InteractionRespond method.

There are multiple interaction response types:

Note: if you want to respond to the interaction later, you might use InteractionResponseDeferred response types.

const (
	// InteractionResponsePong is for ACK ping event.
	InteractionResponsePong InteractionResponseType = 1
	// InteractionResponseChannelMessageWithSource is for responding with a message, showing the user's input.
	InteractionResponseChannelMessageWithSource InteractionResponseType = 4
	// InteractionResponseDeferredChannelMessageWithSource acknowledges that the event was received, and that a follow-up will come later.
	InteractionResponseDeferredChannelMessageWithSource InteractionResponseType = 5
	// InteractionResponseDeferredMessageUpdate acknowledges that the message component interaction event was received, and message will be updated later.
	InteractionResponseDeferredMessageUpdate InteractionResponseType = 6
	// InteractionResponseUpdateMessage is for updating the message to which message component was attached.
	InteractionResponseUpdateMessage InteractionResponseType = 7
)

Sending embeds

Embeds are sent through the ChannelMessageSendEmbed method. To do so, you will need to create an embed.

Creating an embed

embed := &discordgo.MessageEmbed{
    Author:      &discordgo.MessageEmbedAuthor{},
    Color:       0x00ff00, // Green
    Description: "This is a discordgo embed",
    Fields: []*discordgo.MessageEmbedField{
        &discordgo.MessageEmbedField{
            Name:   "I am a field",
            Value:  "I am a value",
            Inline: true,
        },
        &discordgo.MessageEmbedField{
            Name:   "I am a second field",
            Value:  "I am a value",
            Inline: true,
        },
    },
    Image: &discordgo.MessageEmbedImage{
        URL: "https://cdn.discordapp.com/avatars/119249192806776836/cc32c5c3ee602e1fe252f9f595f9010e.jpg?size=2048",
    },
    Thumbnail: &discordgo.MessageEmbedThumbnail{
        URL: "https://cdn.discordapp.com/avatars/119249192806776836/cc32c5c3ee602e1fe252f9f595f9010e.jpg?size=2048",
    },
    Timestamp: time.Now().Format(time.RFC3339), // Discord wants ISO8601; RFC3339 is an extension of ISO8601 and should be completely compatible.
    Title:     "I am an Embed",
}

session.ChannelMessageSendEmbed(channelid, embed)

Simplifying Embeds

Because creating an embed is a lot of typing, you could simplify it by creating a struct that anonymously embeds the *discordgo.MessageEmbed and use that to create helper functions.

Here is an example, which copies discord.js's helper functions for embeds. Using this, the previous method of creating an embed would become

embed := NewEmbed().
    SetTitle("I am an embed").
    SetDescription("This is a discordgo embed").
    AddField("I am a field", "I am a value").
    AddField("I am a second field", "I am a value").
    SetImage("https://cdn.discordapp.com/avatars/119249192806776836/cc32c5c3ee602e1fe252f9f595f9010e.jpg?size=2048").
    SetThumbnail("https://cdn.discordapp.com/avatars/119249192806776836/cc32c5c3ee602e1fe252f9f595f9010e.jpg?size=2048").
    SetColor(0x00ff00).MessageEmbed


session.ChannelMessageSendEmbed(channelid, embed)

Embeds using attachments

Attachments can be used to send files along with embed messages.

f, err := os.Open(fileName)
if err != nil {
    return nil, err
}
defer f.Close()

ms := &discordgo.MessageSend{
    Embed: &discordgo.MessageEmbed{
        Image: &discordgo.MessageEmbedImage{
            URL: "attachment://" + fileName,
        },
    },
    Files: []*discordgo.File{
        &discordgo.File{
            Name:   fileName,
            Reader: f,
        },
    },
}

s.ChannelMessageSendComplex(channelID, ms)

Permissions and Roles

Determining if a role has a permission

Discord permissions are stored as an integer. To extract the permission from the role you need to use the bitwise AND (&) operator and see if the result is not zero.

If you are checking permissions specific to channels, make sure to remember the channel PermissionOverides which can either allow or deny a permission to a role, this is done for you with UserChannelPermissions

// MemberHasPermission checks if a member has the given permission
// for example, If you would like to check if user has the administrator
// permission you would use
// --- MemberHasPermission(s, guildID, userID, discordgo.PermissionAdministrator)
// If you want to check for multiple permissions you would use the bitwise OR
// operator to pack more bits in. (e.g): PermissionAdministrator|PermissionAddReactions
// =================================================================================
//     s          :  discordgo session
//     guildID    :  guildID of the member you wish to check the roles of
//     userID     :  userID of the member you wish to retrieve
//     permission :  the permission you wish to check for
func MemberHasPermission(s *discordgo.Session, guildID string, userID string, permission int) (bool, error) {
	member, err := s.State.Member(guildID, userID)
	if err != nil {
		if member, err = s.GuildMember(guildID, userID); err != nil {
			return false, err
		}
	}

    // Iterate through the role IDs stored in member.Roles
    // to check permissions
	for _, roleID := range member.Roles {
		role, err := s.State.Role(guildID, roleID)
		if err != nil {
			return false, err
		}
		if role.Permissions&permission != 0 {
			return true, nil
		}
	}

	return false, nil
}

Get the time a snowflake was created

A snowflake is what discord uses for its ID system, (e.g) user.ID, channel.ID, guild.ID.

// CreationTime returns the creation time of a Snowflake ID relative to the creation of Discord.
// Taken from https://github.com/Moonlington/FloSelfbot/blob/master/commands/commandutils.go#L117
func CreationTime(ID string) (t time.Time, err error) {
	i, err := strconv.ParseInt(ID, 10, 64)
	if err != nil {
		return
	}
	timestamp := (i >> 22) + 1420070400000
	t = time.Unix(timestamp/1000, 0)
	return
}

Getting the guild from a message

To get a guild from a message you first need to obtain the guildID from the channel. then get the guild from the guildID property of the channel.

Getting the channel and guild from the state is optional.

NOTE: the old method of getting a guild from a message required first getting the channel to get the GuildID and then retrieving the guild from there. Now that the guildID is stored in the message struct, as well as many others, this is no longer needed.

// Attempt to get the guild from the state, 
// If there is an error, fall back to the restapi.
guild, err := session.State.Guild(message.GuildID)
if err != nil {
    guild, err = session.Guild(message.GuildID)
    if err != nil {
        return
    }
}

Important note about guild members

I want to let you know that the Session.GuildMember() function does not fill in the GuildID field of the Member struct. Session.State.Member() does fill it. Because I know some people who did have some issues with that fact, I decided to create a little FAQ entry.

Checking if a message is a direct message (DM)

// ComesFromDM returns true if a message comes from a DM channel
func ComesFromDM(s *discordgo.Session, m *discordgo.MessageCreate) (bool, error) {
	channel, err := s.State.Channel(m.ChannelID)
	if err != nil {
		if channel, err = s.Channel(m.ChannelID); err != nil {
			return false, err
		}
	}

	return channel.Type == discordgo.ChannelTypeDM, nil
}

Getting multiple messages from a channel

Only bots have access to the ChannelMessages function. If you are using a user account, you will have to fetch the messages through the state.

Bot account

example:

// This will fetch a limit of 100 messages from channelID
// The beforeID, aroundID, and afterID are optional parameters so they can 
// Be left blank
messages, _ := session.ChannelMessages(channelID, 100, "", "", "")

Message Caching

To utilize the message caching feature of the state, you have to set State.MaxMessageLimit to a value greater than zero.

example:

// Create a discordgo session and set the state message tracking limit
session, _ := discordgo.New("Bot token")
session.State.MaxMessageLimit = 100;


// Obtain messages from channel
channel, _ := session.State.Channel(channelID)
for i := 0; i < 100; i++ {
    fmt.Println(channel.Messages[i].Content)
}

Playing audio over a voice connection

Finding a user's voice channel

If you want to find the voice channel a user is currently in, you need to search for their voice state in the State. This example scans over every guild's voice states to find the User's.

func findUserVoiceState(session *discordgo.Session, userid string) (*discordgo.VoiceState, error) {
	for _, guild := range session.State.Guilds {
		for _, vs := range guild.VoiceStates {
			if vs.UserID == userid {
				return vs, nil
			}
		}
	}
	return nil, errors.New("Could not find user's voice state")
}

If you only want to scan a single guild, you can obtain a guild from state using State.Guild and scan for the userID in the voice states.

Connecting to a voice channel

// joinUserVoiceChannel joins a session to the same channel as another user.
func joinUserVoiceChannel(session *discordgo.Session, userID string) (*discordgo.VoiceConnection, error) {
	// Find a user's current voice channel
	vs, err := findUserVoiceState(session, userID)
	if err != nil {
		return nil, err
	}

    // Join the user's channel and start unmuted and deafened.
    return session.ChannelVoiceJoin(vs.GuildID, vs.ChannelID, false, true)
}

Reading opus

// Reads an opus packet to send over the vc.OpusSend channel
func readOpus(source io.Reader) ([]byte, error) {
	var opuslen int16
	err := binary.Read(source, binary.LittleEndian, &opuslen)
	if err != nil {
		if err == io.EOF || err == io.ErrUnexpectedEOF {
			return nil, err
		}
		return nil, errors.New("ERR reading opus header")
	}

	var opusframe = make([]byte, opuslen)
	err = binary.Read(source, binary.LittleEndian, &opusframe)
	if err != nil {
		if err == io.EOF || err == io.ErrUnexpectedEOF {
			return nil, err
		}
		return nil, errors.New("ERR reading opus frame")
	}

	return opusframe, nil
}

Sending opus

Follow the instructions here.