From b30d6c3ee1cdb3640713eea461845a384013c1dc Mon Sep 17 00:00:00 2001
From: HekeHokkus <hekehokkus@gmail.com>
Date: Thu, 28 Sep 2023 15:54:48 -0400
Subject: [PATCH 01/17] Update discord.go

---
 discord.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/discord.go b/discord.go
index 8efd8fe..6fb1453 100644
--- a/discord.go
+++ b/discord.go
@@ -50,6 +50,7 @@ func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) {
 	dd.commandHandlers[app.config.Section("discord").Key("start_command").MustString("start")] = dd.cmdStart
 	dd.commandHandlers["lang"] = dd.cmdLang
 	dd.commandHandlers["pin"] = dd.cmdPIN
+	dd.commandHandlers["invite"] = dd.cmdInvite
 	for _, user := range app.storage.GetDiscord() {
 		dd.users[user.ID] = user
 	}

From edf6c13f03cb9bb9fc8c90800e1432de2d384728 Mon Sep 17 00:00:00 2001
From: HekeHokkus <hekehokkus@gmail.com>
Date: Thu, 28 Sep 2023 17:55:47 -0400
Subject: [PATCH 02/17] Update discord.go

---
 discord.go | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/discord.go b/discord.go
index 6fb1453..b674ce9 100644
--- a/discord.go
+++ b/discord.go
@@ -128,6 +128,8 @@ func (d *DiscordDaemon) run() {
 			d.inviteChannelName = invChannel
 		}
 	}
+	d.bot.Activity.Name = "/" + app.config.Section("discord").Key("start_command").MustString("start")
+	d.bot.Activity.Type = dg.ActivityTypeGame
 	defer d.deregisterCommands()
 	defer d.bot.Close()
 
@@ -339,6 +341,18 @@ func (d *DiscordDaemon) registerCommands() {
 				},
 			},
 		},
+		{
+			Name:        "invite",
+			Description: "Send an invite to a discord user (admin only).",
+			Options: []*dg.ApplicationCommandOption{
+				{
+					Type:        dg.ApplicationCommandOptionUser,
+					Name:        "user",
+					Description: "User to Invite",
+					Required:    true,
+				},
+			},
+		},
 	}
 	commands[1].Options[0].Choices = make([]*dg.ApplicationCommandOptionChoice, len(d.app.storage.lang.Telegram))
 	i := 0
@@ -504,6 +518,13 @@ func (d *DiscordDaemon) cmdLang(s *dg.Session, i *dg.InteractionCreate, lang str
 	}
 }
 
+func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang string) {
+	requestor := d.MustGetUser(channel.ID, i.Interaction.Member.User.ID, i.Interaction.Member.User.Discriminator, i.Interaction.Member.User.Username)
+	d.users[i.Interaction.Member.User.ID] = requestor
+	invuser := i.ApplicationCommandData().Options[0].StringValue()
+	//	Check whether requestor is linked to the admin account
+}
+	
 func (d *DiscordDaemon) messageHandler(s *dg.Session, m *dg.MessageCreate) {
 	if m.GuildID != "" && d.channelName != "" {
 		if d.channelID == "" {

From 3538935d3ba3c472e07a6ae801b6e4870758ad47 Mon Sep 17 00:00:00 2001
From: HekeHokkus <hekehokkus@gmail.com>
Date: Thu, 28 Sep 2023 19:37:35 -0400
Subject: [PATCH 03/17] Update discord.go

Adding /invite command and Discord status message to the bot
---
 discord.go | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 93 insertions(+), 1 deletion(-)

diff --git a/discord.go b/discord.go
index b674ce9..733135a 100644
--- a/discord.go
+++ b/discord.go
@@ -351,6 +351,24 @@ func (d *DiscordDaemon) registerCommands() {
 					Description: "User to Invite",
 					Required:    true,
 				},
+				{
+					Type:        dg.ApplicationCommandOptionInteger,
+					Name:        "expire after",
+					Description: "Time in minutes before expiration",
+					Required:    false,
+				},
+				{
+					Type:        dg.ApplicationCommandOptionString,
+					Name:        "label",
+					Description: "Label to apply to the user created with this invite",
+					Required:    false,
+				},
+				{
+					Type:        dg.ApplicationCommandOptionString,
+					Name:        "Profile",
+					Description: "Profile to apply to the user created with this invite",
+					Required:    false,
+				},
 			},
 		},
 	}
@@ -518,11 +536,85 @@ func (d *DiscordDaemon) cmdLang(s *dg.Session, i *dg.InteractionCreate, lang str
 	}
 }
 
