mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-22 09:00:10 +00:00
Discord: Add option to provide server invite
When enabled, a temporary one-use invite is created and shown to the user on the account creation form.
This commit is contained in:
parent
0676b6c41f
commit
1f9af8df89
27
api.go
27
api.go
@ -2206,6 +2206,33 @@ func (app *appContext) DiscordVerifiedInvite(gc *gin.Context) {
|
|||||||
respondBool(200, ok, gc)
|
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).
|
// @Summary Returns a list of matching users from a Discord guild, given a username (discriminator optional).
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} DiscordUsersDTO
|
// @Success 200 {object} DiscordUsersDTO
|
||||||
|
@ -592,11 +592,29 @@
|
|||||||
"name": "Channel to monitor",
|
"name": "Channel to monitor",
|
||||||
"required": false,
|
"required": false,
|
||||||
"requires_restart": true,
|
"requires_restart": true,
|
||||||
"depens_true": "enabled",
|
"depends_true": "enabled",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"value": "",
|
"value": "",
|
||||||
"description": "Only listen to commands in specified channel. Leave blank to monitor all."
|
"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": {
|
"language": {
|
||||||
"name": "Language",
|
"name": "Language",
|
||||||
"required": false,
|
"required": false,
|
||||||
|
@ -498,6 +498,11 @@ td.img-circle {
|
|||||||
height: 32px;
|
height: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.img-circle.lg {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
span.shield.img-circle {
|
span.shield.img-circle {
|
||||||
padding: 0.2rem;
|
padding: 0.2rem;
|
||||||
}
|
}
|
||||||
|
93
discord.go
93
discord.go
@ -8,16 +8,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DiscordDaemon struct {
|
type DiscordDaemon struct {
|
||||||
Stopped bool
|
Stopped bool
|
||||||
ShutdownChannel chan string
|
ShutdownChannel chan string
|
||||||
bot *dg.Session
|
bot *dg.Session
|
||||||
username string
|
username string
|
||||||
tokens []string
|
tokens []string
|
||||||
verifiedTokens map[string]DiscordUser // Map of tokens to discord users.
|
verifiedTokens map[string]DiscordUser // Map of tokens to discord users.
|
||||||
channelID, channelName string
|
channelID, channelName, inviteChannelID, inviteChannelName string
|
||||||
serverChannelName string
|
guildID string
|
||||||
users map[string]DiscordUser // Map of user IDs to users. Added to on first interaction, and loaded from app.storage.discord on start.
|
serverChannelName, serverName string
|
||||||
app *appContext
|
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) {
|
func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) {
|
||||||
@ -71,7 +72,7 @@ func (d *DiscordDaemon) MustGetUser(channelID, userID, discrim, username string)
|
|||||||
|
|
||||||
func (d *DiscordDaemon) run() {
|
func (d *DiscordDaemon) run() {
|
||||||
d.bot.AddHandler(d.messageHandler)
|
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 {
|
if err := d.bot.Open(); err != nil {
|
||||||
d.app.err.Printf("Discord: Failed to start daemon: %v", err)
|
d.app.err.Printf("Discord: Failed to start daemon: %v", err)
|
||||||
return
|
return
|
||||||
@ -82,26 +83,92 @@ func (d *DiscordDaemon) run() {
|
|||||||
}
|
}
|
||||||
d.username = d.bot.State.User.Username
|
d.username = d.bot.State.User.Username
|
||||||
// Choose the last guild (server), for now we don't really support multiple anyway
|
// 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 {
|
if err != nil {
|
||||||
d.app.err.Printf("Discord: Failed to get guild: %v", err)
|
d.app.err.Printf("Discord: Failed to get guild: %v", err)
|
||||||
}
|
}
|
||||||
d.serverChannelName = guild.Name
|
d.serverChannelName = guild.Name
|
||||||
|
d.serverName = guild.Name
|
||||||
if channel := d.app.config.Section("discord").Key("channel").String(); channel != "" {
|
if channel := d.app.config.Section("discord").Key("channel").String(); channel != "" {
|
||||||
d.channelName = channel
|
d.channelName = channel
|
||||||
d.serverChannelName += "/" + 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()
|
defer d.bot.Close()
|
||||||
<-d.ShutdownChannel
|
<-d.ShutdownChannel
|
||||||
d.ShutdownChannel <- "Down"
|
d.ShutdownChannel <- "Down"
|
||||||
return
|
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).
|
// 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.
|
// 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 {
|
func (d *DiscordDaemon) GetUsers(username string) []*dg.Member {
|
||||||
members, err := d.bot.GuildMembers(
|
members, err := d.bot.GuildMembers(
|
||||||
d.bot.State.Guilds[len(d.bot.State.Guilds)-1].ID,
|
d.guildID,
|
||||||
"",
|
"",
|
||||||
1000,
|
1000,
|
||||||
)
|
)
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
window.discordEnabled = {{ .discordEnabled }};
|
window.discordEnabled = {{ .discordEnabled }};
|
||||||
window.discordRequired = {{ .discordRequired }};
|
window.discordRequired = {{ .discordRequired }};
|
||||||
window.discordPIN = "{{ .discordPIN }}";
|
window.discordPIN = "{{ .discordPIN }}";
|
||||||
|
window.discordInviteLink = {{ .discordInviteLink }};
|
||||||
|
window.discordServerName = "{{ .discordServerName }}";
|
||||||
</script>
|
</script>
|
||||||
<script src="js/form.js" type="module"></script>
|
<script src="js/form.js" type="module"></script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
<span class="heading mb-1">{{ .strings.linkDiscord }}</span>
|
<span class="heading mb-1">{{ .strings.linkDiscord }}</span>
|
||||||
<p class="content mb-1"> {{ .discordSendPINMessage }}</p>
|
<p class="content mb-1"> {{ .discordSendPINMessage }}</p>
|
||||||
<h1 class="ac">{{ .discordPIN }}</h1>
|
<h1 class="ac">{{ .discordPIN }}</h1>
|
||||||
|
<a id="discord-invite"></a>
|
||||||
<span class="button ~info !normal full-width center mt-1" id="discord-waiting">{{ .strings.success }}</span>
|
<span class="button ~info !normal full-width center mt-1" id="discord-waiting">{{ .strings.success }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -276,3 +276,8 @@ type DiscordConnectUserDTO struct {
|
|||||||
JellyfinID string `json:"jf_id"`
|
JellyfinID string `json:"jf_id"`
|
||||||
DiscordID string `json:"discord_id"`
|
DiscordID string `json:"discord_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DiscordInviteDTO struct {
|
||||||
|
InviteURL string `json:"invite"`
|
||||||
|
IconURL string `json:"icon"`
|
||||||
|
}
|
||||||
|
@ -123,6 +123,9 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
|||||||
}
|
}
|
||||||
if discordEnabled {
|
if discordEnabled {
|
||||||
router.GET(p+"/invite/:invCode/discord/verified/:pin", app.DiscordVerifiedInvite)
|
router.GET(p+"/invite/:invCode/discord/verified/:pin", app.DiscordVerifiedInvite)
|
||||||
|
if app.config.Section("discord").Key("provide_invite").MustBool(false) {
|
||||||
|
router.GET(p+"/invite/:invCode/discord/invite", app.DiscordServerInvite)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if *SWAGGER {
|
if *SWAGGER {
|
||||||
|
22
ts/form.ts
22
ts/form.ts
@ -18,6 +18,8 @@ interface formWindow extends Window {
|
|||||||
discordRequired: boolean;
|
discordRequired: boolean;
|
||||||
discordPIN: string;
|
discordPIN: string;
|
||||||
discordStartCommand: string;
|
discordStartCommand: string;
|
||||||
|
discordInviteLink: boolean;
|
||||||
|
discordServerName: string;
|
||||||
userExpiryEnabled: boolean;
|
userExpiryEnabled: boolean;
|
||||||
userExpiryMonths: number;
|
userExpiryMonths: number;
|
||||||
userExpiryDays: number;
|
userExpiryDays: number;
|
||||||
@ -88,10 +90,30 @@ if (window.telegramEnabled) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DiscordInvite {
|
||||||
|
invite: string;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
var discordVerified = false;
|
var discordVerified = false;
|
||||||
if (window.discordEnabled) {
|
if (window.discordEnabled) {
|
||||||
window.discordModal = new Modal(document.getElementById("modal-discord"), window.discordRequired);
|
window.discordModal = new Modal(document.getElementById("modal-discord"), window.discordRequired);
|
||||||
const discordButton = document.getElementById("link-discord") as HTMLSpanElement;
|
const discordButton = document.getElementById("link-discord") as HTMLSpanElement;
|
||||||
|
if (window.discordInviteLink) {
|
||||||
|
_get("/invite/" + window.code + "/discord/invite", null, (req: XMLHttpRequest) => {
|
||||||
|
if (req.readyState == 4) {
|
||||||
|
if (req.status != 200) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const inv = req.response as DiscordInvite;
|
||||||
|
const link = document.getElementById("discord-invite") as HTMLAnchorElement;
|
||||||
|
link.classList.add("subheading", "link-center");
|
||||||
|
link.href = inv.invite;
|
||||||
|
link.target = "_blank";
|
||||||
|
link.innerHTML = `<span class="img-circle lg mr-1"><img class="img-circle" src="${inv.icon}" width="64" height="64"></span>${window.discordServerName}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
discordButton.onclick = () => {
|
discordButton.onclick = () => {
|
||||||
const waiting = document.getElementById("discord-waiting") as HTMLSpanElement;
|
const waiting = document.getElementById("discord-waiting") as HTMLSpanElement;
|
||||||
toggleLoader(waiting);
|
toggleLoader(waiting);
|
||||||
|
2
views.go
2
views.go
@ -302,6 +302,8 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
|||||||
"command": `<code class="code">` + app.config.Section("discord").Key("start_command").MustString("!start") + `</code>`,
|
"command": `<code class="code">` + app.config.Section("discord").Key("start_command").MustString("!start") + `</code>`,
|
||||||
"server_channel": app.discord.serverChannelName,
|
"server_channel": app.discord.serverChannelName,
|
||||||
}))
|
}))
|
||||||
|
data["discordServerName"] = app.discord.serverName
|
||||||
|
data["discordInviteLink"] = app.discord.inviteChannelName != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// if discordEnabled {
|
// if discordEnabled {
|
||||||
|
Loading…
Reference in New Issue
Block a user