Browse Source

initial commit

Thisnthat 1 year ago
commit
eee5e7e5ae
9 changed files with 717 additions and 0 deletions
  1. 87 0
      bot.go
  2. 118 0
      bot/event/mux/help.go
  3. 94 0
      bot/event/mux/message.go
  4. 217 0
      bot/event/mux/router.go
  5. 97 0
      bot/event/mux/slashRouter.go
  6. 16 0
      go.mod
  7. 36 0
      go.sum
  8. 24 0
      members.go
  9. 28 0
      session.go

+ 87 - 0
bot.go

@@ -0,0 +1,87 @@
+package discord
+
+import (
+	"fmt"
+	"os"
+	"os/signal"
+	"sync"
+	"syscall"
+
+	"git.mgmcomp.net/thisnthat/discord/bot/event/mux"
+	"github.com/bwmarrin/discordgo"
+	"github.com/sirupsen/logrus"
+)
+
+type Bot struct {
+	token       string
+	session     *discordgo.Session
+	online      bool
+	running     chan os.Signal
+	slashRouter *mux.SlashRouter
+}
+
+func (bot *Bot) AddHandler(handler interface{}) error {
+	// If the bot is online, we can not add a handler
+	if bot.online {
+		err := fmt.Errorf("unable to add handler to a bot that is already online")
+		return err
+	}
+
+	bot.session.AddHandler(handler)
+	return nil
+}
+
+func (bot *Bot) SetEventRouter(router *mux.Router) {
+	bot.AddHandler(router.OnMessageCreate)
+}
+
+func (bot *Bot) SetSlashEventRouter(slashRouter *mux.SlashRouter) {
+	bot.slashRouter = slashRouter
+}
+
+func (bot *Bot) Start(wg *sync.WaitGroup) error {
+	defer wg.Done()
+
+	err := bot.session.Open()
+	if err != nil {
+		return err
+	}
+
+	err = bot.slashRouter.GenerateCommands(bot.session)
+	if err != nil {
+		return err
+	}
+
+	go logrus.Info("The bot has started")
+	bot.running = make(chan os.Signal, 1)
+	bot.online = true
+	signal.Notify(bot.running, syscall.SIGINT, syscall.SIGTERM)
+	<-bot.running
+
+	bot.slashRouter.CleanCommands(bot.session)
+	bot.session.Close()
+	go logrus.Info("The bot has gone offline")
+	bot.online = false
+
+	return nil
+}
+
+func (bot *Bot) Stop() {
+	if bot.online {
+		bot.running <- syscall.SIGINT
+	}
+}
+
+func NewBot(token string) (*Bot, error) {
+	bot := &Bot{}
+	bot.token = token
+
+	var err error
+	bot.session, err = discordgo.New("Bot " + bot.token)
+
+	if err != nil {
+		return bot, err
+	}
+
+	return bot, nil
+}

+ 118 - 0
bot/event/mux/help.go