-func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang string) {
+func (d *DiscordDaemon) cmdInvite(app *appContext, s *dg.Session, i *dg.InteractionCreate, lang string) {
 	requestor := d.MustGetUser(channel.ID, i.Interaction.Member.User.ID, i.Interaction.Member.User.Discriminator, i.Interaction.Member.User.Username)
 	d.users[i.Interaction.Member.User.ID] = requestor
 	invuser := i.ApplicationCommandData().Options[0].StringValue()
+	label := i.ApplicationCommandData().Options[2].StringValue()
+	profile := i.ApplicationCommandData().Options[3].StringValue()
+	if i.ApplicationCommandData().Options[1] != "" {
+		expmin := i.ApplicationCommandData().Options[1]
+	} else {
+		expmin := 30
+	}
 	//	Check whether requestor is linked to the admin account
+	//	variation of app.GenerateInvite, some parts commented to potentially add back in later
+	app.debug.Println("Generating new invite with options: %s: %i: %s: %s", invuser, expmin, profile, label)
+	currentTime := time.Now()
+	validTill = currentTime.Add(time.Minute*time.Duration(expmin))
+	// make sure code doesn't begin with number
+	inviteCode := shortuuid.New()
+	_, err := strconv.Atoi(string(inviteCode[0]))
+	for err == nil {
+		inviteCode = shortuuid.New()
+		_, err = strconv.Atoi(string(inviteCode[0]))
+	}
+	var invite Invite
+	if label != "" {
+		invite.Label = label
+	}
+	invite.Created = currentTime
+	invite.RemainingUses = 1
+	invite.UserExpiry = false
+	/*if invite.UserExpiry {
+		invite.UserMonths = req.UserMonths
+		invite.UserDays = req.UserDays
+		invite.UserHours = req.UserHours
+		invite.UserMinutes = req.UserMinutes
+	}*/
+	invite.ValidTill = validTill
+	if invuser != "" && app.config.Section("invite_emails").Key("enabled").MustBool(false) {
+		addressValid := false
+		discord := ""
+		app.debug.Printf("%s: Sending invite message", inviteCode)
+		if discordEnabled && !strings.Contains(invuser, "@") {
+			users := app.discord.GetUsers(invuser)
+			if len(users) == 0 {
+				invite.SendTo = fmt.Sprintf("Failed: User not found: \"%s\"", invuser)
+			} else if len(users) > 1 {
+				invite.SendTo = fmt.Sprintf("Failed: Multiple users found: \"%s\"", invuser)
+			} else {
+				invite.SendTo = invuser
+				addressValid = true
+				discord = users[0].User.ID
+			}
+		}
+		if addressValid {
+			msg, err := app.email.constructInvite(inviteCode, invite, app, false)
+			if err != nil {
+				invite.SendTo = fmt.Sprintf("Failed to send to %s", invuser)
+				app.err.Printf("%s: Failed to construct invite message: %v", inviteCode, err)
+			} else {
+				var err error
+				err = app.discord.SendDM(msg, discord)
+				if err != nil {
+					invite.SendTo = fmt.Sprintf("Failed to send to %s", invuser)
+					app.err.Printf("%s: %s: %v", inviteCode, invite.SendTo, err)
+				} else {
+					app.info.Printf("%s: Sent invite email to \"%s\"", inviteCode, invuser)
+				}
+			}
+		}
+	}
+	if profile != "" {
+		if _, ok := app.storage.GetProfileKey(profile); ok {
+			invite.Profile = profile
+		} else {
+			invite.Profile = "Default"
+		}
+	}
+	app.storage.SetInvitesKey(inviteCode, invite)
+	respondBool(200, true, gc)
 }
 	
 func (d *DiscordDaemon) messageHandler(s *dg.Session, m *dg.MessageCreate) {

From c43d5cf1b0b2fde14022f5de94f85672b435057e Mon Sep 17 00:00:00 2001
From: Violet Scheen <hekehokkus@gmail.com>
Date: Sat, 30 Sep 2023 11:25:36 -0400
Subject: [PATCH 04/17] Update discord.go

---
 discord.go | 137 +++++++++++++++++++++++++----------------------------
 1 file changed, 64 insertions(+), 73 deletions(-)

diff --git a/discord.go b/discord.go
index 733135a..24f51da 100644
--- a/discord.go
+++ b/discord.go
@@ -3,9 +3,11 @@ package main
 import (
 	"fmt"
 	"strings"
+	"strconv"
 	"time"
 
 	dg "github.com/bwmarrin/discordgo"
+	"github.com/lithammer/shortuuid/v3"
 	"github.com/timshannon/badgerhold/v4"
 )
 
@@ -50,7 +52,7 @@ func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) {
 	dd.commandHandlers[app.config.Section("discord").Key("start_command").MustString("start")] = dd.cmdStart
 	dd.commandHandlers["lang"] = dd.cmdLang
 	dd.commandHandlers["pin"] = dd.cmdPIN
-	dd.commandHandlers["invite"] = dd.cmdInvite
+	dd.commandHandlers["inv"] = dd.cmdInvite
 	for _, user := range app.storage.GetDiscord() {
 		dd.users[user.ID] = user
 	}
@@ -128,8 +130,8 @@ func (d *DiscordDaemon) run() {
 			d.inviteChannelName = invChannel
 		}
 	}
-	d.bot.Activity.Name = "/" + app.config.Section("discord").Key("start_command").MustString("start")
-	d.bot.Activity.Type = dg.ActivityTypeGame
+	//dg.Activity.Name = "/" + d.app.config.Section("discord").Key("start_command").MustString("start")
+	//dg.Activity.Type = dg.ActivityTypeGame
 	defer d.deregisterCommands()
 	defer d.bot.Close()
 
@@ -342,7 +344,7 @@ func (d *DiscordDaemon) registerCommands() {
 			},
 		},
 		{
-			Name:        "invite",
+			Name:        "inv",
 			Description: "Send an invite to a discord user (admin only).",
 			Options: []*dg.ApplicationCommandOption{
 				{
@@ -351,24 +353,24 @@ func (d *DiscordDaemon) registerCommands() {
 					Description: "User to Invite",
 					Required:    true,
 				},
-				{
-					Type:        dg.ApplicationCommandOptionInteger,
-					Name:        "expire after",
-					Description: "Time in minutes before expiration",
-					Required:    false,
-				},
-				{
-					Type:        dg.ApplicationCommandOptionString,
-					Name:        "label",
-					Description: "Label to apply to the user created with this invite",
-					Required:    false,
-				},
-				{
-					Type:        dg.ApplicationCommandOptionString,
-					Name:        "Profile",
-					Description: "Profile to apply to the user created with this invite",
-					Required:    false,
-				},
+				//{
+				//	Type:        dg.ApplicationCommandOptionInteger,
+				//	Name:        "expire after",
+				//	Description: "Time in minutes before expiration",
+				//	Required:    false,
+				//},
+				//{
+				//	Type:        dg.ApplicationCommandOptionString,
+				//	Name:        "label",
+				//	Description: "Label to apply to the user created with this invite",
+				//	Required:    false,
+				//},
+				//{
+				//	Type:        dg.ApplicationCommandOptionString,
+				//	Name:        "profile",
+				//	Description: "Profile to apply to the user created with this invite",
+				//	Required:    false,
+				//},
 			},
 		},
 	}
@@ -536,33 +538,35 @@ func (d *DiscordDaemon) cmdLang(s *dg.Session, i *dg.InteractionCreate, lang str
 	}
 }
 
