diff --git a/api.go b/api.go
index 6888868..31d4816 100644
--- a/api.go
+++ b/api.go
@@ -330,6 +330,30 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
success = false
return
}
+ var discordUser DiscordUser
+ discordVerified := false
+ if discordEnabled {
+ if req.DiscordPIN == "" {
+ if app.config.Section("discord").Key("required").MustBool(false) {
+ f = func(gc *gin.Context) {
+ app.debug.Printf("%s: New user failed: Discord verification not completed", req.Code)
+ respond(401, "errorDiscordVerification", gc)
+ }
+ success = false
+ return
+ }
+ } else {
+ discordUser, discordVerified = app.discord.verifiedTokens[req.DiscordPIN]
+ if !discordVerified {
+ f = func(gc *gin.Context) {
+ app.debug.Printf("%s: New user failed: Discord PIN was invalid", req.Code)
+ respond(401, "errorInvalidPIN", gc)
+ }
+ success = false
+ return
+ }
+ }
+ }
telegramTokenIndex := -1
if telegramEnabled {
if req.TelegramPIN == "" {
@@ -479,7 +503,18 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
app.err.Printf("Failed to store user duration: %v", err)
}
}
-
+ if discordEnabled && discordVerified {
+ discordUser.Contact = req.DiscordContact
+ if app.storage.discord == nil {
+ app.storage.discord = map[string]DiscordUser{}
+ }
+ app.storage.discord[user.ID] = discordUser
+ if err := app.storage.storeDiscordUsers(); err != nil {
+ app.err.Printf("Failed to store Discord users: %v", err)
+ } else {
+ delete(app.discord.verifiedTokens, req.DiscordPIN)
+ }
+ }
if telegramEnabled && telegramTokenIndex != -1 {
tgToken := app.telegram.verifiedTokens[telegramTokenIndex]
tgUser := TelegramUser{
@@ -494,8 +529,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
app.storage.telegram = map[string]TelegramUser{}
}
app.storage.telegram[user.ID] = tgUser
- err := app.storage.storeTelegramUsers()
- if err != nil {
+ if err := app.storage.storeTelegramUsers(); err != nil {
app.err.Printf("Failed to store Telegram users: %v", err)
} else {
app.telegram.verifiedTokens[len(app.telegram.verifiedTokens)-1], app.telegram.verifiedTokens[telegramTokenIndex] = app.telegram.verifiedTokens[telegramTokenIndex], app.telegram.verifiedTokens[len(app.telegram.verifiedTokens)-1]
@@ -503,7 +537,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
}
}
- if (emailEnabled && app.config.Section("welcome_email").Key("enabled").MustBool(false) && req.Email != "") || telegramTokenIndex != -1 {
+ if (emailEnabled && app.config.Section("welcome_email").Key("enabled").MustBool(false) && req.Email != "") || telegramTokenIndex != -1 || discordVerified {
name := app.getAddressOrName(user.ID)
app.debug.Printf("%s: Sending welcome message to %s", req.Username, name)
msg, err := app.email.constructWelcome(req.Username, expiry, app, false)
@@ -1169,6 +1203,7 @@ func (app *appContext) GetUsers(gc *gin.Context) {
}
if email, ok := app.storage.emails[jfUser.ID]; ok {
user.Email = email.(string)
+ user.NotifyThroughEmail = user.Email != ""
}
expiry, ok := app.storage.users[jfUser.ID]
if ok {
@@ -1178,6 +1213,11 @@ func (app *appContext) GetUsers(gc *gin.Context) {
user.Telegram = tgUser.Username
user.NotifyThroughTelegram = tgUser.Contact
}
+ if dc, ok := app.storage.discord[jfUser.ID]; ok {
+ user.Discord = dc.Username + "#" + dc.Discriminator
+ user.DiscordID = dc.ID
+ user.NotifyThroughDiscord = dc.Contact
+ }
resp.UserList[i] = user
i++
}
@@ -2011,7 +2051,7 @@ func (app *appContext) TelegramAddUser(gc *gin.Context) {
// @Router /users/telegram/notify [post]
// @Security Bearer
// @tags Other
-func (app *appContext) TelegramSetNotify(gc *gin.Context) {
+func (app *appContext) SetContactMethods(gc *gin.Context) {
var req telegramNotifyDTO
gc.BindJSON(&req)
if req.ID == "" {
@@ -2019,23 +2059,34 @@ func (app *appContext) TelegramSetNotify(gc *gin.Context) {
return
}
if tgUser, ok := app.storage.telegram[req.ID]; ok {
- tgUser.Contact = req.Enabled
+ tgUser.Contact = req.Telegram
app.storage.telegram[req.ID] = tgUser
if err := app.storage.storeTelegramUsers(); err != nil {
respondBool(500, false, gc)
app.err.Printf("Telegram: Failed to store users: %v", err)
return
}
- respondBool(200, true, gc)
msg := ""
- if !req.Enabled {
+ if !req.Telegram {
msg = "not"
}
app.debug.Printf("Telegram: User \"%s\" will %s be notified through Telegram.", tgUser.Username, msg)
- return
}
- app.err.Printf("Telegram: User \"%s\" does not have a telegram account registered.", req.ID)
- respondBool(400, false, gc)
+ if dcUser, ok := app.storage.discord[req.ID]; ok {
+ dcUser.Contact = req.Discord
+ app.storage.discord[req.ID] = dcUser
+ if err := app.storage.storeDiscordUsers(); err != nil {
+ respondBool(500, false, gc)
+ app.err.Printf("Discord: Failed to store users: %v", err)
+ return
+ }
+ msg := ""
+ if !req.Discord {
+ msg = "not"
+ }
+ app.debug.Printf("Discord: User \"%s\" will %s be notified through Discord.", dcUser.Username, msg)
+ }
+ respondBool(200, true, gc)
}
// @Summary Returns true/false on whether or not a telegram PIN was verified. Requires bearer auth.
@@ -2092,6 +2143,25 @@ func (app *appContext) TelegramVerifiedInvite(gc *gin.Context) {
respondBool(200, tokenIndex != -1, gc)
}
+// @Summary Returns true/false on whether or not a discord PIN was verified. Requires invite code.
+// @Produce json
+// @Success 200 {object} boolResponse
+// @Success 401 {object} boolResponse
+// @Param pin path string true "PIN code to check"
+// @Param invCode path string true "invite Code"
+// @Router /invite/{invCode}/discord/verified/{pin} [get]
+// @tags Other
+func (app *appContext) DiscordVerifiedInvite(gc *gin.Context) {
+ code := gc.Param("invCode")
+ if _, ok := app.storage.invites[code]; !ok {
+ respondBool(401, false, gc)
+ return
+ }
+ pin := gc.Param("pin")
+ _, ok := app.discord.verifiedTokens[pin]
+ respondBool(200, ok, gc)
+}
+
// @Summary Restarts the program. No response means success.
// @Router /restart [post]
// @Security Bearer
diff --git a/config/config-base.json b/config/config-base.json
index 5756d1c..48fb173 100644
--- a/config/config-base.json
+++ b/config/config-base.json
@@ -588,6 +588,15 @@
"value": "!start",
"description": "Command to start the user verification process."
},
+ "channel": {
+ "name": "Channel to monitor",
+ "required": false,
+ "requires_restart": true,
+ "depens_true": "enabled",
+ "type": "text",
+ "value": "",
+ "description": "Only listen to commands in specified channel. Leave blank to monitor all."
+ },
"language": {
"name": "Language",
"required": false,
diff --git a/discord.go b/discord.go
index e91eca5..f80d216 100644
--- a/discord.go
+++ b/discord.go
@@ -7,22 +7,17 @@ import (
dg "github.com/bwmarrin/discordgo"
)
-type DiscordToken struct {
- Token string
- ChannelID string
- UserID string
- Username string
-}
-
type DiscordDaemon struct {
- Stopped bool
- ShutdownChannel chan string
- bot *dg.Session
- username string
- tokens map[string]DiscordToken // map of user IDs to tokens.
- verifiedTokens []DiscordToken
- languages map[string]string // Store of languages for user IDs. Added to on first interaction, and loaded from app.storage.discord on start.
- app *appContext
+ Stopped bool
+ ShutdownChannel chan string
+ bot *dg.Session
+ username string
+ tokens []string
+ verifiedTokens map[string]DiscordUser // Map of tokens to discord users.
+ channelID, channelName string
+ serverChannelName string
+ users map[string]DiscordUser // Map of user IDs to users. Added to on first interaction, and loaded from app.storage.discord on start.
+ app *appContext
}
func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) {
@@ -38,28 +33,39 @@ func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) {
Stopped: false,
ShutdownChannel: make(chan string),
bot: bot,
- tokens: map[string]DiscordToken{},
- verifiedTokens: []DiscordToken{},
- languages: map[string]string{},
+ tokens: []string{},
+ verifiedTokens: map[string]DiscordUser{},
+ users: map[string]DiscordUser{},
app: app,
}
for _, user := range app.storage.discord {
- if user.Lang != "" {
- dd.languages[user.ID] = user.Lang
- }
+ dd.users[user.ID] = user
}
return dd, nil
}
-func (d *DiscordDaemon) NewAuthToken(channelID, userID, username string) DiscordToken {
+// NewAuthToken generates an 8-character pin in the form "A1-2B-CD".
+func (d *DiscordDaemon) NewAuthToken() string {
pin := genAuthToken()
- token := DiscordToken{
- Token: pin,
- ChannelID: channelID,
- UserID: userID,
- Username: username,
+ d.tokens = append(d.tokens, pin)
+ return pin
+}
+
+func (d *DiscordDaemon) NewUnknownUser(channelID, userID, discrim, username string) DiscordUser {
+ user := DiscordUser{
+ ChannelID: channelID,
+ ID: userID,
+ Username: username,
+ Discriminator: discrim,
+ }
+ return user
+}
+
+func (d *DiscordDaemon) MustGetUser(channelID, userID, discrim, username string) DiscordUser {
+ if user, ok := d.users[userID]; ok {
+ return user
}
- return token
+ return d.NewUnknownUser(channelID, userID, discrim, username)
}
func (d *DiscordDaemon) run() {
@@ -70,6 +76,15 @@ func (d *DiscordDaemon) run() {
return
}
d.username = d.bot.State.User.Username
+ // Choose the last guild (server), for now we don't really support multiple anyway
+ guild, err := d.bot.Guild(d.bot.State.Guilds[len(d.bot.State.Guilds)-1].ID)
+ if err != nil {
+ d.app.err.Printf("Discord: Failed to get guild: %v", err)
+ }
+ d.serverChannelName = guild.Name
+ if channel := d.app.config.Section("discord").Key("channel").String(); channel != "" {
+ d.channelName = channel
+ }
defer d.bot.Close()
<-d.ShutdownChannel
d.ShutdownChannel <- "Down"
@@ -84,6 +99,22 @@ func (d *DiscordDaemon) Shutdown() {
}
func (d *DiscordDaemon) messageHandler(s *dg.Session, m *dg.MessageCreate) {
+ if m.GuildID != "" && d.channelName != "" {
+ if d.channelID == "" {
+ channel, err := s.Channel(m.ChannelID)
+ if err != nil {
+ d.app.err.Printf("Discord: Couldn't get channel, will monitor all: %v", err)
+ d.channelName = ""
+ }
+ if channel.Name == d.channelName {
+ d.channelID = channel.ID
+ }
+ }
+ if d.channelID != m.ChannelID {
+ d.app.debug.Printf("Discord: Ignoring message as not in specified channel")
+ return
+ }
+ }
if m.Author.ID == s.State.User.ID {
return
}
@@ -92,11 +123,13 @@ func (d *DiscordDaemon) messageHandler(s *dg.Session, m *dg.MessageCreate) {
return
}
lang := d.app.storage.lang.chosenTelegramLang
- if storedLang, ok := d.languages[m.Author.ID]; ok {
- lang = storedLang
+ if user, ok := d.users[m.Author.ID]; ok {
+ if _, ok := d.app.storage.lang.Telegram[user.Lang]; ok {
+ lang = user.Lang
+ }
}
switch msg := sects[0]; msg {
- case d.app.config.Section("telegram").Key("start_command").MustString("!start"):
+ case d.app.config.Section("discord").Key("start_command").MustString("!start"):
d.commandStart(s, m, lang)
case "!lang":
d.commandLang(s, m, sects, lang)
@@ -111,8 +144,8 @@ func (d *DiscordDaemon) commandStart(s *dg.Session, m *dg.MessageCreate, lang st
d.app.err.Printf("Discord: Failed to create private channel with \"%s\": %v", m.Author.Username, err)
return
}
- token := d.NewAuthToken(channel.ID, m.Author.ID, m.Author.Username)
- d.tokens[m.Author.ID] = token
+ user := d.MustGetUser(channel.ID, m.Author.ID, m.Author.Discriminator, m.Author.Username)
+ d.users[m.Author.ID] = user
content := d.app.storage.lang.Telegram[lang].Strings.get("startMessage") + "\n"
content += d.app.storage.lang.Telegram[lang].Strings.template("languageMessage", tmpl{"command": "!lang"})
_, err = s.ChannelMessageSend(channel.ID, content)
@@ -139,7 +172,7 @@ func (d *DiscordDaemon) commandLang(s *dg.Session, m *dg.MessageCreate, sects []
return
}
if _, ok := d.app.storage.lang.Telegram[sects[1]]; ok {
- d.languages[m.Author.ID] = sects[1]
+ var user DiscordUser
for jfID, user := range d.app.storage.discord {
if user.ID == m.Author.ID {
user.Lang = sects[1]
@@ -150,30 +183,69 @@ func (d *DiscordDaemon) commandLang(s *dg.Session, m *dg.MessageCreate, sects []
break
}
}
+ d.users[m.Author.ID] = user
}
}
func (d *DiscordDaemon) commandPIN(s *dg.Session, m *dg.MessageCreate, sects []string, lang string) {
- token, ok := d.tokens[m.Author.ID]
- if !ok || token.Token != sects[0] {
- _, err := s.ChannelMessageSendReply(
+ if _, ok := d.users[m.Author.ID]; ok {
+ channel, err := s.Channel(m.ChannelID)
+ if err != nil {
+ d.app.err.Printf("Discord: Failed to get channel: %v", err)
+ return
+ }
+ if channel.Type != dg.ChannelTypeDM {
+ d.app.debug.Println("Discord: Ignoring message as not a DM")
+ return
+ }
+ } else {
+ d.app.debug.Println("Discord: Ignoring message as user was not found")
+ return
+ }
+ tokenIndex := -1
+ for i, token := range d.tokens {
+ if sects[0] == token {
+ tokenIndex = i
+ break
+ }
+ }
+ if tokenIndex == -1 {
+ _, err := s.ChannelMessageSend(
m.ChannelID,
d.app.storage.lang.Telegram[lang].Strings.get("invalidPIN"),
- m.Reference(),
)
if err != nil {
d.app.err.Printf("Discord: Failed to send message to \"%s\": %v", m.Author.Username, err)
}
return
}
- _, err := s.ChannelMessageSendReply(
+ _, err := s.ChannelMessageSend(
m.ChannelID,
d.app.storage.lang.Telegram[lang].Strings.get("pinSuccess"),
- m.Reference(),
)
if err != nil {
d.app.err.Printf("Discord: Failed to send message to \"%s\": %v", m.Author.Username, err)
}
- d.verifiedTokens = append(d.verifiedTokens, token)
- delete(d.tokens, m.Author.ID)
+ d.verifiedTokens[sects[0]] = d.users[m.Author.ID]
+ d.tokens[len(d.tokens)-1], d.tokens[tokenIndex] = d.tokens[tokenIndex], d.tokens[len(d.tokens)-1]
+ d.tokens = d.tokens[:len(d.tokens)-1]
+}
+
+func (d *DiscordDaemon) Send(message *Message, channelID ...string) error {
+ for _, id := range channelID {
+ msg := ""
+ if message.Markdown == "" {
+ msg = message.Text
+ } else {
+ msg = message.Markdown
+ }
+ _, err := d.bot.ChannelMessageSend(
+ id,
+ msg,
+ )
+ if err != nil {
+ return err
+ }
+ }
+ return nil
}
diff --git a/email.go b/email.go
index e81d6b5..5f752fd 100644
--- a/email.go
+++ b/email.go
@@ -230,7 +230,7 @@ func (emailer *Emailer) construct(app *appContext, section, keyFragment string,
var keys []string
plaintext := app.config.Section("email").Key("plaintext").MustBool(false)
if plaintext {
- if telegramEnabled {
+ if telegramEnabled || discordEnabled {
keys = []string{"text"}
text, markdown = "", ""
} else {
@@ -238,7 +238,7 @@ func (emailer *Emailer) construct(app *appContext, section, keyFragment string,
text = ""
}
} else {
- if telegramEnabled {
+ if telegramEnabled || discordEnabled {
keys = []string{"html", "text", "markdown"}
} else {
keys = []string{"html", "text"}
@@ -807,8 +807,21 @@ func (app *appContext) sendByID(email *Message, ID ...string) error {
var err error
if tgChat, ok := app.storage.telegram[id]; ok && tgChat.Contact && telegramEnabled {
err = app.telegram.Send(email, tgChat.ChatID)
- } else if address, ok := app.storage.emails[id]; ok {
+ if err != nil {
+ return err
+ }
+ }
+ if dcChat, ok := app.storage.discord[id]; ok && dcChat.Contact && discordEnabled {
+ err = app.discord.Send(email, dcChat.ChannelID)
+ if err != nil {
+ return err
+ }
+ }
+ if address, ok := app.storage.emails[id]; ok {
err = app.email.send(email, address.(string))
+ if err != nil {
+ return err
+ }
}
if err != nil {
return err
@@ -818,6 +831,9 @@ func (app *appContext) sendByID(email *Message, ID ...string) error {
}
func (app *appContext) getAddressOrName(jfID string) string {
+ if dcChat, ok := app.storage.discord[jfID]; ok && dcChat.Contact && discordEnabled {
+ return dcChat.Username + "#" + dcChat.Discriminator
+ }
if tgChat, ok := app.storage.telegram[jfID]; ok && tgChat.Contact && telegramEnabled {
return "@" + tgChat.Username
}
diff --git a/html/admin.html b/html/admin.html
index a5b784f..6286144 100644
--- a/html/admin.html
+++ b/html/admin.html
@@ -7,6 +7,7 @@
window.notificationsEnabled = {{ .notifications }};
window.emailEnabled = {{ .email_enabled }};
window.telegramEnabled = {{ .telegram_enabled }};
+ window.discordEnabled = {{ .discord_enabled }};
window.ombiEnabled = {{ .ombiEnabled }};
window.usernameEnabled = {{ .username }};
window.langFile = JSON.parse({{ .language }});
@@ -525,6 +526,9 @@
{{ if .telegram_enabled }}
Telegram |
{{ end }}
+ {{ if .discord_enabled }}
+ Discord |
+ {{ end }}
{{ .strings.expiry }} |
{{ .strings.lastActiveTime }} |
diff --git a/html/form-base.html b/html/form-base.html
index 8a4a9ee..e144f58 100644
--- a/html/form-base.html
+++ b/html/form-base.html
@@ -17,6 +17,9 @@
window.telegramEnabled = {{ .telegramEnabled }};
window.telegramRequired = {{ .telegramRequired }};
window.telegramPIN = "{{ .telegramPIN }}";
+ window.discordEnabled = {{ .discordEnabled }};
+ window.discordRequired = {{ .discordRequired }};
+ window.discordPIN = "{{ .discordPIN }}";
{{ end }}
diff --git a/html/form.html b/html/form.html
index df3ee14..c391707 100644
--- a/html/form.html
+++ b/html/form.html
@@ -37,6 +37,16 @@
{{ end }}
+ {{ if .discordEnabled }}
+
+
+
{{ .strings.linkDiscord }}
+
{{ .discordSendPINMessage }}
+
{{ .discordPIN }}
+
{{ .strings.success }}
+
+
+ {{ end }}
@@ -69,13 +79,25 @@
{{ if .telegramEnabled }}
{{ .strings.linkTelegram }}
+ {{ end }}
+ {{ if .discordEnabled }}
+ {{ .strings.linkDiscord }}
+ {{ end }}
+ {{ if or (.telegramEnabled) (.discordEnabled) }}
+ {{ if .telegramEnabled }}
+ {{ end }}
+ {{ if .discordEnabled }}
+
+ {{ end }}
{{ end }}
diff --git a/lang/common/en-us.json b/lang/common/en-us.json
index 9d77a89..3e9ef53 100644
--- a/lang/common/en-us.json
+++ b/lang/common/en-us.json
@@ -17,6 +17,8 @@
"linkTelegram": "Link Telegram",
"contactEmail": "Contact through Email",
"contactTelegram": "Contact through Telegram",
+ "linkDiscord": "Link Discord",
+ "contactDiscord": "Contact through Discord",
"theme": "Theme"
}
}
diff --git a/lang/form/en-us.json b/lang/form/en-us.json
index 391d672..6726c0b 100644
--- a/lang/form/en-us.json
+++ b/lang/form/en-us.json
@@ -18,14 +18,16 @@
"confirmationRequired": "Email confirmation required",
"confirmationRequiredMessage": "Please check your email inbox to verify your address.",
"yourAccountIsValidUntil": "Your account will be valid until {date}.",
- "sendPIN": "Send the PIN below to the bot, then come back here to link your account."
+ "sendPIN": "Send the PIN below to the bot, then come back here to link your account.",
+ "sendPINDiscord": "Type {command} in {server_channel} on Discord, then send the PIN below via DM to the bot."
},
"notifications": {
"errorUserExists": "User already exists.",
"errorInvalidCode": "Invalid invite code.",
"errorTelegramVerification": "Telegram verification required.",
- "errorInvalidPIN": "Telegram PIN is invalid.",
- "telegramVerified": "Telegram account verified."
+ "errorDiscordVerification": "Discord verification required.",
+ "errorInvalidPIN": "PIN is invalid.",
+ "verified": "Account verified."
},
"validationStrings": {
"length": {
diff --git a/models.go b/models.go
index 772238b..c79cab4 100644
--- a/models.go
+++ b/models.go
@@ -17,6 +17,8 @@ type newUserDTO struct {
Code string `json:"code" example:"abc0933jncjkcjj"` // Invite code (required on /newUser)
TelegramPIN string `json:"telegram_pin" example:"A1-B2-3C"` // Telegram verification PIN (if used)
TelegramContact bool `json:"telegram_contact"` // Whether or not to use telegram for notifications/pwrs
+ DiscordPIN string `json:"discord_pin" example:"A1-B2-3C"` // Discord verification PIN (if used)
+ DiscordContact bool `json:"discord_contact"` // Whether or not to use discord for notifications/pwrs
}
type newUserResponse struct {
@@ -125,12 +127,16 @@ type respUser struct {
ID string `json:"id" example:"fdgsdfg45534fa"` // userID of user
Name string `json:"name" example:"jeff"` // Username of user
Email string `json:"email,omitempty" example:"jeff@jellyf.in"` // Email address of user (if available)
- LastActive int64 `json:"last_active" example:"1617737207510"` // Time of last activity on Jellyfin
- Admin bool `json:"admin" example:"false"` // Whether or not the user is Administrator
- Expiry int64 `json:"expiry" example:"1617737207510"` // Expiry time of user as Epoch/Unix time.
- Disabled bool `json:"disabled"` // Whether or not the user is disabled.
- Telegram string `json:"telegram"` // Telegram username (if known)
+ NotifyThroughEmail bool `json:"notify_email"`
+ LastActive int64 `json:"last_active" example:"1617737207510"` // Time of last activity on Jellyfin
+ Admin bool `json:"admin" example:"false"` // Whether or not the user is Administrator
+ Expiry int64 `json:"expiry" example:"1617737207510"` // Expiry time of user as Epoch/Unix time.
+ Disabled bool `json:"disabled"` // Whether or not the user is disabled.
+ Telegram string `json:"telegram"` // Telegram username (if known)
NotifyThroughTelegram bool `json:"notify_telegram"`
+ Discord string `json:"discord"` // Discord username (if known)
+ DiscordID string `json:"discord_id"` // Discord user ID for creating links.
+ NotifyThroughDiscord bool `json:"notify_discord"`
}
type getUsersDTO struct {
@@ -250,6 +256,8 @@ type telegramSetDTO struct {
}
type telegramNotifyDTO struct {
- ID string `json:"id"`
- Enabled bool `json:"enabled"`
+ ID string `json:"id"`
+ Email bool `json:"email"`
+ Discord bool `json:"discord"`
+ Telegram bool `json:"telegram"`
}
diff --git a/router.go b/router.go
index 355fb3f..4bad4d8 100644
--- a/router.go
+++ b/router.go
@@ -121,6 +121,9 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
if telegramEnabled {
router.GET(p+"/invite/:invCode/telegram/verified/:pin", app.TelegramVerifiedInvite)
}
+ if discordEnabled {
+ router.GET(p+"/invite/:invCode/discord/verified/:pin", app.DiscordVerifiedInvite)
+ }
}
if *SWAGGER {
app.info.Print(warning("\n\nWARNING: Swagger should not be used on a public instance.\n\n"))
@@ -162,7 +165,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
api.GET(p+"/telegram/pin", app.TelegramGetPin)
api.GET(p+"/telegram/verified/:pin", app.TelegramVerified)
api.POST(p+"/users/telegram", app.TelegramAddUser)
- api.POST(p+"/users/telegram/notify", app.TelegramSetNotify)
+ api.POST(p+"/users/contact", app.SetContactMethods)
}
if app.config.Section("ombi").Key("enabled").MustBool(false) {
api.GET(p+"/ombi/users", app.OmbiUsers)
diff --git a/storage.go b/storage.go
index 61a2e2d..b0f0ee8 100644
--- a/storage.go
+++ b/storage.go
@@ -39,11 +39,12 @@ type TelegramUser struct {
}
type DiscordUser struct {
- ChannelID string
- ID string
- Username string
- Lang string
- Contact bool
+ ChannelID string
+ ID string
+ Username string
+ Discriminator string
+ Lang string
+ Contact bool
}
type customEmails struct {
diff --git a/ts/form.ts b/ts/form.ts
index 0cd1611..8f76c89 100644
--- a/ts/form.ts
+++ b/ts/form.ts
@@ -8,12 +8,16 @@ interface formWindow extends Window {
invalidPassword: string;
successModal: Modal;
telegramModal: Modal;
+ discordModal: Modal;
confirmationModal: Modal
code: string;
messages: { [key: string]: string };
confirmation: boolean;
telegramRequired: boolean;
telegramPIN: string;
+ discordRequired: boolean;
+ discordPIN: string;
+ discordStartCommand: string;
userExpiryEnabled: boolean;
userExpiryMonths: number;
userExpiryDays: number;
@@ -68,7 +72,7 @@ if (window.telegramEnabled) {
telegramVerified = true;
waiting.classList.add("~positive");
waiting.classList.remove("~info");
- window.notifications.customPositive("telegramVerified", "", window.messages["telegramVerified"]);
+ window.notifications.customPositive("telegramVerified", "", window.messages["verified"]);
setTimeout(window.telegramModal.close, 2000);
telegramButton.classList.add("unfocused");
document.getElementById("contact-via").classList.remove("unfocused");
@@ -84,6 +88,46 @@ if (window.telegramEnabled) {
};
}
+var discordVerified = false;
+if (window.discordEnabled) {
+ window.discordModal = new Modal(document.getElementById("modal-discord"), window.discordRequired);
+ const discordButton = document.getElementById("link-discord") as HTMLSpanElement;
+ discordButton.onclick = () => {
+ const waiting = document.getElementById("discord-waiting") as HTMLSpanElement;
+ toggleLoader(waiting);
+ window.discordModal.show();
+ let modalClosed = false;
+ window.discordModal.onclose = () => {
+ modalClosed = true;
+ toggleLoader(waiting);
+ }
+ const checkVerified = () => _get("/invite/" + window.code + "/discord/verified/" + window.discordPIN, null, (req: XMLHttpRequest) => {
+ if (req.readyState == 4) {
+ if (req.status == 401) {
+ window.discordModal.close();
+ window.notifications.customError("invalidCodeError", window.messages["errorInvalidCode"]);
+ return;
+ } else if (req.status == 200) {
+ if (req.response["success"] as boolean) {
+ discordVerified = true;
+ waiting.classList.add("~positive");
+ waiting.classList.remove("~info");
+ window.notifications.customPositive("discordVerified", "", window.messages["verified"]);
+ setTimeout(window.discordModal.close, 2000);
+ discordButton.classList.add("unfocused");
+ document.getElementById("contact-via").classList.remove("unfocused");
+ const radio = document.getElementById("contact-via-discord") as HTMLInputElement;
+ radio.checked = true;
+ } else if (!modalClosed) {
+ setTimeout(checkVerified, 1500);
+ }
+ }
+ }
+ });
+ checkVerified();
+ };
+}
+
if (window.confirmation) {
window.confirmationModal = new Modal(document.getElementById("modal-confirmation"), true);
}
@@ -161,6 +205,8 @@ interface sendDTO {
password: string;
telegram_pin?: string;
telegram_contact?: boolean;
+ discord_pin?: string;
+ discord_contact?: boolean;
}
const create = (event: SubmitEvent) => {
@@ -179,6 +225,13 @@ const create = (event: SubmitEvent) => {
send.telegram_contact = true;
}
}
+ if (discordVerified) {
+ send.discord_pin = window.discordPIN;
+ const radio = document.getElementById("contact-via-discord") as HTMLInputElement;
+ if (radio.checked) {
+ send.discord_contact = true;
+ }
+ }
_post("/newUser", send, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
let vals = req.response as respDTO;
diff --git a/ts/modules/accounts.ts b/ts/modules/accounts.ts
index e383b4a..21f19ef 100644
--- a/ts/modules/accounts.ts
+++ b/ts/modules/accounts.ts
@@ -7,12 +7,16 @@ interface User {
id: string;
name: string;
email: string | undefined;
+ notify_email: boolean;
last_active: number;
admin: boolean;
disabled: boolean;
expiry: number;
telegram: string;
notify_telegram: boolean;
+ discord: string;
+ notify_discord: boolean;
+ discord_id: string;
}
interface getPinResponse {
@@ -27,11 +31,16 @@ class user implements User {
private _admin: HTMLSpanElement;
private _disabled: HTMLSpanElement;
private _email: HTMLInputElement;
+ private _notifyEmail: boolean;
private _emailAddress: string;
private _emailEditButton: HTMLElement;
private _telegram: HTMLTableDataCellElement;
private _telegramUsername: string;
private _notifyTelegram: boolean;
+ private _discord: HTMLTableDataCellElement;
+ private _discordUsername: string;
+ private _discordID: string;
+ private _notifyDiscord: boolean;
private _expiry: HTMLTableDataCellElement;
private _expiryUnix: number;
private _lastActive: HTMLTableDataCellElement;
@@ -81,6 +90,19 @@ class user implements User {
this._email.textContent = value;
}
}
+
+ get notify_email(): boolean { return this._notifyEmail; }
+ set notify_email(s: boolean) {
+ this._notifyEmail = s;
+ if (window.telegramEnabled && this._telegramUsername != "") {
+ const email = this._telegram.getElementsByClassName("accounts-contact-email")[0] as HTMLInputElement;
+ email.checked = s;
+ }
+ if (window.discordEnabled && this._discordUsername != "") {
+ const email = this._discord.getElementsByClassName("accounts-contact-email")[0] as HTMLInputElement;
+ email.checked = s;
+ }
+ }
get telegram(): string { return this._telegramUsername; }
set telegram(u: string) {
@@ -90,7 +112,7 @@ class user implements User {
this._telegram.innerHTML = `Add`;
(this._telegram.querySelector("span") as HTMLSpanElement).onclick = this._addTelegram;
} else {
- this._telegram.innerHTML = `
+ let innerHTML = `
@${u}
`;
+ this._discord.innerHTML = innerHTML;
// 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 dropdown = this._telegram.querySelector("div.dropdown") as HTMLDivElement;
- const radios = this._telegram.querySelectorAll("input") as NodeListOf;
- for (let i = 0; i < radios.length; i++) {
- radios[i].onclick = this._setTelegramNotify;
+ const checks = this._telegram.querySelectorAll("input") as NodeListOf;
+ for (let i = 0; i < checks.length; i++) {
+ checks[i].onclick = () => this._setNotifyMethod("telegram");
}
button.onclick = () => {
@@ -134,36 +167,128 @@ class user implements User {
set notify_telegram(s: boolean) {
if (!window.telegramEnabled || !this._telegramUsername) return;
this._notifyTelegram = s;
- const radios = this._telegram.querySelectorAll("input") as NodeListOf;
- radios[0].checked = !s;
- radios[1].checked = s;
+ const telegram = this._telegram.getElementsByClassName("accounts-contact-telegram")[0] as HTMLInputElement;
+ telegram.checked = s;
+ if (window.discordEnabled && this._discordUsername != "") {
+ const telegram = this._discord.getElementsByClassName("accounts-contact-telegram")[0] as HTMLInputElement;
+ telegram.checked = s;
+ }
}
- private _setTelegramNotify = () => {
- const radios = this._telegram.querySelectorAll("input") as NodeListOf;
+ private _setNotifyMethod = (mode: string = "telegram") => {
+ let el: HTMLElement;
+ if (mode == "telegram") { el = this._telegram }
+ else if (mode == "discord") { el = this._discord }
+ const email = el.getElementsByClassName("accounts-contact-email")[0] as HTMLInputElement;
let send = {
id: this.id,
- enabled: radios[1].checked
- };
- _post("/users/telegram/notify", send, (req: XMLHttpRequest) => {
+ email: email.checked
+ }
+ if (window.telegramEnabled && this._telegramUsername != "") {
+ const telegram = el.getElementsByClassName("accounts-contact-telegram")[0] as HTMLInputElement;
+ send["telegram"] = telegram.checked;
+ }
+ if (window.discordEnabled && this._discordUsername != "") {
+ const discord = el.getElementsByClassName("accounts-contact-discord")[0] as HTMLInputElement;
+ send["discord"] = discord.checked;
+ }
+ _post("/users/contact", send, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
if (req.status != 200) {
- window.notifications.customError("errorSetTelegramNotify", window.lang.notif("errorSaveSettings"));
- radios[0].checked, radios[1].checked= radios[1].checked, radios[0].checked;
+ window.notifications.customError("errorSetNotify", window.lang.notif("errorSaveSettings"));
+ document.dispatchEvent(new CustomEvent("accounts-reload"));
return;
}
}
}, false, (req: XMLHttpRequest) => {
if (req.status == 0) {
window.notifications.connectionError();
- radios[0].checked, radios[1].checked= radios[1].checked, radios[0].checked;
+ document.dispatchEvent(new CustomEvent("accounts-reload"));
return;
} else if (req.status == 401) {
- radios[0].checked, radios[1].checked= radios[1].checked, radios[0].checked;
window.notifications.customError("401Error", window.lang.notif("error401Unauthorized"));
+ document.dispatchEvent(new CustomEvent("accounts-reload"));
}
});
}
+
+ get discord(): string { return this._discordUsername; }
+ set discord(u: string) {
+ if (!window.discordEnabled) return;
+ this._discordUsername = u;
+ if (u == "") {
+ this._discord.innerHTML = `Add`;
+ // (this._discord.querySelector("span") as HTMLSpanElement).onclick = this._addDiscord;
+ } else {
+ let innerHTML = `
+ @${u}
+
+
+ `;
+ this._discord.innerHTML = innerHTML;
+ // Javascript is necessary as including the button inside the dropdown would make it too wide to display next to the username.
+ const button = this._discord.querySelector("i");
+ const dropdown = this._discord.querySelector("div.dropdown") as HTMLDivElement;
+ const checks = this._discord.querySelectorAll("input") as NodeListOf;
+ for (let i = 0; i < checks.length; i++) {
+ checks[i].onclick = () => this._setNotifyMethod("discord");
+ }
+
+ button.onclick = () => {
+ dropdown.classList.add("selected");
+ document.addEventListener("click", outerClickListener);
+ };
+ const outerClickListener = (event: Event) => {
+ if (!(event.target instanceof HTMLElement && (this._discord.contains(event.target) || button.contains(event.target)))) {
+ dropdown.classList.remove("selected");
+ document.removeEventListener("click", outerClickListener);
+ }
+ };
+ }
+ }
+
+ get discord_id(): string { return this._discordID; }
+ set discord_id(id: string) {
+ this._discordID = id;
+ const link = this._discord.getElementsByClassName("discord-link")[0] as HTMLAnchorElement;
+ link.href = `https://discord.com/users/${id}`;
+ }
+
+ get notify_discord(): boolean { return this._notifyDiscord; }
+ set notify_discord(s: boolean) {
+ if (!window.discordEnabled || !this._discordUsername) return;
+ this._notifyDiscord = s;
+ const discord = this._discord.getElementsByClassName("accounts-contact-discord")[0] as HTMLInputElement;
+ discord.checked = s;
+ if (window.telegramEnabled && this._telegramUsername != "") {
+ const discord = this._discord.getElementsByClassName("accounts-contact-discord")[0] as HTMLInputElement;
+ discord.checked = s;
+ }
+ }
get expiry(): number { return this._expiryUnix; }
set expiry(unix: number) {
@@ -200,6 +325,11 @@ class user implements User {
|
`;
}
+ if (window.discordEnabled) {
+ innerHTML += `
+ |
+ `;
+ }
innerHTML += `
|
|
@@ -213,6 +343,7 @@ class user implements User {
this._email = this._row.querySelector(".accounts-email-container") as HTMLInputElement;
this._emailEditButton = this._row.querySelector(".accounts-email-edit") as HTMLElement;
this._telegram = this._row.querySelector(".accounts-telegram") as HTMLTableDataCellElement;
+ this._discord = this._row.querySelector(".accounts-discord") as HTMLTableDataCellElement;
this._expiry = this._row.querySelector(".accounts-expiry") as HTMLTableDataCellElement;
this._lastActive = this._row.querySelector(".accounts-last-active") as HTMLTableDataCellElement;
this._check.onchange = () => { this.selected = this._check.checked; }
@@ -320,11 +451,14 @@ class user implements User {
this.name = user.name;
this.email = user.email || "";
this.telegram = user.telegram;
+ this.discord = user.discord;
this.last_active = user.last_active;
this.admin = user.admin;
this.disabled = user.disabled;
this.expiry = user.expiry;
this.notify_telegram = user.notify_telegram;
+ this.notify_discord = user.notify_discord;
+ this.notify_email = user.notify_email;
}
asElement = (): HTMLTableRowElement => { return this._row; }
diff --git a/ts/typings/d.ts b/ts/typings/d.ts
index 7e0bf3e..f768063 100644
--- a/ts/typings/d.ts
+++ b/ts/typings/d.ts
@@ -21,6 +21,7 @@ declare interface Window {
notificationsEnabled: boolean;
emailEnabled: boolean;
telegramEnabled: boolean;
+ discordEnabled: boolean;
ombiEnabled: boolean;
usernameEnabled: boolean;
token: string;
diff --git a/views.go b/views.go
index 8841c09..241dd47 100644
--- a/views.go
+++ b/views.go
@@ -1,6 +1,7 @@
package main
import (
+ "html/template"
"io/fs"
"net/http"
"strconv"
@@ -121,6 +122,7 @@ func (app *appContext) AdminPage(gc *gin.Context) {
"contactMessage": "",
"email_enabled": emailEnabled,
"telegram_enabled": telegramEnabled,
+ "discord_enabled": discordEnabled,
"notifications": notificationsEnabled,
"version": version,
"commit": commit,
@@ -284,13 +286,28 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
"userExpiryMessage": app.storage.lang.Form[lang].Strings.get("yourAccountIsValidUntil"),
"langName": lang,
"telegramEnabled": telegramEnabled,
+ "discordEnabled": discordEnabled,
}
- if data["telegramEnabled"].(bool) {
+ if telegramEnabled {
data["telegramPIN"] = app.telegram.NewAuthToken()
data["telegramUsername"] = app.telegram.username
data["telegramURL"] = app.telegram.link
data["telegramRequired"] = app.config.Section("telegram").Key("required").MustBool(false)
}
+ if discordEnabled {
+ data["discordPIN"] = app.discord.NewAuthToken()
+ data["discordUsername"] = app.discord.username
+ data["discordRequired"] = app.config.Section("discord").Key("required").MustBool(false)
+ data["discordSendPINMessage"] = template.HTML(app.storage.lang.Form[lang].Strings.template("sendPINDiscord", tmpl{
+ "command": `` + app.config.Section("discord").Key("start_command").MustString("!start") + `
`,
+ "server_channel": app.discord.serverChannelName,
+ }))
+ }
+
+ // if discordEnabled {
+ // pin := ""
+ // for _, token := range app.discord.tokens {
+ // if
gcHTML(gc, http.StatusOK, "form-loader.html", data)
}