@@ -0,0 +1,118 @@
+package mux
+
+import (
+	"fmt"
+	"sort"
+	"strconv"
+
+	"git.mgmcomp.net/thisnthat/discordgo"
+)
+
+func (r *Router) helpCommandHandler(s *discordgo.Session, m *discordgo.Message, ctx *Context) {
+	displayPrefix := ""
+
+	switch ctx.Method {
+	case DirectMethod:
+		displayPrefix = ""
+	case PrefixMethod:
+		displayPrefix = r.prefix
+	case MentionMethod:
+		displayPrefix = fmt.Sprintf("@%s ", s.State.User.Username)
+	}
+
+	maxDisplayLen := 0
+	cmdmap := make(map[string]*route)
+	catMap := make(map[string][]string)
+	var cats []string
+
+	var isAdmin = r.isAdminRoute(s, m, ctx)
+
+	for _, v := range r.categories {
+		keys := make([]string, 0, len(r.routes))
+		for _, route := range r.routes {
+			// If the route is hidde, do not add it to the help list
+			if route.Hidden {
+				continue
+			}
+
+			if route.Admin && !isAdmin {
+				continue
+			}
+
+			if route.CategoryID == v.ID {
+				l := len(route.Usage) // TODO: Add the +args part :)
+				if l > maxDisplayLen {
+					maxDisplayLen = l
+				}
+				cmdmap[route.Name] = route
+				keys = append(keys, route.Name)
+			}
+		}
+
+		if len(keys) > 0 {
+			sort.Strings(keys)
+			catMap[v.Description] = keys
+			cats = append(cats, v.Description)
+		}
+	}
+
+	keys := make([]string, 0, len(r.routes))
+	for _, route := range r.routes {
+		if route.CategoryID == 0 {
+			if route.Admin && !isAdmin {
+				continue
+			}
+
+			l := len(route.Usage) // TODO: Add the +args part :)
+			if l > maxDisplayLen {
+				maxDisplayLen = l
+			}
+			cmdmap[route.Name] = route
+
+			keys = append(keys, route.Name)
+		}
+	}
+
+	v, ok := cmdmap["help"]
+	if ok {
+		keys = append([]string{v.Name}, keys...)
+	}
+
+	v, ok = cmdmap["about"]
+	if ok {
+		keys = append([]string{v.Name}, keys...)
+	}
+
+	if len(keys) > 0 {
+		sort.Strings(keys)
+		catMap[""] = keys
+		cats = append(cats, "")
+	}
+
+	maxDisplayLen = maxDisplayLen + 3
+
+	// TODO: Learn more link needs to be configurable
+	innerResp := ""
+
+	// Add sorted result to help msg
+	for _, catKey := range cats {
+		if catKey != "" {
+			innerResp += fmt.Sprintf("\n%s\n", catKey)
+		} else {
+			innerResp += "\n\n"
+		}
+
+		routes := catMap[catKey]
+		for _, routeKey := range routes {
+			route := cmdmap[routeKey]
+			innerResp += fmt.Sprintf("%s%-"+strconv.Itoa(maxDisplayLen)+"s # %s\n", displayPrefix, route.Usage, route.Description)
+		}
+	}
+
+	if innerResp == "" {
+		return
+	}
+
+	resp := "```autoit\n" + innerResp + "```\n"
+	s.ChannelMessageSend(m.ChannelID, resp)
+}

+ 94 - 0
bot/event/mux/message.go

@@ -0,0 +1,94 @@
+package mux
+
+import (
+	"fmt"
+	"regexp"
+	"strings"
+
+	"git.mgmcomp.net/thisnthat/discordgo"
+)
+
+// OnMessageCreate is a DiscordGo Event Handler function.  This must be
+// registered using the DiscordGo.Session.AddHandler function.  This function
+// will receive all Discord messages and parse them for matches to registered
+// routes.
+func (r *Router) OnMessageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
+	var err error
+
+	// Ignore all messages created by the Bot account itself
+	if m.Author.ID == s.State.User.ID {
+		return
+	}
+
+	// Start building the command context
+	ctx := &Context{
+		Content: strings.TrimSpace(m.Content),
+	}
+
+	var channel *discordgo.Channel
+	channel, err = s.State.Channel(m.ChannelID)
+	if err != nil {
+		channel, err = s.Channel(m.ChannelID)
+		if err != nil {
+		} else {
+			_ = s.State.ChannelAdd(channel)
+		}
+	}
+
+	var isDirectMessage bool // The message was received in a direct message
+	var isCommand bool       // The message is intended as a command to the bot
+
+	if channel != nil {
+		if channel.Type == discordgo.ChannelTypeDM {
+			isDirectMessage = true
+			isCommand = true
+			ctx.Method = DirectMethod
+		}
+	}
+
+	if !isDirectMessage {
+		reg := regexp.MustCompile(fmt.Sprintf("<@!?(%s)>", s.State.User.ID))
+
+		for _, mention := range m.Mentions {
+			if mention.ID == s.State.User.ID {
+				// Was the @mention the first part of the string?
+				if reg.FindStringIndex(ctx.Content)[0] == 0 {
+					isCommand = true
+					ctx.Method = MentionMethod
+				}
+
+				ctx.Content = reg.ReplaceAllString(ctx.Content, "")
+			}
+		}
+
+		// Check to see if the context string starts with the command prefix
+		if len(r.prefix) > 0 {
+			// If the content start with the command prefix, then this is intended as a command
+			if strings.HasPrefix(ctx.Content, r.prefix) {
+				isCommand = true
+				ctx.Method = PrefixMethod
+				// Strip off the command prefix
+				ctx.Content = strings.TrimPrefix(ctx.Content, r.prefix)
+			}
+		}
+	}
+
+	if r.forcePrefixMethod && ctx.Method != PrefixMethod {
+		return
+	}
+
+	// If this is not a command do nothing
+	if !isCommand {
+		return
+	}
+
+	route, fields, args := r.findRoute(ctx.Content, MessageRoute)
+	if route != nil {
+		ctx.Fields = fields
+		ctx.Args = args
+		route.Run(s, m.Message, ctx)
+		return
+	}
+
+	r.helpRoute.Run(s, m.Message, ctx)
+}