-func (d *DiscordDaemon) cmdInvite(app *appContext, s *dg.Session, i *dg.InteractionCreate, lang string) {
+func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang string) {
+	channel, err := s.UserChannelCreate(i.Interaction.Member.User.ID)
 	requestor := d.MustGetUser(channel.ID, i.Interaction.Member.User.ID, i.Interaction.Member.User.Discriminator, i.Interaction.Member.User.Username)
 	d.users[i.Interaction.Member.User.ID] = requestor
-	invuser := i.ApplicationCommandData().Options[0].StringValue()
-	label := i.ApplicationCommandData().Options[2].StringValue()
-	profile := i.ApplicationCommandData().Options[3].StringValue()
-	if i.ApplicationCommandData().Options[1] != "" {
-		expmin := i.ApplicationCommandData().Options[1]
-	} else {
-		expmin := 30
-	}
-	//	Check whether requestor is linked to the admin account
+	invuser := fmt.Sprintf("%v", i.ApplicationCommandData().Options[0].Value)
+	d.app.debug.Println(invuser)
+//	label := i.ApplicationCommandData().Options[2].StringValue()
+//	profile := i.ApplicationCommandData().Options[3].StringValue()
+//	mins, err := strconv.Atoi(i.ApplicationCommandData().Options[1].StringValue())
+	expmin := 30
+//	if mins > 0 {
+//		expmin = mins
+//	}
+	//	Check whether requestor is linked to the admin account*
 	//	variation of app.GenerateInvite, some parts commented to potentially add back in later
-	app.debug.Println("Generating new invite with options: %s: %i: %s: %s", invuser, expmin, profile, label)
+	// d.app.debug.Println("Generating new invite with options: %s: %i: %s: %s", invuser, expmin, profile, label)
 	currentTime := time.Now()
-	validTill = currentTime.Add(time.Minute*time.Duration(expmin))
+	validTill := currentTime.Add(time.Minute*time.Duration(expmin))
 	// make sure code doesn't begin with number
 	inviteCode := shortuuid.New()
-	_, err := strconv.Atoi(string(inviteCode[0]))
+	_, err = strconv.Atoi(string(inviteCode[0]))
 	for err == nil {
 		inviteCode = shortuuid.New()
 		_, err = strconv.Atoi(string(inviteCode[0]))
 	}
 	var invite Invite
-	if label != "" {
-		invite.Label = label
-	}
+	//if label != "" {
+	//	invite.Label = label
+	//}
 	invite.Created = currentTime
 	invite.RemainingUses = 1
 	invite.UserExpiry = false
@@ -573,48 +577,35 @@ func (d *DiscordDaemon) cmdInvite(app *appContext, s *dg.Session, i *dg.Interact
 		invite.UserMinutes = req.UserMinutes
 	}*/
 	invite.ValidTill = validTill
-	if invuser != "" && app.config.Section("invite_emails").Key("enabled").MustBool(false) {
-		addressValid := false
+	if invuser != "" && d.app.config.Section("invite_emails").Key("enabled").MustBool(false) {
 		discord := ""
-		app.debug.Printf("%s: Sending invite message", inviteCode)
-		if discordEnabled && !strings.Contains(invuser, "@") {
-			users := app.discord.GetUsers(invuser)
-			if len(users) == 0 {
-				invite.SendTo = fmt.Sprintf("Failed: User not found: \"%s\"", invuser)
-			} else if len(users) > 1 {
-				invite.SendTo = fmt.Sprintf("Failed: Multiple users found: \"%s\"", invuser)
-			} else {
-				invite.SendTo = invuser
-				addressValid = true
-				discord = users[0].User.ID
-			}
-		}
-		if addressValid {
-			msg, err := app.email.constructInvite(inviteCode, invite, app, false)
+		d.app.debug.Printf("%s: Sending invite message", inviteCode)
+		invname, err := d.bot.GuildMember(d.guildID, invuser)
+		invite.SendTo = invname.User.Username
+
+		msg, err := d.app.email.constructInvite(inviteCode, invite, d.app, false)
+		if err != nil {
+			invite.SendTo = fmt.Sprintf("Failed to send to %s", invuser)
+			d.app.err.Printf("%s: Failed to construct invite message: %v", inviteCode, err)
+		} else {
+			var err error
+			err = d.app.discord.SendDM(msg, discord)
 			if err != nil {
 				invite.SendTo = fmt.Sprintf("Failed to send to %s", invuser)
-				app.err.Printf("%s: Failed to construct invite message: %v", inviteCode, err)
+				d.app.err.Printf("%s: %s: %v", inviteCode, invite.SendTo, err)
 			} else {
-				var err error
-				err = app.discord.SendDM(msg, discord)
-				if err != nil {
-					invite.SendTo = fmt.Sprintf("Failed to send to %s", invuser)
-					app.err.Printf("%s: %s: %v", inviteCode, invite.SendTo, err)
-				} else {
-					app.info.Printf("%s: Sent invite email to \"%s\"", inviteCode, invuser)
-				}
+				d.app.info.Printf("%s: Sent invite email to \"%s\"", inviteCode, invuser)
 			}
 		}
 	}
-	if profile != "" {
-		if _, ok := app.storage.GetProfileKey(profile); ok {
-			invite.Profile = profile
-		} else {
-			invite.Profile = "Default"
-		}
-	}
-	app.storage.SetInvitesKey(inviteCode, invite)
-	respondBool(200, true, gc)
+	//if profile != "" {
+	//	if _, ok := d.app.storage.GetProfileKey(profile); ok {
+	//		invite.Profile = profile
+	//	} else {
+	//		invite.Profile = "Default"
+	//	}
+	//}
+	d.app.storage.SetInvitesKey(inviteCode, invite)
 }
 	
 func (d *DiscordDaemon) messageHandler(s *dg.Session, m *dg.MessageCreate) {

From 27f85f866e2add2ab3f0e93880735b502cd39f6a Mon Sep 17 00:00:00 2001
From: Violet Scheen <hekehokkus@gmail.com>
Date: Sat, 30 Sep 2023 12:10:38 -0400
Subject: [PATCH 05/17] Update discord.go

Hopefully functional, any errors are coming from elsewhere
---
 discord.go | 58 +++++++++++++++++++++++++++---------------------------
 1 file changed, 29 insertions(+), 29 deletions(-)

diff --git a/discord.go b/discord.go
index 24f51da..c862955 100644
--- a/discord.go
+++ b/discord.go
@@ -130,8 +130,7 @@ func (d *DiscordDaemon) run() {
 			d.inviteChannelName = invChannel
 		}
 	}
-	//dg.Activity.Name = "/" + d.app.config.Section("discord").Key("start_command").MustString("start")
-	//dg.Activity.Type = dg.ActivityTypeGame
+	err = d.bot.UpdateGameStatus(0, "/"+d.app.config.Section("discord").Key("start_command").MustString("start"))
 	defer d.deregisterCommands()
 	defer d.bot.Close()
 
@@ -352,25 +351,25 @@ func (d *DiscordDaemon) registerCommands() {
 					Name:        "user",
 					Description: "User to Invite",
 					Required:    true,
+				},	//	running with just one option for now to mesh with what we've got, also may have the syntax wrong here
+				/*{
+					Type:        dg.ApplicationCommandOptionInteger,
+					Name:        "expire after",
+					Description: "Time in minutes before expiration",
+					Required:    false,
 				},
-				//{
-				//	Type:        dg.ApplicationCommandOptionInteger,
-				//	Name:        "expire after",
-				//	Description: "Time in minutes before expiration",
-				//	Required:    false,
-				//},
-				//{
-				//	Type:        dg.ApplicationCommandOptionString,
-				//	Name:        "label",
-				//	Description: "Label to apply to the user created with this invite",
-				//	Required:    false,
-				//},
-				//{
-				//	Type:        dg.ApplicationCommandOptionString,
-				//	Name:        "profile",
-				//	Description: "Profile to apply to the user created with this invite",
-				//	Required:    false,
-				//},
+				{
+					Type:        dg.ApplicationCommandOptionString,
+					Name:        "label",
+					Description: "Label to apply to the user created with this invite",
+					Required:    false,
+				},
+				{
+					Type:        dg.ApplicationCommandOptionString,
+					Name:        "profile",
+					Description: "Profile to apply to the user created with this invite",
+					Required:    false,
+				}, */
 			},
 		},
 	}
@@ -542,18 +541,19 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
 	channel, err := s.UserChannelCreate(i.Interaction.Member.User.ID)
 	requestor := d.MustGetUser(channel.ID, i.Interaction.Member.User.ID, i.Interaction.Member.User.Discriminator, i.Interaction.Member.User.Username)
 	d.users[i.Interaction.Member.User.ID] = requestor
+	d.app.debug.Println("Requested by: %v: %s:", requestor.JellyfinID, d.users[i.Interaction.Member.User.ID].JellyfinID)
 	invuser := fmt.Sprintf("%v", i.ApplicationCommandData().Options[0].Value)
 	d.app.debug.Println(invuser)
-//	label := i.ApplicationCommandData().Options[2].StringValue()
-//	profile := i.ApplicationCommandData().Options[3].StringValue()
-//	mins, err := strconv.Atoi(i.ApplicationCommandData().Options[1].StringValue())
+	//label := i.ApplicationCommandData().Options[2].StringValue()
+	//profile := i.ApplicationCommandData().Options[3].StringValue()
+	//mins, err := strconv.Atoi(i.ApplicationCommandData().Options[1].StringValue())
 	expmin := 30
-//	if mins > 0 {
-//		expmin = mins
-//	}
-	//	Check whether requestor is linked to the admin account*
-	//	variation of app.GenerateInvite, some parts commented to potentially add back in later
-	// d.app.debug.Println("Generating new invite with options: %s: %i: %s: %s", invuser, expmin, profile, label)
+	//if mins > 0 {
+	//	expmin = mins
+	//}
+	//	Need to check whether requestor is linked to the admin account *possibly add Admin bool to DiscordUser struct
+	//	variation of app.GenerateInvite, some parts commented to potentially add back in later with the other options
+	//d.app.debug.Println("Generating new invite with options: %s: %i: %s: %s", invuser, expmin, profile, label)
 	currentTime := time.Now()
 	validTill := currentTime.Add(time.Minute*time.Duration(expmin))
 	// make sure code doesn't begin with number

From 729548334de6b3fa356d7fa5ec756c8fb36685b9 Mon Sep 17 00:00:00 2001
From: Violet Scheen <hekehokkus@gmail.com>
Date: Sat, 30 Sep 2023 12:16:06 -0400
Subject: [PATCH 06/17] Update discord.go

