1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2025-01-08 09:20:11 +00:00

Compare commits

..

No commits in common. "1f9af8df89fbf6279ced5f435569bd48d2bf113b" and "035dbde819c2205f8381174acdf6a7724b0c045a" have entirely different histories.

19 changed files with 163 additions and 413 deletions

83
api.go
View File

@ -826,44 +826,18 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
invite.UserMinutes = req.UserMinutes invite.UserMinutes = req.UserMinutes
} }
invite.ValidTill = validTill invite.ValidTill = validTill
if req.SendTo != "" && app.config.Section("invite_emails").Key("enabled").MustBool(false) { if emailEnabled && req.Email != "" && app.config.Section("invite_emails").Key("enabled").MustBool(false) {
addressValid := false app.debug.Printf("%s: Sending invite email", inviteCode)
discord := "" invite.Email = req.Email
app.debug.Printf("%s: Sending invite message", inviteCode) msg, err := app.email.constructInvite(inviteCode, invite, app, false)
if discordEnabled && !strings.Contains(req.SendTo, "@") { if err != nil {
users := app.discord.GetUsers(req.SendTo) invite.Email = fmt.Sprintf("Failed to send to %s", req.Email)
if len(users) == 0 { app.err.Printf("%s: Failed to construct invite email: %v", inviteCode, err)
invite.SendTo = fmt.Sprintf("Failed: User not found: \"%s\"", req.SendTo) } else if err := app.email.send(msg, req.Email); err != nil {
} else if len(users) > 1 { invite.Email = fmt.Sprintf("Failed to send to %s", req.Email)
invite.SendTo = fmt.Sprintf("Failed: Multiple users found: \"%s\"", req.SendTo) app.err.Printf("%s: %s: %v", inviteCode, invite.Email, err)
} else { } else {
invite.SendTo = req.SendTo app.info.Printf("%s: Sent invite email to \"%s\"", inviteCode, req.Email)
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 != "" {
@ -927,8 +901,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.SendTo != "" { if inv.Email != "" {
invite.SendTo = inv.SendTo invite.Email = inv.Email
} }
if len(inv.Notify) != 0 { if len(inv.Notify) != 0 {
var address string var address string
@ -1229,7 +1203,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 = email.Contact user.NotifyThroughEmail = user.Email != ""
} }
expiry, ok := app.storage.users[jfUser.ID] expiry, ok := app.storage.users[jfUser.ID]
if ok { if ok {
@ -2206,33 +2180,6 @@ 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

View File

@ -592,29 +592,11 @@
"name": "Channel to monitor", "name": "Channel to monitor",
"required": false, "required": false,
"requires_restart": true, "requires_restart": true,
"depends_true": "enabled", "depens_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,

View File

@ -498,11 +498,6 @@ 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;
} }

View File

@ -8,17 +8,16 @@ 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, inviteChannelID, inviteChannelName string channelID, channelName string
guildID string serverChannelName 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.
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
app *appContext
} }
func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) { func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) {
@ -72,7 +71,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 | dg.IntentsGuildInvites d.bot.Identify.Intents = dg.IntentsGuildMessages | dg.IntentsDirectMessages | dg.IntentsGuildMembers
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
@ -83,21 +82,13 @@ 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
d.guildID = d.bot.State.Guilds[len(d.bot.State.Guilds)-1].ID guild, err := d.bot.Guild(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
@ -105,70 +96,11 @@ 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.guildID, d.bot.State.Guilds[len(d.bot.State.Guilds)-1].ID,
"", "",
1000, 1000,
) )
@ -179,13 +111,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 {
if member.User.Username+"#"+member.User.Discriminator == username { userSplit := strings.Split(member.User.Username, "#")
return []*dg.Member{member} if strings.Contains(userSplit[0], username) {
users = append(users, member)
} }
} } else if strings.Contains(member.User.Username, username) {
if strings.Contains(member.User.Username, username) { return nil
users = append(users, member)
} }
} }
return users return users
@ -351,18 +283,6 @@ 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

View File

@ -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"><span id="discord-header"></span><span class="modal-close">&times;</span></span> <span class="heading mb-1">{{ .strings.linkDiscord }}<span class="modal-close">&times;</span></span>
<p class="content mb-1" id="discord-description"></p> <p class="content mb-1">{{ .strings.searchDiscordUser }}</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,14 +501,7 @@
<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>

View File

@ -20,8 +20,6 @@
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 }}

View File

@ -43,7 +43,6 @@
<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>

View File

@ -21,7 +21,6 @@
"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",
@ -97,8 +96,7 @@
"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 find the user.", "searchDiscordUser": "Start typing the Discord username to link it."
"findDiscordUser": "Find Discord user"
}, },
"notifications": { "notifications": {
"changedEmailAddress": "Changed email address of {n}.", "changedEmailAddress": "Changed email address of {n}.",

View File

@ -570,7 +570,6 @@ 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()
@ -580,7 +579,6 @@ 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()

View File

@ -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
SendTo string `json:"send-to" example:"jeff@jellyf.in"` // Send invite to this address or discord name Email string `json:"email" example:"jeff@jellyf.in"` // Send invite to this address
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)
SendTo string `json:"send_to,omitempty"` // Email/Discord username the invite was sent to (if applicable) Email string `json:"email,omitempty"` // Email 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,8 +276,3 @@ 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"`
}