+ 217 - 0
bot/event/mux/router.go

@@ -0,0 +1,217 @@
+package mux
+
+import (
+	"errors"
+	"strings"
+
+	"git.mgmcomp.net/thisnthat/discordgo"
+)
+
+// RouteOptions holds information about a specific message route handler
+type RouteOptions struct {
+	Name        string    // match name that should trigger this route handler
+	Description string    // short description of this route
+	Usage       string    // How to use the route
+	Category    string    // The category to put the route under
+	Type        RouteType // The type of route this is
+	Callback    Handler   // route handler function to call
+	Hidden      bool      // The route is hidden from the help menu'
+	Admin       bool      // The route is only visible to admins
+}
+
+type route struct {
+	Name        string
+	Description string
+	Usage       string
+	CategoryID  int
+	Type        RouteType
+	Run         Handler
+	Hidden      bool
+	Admin       bool
+}
+
+type category struct {
+	Name        string
+	Description string
+	ID          int
+}
+
+// RouteType is the type of route being created
+type RouteType int
+
+const (
+	// MessageRoute is a route for handling OnMessageCreate events
+	MessageRoute RouteType = 1001
+)
+
+// Handler is the function signature required for a message route handler.
+type Handler func(*discordgo.Session, *discordgo.Message, *Context)
+type IsAdminRouteHandler func(*discordgo.Session, *discordgo.Message, *Context) bool
+
+// Context holds a bit of extra data we pass along to route handlers
+// This way processing some of this only needs to happen once.
+type Context struct {
+	Fields  []string
+	Args    []string
+	Content string
+	Method  Method
+}
+
+// Method is the method a command was received
+type Method int
+
+const (
+	// DirectMethod Discord DM to the bot
+	DirectMethod Method = 1001
+	// MentionMethod is a message started with a @mention to the bot
+	MentionMethod Method = 1002
+	// PrefixMethod is a message started with a command prefix
+	PrefixMethod Method = 1003
+)
+
+// Router is the main struct for all mux methods.
+type Router struct {
+	routes            []*route
+	helpRoute         *route
+	prefix            string
+	categories        []*category
+	isAdminRoute      IsAdminRouteHandler // the admin check handler func to call
+	forcePrefixMethod bool
+}
+
+// New returns a new Discord message route mux
+func New() *Router {
+	r := &Router{}
+
+	r.isAdminRoute = isAdminRouteDefault
+
+	helpRoute := &route{}
+	helpRoute.Name = "help"
+	helpRoute.Usage = "help"
+	helpRoute.Description = "Get Help"
+	helpRoute.Run = r.helpCommandHandler
+
+	r.helpRoute = helpRoute
+
+	return r
+}
+
+// SetCommandPrefix will set the prefix used for message commands
+func (r *Router) SetCommandPrefix(cmdPrefix string) {
+	r.prefix = cmdPrefix + " "
+}
+
+func (r *Router) SetAdminRouteCallback(callback IsAdminRouteHandler) {
+	r.isAdminRoute = callback
+}
+
+func (r *Router) ForcePrefixMethod(force bool) {
+	r.forcePrefixMethod = force
+}
+
+// ErrRegisterRouteNameRequired is returned when registering a new route without a name
+var ErrRegisterRouteNameRequired = errors.New("all routes must have a name")
+
+// ErrRegisterInvalidRouteType is returned when registering a new route with an invalid type
+var ErrRegisterInvalidRouteType = errors.New("invalid route type")
+
+// ErrRegisterCallbackRequired is returned when registering a new route without a callback
+var ErrRegisterCallbackRequired = errors.New("a valid callback must be provided")
+
+// Register allows you to register a route
+//func (r *Router) Register(routeType RouteType, name, desc string, callback Handler) (*Route, error) {
+func (r *Router) Register(name string, options RouteOptions) error {
+	if name == "" {
+		return ErrRegisterRouteNameRequired
+	}
+
+	if !isValidRouteType(options.Type) {
+		return ErrRegisterInvalidRouteType
+	}
+
+	if options.Callback == nil {
+		return ErrRegisterCallbackRequired
+	}
+
+	route := route{}
+	route.Type = options.Type
+	route.Name = name
+	route.Description = options.Description
+	route.Usage = options.Usage
+	route.Run = options.Callback
+	route.Hidden = options.Hidden
+	route.Admin = options.Admin
+
+	if options.Category != "" {
+		for _, category := range r.categories {
+			if category.Name == options.Category {
+				route.CategoryID = category.ID
+				break
+			}
+		}
+	}
+
+	r.routes = append(r.routes, &route)
+
+	return nil
+}
+
+// ErrAddCategoryNameRequired is returned when registering a new route without a name
+var ErrAddCategoryNameRequired = errors.New("all categories must have a name")
+
+// AddCategory allows you to a a router category
+func (r *Router) AddCategory(name, description string) error {
+	if name == "" {
+		return ErrAddCategoryNameRequired
+	}
+
+	category := category{}
+	category.Name = name
+	category.Description = description
+	category.ID = len(r.categories) + 1
+	r.categories = append(r.categories, &category)
+
+	return nil
+}
+
+func isValidRouteType(routeType RouteType) bool {
+	switch routeType {
+	case MessageRoute:
+		return true
+	}
+
+	return false
+}
+
+func (r *Router) findRoute(searchString string, routeType RouteType) (*route, []string, []string) {
+	fields := strings.Fields(searchString)
+
+	if len(fields) == 0 {
+		return nil, nil, nil
+	}
+
+	commandKey := fields[0]
+
+	for _, route := range r.routes {
+		if route.Type != routeType {
+			continue
+		}
+
+		if strings.EqualFold(commandKey, route.Name) {
+			return route, fields[0:], fields[1:]
+		}
+	}
+
+	return nil, nil, nil
+}
+
+// Default callback for determining if the caller is an admin
+func isAdminRouteDefault(s *discordgo.Session, m *discordgo.Message, ctx *Context) bool {
+	channelPermissions, err := s.UserChannelPermissions(m.Author.ID, m.ChannelID)
+
+	if err != nil {
+		return false
+	}
+
+	return (channelPermissions&discordgo.PermissionAdministrator > 0)
+}

