diff --git a/api.go b/api.go index 24868b8..cb02894 100644 --- a/api.go +++ b/api.go @@ -826,18 +826,44 @@ func (app *appContext) GenerateInvite(gc *gin.Context) { invite.UserMinutes = req.UserMinutes } invite.ValidTill = validTill - if emailEnabled && req.Email != "" && app.config.Section("invite_emails").Key("enabled").MustBool(false) { - app.debug.Printf("%s: Sending invite email", inviteCode) - invite.Email = req.Email - msg, err := app.email.constructInvite(inviteCode, invite, app, false) - if err != nil { - invite.Email = fmt.Sprintf("Failed to send to %s", req.Email) - app.err.Printf("%s: Failed to construct invite email: %v", inviteCode, err) - } else if err := app.email.send(msg, req.Email); err != nil { - invite.Email = fmt.Sprintf("Failed to send to %s", req.Email) - app.err.Printf("%s: %s: %v", inviteCode, invite.Email, err) - } else { - app.info.Printf("%s: Sent invite email to \"%s\"", inviteCode, req.Email) + if req.SendTo != "" && app.config.Section("invite_emails").Key("enabled").MustBool(false) { + addressValid := false + discord := "" + app.debug.Printf("%s: Sending invite message", inviteCode) + if discordEnabled && !strings.Contains(req.SendTo, "@") { + users := app.discord.GetUsers(req.SendTo) + if len(users) == 0 { + invite.SendTo = fmt.Sprintf("Failed: User not found: \"%s\"", req.SendTo) + } else if len(users) > 1 { + invite.SendTo = fmt.Sprintf("Failed: Multiple users found: \"%s\"", req.SendTo) + } else { + 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 != "" { @@ -901,8 +927,8 @@ func (app *appContext) GetInvites(gc *gin.Context) { if inv.RemainingUses != 0 { invite.RemainingUses = inv.RemainingUses } - if inv.Email != "" { - invite.Email = inv.Email + if inv.SendTo != "" { + invite.SendTo = inv.SendTo } if len(inv.Notify) != 0 { var address string diff --git a/discord.go b/discord.go index 9129ffb..63ecf6d 100644 --- a/discord.go +++ b/discord.go @@ -111,13 +111,13 @@ func (d *DiscordDaemon) GetUsers(username string) []*dg.Member { hasDiscriminator := strings.Contains(username, "#") var users []*dg.Member for _, member := range members { - if !hasDiscriminator { - userSplit := strings.Split(member.User.Username, "#") - if strings.Contains(userSplit[0], username) { - users = append(users, member) + if hasDiscriminator { + if member.User.Username+"#"+member.User.Discriminator == username { + return []*dg.Member{member} } - } else if strings.Contains(member.User.Username, username) { - return nil + } + if strings.Contains(member.User.Username, username) { + users = append(users, member) } } return users @@ -283,6 +283,18 @@ func (d *DiscordDaemon) commandPIN(s *dg.Session, m *dg.MessageCreate, sects []s 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 { msg := "" var embeds []*dg.MessageEmbed diff --git a/html/admin.html b/html/admin.html index 31d5db6..31db2b2 100644 --- a/html/admin.html +++ b/html/admin.html @@ -501,7 +501,14 @@
+ {{ if .discord_enabled }} + + + + + {{ else }} + {{ end }} diff --git a/lang/admin/en-us.json b/lang/admin/en-us.json index a308a99..3936b8c 100644 --- a/lang/admin/en-us.json +++ b/lang/admin/en-us.json @@ -97,7 +97,8 @@ "notifyInviteExpiry": "On expiry", "notifyUserCreation": "On user creation", "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": { "changedEmailAddress": "Changed email address of {n}.", diff --git a/models.go b/models.go index 3641eb1..890bb62 100644 --- a/models.go +++ b/models.go @@ -50,7 +50,7 @@ type generateInviteDTO struct { 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 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 NoLimit bool `json:"no-limit" example:"false"` // No invite use limit 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 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) - 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 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 diff --git a/storage.go b/storage.go index a190961..bd1b569 100644 --- a/storage.go +++ b/storage.go @@ -95,7 +95,7 @@ type Invite struct { UserDays int `json:"user-days,omitempty"` UserHours int `json:"user-hours,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. UsedBy [][]string `json:"used-by"` Notify map[string]map[string]bool `json:"notify"` diff --git a/ts/modules/invites.ts b/ts/modules/invites.ts index ea7fff3..e43cd50 100644 --- a/ts/modules/invites.ts +++ b/ts/modules/invites.ts @@ -1,5 +1,5 @@ import { _get, _post, _delete, toClipboard, toggleLoader, toDateString } from "../modules/common.js"; -import { newDiscordSearch } from "../modules/discord.js"; +import { DiscordUser, newDiscordSearch } from "../modules/discord.js"; class DOMInvite implements Invite { updateNotify = (checkbox: HTMLInputElement) => { @@ -26,6 +26,7 @@ class DOMInvite implements Invite { document.dispatchEvent(inviteDeletedEvent); } }) + private _label: string = ""; get label(): string { return this._label; } set label(label: string) { @@ -83,10 +84,10 @@ class DOMInvite implements Invite { this._middle.querySelector("strong.inv-remaining").textContent = remaining; } - private _email: string = ""; - get email(): string { return this._email }; - set email(address: string) { - this._email = address; + private _send_to: string = ""; + get send_to(): string { return this._send_to }; + set send_to(address: string) { + this._send_to = address; const container = this._infoArea.querySelector(".tooltip") as HTMLDivElement; const icon = container.querySelector("i"); const chip = container.querySelector("span.inv-email-chip"); @@ -101,7 +102,7 @@ class DOMInvite implements Invite { } else { container.classList.add("mr-1"); chip.classList.add("chip"); - if (address.includes("Failed to send to")) { + if (address.includes("Failed")) { icon.classList.remove("ri-mail-line"); icon.classList.add("ri-mail-close-line"); chip.classList.remove("~neutral"); @@ -373,7 +374,7 @@ class DOMInvite implements Invite { update = (invite: Invite) => { this.code = invite.code; this.created = invite.created; - this.email = invite.email; + this.send_to = invite.send_to; this.expiresIn = invite.expiresIn; if (window.notificationsEnabled) { this.notifyCreation = invite.notifyCreation; @@ -483,7 +484,7 @@ export class inviteList implements inviteList { function parseInvite(invite: { [f: string]: string | number | { [name: string]: number } | boolean }): Invite { let parsed: Invite = {}; 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 || ""; let time = ""; let userExpiryTime = ""; @@ -521,6 +522,7 @@ function parseInvite(invite: { [f: string]: string | number | { [name: string]: export class createInvite { private _sendToEnabled = document.getElementById("create-send-to-enabled") 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 _uses = document.getElementById('create-uses') as HTMLInputElement; private _infUses = document.getElementById("create-inf-uses") as HTMLInputElement; @@ -543,6 +545,8 @@ export class createInvite { private _invDuration = document.getElementById('inv-duration'); private _userExpiry = document.getElementById('user-expiry'); + private _sendToDiscord: (passData: string) => void; + // Broadcast when new invite created private _newInviteEvent = new CustomEvent("newInviteEvent"); private _firstLoad = true; @@ -577,9 +581,19 @@ export class createInvite { if (state) { this._sendToEnabled.parentElement.classList.remove("~neutral"); 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 { this._sendToEnabled.parentElement.classList.remove("~urge"); this._sendToEnabled.parentElement.classList.add("~neutral"); + if (window.discordEnabled) { + this._discordSearch.classList.remove("~urge"); + this._discordSearch.classList.add("~neutral"); + this._discordSearch.onclick = null; + } } } @@ -733,7 +747,7 @@ export class createInvite { "multiple-uses": (this.uses > 1 || this.infiniteUses), "no-limit": this.infiniteUses, "remaining-uses": this.uses, - "email": this.sendToEnabled ? this.sendTo : "", + "send-to": this.sendToEnabled ? this.sendTo : "", "profile": this.profile, "label": this.label }; @@ -762,7 +776,6 @@ export class createInvite { this._userDays.disabled = true; this._userHours.disabled = true; this._userMinutes.disabled = true; - this.sendToEnabled = false; this._createButton.onclick = this.create; this.sendTo = ""; this.uses = 1; @@ -799,11 +812,22 @@ export class createInvite { this._minutes.onchange = this._checkDurationValidity; document.addEventListener("profileLoadEvent", () => { this.loadProfiles(); }, false); - if (!window.emailEnabled) { + if (!window.emailEnabled && !window.discordEnabled) { 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; } } - - - diff --git a/ts/typings/d.ts b/ts/typings/d.ts index ba40de0..eb5be55 100644 --- a/ts/typings/d.ts +++ b/ts/typings/d.ts @@ -109,7 +109,7 @@ interface Invite { code?: string; expiresIn?: string; remainingUses?: string; - email?: string; + send_to?: string; usedBy?: { [name: string]: number }; created?: number; notifyExpiry?: boolean; diff --git a/views.go b/views.go index 241dd47..b083391 100644 --- a/views.go +++ b/views.go @@ -258,8 +258,8 @@ func (app *appContext) InviteProxy(gc *gin.Context) { } return } - email := app.storage.invites[code].Email - if strings.Contains(email, "Failed") { + email := app.storage.invites[code].SendTo + if strings.Contains(email, "Failed") || !strings.Contains(email, "@") { email = "" } data := gin.H{