---
 discord.go | 1 -
 1 file changed, 1 deletion(-)

diff --git a/discord.go b/discord.go
index c862955..9246e14 100644
--- a/discord.go
+++ b/discord.go
@@ -541,7 +541,6 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
 	channel, err := s.UserChannelCreate(i.Interaction.Member.User.ID)
 	requestor := d.MustGetUser(channel.ID, i.Interaction.Member.User.ID, i.Interaction.Member.User.Discriminator, i.Interaction.Member.User.Username)
 	d.users[i.Interaction.Member.User.ID] = requestor
-	d.app.debug.Println("Requested by: %v: %s:", requestor.JellyfinID, d.users[i.Interaction.Member.User.ID].JellyfinID)
 	invuser := fmt.Sprintf("%v", i.ApplicationCommandData().Options[0].Value)
 	d.app.debug.Println(invuser)
 	//label := i.ApplicationCommandData().Options[2].StringValue()

From 49dfac514d21b1c69bc9628b2e1c804991ca570d Mon Sep 17 00:00:00 2001
From: Violet Scheen <hekehokkus@gmail.com>
Date: Tue, 3 Oct 2023 23:22:20 -0400
Subject: [PATCH 07/17] Update discord.go

---
 discord.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/discord.go b/discord.go
index 9246e14..f6c5433 100644
--- a/discord.go
+++ b/discord.go
@@ -551,6 +551,10 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
 	//	expmin = mins
 	//}
 	//	Need to check whether requestor is linked to the admin account *possibly add Admin bool to DiscordUser struct
+	requestoremail := GetEmailsKey(requestor.JfID)
+	if !requestoremail.admin {
+		d.app.err.Printf("User is not admin")
+	}
 	//	variation of app.GenerateInvite, some parts commented to potentially add back in later with the other options
 	//d.app.debug.Println("Generating new invite with options: %s: %i: %s: %s", invuser, expmin, profile, label)
 	currentTime := time.Now()

From b595d3ea03d786bcc326a1e8ae775a09fedb9346 Mon Sep 17 00:00:00 2001
From: Violet Scheen <hekehokkus@gmail.com>
Date: Tue, 3 Oct 2023 23:37:24 -0400
Subject: [PATCH 08/17] Update discord.go

---
 discord.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/discord.go b/discord.go
index f6c5433..2a9029f 100644
--- a/discord.go
+++ b/discord.go
@@ -551,7 +551,7 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
 	//	expmin = mins
 	//}
 	//	Need to check whether requestor is linked to the admin account *possibly add Admin bool to DiscordUser struct
-	requestoremail := GetEmailsKey(requestor.JfID)
+	requestoremail := d.app.storage.GetEmailsKey(requestor.JellyfinID)
 	if !requestoremail.admin {
 		d.app.err.Printf("User is not admin")
 	}

From dd93758b0e9d2d44bd807123a4a435acdd18538d Mon Sep 17 00:00:00 2001
From: Violet Scheen <hekehokkus@gmail.com>
Date: Tue, 3 Oct 2023 23:59:42 -0400
Subject: [PATCH 09/17] Update discord.go

---
 discord.go | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/discord.go b/discord.go
index 2a9029f..fc7a74f 100644
--- a/discord.go
+++ b/discord.go
@@ -551,8 +551,11 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
 	//	expmin = mins
 	//}
 	//	Need to check whether requestor is linked to the admin account *possibly add Admin bool to DiscordUser struct