+ 97 - 0
bot/event/mux/slashRouter.go

@@ -0,0 +1,97 @@
+package mux
+
+import (
+	"github.com/bwmarrin/discordgo"
+	"github.com/sirupsen/logrus"
+)
+
+type SlashRouter struct {
+	registeredCommands []*discordgo.ApplicationCommand
+	commands           []*discordgo.ApplicationCommand
+	commandHandlers    map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate)
+	guildIDs           []string
+}
+
+type SlashRouteOptions struct {
+	Name        string // match name that should trigger this route handler
+	Description string // short description of this route
+	Callback    SlashHandler
+	Options     []*discordgo.ApplicationCommandOption // Array of application command options for the command
+}
+
+// Handler is the function signature required for a message route handler.
+type SlashHandler func(s *discordgo.Session, i *discordgo.InteractionCreate)
+
+func NewSlashRouter() *SlashRouter {
+	r := &SlashRouter{}
+
+	r.commandHandlers = make(map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate))
+
+	return r
+}
+
+func (r *SlashRouter) RegisterGuild(guildID string) {
+	r.guildIDs = append(r.guildIDs, guildID)
+}
+
+func (r *SlashRouter) Register(name string, options SlashRouteOptions) error {
+	if name == "" {
+		return ErrRegisterRouteNameRequired
+	}
+
+	if options.Callback == nil {
+		return ErrRegisterCallbackRequired
+	}
+
+	command := discordgo.ApplicationCommand{
+		Name:        name,
+		Description: options.Description,
+		Options:     options.Options,
+	}
+
+	r.commands = append(r.commands, &command)
+
+	r.commandHandlers[name] = options.Callback
+
+	return nil
+}
+
+func (r *SlashRouter) GenerateCommands(s *discordgo.Session) error {
+	r.registeredCommands = make([]*discordgo.ApplicationCommand, len(r.commands))
+
+	s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) {
+		if h, ok := r.commandHandlers[i.ApplicationCommandData().Name]; ok {
+			h(s, i)
+		}
+	})
+
+	for _, guildID := range r.guildIDs {
+		for i, v := range r.commands {
+			logrus.Info(v)
+			cmd, err := s.ApplicationCommandCreate(s.State.User.ID, guildID, v)
+			if err != nil {
+				logrus.Errorf("Cannot create '%v' command: %v", v.Name, err)
+				return err
+			}
+
+			r.registeredCommands[i] = cmd
+		}
+	}
+
+	return nil
+}
+
+func (r *SlashRouter) CleanCommands(s *discordgo.Session) error {
+
+	for _, guildID := range r.guildIDs {
+		for _, v := range r.registeredCommands {
+			err := s.ApplicationCommandDelete(s.State.User.ID, guildID, v.ID)
+			if err != nil {
+				logrus.Errorf("Cannot delete '%v' command: %v", v.Name, err)
+				return err
+			}
+		}
+	}
+
+	return nil
+}