View File

@ -123,9 +123,6 @@ 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 {

View File

@ -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"`
SendTo string `json:"email"` Email 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,24 +105,23 @@ 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

View File

@ -83,7 +83,6 @@ 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 {

View File

@ -18,8 +18,6 @@ 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;
@ -90,30 +88,10 @@ 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);

View File

@ -2,7 +2,6 @@ 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;
@ -24,8 +23,12 @@ interface getPinResponse {
token: string; token: string;
username: string; username: string;
} }
var addDiscord: (passData: string) => void; interface DiscordUser {
name: string;
avatar_url: string;
id: string;
}
class user implements User { class user implements User {
private _row: HTMLTableRowElement; private _row: HTMLTableRowElement;
@ -48,7 +51,7 @@ class user implements User {
private _expiryUnix: number; private _expiryUnix: number;
private _lastActive: HTMLTableDataCellElement; private _lastActive: HTMLTableDataCellElement;
private _lastActiveUnix: number; private _lastActiveUnix: number;
id = ""; id: string;
private _selected: boolean; private _selected: boolean;
get selected(): boolean { return this._selected; } get selected(): boolean { return this._selected; }
@ -103,7 +106,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;
} }
@ -118,11 +121,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 = `
<div class="table-inline"> <a href="https://t.me/${u}" target="_blank">@${u}</a>
<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">
@ -139,11 +142,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;
@ -174,7 +177,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;
} }
@ -189,11 +192,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;
} }
@ -223,7 +226,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 = () => addDiscord(this.id); (this._discord.querySelector("span") as HTMLSpanElement).onclick = this._addDiscord;
} else { } else {
let innerHTML = ` let innerHTML = `
<div class="table-inline"> <div class="table-inline">
@ -280,7 +283,6 @@ 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}`;
@ -410,6 +412,76 @@ 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");
@ -458,16 +530,15 @@ 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.discord = user.discord;
this.telegram = user.telegram; this.telegram = user.telegram;
this.discord = user.discord;
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_discord = user.notify_discord;
this.notify_telegram = user.notify_telegram; this.notify_telegram = user.notify_telegram;
this.notify_discord = user.notify_discord;
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; }
@ -1078,19 +1149,6 @@ 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) => {

View File

@ -1,79 +0,0 @@
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();
}
}

View File

@ -1,5 +1,4 @@
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) => {
@ -26,7 +25,6 @@ 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) {
@ -84,10 +82,10 @@ class DOMInvite implements Invite {
this._middle.querySelector("strong.inv-remaining").textContent = remaining; this._middle.querySelector("strong.inv-remaining").textContent = remaining;
} }
private _send_to: string = ""; private _email: string = "";
get send_to(): string { return this._send_to }; get email(): string { return this._email };
set send_to(address: string) { set email(address: string) {
this._send_to = address; this._email = 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");
@ -102,7 +100,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")) { if (address.includes("Failed to send to")) {
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");
@ -374,7 +372,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.send_to = invite.send_to; this.email = invite.email;
this.expiresIn = invite.expiresIn; this.expiresIn = invite.expiresIn;
if (window.notificationsEnabled) { if (window.notificationsEnabled) {
this.notifyCreation = invite.notifyCreation; this.notifyCreation = invite.notifyCreation;
@ -484,7 +482,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.send_to = invite["send_to"] as string || ""; parsed.email = invite["email"] as string || "";
parsed.label = invite["label"] as string || ""; parsed.label = invite["label"] as string || "";
let time = ""; let time = "";
let userExpiryTime = ""; let userExpiryTime = "";
@ -522,7 +520,6 @@ 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;
@ -545,8 +542,6 @@ 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;
@ -581,19 +576,9 @@ 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;
}
} }
} }
@ -747,7 +732,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,
"send-to": this.sendToEnabled ? this.sendTo : "", "email": this.sendToEnabled ? this.sendTo : "",
"profile": this.profile, "profile": this.profile,
"label": this.label "label": this.label
}; };
@ -776,6 +761,7 @@ 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;
@ -812,22 +798,11 @@ 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 && !window.discordEnabled) { if (!window.emailEnabled) {
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;
} }
} }

View File

@ -109,7 +109,7 @@ interface Invite {
code?: string; code?: string;
expiresIn?: string; expiresIn?: string;
remainingUses?: string; remainingUses?: string;
send_to?: string; email?: string;
usedBy?: { [name: string]: number }; usedBy?: { [name: string]: number };
created?: number; created?: number;
notifyExpiry?: boolean; notifyExpiry?: boolean;

View File

@ -258,8 +258,8 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
} }
return return
} }
email := app.storage.invites[code].SendTo email := app.storage.invites[code].Email
if strings.Contains(email, "Failed") || !strings.Contains(email, "@") { if strings.Contains(email, "Failed") {
email = "" email = ""
} }
data := gin.H{ data := gin.H{
@ -302,8 +302,6 @@ 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 {