-	requestoremail := d.app.storage.GetEmailsKey(requestor.JellyfinID)
-	if !requestoremail.admin {
+	requestoremail, ok := d.app.storage.GetEmailsKey(requestor.JellyfinID)
+	if !ok {
+		d.app.err.Printf("Failed to verify admin")
+	}
+	if !requestoremail.Admin {
 		d.app.err.Printf("User is not admin")
 	}
 	//	variation of app.GenerateInvite, some parts commented to potentially add back in later with the other options

From e52e21a54b5855aaeda25b08d7605e514ed14d61 Mon Sep 17 00:00:00 2001
From: Harvey Tindall <hrfee@protonmail.ch>
Date: Tue, 10 Oct 2023 13:45:29 +0100
Subject: [PATCH 10/17] discord: fix up /inv basic functionality

sending now succeeds, and a reponse of "Invite sent." is given to the
requester. Also some formatting changes.
---
 discord.go               | 53 +++++++++++++++++++++++++++-------------
 lang/telegram/en-us.json |  6 +++--
 2 files changed, 40 insertions(+), 19 deletions(-)

diff --git a/discord.go b/discord.go
index fc7a74f..f53b1d7 100644
--- a/discord.go
+++ b/discord.go
@@ -2,8 +2,8 @@ package main
 
 import (
 	"fmt"
-	"strings"
 	"strconv"
+	"strings"
 	"time"
 
 	dg "github.com/bwmarrin/discordgo"
@@ -351,7 +351,7 @@ func (d *DiscordDaemon) registerCommands() {
 					Name:        "user",
 					Description: "User to Invite",
 					Required:    true,
-				},	//	running with just one option for now to mesh with what we've got, also may have the syntax wrong here
+				}, //	running with just one option for now to mesh with what we've got, also may have the syntax wrong here
 				/*{
 					Type:        dg.ApplicationCommandOptionInteger,
 					Name:        "expire after",
@@ -539,10 +539,10 @@ func (d *DiscordDaemon) cmdLang(s *dg.Session, i *dg.InteractionCreate, lang str
 
 func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang string) {
 	channel, err := s.UserChannelCreate(i.Interaction.Member.User.ID)
-	requestor := d.MustGetUser(channel.ID, i.Interaction.Member.User.ID, i.Interaction.Member.User.Discriminator, i.Interaction.Member.User.Username)
-	d.users[i.Interaction.Member.User.ID] = requestor
-	invuser := fmt.Sprintf("%v", i.ApplicationCommandData().Options[0].Value)
-	d.app.debug.Println(invuser)
+	requester := d.MustGetUser(channel.ID, i.Interaction.Member.User.ID, i.Interaction.Member.User.Discriminator, i.Interaction.Member.User.Username)
+	d.users[i.Interaction.Member.User.ID] = requester
+	recipient := i.ApplicationCommandData().Options[0].UserValue(s)
+	// d.app.debug.Println(invuser)
 	//label := i.ApplicationCommandData().Options[2].StringValue()
 	//profile := i.ApplicationCommandData().Options[3].StringValue()
 	//mins, err := strconv.Atoi(i.ApplicationCommandData().Options[1].StringValue())
@@ -551,17 +551,17 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
 	//	expmin = mins
 	//}
 	//	Need to check whether requestor is linked to the admin account *possibly add Admin bool to DiscordUser struct
-	requestoremail, ok := d.app.storage.GetEmailsKey(requestor.JellyfinID)
+	requesterEmail, ok := d.app.storage.GetEmailsKey(requester.JellyfinID)
 	if !ok {
 		d.app.err.Printf("Failed to verify admin")
 	}
-	if !requestoremail.Admin {
+	if !requesterEmail.Admin {
 		d.app.err.Printf("User is not admin")
 	}
 	//	variation of app.GenerateInvite, some parts commented to potentially add back in later with the other options
 	//d.app.debug.Println("Generating new invite with options: %s: %i: %s: %s", invuser, expmin, profile, label)
 	currentTime := time.Now()
-	validTill := currentTime.Add(time.Minute*time.Duration(expmin))
+	validTill := currentTime.Add(time.Minute * time.Duration(expmin))
 	// make sure code doesn't begin with number
 	inviteCode := shortuuid.New()
 	_, err = strconv.Atoi(string(inviteCode[0]))
@@ -583,24 +583,43 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
 		invite.UserMinutes = req.UserMinutes
 	}*/
 	invite.ValidTill = validTill
-	if invuser != "" && d.app.config.Section("invite_emails").Key("enabled").MustBool(false) {
-		discord := ""
+	if recipient != nil && d.app.config.Section("invite_emails").Key("enabled").MustBool(false) {
 		d.app.debug.Printf("%s: Sending invite message", inviteCode)
-		invname, err := d.bot.GuildMember(d.guildID, invuser)
+		invname, err := d.bot.GuildMember(d.guildID, recipient.ID)
 		invite.SendTo = invname.User.Username
 
 		msg, err := d.app.email.constructInvite(inviteCode, invite, d.app, false)
 		if err != nil {
-			invite.SendTo = fmt.Sprintf("Failed to send to %s", invuser)
+			invite.SendTo = fmt.Sprintf("Failed to send to %s", RenderDiscordUsername(recipient))
 			d.app.err.Printf("%s: Failed to construct invite message: %v", inviteCode, err)
 		} else {
 			var err error
-			err = d.app.discord.SendDM(msg, discord)
+			err = d.app.discord.SendDM(msg, recipient.ID)
 			if err != nil {
-				invite.SendTo = fmt.Sprintf("Failed to send to %s", invuser)
+				invite.SendTo = fmt.Sprintf("Failed to send to %s", RenderDiscordUsername(recipient))
 				d.app.err.Printf("%s: %s: %v", inviteCode, invite.SendTo, err)
+				err := s.InteractionRespond(i.Interaction, &dg.InteractionResponse{
+					Type: dg.InteractionResponseChannelMessageWithSource,
+					Data: &dg.InteractionResponseData{
+						Content: d.app.storage.lang.Telegram[lang].Strings.get("sentInviteFailure"),
+						Flags:   64, // Ephemeral
+					},
+				})
+				if err != nil {
+					d.app.err.Printf("Discord: Failed to send message to \"%s\": %v", RenderDiscordUsername(requester), err)
+				}
 			} else {
-				d.app.info.Printf("%s: Sent invite email to \"%s\"", inviteCode, invuser)
+				d.app.info.Printf("%s: Sent invite email to \"%s\"", inviteCode, RenderDiscordUsername(recipient))
+				err := s.InteractionRespond(i.Interaction, &dg.InteractionResponse{
+					Type: dg.InteractionResponseChannelMessageWithSource,
+					Data: &dg.InteractionResponseData{
+						Content: d.app.storage.lang.Telegram[lang].Strings.get("sentInvite"),
+						Flags:   64, // Ephemeral
+					},
+				})
+				if err != nil {
+					d.app.err.Printf("Discord: Failed to send message to \"%s\": %v", RenderDiscordUsername(requester), err)
+				}
 			}
 		}
 	}
@@ -613,7 +632,7 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
 	//}
 	d.app.storage.SetInvitesKey(inviteCode, invite)
 }
-	
+
 func (d *DiscordDaemon) messageHandler(s *dg.Session, m *dg.MessageCreate) {
 	if m.GuildID != "" && d.channelName != "" {
 		if d.channelID == "" {
diff --git a/lang/telegram/en-us.json b/lang/telegram/en-us.json
index c5144e3..4cb03a7 100644
--- a/lang/telegram/en-us.json
+++ b/lang/telegram/en-us.json
@@ -11,6 +11,8 @@
         "languageMessage": "Note: See available languages with {command}, and set language with {command} <language code>.",
         "languageMessageDiscord": "Note: set your language with /lang <language name>.",
         "languageSet": "Language set to {language}.",
-        "discordDMs": "Please check your DMs for a response."
+        "discordDMs": "Please check your DMs for a response.",
+        "sentInvite": "Sent invite.",
+        "sentInviteFailure": "Failed to send invite, check logs."
     }
-}
\ No newline at end of file
+}

From 10a32ad1ae0e9796b9fb4b93fcd2533e8925c249 Mon Sep 17 00:00:00 2001
From: Harvey Tindall <hrfee@protonmail.ch>
Date: Tue, 10 Oct 2023 14:52:54 +0100
Subject: [PATCH 11/17] discord: re-add optional args

---
 discord.go | 79 ++++++++++++++++++++++++++++++++++--------------------
 1 file changed, 50 insertions(+), 29 deletions(-)

diff --git a/discord.go b/discord.go
index f53b1d7..3c187ec 100644
--- a/discord.go
+++ b/discord.go
@@ -351,17 +351,24 @@ func (d *DiscordDaemon) registerCommands() {
 					Name:        "user",
 					Description: "User to Invite",
 					Required:    true,
-				}, //	running with just one option for now to mesh with what we've got, also may have the syntax wrong here
-				/*{
+				},
+				{
 					Type:        dg.ApplicationCommandOptionInteger,
-					Name:        "expire after",
+					Name:        "expiry",
 					Description: "Time in minutes before expiration",
 					Required:    false,
 				},
+				/* Label should be automatically set to something like "Discord invite for @username"
 				{
 					Type:        dg.ApplicationCommandOptionString,
 					Name:        "label",
-					Description: "Label to apply to the user created with this invite",
+					Description: "Label given to this invite (shown on the Admin page)",
+					Required:    false,
+				}, */
+				{
+					Type:        dg.ApplicationCommandOptionString,
+					Name:        "user_label",
+					Description: "Label given to users created with this invite",
 					Required:    false,
 				},
 				{
@@ -369,7 +376,7 @@ func (d *DiscordDaemon) registerCommands() {
 					Name:        "profile",
 					Description: "Profile to apply to the user created with this invite",
 					Required:    false,
-				}, */
+				},
 			},
 		},
 	}
@@ -546,7 +553,6 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
 	//label := i.ApplicationCommandData().Options[2].StringValue()
 	//profile := i.ApplicationCommandData().Options[3].StringValue()
 	//mins, err := strconv.Atoi(i.ApplicationCommandData().Options[1].StringValue())
-	expmin := 30
 	//if mins > 0 {
 	//	expmin = mins
 	//}
@@ -558,10 +564,28 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
 	if !requesterEmail.Admin {
 		d.app.err.Printf("User is not admin")
 	}
-	//	variation of app.GenerateInvite, some parts commented to potentially add back in later with the other options
-	//d.app.debug.Println("Generating new invite with options: %s: %i: %s: %s", invuser, expmin, profile, label)
+
+	var expiryMinutes int64 = 30
+	userLabel := ""
+	profileName := ""
+
+	for i, opt := range i.ApplicationCommandData().Options {
+		if i == 0 {
+			continue
+		}
+		switch opt.Name {
+		case "expiry":
+			expiryMinutes = opt.IntValue()
+		case "user_label":
+			userLabel = opt.StringValue()
+		case "profile":
+			profileName = opt.StringValue()
+		}
+	}
+
 	currentTime := time.Now()
-	validTill := currentTime.Add(time.Minute * time.Duration(expmin))
+
+	validTill := currentTime.Add(time.Minute * time.Duration(expiryMinutes))
 	// make sure code doesn't begin with number
 	inviteCode := shortuuid.New()
 	_, err = strconv.Atoi(string(inviteCode[0]))
@@ -569,20 +593,23 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
 		inviteCode = shortuuid.New()
 		_, err = strconv.Atoi(string(inviteCode[0]))
 	}
