mirror of
https://github.com/hrfee/jfa-go.git
synced 2025-01-06 08:20:11 +00:00
Compare commits
7 Commits
035dbde819
...
1f9af8df89
Author | SHA1 | Date | |
---|---|---|---|
1f9af8df89 | |||
0676b6c41f | |||
ce8cdced4d | |||
b8e3fc636c | |||
519a5615cc | |||
168b217553 | |||
7d698d63e3 |
83
api.go
83
api.go
@ -826,18 +826,44 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
|
|||||||
invite.UserMinutes = req.UserMinutes
|
invite.UserMinutes = req.UserMinutes
|
||||||
}
|
}
|
||||||
invite.ValidTill = validTill
|
invite.ValidTill = validTill
|
||||||
if emailEnabled && req.Email != "" && app.config.Section("invite_emails").Key("enabled").MustBool(false) {
|
if req.SendTo != "" && app.config.Section("invite_emails").Key("enabled").MustBool(false) {
|
||||||
app.debug.Printf("%s: Sending invite email", inviteCode)
|
addressValid := false
|
||||||
invite.Email = req.Email
|
discord := ""
|
||||||
msg, err := app.email.constructInvite(inviteCode, invite, app, false)
|
app.debug.Printf("%s: Sending invite message", inviteCode)
|
||||||
if err != nil {
|
if discordEnabled && !strings.Contains(req.SendTo, "@") {
|
||||||
invite.Email = fmt.Sprintf("Failed to send to %s", req.Email)
|
users := app.discord.GetUsers(req.SendTo)
|
||||||
app.err.Printf("%s: Failed to construct invite email: %v", inviteCode, err)
|
if len(users) == 0 {
|
||||||
} else if err := app.email.send(msg, req.Email); err != nil {
|
invite.SendTo = fmt.Sprintf("Failed: User not found: \"%s\"", req.SendTo)
|
||||||
invite.Email = fmt.Sprintf("Failed to send to %s", req.Email)
|
} else if len(users) > 1 {
|
||||||
app.err.Printf("%s: %s: %v", inviteCode, invite.Email, err)
|
invite.SendTo = fmt.Sprintf("Failed: Multiple users found: \"%s\"", req.SendTo)
|
||||||
} else {
|
} else {
|
||||||
app.info.Printf("%s: Sent invite email to \"%s\"", inviteCode, req.Email)
|
invite.SendTo = req.SendTo
|
||||||
|
addressValid = true
|
||||||
|
discord = users[0].User.ID
|
||||||
|
}
|
||||||
|
} else if emailEnabled {
|
||||||
|
addressValid = true
|
||||||
|
invite.SendTo = req.SendTo
|
||||||
|
}
|
||||||
|
if addressValid {
|
||||||
|
msg, err := app.email.constructInvite(inviteCode, invite, app, false)
|
||||||
|
if err != nil {
|
||||||
|
invite.SendTo = fmt.Sprintf("Failed to send to %s", req.SendTo)
|
||||||
|
app.err.Printf("%s: Failed to construct invite message: %v", inviteCode, err)
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
if discord != "" {
|
||||||
|
err = app.discord.SendDM(msg, discord)
|
||||||
|
} else {
|
||||||
|
err = app.email.send(msg, req.SendTo)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
invite.SendTo = fmt.Sprintf("Failed to send to %s", req.SendTo)
|
||||||
|
app.err.Printf("%s: %s: %v", inviteCode, invite.SendTo, err)
|
||||||
|
} else {
|
||||||
|
app.info.Printf("%s: Sent invite email to \"%s\"", inviteCode, req.SendTo)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if req.Profile != "" {
|
if req.Profile != "" {
|
||||||
@ -901,8 +927,8 @@ func (app *appContext) GetInvites(gc *gin.Context) {
|
|||||||
if inv.RemainingUses != 0 {
|
if inv.RemainingUses != 0 {
|
||||||
invite.RemainingUses = inv.RemainingUses
|
invite.RemainingUses = inv.RemainingUses
|
||||||
}
|
}
|
||||||
if inv.Email != "" {
|
if inv.SendTo != "" {
|
||||||
invite.Email = inv.Email
|
invite.SendTo = inv.SendTo
|
||||||
}
|
}
|
||||||
if len(inv.Notify) != 0 {
|
if len(inv.Notify) != 0 {
|
||||||
var address string
|
var address string
|
||||||
@ -1203,7 +1229,7 @@ func (app *appContext) GetUsers(gc *gin.Context) {
|
|||||||
}
|
}
|
||||||
if email, ok := app.storage.emails[jfUser.ID]; ok {
|
if email, ok := app.storage.emails[jfUser.ID]; ok {
|
||||||
user.Email = email.Addr
|
user.Email = email.Addr
|
||||||
user.NotifyThroughEmail = user.Email != ""
|
user.NotifyThroughEmail = email.Contact
|
||||||
}
|
}
|
||||||
expiry, ok := app.storage.users[jfUser.ID]
|
expiry, ok := app.storage.users[jfUser.ID]
|
||||||
if ok {
|
if ok {
|
||||||
@ -2180,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;
|
||||||
}
|
}
|
||||||
|
118
discord.go
118
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,13 +83,21 @@ 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
|
||||||
|
}
|
||||||
|
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
|
||||||
@ -96,11 +105,70 @@ func (d *DiscordDaemon) run() {
|
|||||||
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,
|
||||||
)
|
)
|
||||||
@ -111,13 +179,13 @@ func (d *DiscordDaemon) GetUsers(username string) []*dg.Member {
|
|||||||
hasDiscriminator := strings.Contains(username, "#")
|
hasDiscriminator := strings.Contains(username, "#")
|
||||||
var users []*dg.Member
|
var users []*dg.Member
|
||||||
for _, member := range members {
|
for _, member := range members {
|
||||||
if !hasDiscriminator {
|
if hasDiscriminator {
|
||||||
userSplit := strings.Split(member.User.Username, "#")
|
if member.User.Username+"#"+member.User.Discriminator == username {
|
||||||
if strings.Contains(userSplit[0], username) {
|
return []*dg.Member{member}
|
||||||
users = append(users, member)
|
|
||||||
}
|
}
|
||||||
} else if strings.Contains(member.User.Username, username) {
|
}
|
||||||
return nil
|
if strings.Contains(member.User.Username, username) {
|
||||||
|
users = append(users, member)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return users
|
return users
|
||||||
@ -283,6 +351,18 @@ func (d *DiscordDaemon) commandPIN(s *dg.Session, m *dg.MessageCreate, sects []s
|
|||||||
d.tokens = d.tokens[:len(d.tokens)-1]
|
d.tokens = d.tokens[:len(d.tokens)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DiscordDaemon) SendDM(message *Message, userID ...string) error {
|
||||||
|
channels := make([]string, len(userID))
|
||||||
|
for i, id := range userID {
|
||||||
|
channel, err := d.bot.UserChannelCreate(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
channels[i] = channel.ID
|
||||||
|
}
|
||||||
|
return d.Send(message, channels...)
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DiscordDaemon) Send(message *Message, channelID ...string) error {
|
func (d *DiscordDaemon) Send(message *Message, channelID ...string) error {
|
||||||
msg := ""
|
msg := ""
|
||||||
var embeds []*dg.MessageEmbed
|
var embeds []*dg.MessageEmbed
|
||||||
|
@ -331,8 +331,8 @@
|
|||||||
{{ if .discord_enabled }}
|
{{ if .discord_enabled }}
|
||||||
<div id="modal-discord" class="modal">
|
<div id="modal-discord" class="modal">
|
||||||
<div class="modal-content card">
|
<div class="modal-content card">
|
||||||
<span class="heading mb-1">{{ .strings.linkDiscord }}<span class="modal-close">×</span></span>
|
<span class="heading mb-1"><span id="discord-header"></span><span class="modal-close">×</span></span>
|
||||||
<p class="content mb-1">{{ .strings.searchDiscordUser }}</p>
|
<p class="content mb-1" id="discord-description"></p>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<input type="search" class="col sm field ~neutral !normal input" id="discord-search" placeholder="user#1234">
|
<input type="search" class="col sm field ~neutral !normal input" id="discord-search" placeholder="user#1234">
|
||||||
</div>
|
</div>
|
||||||
@ -501,7 +501,14 @@
|
|||||||
<div id="create-send-to-container">
|
<div id="create-send-to-container">
|
||||||
<label class="label supra">{{ .strings.inviteSendToEmail }}</label>
|
<label class="label supra">{{ .strings.inviteSendToEmail }}</label>
|
||||||
<div class="flex-expand mb-1 mt-half">
|
<div class="flex-expand mb-1 mt-half">
|
||||||
|
{{ if .discord_enabled }}
|
||||||
|
<input type="text" id="create-send-to" class="input ~neutral !normal mr-1" placeholder="example@example.com | user#1234">
|
||||||
|
<span id="create-send-to-search" class="button ~neutral !normal mr-1">
|
||||||
|
<i class="icon ri-search-2-line" title="{{ .strings.search }}"></i>
|
||||||
|
</span>
|
||||||
|
{{ else }}
|
||||||
<input type="email" id="create-send-to" class="input ~neutral !normal mr-1" placeholder="example@example.com">
|
<input type="email" id="create-send-to" class="input ~neutral !normal mr-1" placeholder="example@example.com">
|
||||||
|
{{ end }}
|
||||||
<label for="create-send-to-enabled" class="button ~neutral !normal">
|
<label for="create-send-to-enabled" class="button ~neutral !normal">
|
||||||
<input type="checkbox" id="create-send-to-enabled" aria-label="Send to address enabled">
|
<input type="checkbox" id="create-send-to-enabled" aria-label="Send to address enabled">
|
||||||
</label>
|
</label>
|
||||||
|
@ -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>
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
"apply": "Apply",
|
"apply": "Apply",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
|
"select": "Select",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"enabled": "Enabled",
|
"enabled": "Enabled",
|
||||||
@ -96,7 +97,8 @@
|
|||||||
"notifyInviteExpiry": "On expiry",
|
"notifyInviteExpiry": "On expiry",
|
||||||
"notifyUserCreation": "On user creation",
|
"notifyUserCreation": "On user creation",
|
||||||
"sendPIN": "Ask the user to send the PIN below to the bot.",
|
"sendPIN": "Ask the user to send the PIN below to the bot.",
|
||||||
"searchDiscordUser": "Start typing the Discord username to link it."
|
"searchDiscordUser": "Start typing the Discord username to find the user.",
|
||||||
|
"findDiscordUser": "Find Discord user"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"changedEmailAddress": "Changed email address of {n}.",
|
"changedEmailAddress": "Changed email address of {n}.",
|
||||||
|
2
main.go
2
main.go
@ -570,6 +570,7 @@ func start(asDaemon, firstCall bool) {
|
|||||||
app.telegram, err = newTelegramDaemon(app)
|
app.telegram, err = newTelegramDaemon(app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.err.Printf("Failed to authenticate with Telegram: %v", err)
|
app.err.Printf("Failed to authenticate with Telegram: %v", err)
|
||||||
|
telegramEnabled = false
|
||||||
} else {
|
} else {
|
||||||
go app.telegram.run()
|
go app.telegram.run()
|
||||||
defer app.telegram.Shutdown()
|
defer app.telegram.Shutdown()
|
||||||
@ -579,6 +580,7 @@ func start(asDaemon, firstCall bool) {
|
|||||||
app.discord, err = newDiscordDaemon(app)
|
app.discord, err = newDiscordDaemon(app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.err.Printf("Failed to authenticate with Discord: %v", err)
|
app.err.Printf("Failed to authenticate with Discord: %v", err)
|
||||||
|
discordEnabled = false
|
||||||
} else {
|
} else {
|
||||||
go app.discord.run()
|
go app.discord.run()
|
||||||
defer app.discord.Shutdown()
|
defer app.discord.Shutdown()
|
||||||
|
@ -50,7 +50,7 @@ type generateInviteDTO struct {
|
|||||||
UserDays int `json:"user-days,omitempty" example:"1"` // Number of days till user expiry
|
UserDays int `json:"user-days,omitempty" example:"1"` // Number of days till user expiry
|
||||||
UserHours int `json:"user-hours,omitempty" example:"2"` // Number of hours till user expiry
|
UserHours int `json:"user-hours,omitempty" example:"2"` // Number of hours till user expiry
|
||||||
UserMinutes int `json:"user-minutes,omitempty" example:"3"` // Number of minutes till user expiry
|
UserMinutes int `json:"user-minutes,omitempty" example:"3"` // Number of minutes till user expiry
|
||||||
Email string `json:"email" example:"jeff@jellyf.in"` // Send invite to this address
|
SendTo string `json:"send-to" example:"jeff@jellyf.in"` // Send invite to this address or discord name
|
||||||
MultipleUses bool `json:"multiple-uses" example:"true"` // Allow multiple uses
|
MultipleUses bool `json:"multiple-uses" example:"true"` // Allow multiple uses
|
||||||
NoLimit bool `json:"no-limit" example:"false"` // No invite use limit
|
NoLimit bool `json:"no-limit" example:"false"` // No invite use limit
|
||||||
RemainingUses int `json:"remaining-uses" example:"5"` // Remaining invite uses
|
RemainingUses int `json:"remaining-uses" example:"5"` // Remaining invite uses
|
||||||
@ -100,7 +100,7 @@ type inviteDTO struct {
|
|||||||
UsedBy map[string]int64 `json:"used-by,omitempty"` // Users who have used this invite mapped to their creation time in Epoch/Unix time
|
UsedBy map[string]int64 `json:"used-by,omitempty"` // Users who have used this invite mapped to their creation time in Epoch/Unix time
|
||||||
NoLimit bool `json:"no-limit,omitempty"` // If true, invite can be used any number of times
|
NoLimit bool `json:"no-limit,omitempty"` // If true, invite can be used any number of times
|
||||||
RemainingUses int `json:"remaining-uses,omitempty"` // Remaining number of uses (if applicable)
|
RemainingUses int `json:"remaining-uses,omitempty"` // Remaining number of uses (if applicable)
|
||||||
Email string `json:"email,omitempty"` // Email the invite was sent to (if applicable)
|
SendTo string `json:"send_to,omitempty"` // Email/Discord username the invite was sent to (if applicable)
|
||||||
NotifyExpiry bool `json:"notify-expiry,omitempty"` // Whether to notify the requesting user of expiry or not
|
NotifyExpiry bool `json:"notify-expiry,omitempty"` // Whether to notify the requesting user of expiry or not
|
||||||
NotifyCreation bool `json:"notify-creation,omitempty"` // Whether to notify the requesting user of account creation or not
|
NotifyCreation bool `json:"notify-creation,omitempty"` // Whether to notify the requesting user of account creation or not
|
||||||
Label string `json:"label,omitempty" example:"For Friends"` // Optional label for the invite
|
Label string `json:"label,omitempty" example:"For Friends"` // Optional label for the invite
|
||||||
@ -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 {
|
||||||
|
37
storage.go
37
storage.go
@ -95,7 +95,7 @@ type Invite struct {
|
|||||||
UserDays int `json:"user-days,omitempty"`
|
UserDays int `json:"user-days,omitempty"`
|
||||||
UserHours int `json:"user-hours,omitempty"`
|
UserHours int `json:"user-hours,omitempty"`
|
||||||
UserMinutes int `json:"user-minutes,omitempty"`
|
UserMinutes int `json:"user-minutes,omitempty"`
|
||||||
Email string `json:"email"`
|
SendTo string `json:"email"`
|
||||||
// Used to be stored as formatted time, now as Unix.
|
// Used to be stored as formatted time, now as Unix.
|
||||||
UsedBy [][]string `json:"used-by"`
|
UsedBy [][]string `json:"used-by"`
|
||||||
Notify map[string]map[string]bool `json:"notify"`
|
Notify map[string]map[string]bool `json:"notify"`
|
||||||
@ -105,23 +105,24 @@ type Invite struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Lang struct {
|
type Lang struct {
|
||||||
AdminPath string
|
AdminPath string
|
||||||
chosenAdminLang string
|
chosenAdminLang string
|
||||||
Admin adminLangs
|
Admin adminLangs
|
||||||
AdminJSON map[string]string
|
AdminJSON map[string]string
|
||||||
FormPath string
|
FormPath string
|
||||||
chosenFormLang string
|
chosenFormLang string
|
||||||
Form formLangs
|
Form formLangs
|
||||||
PasswordResetPath string
|
PasswordResetPath string
|
||||||
chosenPWRLang string
|
chosenPWRLang string
|
||||||
PasswordReset pwrLangs
|
PasswordReset pwrLangs
|
||||||
EmailPath string
|
EmailPath string
|
||||||
chosenEmailLang string
|
chosenEmailLang string
|
||||||
Email emailLangs
|
Email emailLangs
|
||||||
CommonPath string
|
CommonPath string
|
||||||
Common commonLangs
|
Common commonLangs
|
||||||
SetupPath string
|
SetupPath string
|
||||||
Setup setupLangs
|
Setup setupLangs
|
||||||
|
// Telegram translations are also used for Discord bots (and likely future ones).
|
||||||
chosenTelegramLang string
|
chosenTelegramLang string
|
||||||
TelegramPath string
|
TelegramPath string
|
||||||
Telegram telegramLangs
|
Telegram telegramLangs
|
||||||
|
@ -83,6 +83,7 @@ func (t *TelegramDaemon) run() {
|
|||||||
updates, err := t.bot.GetUpdatesChan(u)
|
updates, err := t.bot.GetUpdatesChan(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.app.err.Printf("Failed to start Telegram daemon: %v", err)
|
t.app.err.Printf("Failed to start Telegram daemon: %v", err)
|
||||||
|
telegramEnabled = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
|
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,6 +2,7 @@ import { _get, _post, _delete, toggleLoader, addLoader, removeLoader, toDateStri
|
|||||||
import { templateEmail } from "../modules/settings.js";
|
import { templateEmail } from "../modules/settings.js";
|
||||||
import { Marked } from "@ts-stack/markdown";
|
import { Marked } from "@ts-stack/markdown";
|
||||||
import { stripMarkdown } from "../modules/stripmd.js";
|
import { stripMarkdown } from "../modules/stripmd.js";
|
||||||
|
import { DiscordUser, newDiscordSearch } from "../modules/discord.js";
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
id: string;
|
id: string;
|
||||||
@ -23,12 +24,8 @@ interface getPinResponse {
|
|||||||
token: string;
|
token: string;
|
||||||
username: string;
|
username: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DiscordUser {
|
var addDiscord: (passData: string) => void;
|
||||||
name: string;
|
|
||||||
avatar_url: string;
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class user implements User {
|
class user implements User {
|
||||||
private _row: HTMLTableRowElement;
|
private _row: HTMLTableRowElement;
|
||||||
@ -51,7 +48,7 @@ class user implements User {
|
|||||||
private _expiryUnix: number;
|
private _expiryUnix: number;
|
||||||
private _lastActive: HTMLTableDataCellElement;
|
private _lastActive: HTMLTableDataCellElement;
|
||||||
private _lastActiveUnix: number;
|
private _lastActiveUnix: number;
|
||||||
id: string;
|
id = "";
|
||||||
private _selected: boolean;
|
private _selected: boolean;
|
||||||
|
|
||||||
get selected(): boolean { return this._selected; }
|
get selected(): boolean { return this._selected; }
|
||||||
@ -106,7 +103,7 @@ class user implements User {
|
|||||||
email.checked = s;
|
email.checked = s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (window.discordEnabled && this._discordUsername != "") {
|
if (window.discordEnabled && this._discordUsername) {
|
||||||
const email = this._discord.getElementsByClassName("accounts-contact-email")[0] as HTMLInputElement;
|
const email = this._discord.getElementsByClassName("accounts-contact-email")[0] as HTMLInputElement;
|
||||||
email.checked = s;
|
email.checked = s;
|
||||||
}
|
}
|
||||||
@ -121,11 +118,11 @@ class user implements User {
|
|||||||
(this._telegram.querySelector("span") as HTMLSpanElement).onclick = this._addTelegram;
|
(this._telegram.querySelector("span") as HTMLSpanElement).onclick = this._addTelegram;
|
||||||
} else {
|
} else {
|
||||||
let innerHTML = `
|
let innerHTML = `
|
||||||
<a href="https://t.me/${u}" target="_blank">@${u}</a>
|
<div class="table-inline">
|
||||||
|
<a href="https://t.me/${u}" target="_blank">@${u}</a>
|
||||||
`;
|
`;
|
||||||
if (!window.discordEnabled || this._discordUsername == "") {
|
if (!window.discordEnabled || !this._discordUsername) {
|
||||||
innerHTML += `
|
innerHTML += `
|
||||||
<div class="table-inline">
|
|
||||||
<i class="icon ri-settings-2-line ml-half dropdown-button"></i>
|
<i class="icon ri-settings-2-line ml-half dropdown-button"></i>
|
||||||
<div class="dropdown manual">
|
<div class="dropdown manual">
|
||||||
<div class="dropdown-display lg">
|
<div class="dropdown-display lg">
|
||||||
@ -142,11 +139,11 @@ class user implements User {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
innerHTML += "</div>";
|
||||||
this._telegram.innerHTML = innerHTML;
|
this._telegram.innerHTML = innerHTML;
|
||||||
if (!window.discordEnabled || this._discordUsername == "") {
|
if (!window.discordEnabled || !this._discordUsername) {
|
||||||
// Javascript is necessary as including the button inside the dropdown would make it too wide to display next to the username.
|
// Javascript is necessary as including the button inside the dropdown would make it too wide to display next to the username.
|
||||||
const button = this._telegram.querySelector("i");
|
const button = this._telegram.querySelector("i");
|
||||||
const dropdown = this._telegram.querySelector("div.dropdown") as HTMLDivElement;
|
const dropdown = this._telegram.querySelector("div.dropdown") as HTMLDivElement;
|
||||||
@ -177,7 +174,7 @@ class user implements User {
|
|||||||
if (telegram) {
|
if (telegram) {
|
||||||
telegram.checked = s;
|
telegram.checked = s;
|
||||||
}
|
}
|
||||||
if (window.discordEnabled && this._discordUsername != "") {
|
if (window.discordEnabled && this._discordUsername) {
|
||||||
const telegram = this._discord.getElementsByClassName("accounts-contact-telegram")[0] as HTMLInputElement;
|
const telegram = this._discord.getElementsByClassName("accounts-contact-telegram")[0] as HTMLInputElement;
|
||||||
telegram.checked = s;
|
telegram.checked = s;
|
||||||
}
|
}
|
||||||
@ -192,11 +189,11 @@ class user implements User {
|
|||||||
id: this.id,
|
id: this.id,
|
||||||
email: email.checked
|
email: email.checked
|
||||||
}
|
}
|
||||||
if (window.telegramEnabled && this._telegramUsername != "") {
|
if (window.telegramEnabled && this._telegramUsername) {
|
||||||
const telegram = el.getElementsByClassName("accounts-contact-telegram")[0] as HTMLInputElement;
|
const telegram = el.getElementsByClassName("accounts-contact-telegram")[0] as HTMLInputElement;
|
||||||
send["telegram"] = telegram.checked;
|
send["telegram"] = telegram.checked;
|
||||||
}
|
}
|
||||||
if (window.discordEnabled && this._discordUsername != "") {
|
if (window.discordEnabled && this._discordUsername) {
|
||||||
const discord = el.getElementsByClassName("accounts-contact-discord")[0] as HTMLInputElement;
|
const discord = el.getElementsByClassName("accounts-contact-discord")[0] as HTMLInputElement;
|
||||||
send["discord"] = discord.checked;
|
send["discord"] = discord.checked;
|
||||||
}
|
}
|
||||||
@ -226,7 +223,7 @@ class user implements User {
|
|||||||
this._discordUsername = u;
|
this._discordUsername = u;
|
||||||
if (u == "") {
|
if (u == "") {
|
||||||
this._discord.innerHTML = `<span class="chip btn !low">Add</span>`;
|
this._discord.innerHTML = `<span class="chip btn !low">Add</span>`;
|
||||||
(this._discord.querySelector("span") as HTMLSpanElement).onclick = this._addDiscord;
|
(this._discord.querySelector("span") as HTMLSpanElement).onclick = () => addDiscord(this.id);
|
||||||
} else {
|
} else {
|
||||||
let innerHTML = `
|
let innerHTML = `
|
||||||
<div class="table-inline">
|
<div class="table-inline">
|
||||||
@ -283,6 +280,7 @@ class user implements User {
|
|||||||
|
|
||||||
get discord_id(): string { return this._discordID; }
|
get discord_id(): string { return this._discordID; }
|
||||||
set discord_id(id: string) {
|
set discord_id(id: string) {
|
||||||
|
if (!window.discordEnabled || this._discordUsername == "") return;
|
||||||
this._discordID = id;
|
this._discordID = id;
|
||||||
const link = this._discord.getElementsByClassName("discord-link")[0] as HTMLAnchorElement;
|
const link = this._discord.getElementsByClassName("discord-link")[0] as HTMLAnchorElement;
|
||||||
link.href = `https://discord.com/users/${id}`;
|
link.href = `https://discord.com/users/${id}`;
|
||||||
@ -412,76 +410,6 @@ class user implements User {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _timer: NodeJS.Timer;
|
|
||||||
|
|
||||||
private _discordKbListener = () => {
|
|
||||||
clearTimeout(this._timer);
|
|
||||||
const list = document.getElementById("discord-list") as HTMLTableElement;
|
|
||||||
const input = document.getElementById("discord-search") as HTMLInputElement;
|
|
||||||
if (input.value.length < 2) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
list.innerHTML = ``;
|
|
||||||
addLoader(list);
|
|
||||||
list.parentElement.classList.add("mb-1", "mt-1");
|
|
||||||
this._timer = setTimeout(() => {
|
|
||||||
_get("/users/discord/" + input.value, null, (req: XMLHttpRequest) => {
|
|
||||||
if (req.readyState == 4) {
|
|
||||||
if (req.status != 200) {
|
|
||||||
removeLoader(list);
|
|
||||||
list.parentElement.classList.remove("mb-1", "mt-1");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const users = req.response["users"] as Array<DiscordUser>;
|
|
||||||
let innerHTML = ``;
|
|
||||||
for (let i = 0; i < users.length; i++) {
|
|
||||||
innerHTML += `
|
|
||||||
<tr>
|
|
||||||
<td class="img-circle sm">
|
|
||||||
<img class="img-circle" src="${users[i].avatar_url}" width="32" height="32">
|
|
||||||
</td>
|
|
||||||
<td class="w-100 sm">
|
|
||||||
<p class="content">${users[i].name}</p>
|
|
||||||
</td>
|
|
||||||
<td class="sm">
|
|
||||||
<span id="discord-user-${users[i].id}" class="button ~info !high">${window.lang.strings("add")}</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
list.innerHTML = innerHTML;
|
|
||||||
removeLoader(list);
|
|
||||||
list.parentElement.classList.remove("mb-1", "mt-1");
|
|
||||||
for (let i = 0; i < users.length; i++) {
|
|
||||||
const button = document.getElementById(`discord-user-${users[i].id}`) as HTMLInputElement;
|
|
||||||
button.onclick = () => _post("/users/discord", {jf_id: this.id, discord_id: users[i].id}, (req: XMLHttpRequest) => {
|
|
||||||
if (req.readyState == 4) {
|
|
||||||
document.dispatchEvent(new CustomEvent("accounts-reload"));
|
|
||||||
if (req.status != 200) {
|
|
||||||
window.notifications.customError("errorConnectDiscord", window.lang.notif("errorFailureCheckLogs"));
|
|
||||||
return
|
|
||||||
}
|
|
||||||
window.notifications.customSuccess("discordConnected", window.lang.notif("accountConnected"));
|
|
||||||
window.modals.discord.close()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, 750);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _addDiscord = () => {
|
|
||||||
if (!window.discordEnabled) { return; }
|
|
||||||
const input = document.getElementById("discord-search") as HTMLInputElement;
|
|
||||||
const list = document.getElementById("discord-list") as HTMLDivElement;
|
|
||||||
list.innerHTML = ``;
|
|
||||||
input.value = "";
|
|
||||||
input.removeEventListener("keyup", this._discordKbListener);
|
|
||||||
input.addEventListener("keyup", this._discordKbListener);
|
|
||||||
window.modals.discord.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _addTelegram = () => _get("/telegram/pin", null, (req: XMLHttpRequest) => {
|
private _addTelegram = () => _get("/telegram/pin", null, (req: XMLHttpRequest) => {
|
||||||
if (req.readyState == 4 && req.status == 200) {
|
if (req.readyState == 4 && req.status == 200) {
|
||||||
const pin = document.getElementById("telegram-pin");
|
const pin = document.getElementById("telegram-pin");
|
||||||
@ -530,15 +458,16 @@ class user implements User {
|
|||||||
this.id = user.id;
|
this.id = user.id;
|
||||||
this.name = user.name;
|
this.name = user.name;
|
||||||
this.email = user.email || "";
|
this.email = user.email || "";
|
||||||
this.telegram = user.telegram;
|
|
||||||
this.discord = user.discord;
|
this.discord = user.discord;
|
||||||
|
this.telegram = user.telegram;
|
||||||
this.last_active = user.last_active;
|
this.last_active = user.last_active;
|
||||||
this.admin = user.admin;
|
this.admin = user.admin;
|
||||||
this.disabled = user.disabled;
|
this.disabled = user.disabled;
|
||||||
this.expiry = user.expiry;
|
this.expiry = user.expiry;
|
||||||
this.notify_telegram = user.notify_telegram;
|
|
||||||
this.notify_discord = user.notify_discord;
|
this.notify_discord = user.notify_discord;
|
||||||
|
this.notify_telegram = user.notify_telegram;
|
||||||
this.notify_email = user.notify_email;
|
this.notify_email = user.notify_email;
|
||||||
|
this.discord_id = user.discord_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
asElement = (): HTMLTableRowElement => { return this._row; }
|
asElement = (): HTMLTableRowElement => { return this._row; }
|
||||||
@ -1149,6 +1078,19 @@ export class accountsList {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this._announceTextarea.onkeyup = this.loadPreview;
|
this._announceTextarea.onkeyup = this.loadPreview;
|
||||||
|
addDiscord = newDiscordSearch(window.lang.strings("linkDiscord"), window.lang.strings("searchDiscordUser"), window.lang.strings("add"), (user: DiscordUser, id: string) => {
|
||||||
|
_post("/users/discord", {jf_id: id, discord_id: user.id}, (req: XMLHttpRequest) => {
|
||||||
|
if (req.readyState == 4) {
|
||||||
|
document.dispatchEvent(new CustomEvent("accounts-reload"));
|
||||||
|
if (req.status != 200) {
|
||||||
|
window.notifications.customError("errorConnectDiscord", window.lang.notif("errorFailureCheckLogs"));
|
||||||
|
return
|
||||||
|
}
|
||||||
|
window.notifications.customSuccess("discordConnected", window.lang.notif("accountConnected"));
|
||||||
|
window.modals.discord.close()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
reload = () => _get("/users", null, (req: XMLHttpRequest) => {
|
reload = () => _get("/users", null, (req: XMLHttpRequest) => {
|
||||||
|
79
ts/modules/discord.ts
Normal file
79
ts/modules/discord.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import {addLoader, removeLoader, _get} from "../modules/common.js";
|
||||||
|
|
||||||
|
export interface DiscordUser {
|
||||||
|
name: string;
|
||||||
|
avatar_url: string;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
var listeners: { [buttonText: string]: (event: CustomEvent) => void } = {};
|
||||||
|
|
||||||
|
export function newDiscordSearch(title: string, description: string, buttonText: string, buttonFunction: (user: DiscordUser, passData: string) => void): (passData: string) => void {
|
||||||
|
if (!window.discordEnabled) {
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
let timer: NodeJS.Timer;
|
||||||
|
listeners[buttonText] = (event: CustomEvent) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
const list = document.getElementById("discord-list") as HTMLTableElement;
|
||||||
|
const input = document.getElementById("discord-search") as HTMLInputElement;
|
||||||
|
if (input.value.length < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
list.innerHTML = ``;
|
||||||
|
addLoader(list);
|
||||||
|
list.parentElement.classList.add("mb-1", "mt-1");
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
_get("/users/discord/" + input.value, null, (req: XMLHttpRequest) => {
|
||||||
|
if (req.readyState == 4) {
|
||||||
|
if (req.status != 200) {
|
||||||
|
removeLoader(list);
|
||||||
|
list.parentElement.classList.remove("mb-1", "mt-1");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const users = req.response["users"] as Array<DiscordUser>;
|
||||||
|
let innerHTML = ``;
|
||||||
|
for (let i = 0; i < users.length; i++) {
|
||||||
|
innerHTML += `
|
||||||
|
<tr>
|
||||||
|
<td class="img-circle sm">
|
||||||
|
<img class="img-circle" src="${users[i].avatar_url}" width="32" height="32">
|
||||||
|
</td>
|
||||||
|
<td class="w-100 sm">
|
||||||
|
<p class="content">${users[i].name}</p>
|
||||||
|
</td>
|
||||||
|
<td class="sm">
|
||||||
|
<span id="discord-user-${users[i].id}" class="button ~info !high">${buttonText}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
list.innerHTML = innerHTML;
|
||||||
|
removeLoader(list);
|
||||||
|
list.parentElement.classList.remove("mb-1", "mt-1");
|
||||||
|
for (let i = 0; i < users.length; i++) {
|
||||||
|
const button = document.getElementById(`discord-user-${users[i].id}`) as HTMLInputElement;
|
||||||
|
button.onclick = () => buttonFunction(users[i], event.detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 750);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (passData: string) => {
|
||||||
|
const input = document.getElementById("discord-search") as HTMLInputElement;
|
||||||
|
const list = document.getElementById("discord-list") as HTMLDivElement;
|
||||||
|
const header = document.getElementById("discord-header") as HTMLSpanElement;
|
||||||
|
const desc = document.getElementById("discord-description") as HTMLParagraphElement;
|
||||||
|
desc.textContent = description;
|
||||||
|
header.textContent = title;
|
||||||
|
list.innerHTML = ``;
|
||||||
|
input.value = "";
|
||||||
|
for (let key in listeners) {
|
||||||
|
input.removeEventListener("keyup", listeners[key]);
|
||||||
|
}
|
||||||
|
input.addEventListener("keyup", listeners[buttonText].bind(null, { detail: passData }));
|
||||||
|
|
||||||
|
window.modals.discord.show();
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import { _get, _post, _delete, toClipboard, toggleLoader, toDateString } from "../modules/common.js";
|
import { _get, _post, _delete, toClipboard, toggleLoader, toDateString } from "../modules/common.js";
|
||||||
|
import { DiscordUser, newDiscordSearch } from "../modules/discord.js";
|
||||||
|
|
||||||
class DOMInvite implements Invite {
|
class DOMInvite implements Invite {
|
||||||
updateNotify = (checkbox: HTMLInputElement) => {
|
updateNotify = (checkbox: HTMLInputElement) => {
|
||||||
@ -25,6 +26,7 @@ class DOMInvite implements Invite {
|
|||||||
document.dispatchEvent(inviteDeletedEvent);
|
document.dispatchEvent(inviteDeletedEvent);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
private _label: string = "";
|
private _label: string = "";
|
||||||
get label(): string { return this._label; }
|
get label(): string { return this._label; }
|
||||||
set label(label: string) {
|
set label(label: string) {
|
||||||
@ -82,10 +84,10 @@ class DOMInvite implements Invite {
|
|||||||
this._middle.querySelector("strong.inv-remaining").textContent = remaining;
|
this._middle.querySelector("strong.inv-remaining").textContent = remaining;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _email: string = "";
|
private _send_to: string = "";
|
||||||
get email(): string { return this._email };
|
get send_to(): string { return this._send_to };
|
||||||
set email(address: string) {
|
set send_to(address: string) {
|
||||||
this._email = address;
|
this._send_to = address;
|
||||||
const container = this._infoArea.querySelector(".tooltip") as HTMLDivElement;
|
const container = this._infoArea.querySelector(".tooltip") as HTMLDivElement;
|
||||||
const icon = container.querySelector("i");
|
const icon = container.querySelector("i");
|
||||||
const chip = container.querySelector("span.inv-email-chip");
|
const chip = container.querySelector("span.inv-email-chip");
|
||||||
@ -100,7 +102,7 @@ class DOMInvite implements Invite {
|
|||||||
} else {
|
} else {
|
||||||
container.classList.add("mr-1");
|
container.classList.add("mr-1");
|
||||||
chip.classList.add("chip");
|
chip.classList.add("chip");
|
||||||
if (address.includes("Failed to send to")) {
|
if (address.includes("Failed")) {
|
||||||
icon.classList.remove("ri-mail-line");
|
icon.classList.remove("ri-mail-line");
|
||||||
icon.classList.add("ri-mail-close-line");
|
icon.classList.add("ri-mail-close-line");
|
||||||
chip.classList.remove("~neutral");
|
chip.classList.remove("~neutral");
|
||||||
@ -372,7 +374,7 @@ class DOMInvite implements Invite {
|
|||||||
update = (invite: Invite) => {
|
update = (invite: Invite) => {
|
||||||
this.code = invite.code;
|
this.code = invite.code;
|
||||||
this.created = invite.created;
|
this.created = invite.created;
|
||||||
this.email = invite.email;
|
this.send_to = invite.send_to;
|
||||||
this.expiresIn = invite.expiresIn;
|
this.expiresIn = invite.expiresIn;
|
||||||
if (window.notificationsEnabled) {
|
if (window.notificationsEnabled) {
|
||||||
this.notifyCreation = invite.notifyCreation;
|
this.notifyCreation = invite.notifyCreation;
|
||||||
@ -482,7 +484,7 @@ export class inviteList implements inviteList {
|
|||||||
function parseInvite(invite: { [f: string]: string | number | { [name: string]: number } | boolean }): Invite {
|
function parseInvite(invite: { [f: string]: string | number | { [name: string]: number } | boolean }): Invite {
|
||||||
let parsed: Invite = {};
|
let parsed: Invite = {};
|
||||||
parsed.code = invite["code"] as string;
|
parsed.code = invite["code"] as string;
|
||||||
parsed.email = invite["email"] as string || "";
|
parsed.send_to = invite["send_to"] as string || "";
|
||||||
parsed.label = invite["label"] as string || "";
|
parsed.label = invite["label"] as string || "";
|
||||||
let time = "";
|
let time = "";
|
||||||
let userExpiryTime = "";
|
let userExpiryTime = "";
|
||||||
@ -520,6 +522,7 @@ function parseInvite(invite: { [f: string]: string | number | { [name: string]:
|
|||||||
export class createInvite {
|
export class createInvite {
|
||||||
private _sendToEnabled = document.getElementById("create-send-to-enabled") as HTMLInputElement;
|
private _sendToEnabled = document.getElementById("create-send-to-enabled") as HTMLInputElement;
|
||||||
private _sendTo = document.getElementById("create-send-to") as HTMLInputElement;
|
private _sendTo = document.getElementById("create-send-to") as HTMLInputElement;
|
||||||
|
private _discordSearch: HTMLSpanElement;
|
||||||
private _userExpiryToggle = document.getElementById("create-user-expiry-enabled") as HTMLInputElement;
|
private _userExpiryToggle = document.getElementById("create-user-expiry-enabled") as HTMLInputElement;
|
||||||
private _uses = document.getElementById('create-uses') as HTMLInputElement;
|
private _uses = document.getElementById('create-uses') as HTMLInputElement;
|
||||||
private _infUses = document.getElementById("create-inf-uses") as HTMLInputElement;
|
private _infUses = document.getElementById("create-inf-uses") as HTMLInputElement;
|
||||||
@ -542,6 +545,8 @@ export class createInvite {
|
|||||||
private _invDuration = document.getElementById('inv-duration');
|
private _invDuration = document.getElementById('inv-duration');
|
||||||
private _userExpiry = document.getElementById('user-expiry');
|
private _userExpiry = document.getElementById('user-expiry');
|
||||||
|
|
||||||
|
private _sendToDiscord: (passData: string) => void;
|
||||||
|
|
||||||
// Broadcast when new invite created
|
// Broadcast when new invite created
|
||||||
private _newInviteEvent = new CustomEvent("newInviteEvent");
|
private _newInviteEvent = new CustomEvent("newInviteEvent");
|
||||||
private _firstLoad = true;
|
private _firstLoad = true;
|
||||||
@ -576,9 +581,19 @@ export class createInvite {
|
|||||||
if (state) {
|
if (state) {
|
||||||
this._sendToEnabled.parentElement.classList.remove("~neutral");
|
this._sendToEnabled.parentElement.classList.remove("~neutral");
|
||||||
this._sendToEnabled.parentElement.classList.add("~urge");
|
this._sendToEnabled.parentElement.classList.add("~urge");
|
||||||
|
if (window.discordEnabled) {
|
||||||
|
this._discordSearch.classList.remove("~neutral");
|
||||||
|
this._discordSearch.classList.add("~urge");
|
||||||
|
this._discordSearch.onclick = () => this._sendToDiscord("");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this._sendToEnabled.parentElement.classList.remove("~urge");
|
this._sendToEnabled.parentElement.classList.remove("~urge");
|
||||||
this._sendToEnabled.parentElement.classList.add("~neutral");
|
this._sendToEnabled.parentElement.classList.add("~neutral");
|
||||||
|
if (window.discordEnabled) {
|
||||||
|
this._discordSearch.classList.remove("~urge");
|
||||||
|
this._discordSearch.classList.add("~neutral");
|
||||||
|
this._discordSearch.onclick = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -732,7 +747,7 @@ export class createInvite {
|
|||||||
"multiple-uses": (this.uses > 1 || this.infiniteUses),
|
"multiple-uses": (this.uses > 1 || this.infiniteUses),
|
||||||
"no-limit": this.infiniteUses,
|
"no-limit": this.infiniteUses,
|
||||||
"remaining-uses": this.uses,
|
"remaining-uses": this.uses,
|
||||||
"email": this.sendToEnabled ? this.sendTo : "",
|
"send-to": this.sendToEnabled ? this.sendTo : "",
|
||||||
"profile": this.profile,
|
"profile": this.profile,
|
||||||
"label": this.label
|
"label": this.label
|
||||||
};
|
};
|
||||||
@ -761,7 +776,6 @@ export class createInvite {
|
|||||||
this._userDays.disabled = true;
|
this._userDays.disabled = true;
|
||||||
this._userHours.disabled = true;
|
this._userHours.disabled = true;
|
||||||
this._userMinutes.disabled = true;
|
this._userMinutes.disabled = true;
|
||||||
this.sendToEnabled = false;
|
|
||||||
this._createButton.onclick = this.create;
|
this._createButton.onclick = this.create;
|
||||||
this.sendTo = "";
|
this.sendTo = "";
|
||||||
this.uses = 1;
|
this.uses = 1;
|
||||||
@ -798,11 +812,22 @@ export class createInvite {
|
|||||||
this._minutes.onchange = this._checkDurationValidity;
|
this._minutes.onchange = this._checkDurationValidity;
|
||||||
document.addEventListener("profileLoadEvent", () => { this.loadProfiles(); }, false);
|
document.addEventListener("profileLoadEvent", () => { this.loadProfiles(); }, false);
|
||||||
|
|
||||||
if (!window.emailEnabled) {
|
if (!window.emailEnabled && !window.discordEnabled) {
|
||||||
document.getElementById("create-send-to-container").classList.add("unfocused");
|
document.getElementById("create-send-to-container").classList.add("unfocused");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (window.discordEnabled) {
|
||||||
|
this._discordSearch = document.getElementById("create-send-to-search") as HTMLSpanElement;
|
||||||
|
this._sendToDiscord = newDiscordSearch(
|
||||||
|
window.lang.strings("findDiscordUser"),
|
||||||
|
window.lang.strings("searchDiscordUser"),
|
||||||
|
window.lang.strings("select"),
|
||||||
|
(user: DiscordUser) => {
|
||||||
|
this.sendTo = user.name;
|
||||||
|
window.modals.discord.close();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.sendToEnabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ interface Invite {
|
|||||||
code?: string;
|
code?: string;
|
||||||
expiresIn?: string;
|
expiresIn?: string;
|
||||||
remainingUses?: string;
|
remainingUses?: string;
|
||||||
email?: string;
|
send_to?: string;
|
||||||
usedBy?: { [name: string]: number };
|
usedBy?: { [name: string]: number };
|
||||||
created?: number;
|
created?: number;
|
||||||
notifyExpiry?: boolean;
|
notifyExpiry?: boolean;
|
||||||
|
6
views.go
6
views.go
@ -258,8 +258,8 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
email := app.storage.invites[code].Email
|
email := app.storage.invites[code].SendTo
|
||||||
if strings.Contains(email, "Failed") {
|
if strings.Contains(email, "Failed") || !strings.Contains(email, "@") {
|
||||||
email = ""
|
email = ""
|
||||||
}
|
}
|
||||||
data := gin.H{
|
data := gin.H{
|
||||||
@ -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