1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-12-22 17:10:10 +00:00

PIN verification, notifications, multiple notif providers

Discord, Email & Telegram can be enabled, although email is always
enabled right now (will fix). Also apparently markdown hyperlinks don't
work in Discord, eventually will implement something to convert them to
embeds.
This commit is contained in:
Harvey Tindall 2021-05-21 21:35:25 +01:00
parent 524941da0c
commit f8f5f35cc1
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
16 changed files with 509 additions and 92 deletions

92
api.go
View File

@ -330,6 +330,30 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
success = false success = false
return 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 telegramTokenIndex := -1
if telegramEnabled { if telegramEnabled {
if req.TelegramPIN == "" { 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) 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 { if telegramEnabled && telegramTokenIndex != -1 {
tgToken := app.telegram.verifiedTokens[telegramTokenIndex] tgToken := app.telegram.verifiedTokens[telegramTokenIndex]
tgUser := TelegramUser{ 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 = map[string]TelegramUser{}
} }
app.storage.telegram[user.ID] = tgUser app.storage.telegram[user.ID] = tgUser
err := app.storage.storeTelegramUsers() if err := app.storage.storeTelegramUsers(); err != nil {
if err != nil {
app.err.Printf("Failed to store Telegram users: %v", err) app.err.Printf("Failed to store Telegram users: %v", err)
} else { } 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] 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) name := app.getAddressOrName(user.ID)
app.debug.Printf("%s: Sending welcome message to %s", req.Username, name) app.debug.Printf("%s: Sending welcome message to %s", req.Username, name)
msg, err := app.email.constructWelcome(req.Username, expiry, app, false) 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 { if email, ok := app.storage.emails[jfUser.ID]; ok {
user.Email = email.(string) user.Email = email.(string)
user.NotifyThroughEmail = user.Email != ""
} }
expiry, ok := app.storage.users[jfUser.ID] expiry, ok := app.storage.users[jfUser.ID]
if ok { if ok {
@ -1178,6 +1213,11 @@ func (app *appContext) GetUsers(gc *gin.Context) {
user.Telegram = tgUser.Username user.Telegram = tgUser.Username
user.NotifyThroughTelegram = tgUser.Contact 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 resp.UserList[i] = user
i++ i++
} }
@ -2011,7 +2051,7 @@ func (app *appContext) TelegramAddUser(gc *gin.Context) {
// @Router /users/telegram/notify [post] // @Router /users/telegram/notify [post]
// @Security Bearer // @Security Bearer
// @tags Other // @tags Other
func (app *appContext) TelegramSetNotify(gc *gin.Context) { func (app *appContext) SetContactMethods(gc *gin.Context) {
var req telegramNotifyDTO var req telegramNotifyDTO
gc.BindJSON(&req) gc.BindJSON(&req)
if req.ID == "" { if req.ID == "" {
@ -2019,23 +2059,34 @@ func (app *appContext) TelegramSetNotify(gc *gin.Context) {
return return
} }
if tgUser, ok := app.storage.telegram[req.ID]; ok { if tgUser, ok := app.storage.telegram[req.ID]; ok {
tgUser.Contact = req.Enabled tgUser.Contact = req.Telegram
app.storage.telegram[req.ID] = tgUser app.storage.telegram[req.ID] = tgUser
if err := app.storage.storeTelegramUsers(); err != nil { if err := app.storage.storeTelegramUsers(); err != nil {
respondBool(500, false, gc) respondBool(500, false, gc)
app.err.Printf("Telegram: Failed to store users: %v", err) app.err.Printf("Telegram: Failed to store users: %v", err)
return return
} }
respondBool(200, true, gc)
msg := "" msg := ""
if !req.Enabled { if !req.Telegram {
msg = "not" msg = "not"
} }
app.debug.Printf("Telegram: User \"%s\" will %s be notified through Telegram.", tgUser.Username, msg) 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) if dcUser, ok := app.storage.discord[req.ID]; ok {
respondBool(400, false, gc) 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. // @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) 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. // @Summary Restarts the program. No response means success.
// @Router /restart [post] // @Router /restart [post]
// @Security Bearer // @Security Bearer

View File

@ -588,6 +588,15 @@
"value": "!start", "value": "!start",
"description": "Command to start the user verification process." "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": { "language": {
"name": "Language", "name": "Language",
"required": false, "required": false,

View File

@ -7,22 +7,17 @@ import (
dg "github.com/bwmarrin/discordgo" dg "github.com/bwmarrin/discordgo"
) )
type DiscordToken struct {
Token string
ChannelID string
UserID string
Username string
}
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 map[string]DiscordToken // map of user IDs to tokens. tokens []string
verifiedTokens []DiscordToken verifiedTokens map[string]DiscordUser // Map of tokens to discord users.
languages map[string]string // Store of languages for user IDs. Added to on first interaction, and loaded from app.storage.discord on start. channelID, channelName string
app *appContext 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) { func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) {
@ -38,28 +33,39 @@ func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) {
Stopped: false, Stopped: false,
ShutdownChannel: make(chan string), ShutdownChannel: make(chan string),
bot: bot, bot: bot,
tokens: map[string]DiscordToken{}, tokens: []string{},
verifiedTokens: []DiscordToken{}, verifiedTokens: map[string]DiscordUser{},
languages: map[string]string{}, users: map[string]DiscordUser{},
app: app, app: app,
} }
for _, user := range app.storage.discord { for _, user := range app.storage.discord {
if user.Lang != "" { dd.users[user.ID] = user
dd.languages[user.ID] = user.Lang
}
} }
return dd, nil 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() pin := genAuthToken()
token := DiscordToken{ d.tokens = append(d.tokens, pin)
Token: pin, return pin
ChannelID: channelID, }
UserID: userID,
Username: username, func (d *DiscordDaemon) NewUnknownUser(channelID, userID, discrim, username string) DiscordUser {
user := DiscordUser{
ChannelID: channelID,
ID: userID,
Username: username,
Discriminator: discrim,
} }
return token return user
}
func (d *DiscordDaemon) MustGetUser(channelID, userID, discrim, username string) DiscordUser {
if user, ok := d.users[userID]; ok {
return user
}
return d.NewUnknownUser(channelID, userID, discrim, username)
} }
func (d *DiscordDaemon) run() { func (d *DiscordDaemon) run() {
@ -70,6 +76,15 @@ func (d *DiscordDaemon) run() {
return return
} }
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
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() defer d.bot.Close()
<-d.ShutdownChannel <-d.ShutdownChannel
d.ShutdownChannel <- "Down" d.ShutdownChannel <- "Down"
@ -84,6 +99,22 @@ func (d *DiscordDaemon) Shutdown() {
} }
func (d *DiscordDaemon) messageHandler(s *dg.Session, m *dg.MessageCreate) { 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 { if m.Author.ID == s.State.User.ID {
return return
} }
@ -92,11 +123,13 @@ func (d *DiscordDaemon) messageHandler(s *dg.Session, m *dg.MessageCreate) {
return return
} }
lang := d.app.storage.lang.chosenTelegramLang lang := d.app.storage.lang.chosenTelegramLang
if storedLang, ok := d.languages[m.Author.ID]; ok { if user, ok := d.users[m.Author.ID]; ok {
lang = storedLang if _, ok := d.app.storage.lang.Telegram[user.Lang]; ok {
lang = user.Lang
}
} }
switch msg := sects[0]; msg { 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) d.commandStart(s, m, lang)
case "!lang": case "!lang":
d.commandLang(s, m, sects, 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) d.app.err.Printf("Discord: Failed to create private channel with \"%s\": %v", m.Author.Username, err)
return return
} }
token := d.NewAuthToken(channel.ID, m.Author.ID, m.Author.Username) user := d.MustGetUser(channel.ID, m.Author.ID, m.Author.Discriminator, m.Author.Username)
d.tokens[m.Author.ID] = token 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.get("startMessage") + "\n"
content += d.app.storage.lang.Telegram[lang].Strings.template("languageMessage", tmpl{"command": "!lang"}) content += d.app.storage.lang.Telegram[lang].Strings.template("languageMessage", tmpl{"command": "!lang"})
_, err = s.ChannelMessageSend(channel.ID, content) _, err = s.ChannelMessageSend(channel.ID, content)
@ -139,7 +172,7 @@ func (d *DiscordDaemon) commandLang(s *dg.Session, m *dg.MessageCreate, sects []
return return
} }
if _, ok := d.app.storage.lang.Telegram[sects[1]]; ok { 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 { for jfID, user := range d.app.storage.discord {
if user.ID == m.Author.ID { if user.ID == m.Author.ID {
user.Lang = sects[1] user.Lang = sects[1]
@ -150,30 +183,69 @@ func (d *DiscordDaemon) commandLang(s *dg.Session, m *dg.MessageCreate, sects []
break break
} }
} }
d.users[m.Author.ID] = user
} }
} }
func (d *DiscordDaemon) commandPIN(s *dg.Session, m *dg.MessageCreate, sects []string, lang string) { func (d *DiscordDaemon) commandPIN(s *dg.Session, m *dg.MessageCreate, sects []string, lang string) {
token, ok := d.tokens[m.Author.ID] if _, ok := d.users[m.Author.ID]; ok {
if !ok || token.Token != sects[0] { channel, err := s.Channel(m.ChannelID)
_, err := s.ChannelMessageSendReply( 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, m.ChannelID,
d.app.storage.lang.Telegram[lang].Strings.get("invalidPIN"), d.app.storage.lang.Telegram[lang].Strings.get("invalidPIN"),
m.Reference(),
) )
if err != nil { if err != nil {
d.app.err.Printf("Discord: Failed to send message to \"%s\": %v", m.Author.Username, err) d.app.err.Printf("Discord: Failed to send message to \"%s\": %v", m.Author.Username, err)
} }
return return
} }
_, err := s.ChannelMessageSendReply( _, err := s.ChannelMessageSend(
m.ChannelID, m.ChannelID,
d.app.storage.lang.Telegram[lang].Strings.get("pinSuccess"), d.app.storage.lang.Telegram[lang].Strings.get("pinSuccess"),
m.Reference(),
) )
if err != nil { if err != nil {
d.app.err.Printf("Discord: Failed to send message to \"%s\": %v", m.Author.Username, err) d.app.err.Printf("Discord: Failed to send message to \"%s\": %v", m.Author.Username, err)
} }
d.verifiedTokens = append(d.verifiedTokens, token) d.verifiedTokens[sects[0]] = d.users[m.Author.ID]
delete(d.tokens, 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
} }

View File

@ -230,7 +230,7 @@ func (emailer *Emailer) construct(app *appContext, section, keyFragment string,
var keys []string var keys []string
plaintext := app.config.Section("email").Key("plaintext").MustBool(false) plaintext := app.config.Section("email").Key("plaintext").MustBool(false)
if plaintext { if plaintext {
if telegramEnabled { if telegramEnabled || discordEnabled {
keys = []string{"text"} keys = []string{"text"}
text, markdown = "", "" text, markdown = "", ""
} else { } else {
@ -238,7 +238,7 @@ func (emailer *Emailer) construct(app *appContext, section, keyFragment string,
text = "" text = ""
} }
} else { } else {
if telegramEnabled { if telegramEnabled || discordEnabled {
keys = []string{"html", "text", "markdown"} keys = []string{"html", "text", "markdown"}
} else { } else {
keys = []string{"html", "text"} keys = []string{"html", "text"}
@ -807,8 +807,21 @@ func (app *appContext) sendByID(email *Message, ID ...string) error {
var err error var err error
if tgChat, ok := app.storage.telegram[id]; ok && tgChat.Contact && telegramEnabled { if tgChat, ok := app.storage.telegram[id]; ok && tgChat.Contact && telegramEnabled {
err = app.telegram.Send(email, tgChat.ChatID) 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)) err = app.email.send(email, address.(string))
if err != nil {
return err
}
} }
if err != nil { if err != nil {
return err return err
@ -818,6 +831,9 @@ func (app *appContext) sendByID(email *Message, ID ...string) error {
} }
func (app *appContext) getAddressOrName(jfID string) string { 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 { if tgChat, ok := app.storage.telegram[jfID]; ok && tgChat.Contact && telegramEnabled {
return "@" + tgChat.Username return "@" + tgChat.Username
} }

View File

@ -7,6 +7,7 @@
window.notificationsEnabled = {{ .notifications }}; window.notificationsEnabled = {{ .notifications }};
window.emailEnabled = {{ .email_enabled }}; window.emailEnabled = {{ .email_enabled }};
window.telegramEnabled = {{ .telegram_enabled }}; window.telegramEnabled = {{ .telegram_enabled }};
window.discordEnabled = {{ .discord_enabled }};
window.ombiEnabled = {{ .ombiEnabled }}; window.ombiEnabled = {{ .ombiEnabled }};
window.usernameEnabled = {{ .username }}; window.usernameEnabled = {{ .username }};
window.langFile = JSON.parse({{ .language }}); window.langFile = JSON.parse({{ .language }});
@ -525,6 +526,9 @@
{{ if .telegram_enabled }} {{ if .telegram_enabled }}
<th>Telegram</th> <th>Telegram</th>
{{ end }} {{ end }}
{{ if .discord_enabled }}
<th>Discord</th>
{{ end }}
<th>{{ .strings.expiry }}</th> <th>{{ .strings.expiry }}</th>
<th>{{ .strings.lastActiveTime }}</th> <th>{{ .strings.lastActiveTime }}</th>
</tr> </tr>

View File

@ -17,6 +17,9 @@
window.telegramEnabled = {{ .telegramEnabled }}; window.telegramEnabled = {{ .telegramEnabled }};
window.telegramRequired = {{ .telegramRequired }}; window.telegramRequired = {{ .telegramRequired }};
window.telegramPIN = "{{ .telegramPIN }}"; window.telegramPIN = "{{ .telegramPIN }}";
window.discordEnabled = {{ .discordEnabled }};
window.discordRequired = {{ .discordRequired }};
window.discordPIN = "{{ .discordPIN }}";
</script> </script>
<script src="js/form.js" type="module"></script> <script src="js/form.js" type="module"></script>
{{ end }} {{ end }}

View File

@ -37,6 +37,16 @@
</div> </div>
</div> </div>
{{ end }} {{ end }}
{{ if .discordEnabled }}
<div id="modal-discord" class="modal">
<div class="modal-content card">
<span class="heading mb-1">{{ .strings.linkDiscord }}</span>
<p class="content mb-1"> {{ .discordSendPINMessage }}</p>
<h1 class="ac">{{ .discordPIN }}</h1>
<span class="button ~info !normal full-width center mt-1" id="discord-waiting">{{ .strings.success }}</span>
</div>
</div>
{{ end }}
<span class="dropdown" tabindex="0" id="lang-dropdown"> <span class="dropdown" tabindex="0" id="lang-dropdown">
<span class="button ~urge dropdown-button"> <span class="button ~urge dropdown-button">
<i class="ri-global-line"></i> <i class="ri-global-line"></i>
@ -69,13 +79,25 @@
<input type="email" class="input ~neutral !high mt-half mb-1" placeholder="{{ .strings.emailAddress }}" id="create-email" aria-label="{{ .strings.emailAddress }}" value="{{ .email }}"> <input type="email" class="input ~neutral !high mt-half mb-1" placeholder="{{ .strings.emailAddress }}" id="create-email" aria-label="{{ .strings.emailAddress }}" value="{{ .email }}">
{{ if .telegramEnabled }} {{ if .telegramEnabled }}
<span class="button ~info !normal full-width center mb-1" id="link-telegram">{{ .strings.linkTelegram }}</span> <span class="button ~info !normal full-width center mb-1" id="link-telegram">{{ .strings.linkTelegram }}</span>
{{ end }}
{{ if .discordEnabled }}
<span class="button ~info !normal full-width center mb-1" id="link-discord">{{ .strings.linkDiscord }}</span>
{{ end }}
{{ if or (.telegramEnabled) (.discordEnabled) }}
<div id="contact-via" class="unfocused"> <div id="contact-via" class="unfocused">
<label class="row switch pb-1"> <label class="row switch pb-1">
<input type="radio" name="contact-via" value="email"><span>Contact through Email</span> <input type="radio" name="contact-via" value="email"><span>Contact through Email</span>
</label> </label>
{{ if .telegramEnabled }}
<label class="row switch pb-1"> <label class="row switch pb-1">
<input type="radio" name="contact-via" value="telegram" id="contact-via-telegram"><span>Contact through Telegram</span> <input type="radio" name="contact-via" value="telegram" id="contact-via-telegram"><span>Contact through Telegram</span>
</label> </label>
{{ end }}
{{ if .discordEnabled }}
<label class="row switch pb-1">
<input type="radio" name="contact-via" value="discord" id="contact-via-discord"><span>Contact through Discord</span>
</label>
{{ end }}
</div> </div>
{{ end }} {{ end }}
<label class="label supra" for="create-password">{{ .strings.password }}</label> <label class="label supra" for="create-password">{{ .strings.password }}</label>

View File

@ -17,6 +17,8 @@
"linkTelegram": "Link Telegram", "linkTelegram": "Link Telegram",
"contactEmail": "Contact through Email", "contactEmail": "Contact through Email",
"contactTelegram": "Contact through Telegram", "contactTelegram": "Contact through Telegram",
"linkDiscord": "Link Discord",
"contactDiscord": "Contact through Discord",
"theme": "Theme" "theme": "Theme"
} }
} }

View File

@ -18,14 +18,16 @@
"confirmationRequired": "Email confirmation required", "confirmationRequired": "Email confirmation required",
"confirmationRequiredMessage": "Please check your email inbox to verify your address.", "confirmationRequiredMessage": "Please check your email inbox to verify your address.",
"yourAccountIsValidUntil": "Your account will be valid until {date}.", "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": { "notifications": {
"errorUserExists": "User already exists.", "errorUserExists": "User already exists.",
"errorInvalidCode": "Invalid invite code.", "errorInvalidCode": "Invalid invite code.",
"errorTelegramVerification": "Telegram verification required.", "errorTelegramVerification": "Telegram verification required.",
"errorInvalidPIN": "Telegram PIN is invalid.", "errorDiscordVerification": "Discord verification required.",
"telegramVerified": "Telegram account verified." "errorInvalidPIN": "PIN is invalid.",
"verified": "Account verified."
}, },
"validationStrings": { "validationStrings": {
"length": { "length": {

View File

@ -17,6 +17,8 @@ type newUserDTO struct {
Code string `json:"code" example:"abc0933jncjkcjj"` // Invite code (required on /newUser) 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) 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 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 { type newUserResponse struct {
@ -125,12 +127,16 @@ type respUser struct {
ID string `json:"id" example:"fdgsdfg45534fa"` // userID of user ID string `json:"id" example:"fdgsdfg45534fa"` // userID of user
Name string `json:"name" example:"jeff"` // Username 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) 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 NotifyThroughEmail bool `json:"notify_email"`
Admin bool `json:"admin" example:"false"` // Whether or not the user is Administrator LastActive int64 `json:"last_active" example:"1617737207510"` // Time of last activity on Jellyfin
Expiry int64 `json:"expiry" example:"1617737207510"` // Expiry time of user as Epoch/Unix time. Admin bool `json:"admin" example:"false"` // Whether or not the user is Administrator
Disabled bool `json:"disabled"` // Whether or not the user is disabled. Expiry int64 `json:"expiry" example:"1617737207510"` // Expiry time of user as Epoch/Unix time.
Telegram string `json:"telegram"` // Telegram username (if known) Disabled bool `json:"disabled"` // Whether or not the user is disabled.
Telegram string `json:"telegram"` // Telegram username (if known)
NotifyThroughTelegram bool `json:"notify_telegram"` 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 { type getUsersDTO struct {
@ -250,6 +256,8 @@ type telegramSetDTO struct {
} }
type telegramNotifyDTO struct { type telegramNotifyDTO struct {
ID string `json:"id"` ID string `json:"id"`
Enabled bool `json:"enabled"` Email bool `json:"email"`
Discord bool `json:"discord"`
Telegram bool `json:"telegram"`
} }

View File

@ -121,6 +121,9 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
if telegramEnabled { if telegramEnabled {
router.GET(p+"/invite/:invCode/telegram/verified/:pin", app.TelegramVerifiedInvite) router.GET(p+"/invite/:invCode/telegram/verified/:pin", app.TelegramVerifiedInvite)
} }
if discordEnabled {
router.GET(p+"/invite/:invCode/discord/verified/:pin", app.DiscordVerifiedInvite)
}
} }
if *SWAGGER { if *SWAGGER {
app.info.Print(warning("\n\nWARNING: Swagger should not be used on a public instance.\n\n")) 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/pin", app.TelegramGetPin)
api.GET(p+"/telegram/verified/:pin", app.TelegramVerified) api.GET(p+"/telegram/verified/:pin", app.TelegramVerified)
api.POST(p+"/users/telegram", app.TelegramAddUser) 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) { if app.config.Section("ombi").Key("enabled").MustBool(false) {
api.GET(p+"/ombi/users", app.OmbiUsers) api.GET(p+"/ombi/users", app.OmbiUsers)

View File

@ -39,11 +39,12 @@ type TelegramUser struct {
} }
type DiscordUser struct { type DiscordUser struct {
ChannelID string ChannelID string
ID string ID string
Username string Username string
Lang string Discriminator string
Contact bool Lang string
Contact bool
} }
type customEmails struct { type customEmails struct {

View File

@ -8,12 +8,16 @@ interface formWindow extends Window {
invalidPassword: string; invalidPassword: string;
successModal: Modal; successModal: Modal;
telegramModal: Modal; telegramModal: Modal;
discordModal: Modal;
confirmationModal: Modal confirmationModal: Modal
code: string; code: string;
messages: { [key: string]: string }; messages: { [key: string]: string };
confirmation: boolean; confirmation: boolean;
telegramRequired: boolean; telegramRequired: boolean;
telegramPIN: string; telegramPIN: string;
discordRequired: boolean;
discordPIN: string;
discordStartCommand: string;
userExpiryEnabled: boolean; userExpiryEnabled: boolean;
userExpiryMonths: number; userExpiryMonths: number;
userExpiryDays: number; userExpiryDays: number;
@ -68,7 +72,7 @@ if (window.telegramEnabled) {
telegramVerified = true; telegramVerified = true;
waiting.classList.add("~positive"); waiting.classList.add("~positive");
waiting.classList.remove("~info"); waiting.classList.remove("~info");
window.notifications.customPositive("telegramVerified", "", window.messages["telegramVerified"]); window.notifications.customPositive("telegramVerified", "", window.messages["verified"]);
setTimeout(window.telegramModal.close, 2000); setTimeout(window.telegramModal.close, 2000);
telegramButton.classList.add("unfocused"); telegramButton.classList.add("unfocused");
document.getElementById("contact-via").classList.remove("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) { if (window.confirmation) {
window.confirmationModal = new Modal(document.getElementById("modal-confirmation"), true); window.confirmationModal = new Modal(document.getElementById("modal-confirmation"), true);
} }
@ -161,6 +205,8 @@ interface sendDTO {
password: string; password: string;
telegram_pin?: string; telegram_pin?: string;
telegram_contact?: boolean; telegram_contact?: boolean;
discord_pin?: string;
discord_contact?: boolean;
} }
const create = (event: SubmitEvent) => { const create = (event: SubmitEvent) => {
@ -179,6 +225,13 @@ const create = (event: SubmitEvent) => {
send.telegram_contact = true; 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) => { _post("/newUser", send, (req: XMLHttpRequest) => {
if (req.readyState == 4) { if (req.readyState == 4) {
let vals = req.response as respDTO; let vals = req.response as respDTO;

View File

@ -7,12 +7,16 @@ interface User {
id: string; id: string;
name: string; name: string;
email: string | undefined; email: string | undefined;
notify_email: boolean;
last_active: number; last_active: number;
admin: boolean; admin: boolean;
disabled: boolean; disabled: boolean;
expiry: number; expiry: number;
telegram: string; telegram: string;
notify_telegram: boolean; notify_telegram: boolean;
discord: string;
notify_discord: boolean;
discord_id: string;
} }
interface getPinResponse { interface getPinResponse {
@ -27,11 +31,16 @@ class user implements User {
private _admin: HTMLSpanElement; private _admin: HTMLSpanElement;
private _disabled: HTMLSpanElement; private _disabled: HTMLSpanElement;
private _email: HTMLInputElement; private _email: HTMLInputElement;
private _notifyEmail: boolean;
private _emailAddress: string; private _emailAddress: string;
private _emailEditButton: HTMLElement; private _emailEditButton: HTMLElement;
private _telegram: HTMLTableDataCellElement; private _telegram: HTMLTableDataCellElement;
private _telegramUsername: string; private _telegramUsername: string;
private _notifyTelegram: boolean; private _notifyTelegram: boolean;
private _discord: HTMLTableDataCellElement;
private _discordUsername: string;
private _discordID: string;
private _notifyDiscord: boolean;
private _expiry: HTMLTableDataCellElement; private _expiry: HTMLTableDataCellElement;
private _expiryUnix: number; private _expiryUnix: number;
private _lastActive: HTMLTableDataCellElement; private _lastActive: HTMLTableDataCellElement;
@ -82,6 +91,19 @@ class user implements User {
} }
} }
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; } get telegram(): string { return this._telegramUsername; }
set telegram(u: string) { set telegram(u: string) {
if (!window.telegramEnabled) return; if (!window.telegramEnabled) return;
@ -90,7 +112,7 @@ class user implements User {
this._telegram.innerHTML = `<span class="chip btn !low">Add</span>`; this._telegram.innerHTML = `<span class="chip btn !low">Add</span>`;
(this._telegram.querySelector("span") as HTMLSpanElement).onclick = this._addTelegram; (this._telegram.querySelector("span") as HTMLSpanElement).onclick = this._addTelegram;
} else { } else {
this._telegram.innerHTML = ` let innerHTML = `
<a href="https://t.me/${u}" target="_blank">@${u}</a> <a href="https://t.me/${u}" target="_blank">@${u}</a>
<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">
@ -98,23 +120,34 @@ class user implements User {
<div class="card ~neutral !low"> <div class="card ~neutral !low">
<span class="supra sm">${window.lang.strings("contactThrough")}</span> <span class="supra sm">${window.lang.strings("contactThrough")}</span>
<label class="switch pb-1 mt-half"> <label class="switch pb-1 mt-half">
<input type="radio" name="accounts-contact-${this.id}" class="accounts-contact-email"> <input type="checkbox" name="accounts-contact-${this.id}" class="accounts-contact-email">
<span>Email</span> <span>Email</span>
</label> </label>
<label class="switch pb-1"> <label class="switch pb-1">
<input type="radio" name="accounts-contact-${this.id}"> <input type="checkbox" name="accounts-contact-${this.id}" class="accounts-contact-telegram">
<span>Telegram</span> <span>Telegram</span>
</label> </label>
`;
if (window.discordEnabled && this._discordUsername != "") {
innerHTML += `
<label class="switch pb-1">
<input type="checkbox" name="accounts-contact-${this.id}" class="accounts-contact-discord">
<span>Discord</span>
</label>
`;
}
innerHTML += `
</div> </div>
</div> </div>
</div> </div>
`; `;
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. // 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;
const radios = this._telegram.querySelectorAll("input") as NodeListOf<HTMLInputElement>; const checks = this._telegram.querySelectorAll("input") as NodeListOf<HTMLInputElement>;
for (let i = 0; i < radios.length; i++) { for (let i = 0; i < checks.length; i++) {
radios[i].onclick = this._setTelegramNotify; checks[i].onclick = () => this._setNotifyMethod("telegram");
} }
button.onclick = () => { button.onclick = () => {
@ -134,37 +167,129 @@ class user implements User {
set notify_telegram(s: boolean) { set notify_telegram(s: boolean) {
if (!window.telegramEnabled || !this._telegramUsername) return; if (!window.telegramEnabled || !this._telegramUsername) return;
this._notifyTelegram = s; this._notifyTelegram = s;
const radios = this._telegram.querySelectorAll("input") as NodeListOf<HTMLInputElement>; const telegram = this._telegram.getElementsByClassName("accounts-contact-telegram")[0] as HTMLInputElement;
radios[0].checked = !s; telegram.checked = s;
radios[1].checked = s; if (window.discordEnabled && this._discordUsername != "") {
const telegram = this._discord.getElementsByClassName("accounts-contact-telegram")[0] as HTMLInputElement;
telegram.checked = s;
}
} }
private _setTelegramNotify = () => { private _setNotifyMethod = (mode: string = "telegram") => {
const radios = this._telegram.querySelectorAll("input") as NodeListOf<HTMLInputElement>; 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 = { let send = {
id: this.id, id: this.id,
enabled: radios[1].checked email: email.checked
}; }
_post("/users/telegram/notify", send, (req: XMLHttpRequest) => { 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.readyState == 4) {
if (req.status != 200) { if (req.status != 200) {
window.notifications.customError("errorSetTelegramNotify", window.lang.notif("errorSaveSettings")); window.notifications.customError("errorSetNotify", window.lang.notif("errorSaveSettings"));
radios[0].checked, radios[1].checked= radios[1].checked, radios[0].checked; document.dispatchEvent(new CustomEvent("accounts-reload"));
return; return;
} }
} }
}, false, (req: XMLHttpRequest) => { }, false, (req: XMLHttpRequest) => {
if (req.status == 0) { if (req.status == 0) {
window.notifications.connectionError(); window.notifications.connectionError();
radios[0].checked, radios[1].checked= radios[1].checked, radios[0].checked; document.dispatchEvent(new CustomEvent("accounts-reload"));
return; return;
} else if (req.status == 401) { } 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")); 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 = `<span class="chip btn !low">Add</span>`;
// (this._discord.querySelector("span") as HTMLSpanElement).onclick = this._addDiscord;
} else {
let innerHTML = `
<a href="https://discord.com/users/${this._discordID}" class="discord-link" target="_blank">@${u}</a>
<i class="icon ri-settings-2-line ml-half dropdown-button"></i>
<div class="dropdown manual">
<div class="dropdown-display">
<div class="card ~neutral !low">
<span class="supra sm">${window.lang.strings("contactThrough")}</span>
<label class="switch pb-1 mt-half">
<input type="radio" name="accounts-contact-${this.id}" class="accounts-contact-email">
<span>Email</span>
</label>
<label class="switch pb-1">
<input type="radio" name="accounts-contact-${this.id}" class="accounts-contact-discord">
<span>Discord</span>
</label>
`;
if (window.telegramEnabled && this._telegramUsername != "") {
innerHTML += `
<label class="switch pb-1">
<input type="radio" name="accounts-contact-${this.id}" class="accounts-contact-telegram">
<span>Telegram</span>
</label>
`;
}
innerHTML += `
</div>
</div>
</div>
`;
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<HTMLInputElement>;
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; } get expiry(): number { return this._expiryUnix; }
set expiry(unix: number) { set expiry(unix: number) {
this._expiryUnix = unix; this._expiryUnix = unix;
@ -200,6 +325,11 @@ class user implements User {
<td class="accounts-telegram"></td> <td class="accounts-telegram"></td>
`; `;
} }
if (window.discordEnabled) {
innerHTML += `
<td class="accounts-discord"></td>
`;
}
innerHTML += ` innerHTML += `
<td class="accounts-expiry"></td> <td class="accounts-expiry"></td>
<td class="accounts-last-active"></td> <td class="accounts-last-active"></td>
@ -213,6 +343,7 @@ class user implements User {
this._email = this._row.querySelector(".accounts-email-container") as HTMLInputElement; this._email = this._row.querySelector(".accounts-email-container") as HTMLInputElement;
this._emailEditButton = this._row.querySelector(".accounts-email-edit") as HTMLElement; this._emailEditButton = this._row.querySelector(".accounts-email-edit") as HTMLElement;
this._telegram = this._row.querySelector(".accounts-telegram") as HTMLTableDataCellElement; 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._expiry = this._row.querySelector(".accounts-expiry") as HTMLTableDataCellElement;
this._lastActive = this._row.querySelector(".accounts-last-active") as HTMLTableDataCellElement; this._lastActive = this._row.querySelector(".accounts-last-active") as HTMLTableDataCellElement;
this._check.onchange = () => { this.selected = this._check.checked; } this._check.onchange = () => { this.selected = this._check.checked; }
@ -320,11 +451,14 @@ class user implements User {
this.name = user.name; this.name = user.name;
this.email = user.email || ""; this.email = user.email || "";
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_telegram = user.notify_telegram; this.notify_telegram = user.notify_telegram;
this.notify_discord = user.notify_discord;
this.notify_email = user.notify_email;
} }
asElement = (): HTMLTableRowElement => { return this._row; } asElement = (): HTMLTableRowElement => { return this._row; }

View File

@ -21,6 +21,7 @@ declare interface Window {
notificationsEnabled: boolean; notificationsEnabled: boolean;
emailEnabled: boolean; emailEnabled: boolean;
telegramEnabled: boolean; telegramEnabled: boolean;
discordEnabled: boolean;
ombiEnabled: boolean; ombiEnabled: boolean;
usernameEnabled: boolean; usernameEnabled: boolean;
token: string; token: string;

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"html/template"
"io/fs" "io/fs"
"net/http" "net/http"
"strconv" "strconv"
@ -121,6 +122,7 @@ func (app *appContext) AdminPage(gc *gin.Context) {
"contactMessage": "", "contactMessage": "",
"email_enabled": emailEnabled, "email_enabled": emailEnabled,
"telegram_enabled": telegramEnabled, "telegram_enabled": telegramEnabled,
"discord_enabled": discordEnabled,
"notifications": notificationsEnabled, "notifications": notificationsEnabled,
"version": version, "version": version,
"commit": commit, "commit": commit,
@ -284,13 +286,28 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
"userExpiryMessage": app.storage.lang.Form[lang].Strings.get("yourAccountIsValidUntil"), "userExpiryMessage": app.storage.lang.Form[lang].Strings.get("yourAccountIsValidUntil"),
"langName": lang, "langName": lang,
"telegramEnabled": telegramEnabled, "telegramEnabled": telegramEnabled,
"discordEnabled": discordEnabled,
} }
if data["telegramEnabled"].(bool) { if telegramEnabled {
data["telegramPIN"] = app.telegram.NewAuthToken() data["telegramPIN"] = app.telegram.NewAuthToken()
data["telegramUsername"] = app.telegram.username data["telegramUsername"] = app.telegram.username
data["telegramURL"] = app.telegram.link data["telegramURL"] = app.telegram.link
data["telegramRequired"] = app.config.Section("telegram").Key("required").MustBool(false) 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": `<code class="code">` + app.config.Section("discord").Key("start_command").MustString("!start") + `</code>`,
"server_channel": app.discord.serverChannelName,
}))
}
// if discordEnabled {
// pin := ""
// for _, token := range app.discord.tokens {
// if
gcHTML(gc, http.StatusOK, "form-loader.html", data) gcHTML(gc, http.StatusOK, "form-loader.html", data)
} }