+ 16 - 0
go.mod

@@ -0,0 +1,16 @@
+module git.mgmcomp.net/thisnthat/discord-bot
+
+go 1.19
+
+require (
+	git.mgmcomp.net/thisnthat/discord v1.0.3
+	git.mgmcomp.net/thisnthat/discordgo v1.0.1
+	github.com/bwmarrin/discordgo v0.27.1
+	github.com/sirupsen/logrus v1.9.0
+)
+
+require (
+	github.com/gorilla/websocket v1.4.2 // indirect
+	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
+	golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
+)

+ 36 - 0
go.sum

@@ -0,0 +1,36 @@
+git.mgmcomp.net/thisnthat/discord v1.0.3 h1:3QNlO4gDagqaqPGr8K3idTUGQXSmZ2SRLnKCHdRPSwM=
+git.mgmcomp.net/thisnthat/discord v1.0.3/go.mod h1:GhsT1HE4c5JcPEnHiIEpf+qLKLvwlwqJmsLXJxUOuZM=
+git.mgmcomp.net/thisnthat/discordgo v0.0.0-20211006203217-5322d32875f4/go.mod h1:Mld4OR1WOQKeKYSMyYX3T18ilnCQCtwus+A1LoNcC50=
+git.mgmcomp.net/thisnthat/discordgo v1.0.1 h1:S/Y0rD2kqWgOV6sWVy8F55D2/2P7vUw1wU98041vTMs=
+git.mgmcomp.net/thisnthat/discordgo v1.0.1/go.mod h1:SuGPRA8Gyf92YuZhwjiXjN6D7ksX0v4naoLd7A+wuYc=
+github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY=
+github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
+github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

+ 24 - 0
members.go

@@ -0,0 +1,24 @@
+package discord
+
+import (
+	"github.com/bwmarrin/discordgo"
+)
+
+// GetMember gets a discord member.
+// session  : An active discordgo session
+// guid		: The ID of the Guild
+// uid	    : The ID of a User.
+func GetMember(session *discordgo.Session, guid, uid string) (*discordgo.Member, error) {
+	return session.GuildMember(guid, uid)
+}
+
+// UpdateMember edits the roles of a member.
+// session  : An active discordgo session
+// guid		: The ID of the Guild
+// template : GuildMemberParams for Role and Nick
+// roles    : A list of role ID's to set on the member.
+func UpdateMember(session *discordgo.Session, guid, uid string, template *discordgo.GuildMemberParams) error {
+	_, err := session.GuildMemberEdit(guid, uid, template)
+
+	return err
+}

+ 28 - 0
session.go

@@ -0,0 +1,28 @@
+package discord
+
+import "github.com/bwmarrin/discordgo"
+
+// GetSession - Get a discord session from the provided token
+func GetSession(token string) (*discordgo.Session, error) {
+	session, err := discordgo.New("Bot " + token)
+
+	// If we can not create the client then just fatal.
+	if err != nil {
+		return nil, err
+	}
+
+	session.Identify.Intents =
+		discordgo.IntentsGuildMembers |
+			discordgo.IntentsGuildVoiceStates |
+			discordgo.IntentsGuildMessages |
+			discordgo.IntentsDirectMessages |
+			discordgo.IntentsDirectMessageReactions
+
+	// Open a websocket connection to Discord and begin listening.
+	err = session.Open()
+	if err != nil {
+		return nil, err
+	}
+
+	return session, nil
+}