-	var invite Invite
-	//if label != "" {
-	//	invite.Label = label
-	//}
-	invite.Created = currentTime
-	invite.RemainingUses = 1
-	invite.UserExpiry = false
-	/*if invite.UserExpiry {
-		invite.UserMonths = req.UserMonths
-		invite.UserDays = req.UserDays
-		invite.UserHours = req.UserHours
-		invite.UserMinutes = req.UserMinutes
-	}*/
-	invite.ValidTill = validTill
+
+	invite := Invite{
+		Code:          inviteCode,
+		Created:       currentTime,
+		RemainingUses: 1,
+		UserExpiry:    false,
+		ValidTill:     validTill,
+		UserLabel:     userLabel,
+		Profile:       "Default",
+		Label:         fmt.Sprintf("Discord Invite for %s", RenderDiscordUsername(recipient)),
+	}
+	if profileName != "" {
+		if _, ok := d.app.storage.GetProfileKey(profileName); ok {
+			invite.Profile = profileName
+		}
+	}
+
 	if recipient != nil && d.app.config.Section("invite_emails").Key("enabled").MustBool(false) {
 		d.app.debug.Printf("%s: Sending invite message", inviteCode)
 		invname, err := d.bot.GuildMember(d.guildID, recipient.ID)
@@ -624,12 +651,6 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
 		}
 	}
 	//if profile != "" {
-	//	if _, ok := d.app.storage.GetProfileKey(profile); ok {
-	//		invite.Profile = profile
-	//	} else {
-	//		invite.Profile = "Default"
-	//	}
-	//}
 	d.app.storage.SetInvitesKey(inviteCode, invite)
 }
 

From 62923d5e45f9bd8a03f9326832618b3e3abab005 Mon Sep 17 00:00:00 2001
From: Harvey Tindall <hrfee@protonmail.ch>
Date: Tue, 10 Oct 2023 15:15:25 +0100
Subject: [PATCH 12/17] discord: register available profiles for /inv

profiles are registered as options for /inv as startup. Note in
description added to restart jfa-go to reload them.
---
 discord.go | 25 ++++++++++++++++++-------
 1 file changed, 18 insertions(+), 7 deletions(-)

