diff --git a/api.go b/api.go index cb02894..4c1ccb2 100644 --- a/api.go +++ b/api.go @@ -2206,6 +2206,33 @@ func (app *appContext) DiscordVerifiedInvite(gc *gin.Context) { respondBool(200, ok, gc) } +// @Summary Returns a 10-minute, one-use Discord server invite +// @Produce json +// @Success 200 {object} DiscordInviteDTO +// @Failure 400 {object} boolResponse +// @Failure 401 {object} boolResponse +// @Failure 500 {object} boolResponse +// @Param invCode path string true "invite Code" +// @Router /invite/{invCode}/discord/invite [get] +// @tags Other +func (app *appContext) DiscordServerInvite(gc *gin.Context) { + if app.discord.inviteChannelName == "" { + respondBool(400, false, gc) + return + } + code := gc.Param("invCode") + if _, ok := app.storage.invites[code]; !ok { + respondBool(401, false, gc) + return + } + invURL, iconURL := app.discord.NewTempInvite(10*60, 1) + if invURL == "" { + respondBool(500, false, gc) + return + } + gc.JSON(200, DiscordInviteDTO{invURL, iconURL}) +} + // @Summary Returns a list of matching users from a Discord guild, given a username (discriminator optional). // @Produce json // @Success 200 {object} DiscordUsersDTO diff --git a/config/config-base.json b/config/config-base.json index 48fb173..36bad28 100644 --- a/config/config-base.json +++ b/config/config-base.json @@ -592,11 +592,29 @@ "name": "Channel to monitor", "required": false, "requires_restart": true, - "depens_true": "enabled", + "depends_true": "enabled", "type": "text", "value": "", "description": "Only listen to commands in specified channel. Leave blank to monitor all." }, + "provide_invite": { + "name": "Provide server invite", + "required": false, + "requires_restart": true, + "depends_true": "enabled", + "type": "bool", + "value": false, + "description": "Generate a one-time discord server invite for the account creation form. Required Bot permission \"Create instant invite\", you may need to re-add the bot to your server after." + }, + "invite_channel": { + "name": "Invite channel", + "required": false, + "requires_restart": true, + "depends_true": "provide_invite", + "type": "text", + "value": "", + "description": "Channel to invite new users to." + }, "language": { "name": "Language", "required": false, diff --git a/css/base.css b/css/base.css index 57d7f31..7d96684 100644 --- a/css/base.css +++ b/css/base.css @@ -498,6 +498,11 @@ td.img-circle { height: 32px; } +span.img-circle.lg { + width: 64px; + height: 64px; +} + span.shield.img-circle { padding: 0.2rem; } diff --git a/discord.go b/discord.go index 3e0b848..43c2ebf 100644 --- a/discord.go +++ b/discord.go @@ -8,16 +8,17 @@ import ( ) type DiscordDaemon struct { - Stopped bool - ShutdownChannel chan string - bot *dg.Session - username string - tokens []string - verifiedTokens map[string]DiscordUser // Map of tokens to discord users. - channelID, channelName string - serverChannelName string - users map[string]DiscordUser // Map of user IDs to users. Added to on first interaction, and loaded from app.storage.discord on start. - app *appContext + Stopped bool + ShutdownChannel chan string + bot *dg.Session + username string + tokens []string + verifiedTokens map[string]DiscordUser // Map of tokens to discord users. + channelID, channelName, inviteChannelID, inviteChannelName string + guildID string + serverChannelName, serverName string + users map[string]DiscordUser // Map of user IDs to users. Added to on first interaction, and loaded from app.storage.discord on start. + app *appContext } func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) { @@ -71,7 +72,7 @@ func (d *DiscordDaemon) MustGetUser(channelID, userID, discrim, username string) func (d *DiscordDaemon) run() { d.bot.AddHandler(d.messageHandler) - d.bot.Identify.Intents = dg.IntentsGuildMessages | dg.IntentsDirectMessages | dg.IntentsGuildMembers + d.bot.Identify.Intents = dg.IntentsGuildMessages | dg.IntentsDirectMessages | dg.IntentsGuildMembers | dg.IntentsGuildInvites if err := d.bot.Open(); err != nil { d.app.err.Printf("Discord: Failed to start daemon: %v", err) return @@ -82,26 +83,92 @@ func (d *DiscordDaemon) run() { } d.username = d.bot.State.User.Username // Choose the last guild (server), for now we don't really support multiple anyway - guild, err := d.bot.Guild(d.bot.State.Guilds[len(d.bot.State.Guilds)-1].ID) + d.guildID = d.bot.State.Guilds[len(d.bot.State.Guilds)-1].ID + guild, err := d.bot.Guild(d.guildID) if err != nil { d.app.err.Printf("Discord: Failed to get guild: %v", err) } d.serverChannelName = guild.Name + d.serverName = guild.Name if channel := d.app.config.Section("discord").Key("channel").String(); channel != "" { d.channelName = channel d.serverChannelName += "/" + channel } + if d.app.config.Section("discord").Key("provide_invite").MustBool(false) { + if invChannel := d.app.config.Section("discord").Key("invite_channel").String(); invChannel != "" { + d.inviteChannelName = invChannel + } + } defer d.bot.Close() <-d.ShutdownChannel d.ShutdownChannel <- "Down" return } +// NewTempInvite creates an invite link, and returns the invite URL, as well as the URL for the server icon. +func (d *DiscordDaemon) NewTempInvite(ageSeconds, maxUses int) (inviteURL, iconURL string) { + var inv *dg.Invite + var err error + if d.inviteChannelName == "" { + d.app.err.Println("Discord: Cannot create invite without channel specified in settings.") + return + } + if d.inviteChannelID == "" { + channels, err := d.bot.GuildChannels(d.guildID) + if err != nil { + d.app.err.Printf("Discord: Couldn't get channel list: %v", err) + return + } + found := false + for _, channel := range channels { + // channel, err := d.bot.Channel(ch.ID) + // if err != nil { + // d.app.err.Printf("Discord: Couldn't get channel: %v", err) + // return + // } + if channel.Name == d.inviteChannelName { + d.inviteChannelID = channel.ID + found = true + break + } + } + if !found { + d.app.err.Printf("Discord: Couldn't find invite channel \"%s\"", d.inviteChannelName) + return + } + } + // channel, err := d.bot.Channel(d.inviteChannelID) + // if err != nil { + // d.app.err.Printf("Discord: Couldn't get invite channel: %v", err) + // return + // } + inv, err = d.bot.ChannelInviteCreate(d.inviteChannelID, dg.Invite{ + // Guild: d.bot.State.Guilds[len(d.bot.State.Guilds)-1], + // Channel: channel, + // Inviter: d.bot.State.User, + MaxAge: ageSeconds, + MaxUses: maxUses, + Temporary: false, + }) + if err != nil { + d.app.err.Printf("Discord: Failed to create invite: %v", err) + return + } + inviteURL = "https://discord.gg/" + inv.Code + guild, err := d.bot.Guild(d.guildID) + if err != nil { + d.app.err.Printf("Discord: Failed to get guild: %v", err) + return + } + iconURL = guild.IconURL() + return +} + // Returns the user(s) roughly corresponding to the username (if they are in the guild). // if no discriminator (#xxxx) is given in the username and there are multiple corresponding users, a list of all matching users is returned. func (d *DiscordDaemon) GetUsers(username string) []*dg.Member { members, err := d.bot.GuildMembers( - d.bot.State.Guilds[len(d.bot.State.Guilds)-1].ID, + d.guildID, "", 1000, ) diff --git a/html/form-base.html b/html/form-base.html index e144f58..855253b 100644 --- a/html/form-base.html +++ b/html/form-base.html @@ -20,6 +20,8 @@ window.discordEnabled = {{ .discordEnabled }}; window.discordRequired = {{ .discordRequired }}; window.discordPIN = "{{ .discordPIN }}"; + window.discordInviteLink = {{ .discordInviteLink }}; + window.discordServerName = "{{ .discordServerName }}"; {{ end }} diff --git a/html/form.html b/html/form.html index c391707..6b785d0 100644 --- a/html/form.html +++ b/html/form.html @@ -43,6 +43,7 @@ {{ .strings.linkDiscord }}
{{ .discordSendPINMessage }}
` + app.config.Section("discord").Key("start_command").MustString("!start") + `
`,
"server_channel": app.discord.serverChannelName,
}))
+ data["discordServerName"] = app.discord.serverName
+ data["discordInviteLink"] = app.discord.inviteChannelName != ""
}
// if discordEnabled {