diff --git a/discord.go b/discord.go
index 3c187ec..72c93c2 100644
--- a/discord.go
+++ b/discord.go
@@ -349,13 +349,13 @@ func (d *DiscordDaemon) registerCommands() {
 				{
 					Type:        dg.ApplicationCommandOptionUser,
 					Name:        "user",
-					Description: "User to Invite",
+					Description: "User to Invite.",
 					Required:    true,
 				},
 				{
 					Type:        dg.ApplicationCommandOptionInteger,
 					Name:        "expiry",
-					Description: "Time in minutes before expiration",
+					Description: "Time in minutes before expiration.",
 					Required:    false,
 				},
 				/* Label should be automatically set to something like "Discord invite for @username"
@@ -368,13 +368,13 @@ func (d *DiscordDaemon) registerCommands() {
 				{
 					Type:        dg.ApplicationCommandOptionString,
 					Name:        "user_label",
-					Description: "Label given to users created with this invite",
+					Description: "Label given to users created with this invite.",
 					Required:    false,
 				},
 				{
 					Type:        dg.ApplicationCommandOptionString,
 					Name:        "profile",
-					Description: "Profile to apply to the user created with this invite",
+					Description: "Profile to apply. Restart jfa-go if there's any missing.",
 					Required:    false,
 				},
 			},
@@ -383,7 +383,7 @@ func (d *DiscordDaemon) registerCommands() {
 	commands[1].Options[0].Choices = make([]*dg.ApplicationCommandOptionChoice, len(d.app.storage.lang.Telegram))
 	i := 0
 	for code := range d.app.storage.lang.Telegram {
-		d.app.debug.Printf("Registering choice \"%s\":\"%s\"\n", d.app.storage.lang.Telegram[code].Meta.Name, code)
+		d.app.debug.Printf("Discord: registering lang choice \"%s\":\"%s\"\n", d.app.storage.lang.Telegram[code].Meta.Name, code)
 		commands[1].Options[0].Choices[i] = &dg.ApplicationCommandOptionChoice{
 			Name:  d.app.storage.lang.Telegram[code].Meta.Name,
 			Value: code,
@@ -391,6 +391,17 @@ func (d *DiscordDaemon) registerCommands() {
 		i++
 	}
 
+	// FIXME: Maybe make this reload when profiles change
+	profiles := d.app.storage.GetProfiles()
+	commands[3].Options[3].Choices = make([]*dg.ApplicationCommandOptionChoice, len(profiles))
+	for i, profile := range profiles {
+		d.app.debug.Printf("Discord: registering profile choice \"%s\"", profile.Name)
+		commands[3].Options[3].Choices[i] = &dg.ApplicationCommandOptionChoice{
+			Name:  profile.Name,
+			Value: profile.Name,
+		}
+	}
+
 	// d.deregisterCommands()
 
 	d.commandIDs = make([]string, len(commands))
@@ -416,7 +427,7 @@ func (d *DiscordDaemon) deregisterCommands() {
 		return
 	}
 	for _, cmd := range existingCommands {
-		if err := d.bot.ApplicationCommandDelete(d.bot.State.User.ID, "", cmd.ID); err != nil {
+		if err := d.bot.ApplicationCommandDelete(d.bot.State.User.ID, d.guildID, cmd.ID); err != nil {
 			d.app.err.Printf("Failed to deregister command: %v", err)
 		}
 	}
@@ -602,7 +613,7 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
 		ValidTill:     validTill,
 		UserLabel:     userLabel,
 		Profile:       "Default",
-		Label:         fmt.Sprintf("Discord Invite for %s", RenderDiscordUsername(recipient)),
+		Label:         fmt.Sprintf("Discord: %s", RenderDiscordUsername(recipient)),
 	}
 	if profileName != "" {
 		if _, ok := d.app.storage.GetProfileKey(profileName); ok {

From 0366e5116d79df4d59985df8ae8c106d0fbfc365 Mon Sep 17 00:00:00 2001
From: Violet Scheen <hekehokkus@gmail.com>
Date: Tue, 10 Oct 2023 11:14:57 -0400
Subject: [PATCH 13/17] Update discord.go

Cleaning up a bit
---
 discord.go | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/discord.go b/discord.go
index fc7a74f..cf1eb1b 100644
--- a/discord.go
+++ b/discord.go
@@ -550,15 +550,17 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
 	//if mins > 0 {
 	//	expmin = mins
 	//}
-	//	Need to check whether requestor is linked to the admin account *possibly add Admin bool to DiscordUser struct
+	//	Check whether requestor is linked to the admin account
 	requestoremail, ok := d.app.storage.GetEmailsKey(requestor.JellyfinID)
 	if !ok {
 		d.app.err.Printf("Failed to verify admin")
 	}
 	if !requestoremail.Admin {
 		d.app.err.Printf("User is not admin")
+		//add response message
+		return
 	}
-	//	variation of app.GenerateInvite, some parts commented to potentially add back in later with the other options
+	//	variation of app.GenerateInvite, some parts commented to potentially add back in later with the other command options
 	//d.app.debug.Println("Generating new invite with options: %s: %i: %s: %s", invuser, expmin, profile, label)
 	currentTime := time.Now()
 	validTill := currentTime.Add(time.Minute*time.Duration(expmin))
@@ -593,14 +595,17 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
 		if err != nil {
 			invite.SendTo = fmt.Sprintf("Failed to send to %s", invuser)
 			d.app.err.Printf("%s: Failed to construct invite message: %v", inviteCode, err)
+			//add response message
 		} else {
 			var err error
 			err = d.app.discord.SendDM(msg, discord)
 			if err != nil {
 				invite.SendTo = fmt.Sprintf("Failed to send to %s", invuser)
 				d.app.err.Printf("%s: %s: %v", inviteCode, invite.SendTo, err)
+				//add response message
 			} else {
 				d.app.info.Printf("%s: Sent invite email to \"%s\"", inviteCode, invuser)
+				//add response message
 			}
 		}
 	}

From 525c13ff6ab9fe503932a3927a1d9994a4ea20a4 Mon Sep 17 00:00:00 2001
From: Violet Scheen <hekehokkus@gmail.com>
Date: Tue, 10 Oct 2023 12:55:56 -0400
Subject: [PATCH 14/17] Update discord.go

---
 discord.go | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/discord.go b/discord.go
index cf1eb1b..fd3de07 100644
--- a/discord.go
+++ b/discord.go
@@ -586,11 +586,9 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
 	}*/
 	invite.ValidTill = validTill
 	if invuser != "" && d.app.config.Section("invite_emails").Key("enabled").MustBool(false) {
-		discord := ""
 		d.app.debug.Printf("%s: Sending invite message", inviteCode)
 		invname, err := d.bot.GuildMember(d.guildID, invuser)
 		invite.SendTo = invname.User.Username
-
 		msg, err := d.app.email.constructInvite(inviteCode, invite, d.app, false)
 		if err != nil {
 			invite.SendTo = fmt.Sprintf("Failed to send to %s", invuser)
@@ -598,7 +596,7 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
 			//add response message
 		} else {
 			var err error
-			err = d.app.discord.SendDM(msg, discord)
+			err = d.app.discord.SendDM(msg, invuser)
 			if err != nil {
 				invite.SendTo = fmt.Sprintf("Failed to send to %s", invuser)
 				d.app.err.Printf("%s: %s: %v", inviteCode, invite.SendTo, err)

From 7813c8c68b39182085436758abfe462dcdfba0aa Mon Sep 17 00:00:00 2001
From: Harvey Tindall <hrfee@protonmail.ch>
Date: Wed, 11 Oct 2023 11:35:08 +0100
Subject: [PATCH 15/17] discord: Use GenerateInviteCode in /inv

---
 discord.go | 21 ++++++---------------
 1 file changed, 6 insertions(+), 15 deletions(-)

diff --git a/discord.go b/discord.go
index fcfb27f..f402d3b 100644
--- a/discord.go
+++ b/discord.go
@@ -2,12 +2,10 @@ package main
 
 import (
 	"fmt"
-	"strconv"
 	"strings"
 	"time"
 
 	dg "github.com/bwmarrin/discordgo"
-	"github.com/lithammer/shortuuid/v3"
 	"github.com/timshannon/badgerhold/v4"
 )
 
@@ -599,16 +597,9 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
 	currentTime := time.Now()
 
 	validTill := currentTime.Add(time.Minute * time.Duration(expiryMinutes))
-	// make sure code doesn't begin with number
-	inviteCode := shortuuid.New()
-	_, err = strconv.Atoi(string(inviteCode[0]))
-	for err == nil {
-		inviteCode = shortuuid.New()
-		_, err = strconv.Atoi(string(inviteCode[0]))
-	}
 
 	invite := Invite{
-		Code:          inviteCode,
+		Code:          GenerateInviteCode(),
 		Created:       currentTime,
 		RemainingUses: 1,
 		UserExpiry:    false,
@@ -624,20 +615,20 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
 	}
 
 	if recipient != nil && d.app.config.Section("invite_emails").Key("enabled").MustBool(false) {
-		d.app.debug.Printf("%s: Sending invite message", inviteCode)
+		d.app.debug.Printf("%s: Sending invite message", invite.Code)
 		invname, err := d.bot.GuildMember(d.guildID, recipient.ID)
 		invite.SendTo = invname.User.Username
-		msg, err := d.app.email.constructInvite(inviteCode, invite, d.app, false)
+		msg, err := d.app.email.constructInvite(invite.Code, invite, d.app, false)
 		if err != nil {
 			invite.SendTo = fmt.Sprintf("Failed to send to %s", RenderDiscordUsername(recipient))
-			d.app.err.Printf("%s: Failed to construct invite message: %v", inviteCode, err)
+			d.app.err.Printf("%s: Failed to construct invite message: %v", invite.Code, err)
 			//add response message
 		} else {
 			var err error
 			err = d.app.discord.SendDM(msg, recipient.ID)
 			if err != nil {
 				invite.SendTo = fmt.Sprintf("Failed to send to %s", RenderDiscordUsername(recipient))
-				d.app.err.Printf("%s: %s: %v", inviteCode, invite.SendTo, err)
+				d.app.err.Printf("%s: %s: %v", invite.Code, invite.SendTo, err)
 				err := s.InteractionRespond(i.Interaction, &dg.InteractionResponse{
 					Type: dg.InteractionResponseChannelMessageWithSource,
 					Data: &dg.InteractionResponseData{
@@ -649,7 +640,7 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
 					d.app.err.Printf("Discord: Failed to send message to \"%s\": %v", RenderDiscordUsername(requester), err)
 				}
 			} else {
-				d.app.info.Printf("%s: Sent invite email to \"%s\"", inviteCode, RenderDiscordUsername(recipient))
+				d.app.info.Printf("%s: Sent invite email to \"%s\"", invite.Code, RenderDiscordUsername(recipient))
 				err := s.InteractionRespond(i.Interaction, &dg.InteractionResponse{
 					Type: dg.InteractionResponseChannelMessageWithSource,
 					Data: &dg.InteractionResponseData{

From 2fb2f3ee74460e2d94274f4cfdb2279e11270a0d Mon Sep 17 00:00:00 2001
From: Harvey Tindall <hrfee@protonmail.ch>
Date: Wed, 11 Oct 2023 11:38:55 +0100
Subject: [PATCH 16/17] discord: send error message when inv construction fails

---
 discord.go | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/discord.go b/discord.go
index f402d3b..1d9caf2 100644
--- a/discord.go
+++ b/discord.go
@@ -555,6 +555,10 @@ func (d *DiscordDaemon) cmdLang(s *dg.Session, i *dg.InteractionCreate, lang str
 
 func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang string) {
 	channel, err := s.UserChannelCreate(i.Interaction.Member.User.ID)
+	if err != nil {
+		d.app.err.Printf("Discord: Failed to create private channel with \"%s\": %v", i.Interaction.Member.User.Username, err)
+		return
+	}
 	requester := d.MustGetUser(channel.ID, i.Interaction.Member.User.ID, i.Interaction.Member.User.Discriminator, i.Interaction.Member.User.Username)
 	d.users[i.Interaction.Member.User.ID] = requester
 	recipient := i.ApplicationCommandData().Options[0].UserValue(s)
@@ -622,7 +626,16 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
 		if err != nil {
 			invite.SendTo = fmt.Sprintf("Failed to send to %s", RenderDiscordUsername(recipient))
 			d.app.err.Printf("%s: Failed to construct invite message: %v", invite.Code, err)
-			//add response message
+			err := s.InteractionRespond(i.Interaction, &dg.InteractionResponse{
+				Type: dg.InteractionResponseChannelMessageWithSource,
+				Data: &dg.InteractionResponseData{
+					Content: d.app.storage.lang.Telegram[lang].Strings.get("sentInviteFailure"),
+					Flags:   64, // Ephemeral
+				},
+			})
+			if err != nil {
+				d.app.err.Printf("Discord: Failed to send message to \"%s\": %v", RenderDiscordUsername(requester), err)
+			}
 		} else {
 			var err error
 			err = d.app.discord.SendDM(msg, recipient.ID)
@@ -655,7 +668,7 @@ func (d *DiscordDaemon) cmdInvite(s *dg.Session, i *dg.InteractionCreate, lang s
 		}
 	}
 	//if profile != "" {
-	d.app.storage.SetInvitesKey(inviteCode, invite)
+	d.app.storage.SetInvitesKey(invite.Code, invite)
 }
 
 func (d *DiscordDaemon) messageHandler(s *dg.Session, m *dg.MessageCreate) {

From 2f3d5e4e3a20af5da0bd91a736e99191352e2928 Mon Sep 17 00:00:00 2001
From: Harvey Tindall <hrfee@protonmail.ch>
Date: Wed, 11 Oct 2023 12:00:38 +0100
Subject: [PATCH 17/17] discord: update profile list when changes occur

---
 api-profiles.go |  4 ++++
 discord.go      | 41 ++++++++++++++++++++++++++++++-----------
 2 files changed, 34 insertions(+), 11 deletions(-)

diff --git a/api-profiles.go b/api-profiles.go
index ce58cbb..b1594b2 100644
--- a/api-profiles.go
+++ b/api-profiles.go
@@ -104,6 +104,10 @@ func (app *appContext) CreateProfile(gc *gin.Context) {
 		}
 	}
 	app.storage.SetProfileKey(req.Name, profile)
+	// Refresh discord bots, profile list
+	if discordEnabled {
+		app.discord.UpdateCommands()
+	}
 	respondBool(200, true, gc)
 }
 
diff --git a/discord.go b/discord.go
index 1d9caf2..e11d9f0 100644
--- a/discord.go
+++ b/discord.go
@@ -24,6 +24,7 @@ type DiscordDaemon struct {
 	app                                                        *appContext
 	commandHandlers                                            map[string]func(s *dg.Session, i *dg.InteractionCreate, lang string)
 	commandIDs                                                 []string
+	commandDescriptions                                        []*dg.ApplicationCommand
 }
 
 func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) {
@@ -222,7 +223,6 @@ func (d *DiscordDaemon) NewTempInvite(ageSeconds, maxUses int) (inviteURL, iconU
 		d.app.err.Printf("Discord: Failed to get guild: %v", err)
 		return
 	}
-	// FIXME: Fix CSS, and handle no icon
 	iconURL = guild.IconURL("256")
 	return
 }
@@ -310,7 +310,7 @@ func (d *DiscordDaemon) Shutdown() {
 }
 
 func (d *DiscordDaemon) registerCommands() {
-	commands := []*dg.ApplicationCommand{
+	d.commandDescriptions = []*dg.ApplicationCommand{
 		{
 			Name:        d.app.config.Section("discord").Key("start_command").MustString("start"),
 			Description: "Start the Discord linking process. The bot will send further instructions.",
@@ -372,29 +372,28 @@ func (d *DiscordDaemon) registerCommands() {
 				{
 					Type:        dg.ApplicationCommandOptionString,
 					Name:        "profile",
-					Description: "Profile to apply. Restart jfa-go if there's any missing.",
+					Description: "Profile to apply to the created user.",
 					Required:    false,
 				},
 			},
 		},
 	}
-	commands[1].Options[0].Choices = make([]*dg.ApplicationCommandOptionChoice, len(d.app.storage.lang.Telegram))
+	d.commandDescriptions[1].Options[0].Choices = make([]*dg.ApplicationCommandOptionChoice, len(d.app.storage.lang.Telegram))
 	i := 0
 	for code := range d.app.storage.lang.Telegram {
 		d.app.debug.Printf("Discord: registering lang choice \"%s\":\"%s\"\n", d.app.storage.lang.Telegram[code].Meta.Name, code)
-		commands[1].Options[0].Choices[i] = &dg.ApplicationCommandOptionChoice{
+		d.commandDescriptions[1].Options[0].Choices[i] = &dg.ApplicationCommandOptionChoice{
 			Name:  d.app.storage.lang.Telegram[code].Meta.Name,
 			Value: code,
 		}
 		i++
 	}
 
-	// FIXME: Maybe make this reload when profiles change
 	profiles := d.app.storage.GetProfiles()
-	commands[3].Options[3].Choices = make([]*dg.ApplicationCommandOptionChoice, len(profiles))
+	d.commandDescriptions[3].Options[3].Choices = make([]*dg.ApplicationCommandOptionChoice, len(profiles))
 	for i, profile := range profiles {
 		d.app.debug.Printf("Discord: registering profile choice \"%s\"", profile.Name)
-		commands[3].Options[3].Choices[i] = &dg.ApplicationCommandOptionChoice{
+		d.commandDescriptions[3].Options[3].Choices[i] = &dg.ApplicationCommandOptionChoice{
 			Name:  profile.Name,
 			Value: profile.Name,
 		}
@@ -402,12 +401,12 @@ func (d *DiscordDaemon) registerCommands() {
 
 	// d.deregisterCommands()
 
-	d.commandIDs = make([]string, len(commands))
+	d.commandIDs = make([]string, len(d.commandDescriptions))
 	// cCommands, err := d.bot.ApplicationCommandBulkOverwrite(d.bot.State.User.ID, d.guildID, commands)
 	// if err != nil {
 	// 	d.app.err.Printf("Discord: Cannot create commands: %v", err)
 	// }
-	for i, cmd := range commands {
+	for i, cmd := range d.commandDescriptions {
 		command, err := d.bot.ApplicationCommandCreate(d.bot.State.User.ID, d.guildID, cmd)
 		if err != nil {
 			d.app.err.Printf("Discord: Cannot create command \"%s\": %v", cmd.Name, err)
@@ -426,11 +425,31 @@ func (d *DiscordDaemon) deregisterCommands() {
 	}
 	for _, cmd := range existingCommands {
 		if err := d.bot.ApplicationCommandDelete(d.bot.State.User.ID, d.guildID, cmd.ID); err != nil {
-			d.app.err.Printf("Failed to deregister command: %v", err)
+			d.app.err.Printf("Discord: Failed to deregister command: %v", err)
 		}
 	}
 }
 
+// UpdateCommands updates commands which have defined lists of options, to be used when changes occur.
+func (d *DiscordDaemon) UpdateCommands() {
+	// Reload Profile List
+	profiles := d.app.storage.GetProfiles()
+	d.commandDescriptions[3].Options[3].Choices = make([]*dg.ApplicationCommandOptionChoice, len(profiles))
+	for i, profile := range profiles {
+		d.app.debug.Printf("Discord: registering profile choice \"%s\"", profile.Name)
+		d.commandDescriptions[3].Options[3].Choices[i] = &dg.ApplicationCommandOptionChoice{
+			Name:  profile.Name,
+			Value: profile.Name,
+		}
+	}
+	cmd, err := d.bot.ApplicationCommandEdit(d.bot.State.User.ID, d.guildID, d.commandIDs[3], d.commandDescriptions[3])
+	if err != nil {
+		d.app.err.Printf("Discord: Failed to update profile list: %v\n", err)
+	} else {
+		d.commandIDs[3] = cmd.ID
+	}
+}
+
 func (d *DiscordDaemon) commandHandler(s *dg.Session, i *dg.InteractionCreate) {
 	if h, ok := d.commandHandlers[i.ApplicationCommandData().Name]; ok {
 		if i.GuildID != "" && d.channelName != "" {