mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-22 09:00:10 +00:00
commit
6fb8f1ed7f
245
api.go
245
api.go
@ -354,6 +354,34 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var matrixUser MatrixUser
|
||||||
|
matrixVerified := false
|
||||||
|
if matrixEnabled {
|
||||||
|
if req.MatrixPIN == "" {
|
||||||
|
if app.config.Section("matrix").Key("required").MustBool(false) {
|
||||||
|
f = func(gc *gin.Context) {
|
||||||
|
app.debug.Printf("%s: New user failed: Matrix verification not completed", req.Code)
|
||||||
|
respond(401, "errorMatrixVerification", gc)
|
||||||
|
}
|
||||||
|
success = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
user, ok := app.matrix.tokens[req.MatrixPIN]
|
||||||
|
if !ok || !user.Verified {
|
||||||
|
matrixVerified = false
|
||||||
|
f = func(gc *gin.Context) {
|
||||||
|
app.debug.Printf("%s: New user failed: Matrix PIN was invalid", req.Code)
|
||||||
|
respond(401, "errorInvalidPIN", gc)
|
||||||
|
}
|
||||||
|
success = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
matrixVerified = user.Verified
|
||||||
|
matrixUser = *user.User
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
telegramTokenIndex := -1
|
telegramTokenIndex := -1
|
||||||
if telegramEnabled {
|
if telegramEnabled {
|
||||||
if req.TelegramPIN == "" {
|
if req.TelegramPIN == "" {
|
||||||
@ -536,7 +564,17 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
|
|||||||
app.telegram.verifiedTokens = app.telegram.verifiedTokens[:len(app.telegram.verifiedTokens)-1]
|
app.telegram.verifiedTokens = app.telegram.verifiedTokens[:len(app.telegram.verifiedTokens)-1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if matrixVerified {
|
||||||
|
matrixUser.Contact = req.MatrixContact
|
||||||
|
delete(app.matrix.tokens, req.MatrixPIN)
|
||||||
|
if app.storage.matrix == nil {
|
||||||
|
app.storage.matrix = map[string]MatrixUser{}
|
||||||
|
}
|
||||||
|
app.storage.matrix[user.ID] = matrixUser
|
||||||
|
if err := app.storage.storeMatrixUsers(); err != nil {
|
||||||
|
app.err.Printf("Failed to store Matrix users: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
if (emailEnabled && app.config.Section("welcome_email").Key("enabled").MustBool(false) && req.Email != "") || telegramTokenIndex != -1 || discordVerified {
|
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)
|
||||||
@ -1239,10 +1277,14 @@ 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 {
|
if mxUser, ok := app.storage.matrix[jfUser.ID]; ok {
|
||||||
user.Discord = dc.Username + "#" + dc.Discriminator
|
user.Matrix = mxUser.UserID
|
||||||
user.DiscordID = dc.ID
|
user.NotifyThroughMatrix = mxUser.Contact
|
||||||
user.NotifyThroughDiscord = dc.Contact
|
}
|
||||||
|
if dcUser, ok := app.storage.discord[jfUser.ID]; ok {
|
||||||
|
user.Discord = dcUser.Username + "#" + dcUser.Discriminator
|
||||||
|
user.DiscordID = dcUser.ID
|
||||||
|
user.NotifyThroughDiscord = dcUser.Contact
|
||||||
}
|
}
|
||||||
resp.UserList[i] = user
|
resp.UserList[i] = user
|
||||||
i++
|
i++
|
||||||
@ -1498,6 +1540,8 @@ func (app *appContext) GetConfig(gc *gin.Context) {
|
|||||||
resp.Sections["email"].Settings["language"] = el
|
resp.Sections["email"].Settings["language"] = el
|
||||||
resp.Sections["password_resets"].Settings["language"] = pl
|
resp.Sections["password_resets"].Settings["language"] = pl
|
||||||
resp.Sections["telegram"].Settings["language"] = tl
|
resp.Sections["telegram"].Settings["language"] = tl
|
||||||
|
resp.Sections["discord"].Settings["language"] = tl
|
||||||
|
resp.Sections["matrix"].Settings["language"] = tl
|
||||||
|
|
||||||
gc.JSON(200, resp)
|
gc.JSON(200, resp)
|
||||||
}
|
}
|
||||||
@ -1506,6 +1550,7 @@ func (app *appContext) GetConfig(gc *gin.Context) {
|
|||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param appConfig body configDTO true "Config split into sections as in config.ini, all values as strings."
|
// @Param appConfig body configDTO true "Config split into sections as in config.ini, all values as strings."
|
||||||
// @Success 200 {object} boolResponse
|
// @Success 200 {object} boolResponse
|
||||||
|
// @Failure 500 {object} boolResponse
|
||||||
// @Router /config [post]
|
// @Router /config [post]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @tags Configuration
|
// @tags Configuration
|
||||||
@ -1531,7 +1576,11 @@ func (app *appContext) ModifyConfig(gc *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tempConfig.SaveTo(app.configPath)
|
if err := tempConfig.SaveTo(app.configPath); err != nil {
|
||||||
|
app.err.Printf("Failed to save config to \"%s\": %v", app.configPath, err)
|
||||||
|
respondBool(500, false, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
app.debug.Println("Config saved")
|
app.debug.Println("Config saved")
|
||||||
gc.JSON(200, map[string]bool{"success": true})
|
gc.JSON(200, map[string]bool{"success": true})
|
||||||
if req["restart-program"] != nil && req["restart-program"].(bool) {
|
if req["restart-program"] != nil && req["restart-program"].(bool) {
|
||||||
@ -2089,6 +2138,7 @@ func (app *appContext) SetContactMethods(gc *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if tgUser, ok := app.storage.telegram[req.ID]; ok {
|
if tgUser, ok := app.storage.telegram[req.ID]; ok {
|
||||||
|
change := tgUser.Contact != req.Telegram
|
||||||
tgUser.Contact = req.Telegram
|
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 {
|
||||||
@ -2096,13 +2146,16 @@ func (app *appContext) SetContactMethods(gc *gin.Context) {
|
|||||||
app.err.Printf("Telegram: Failed to store users: %v", err)
|
app.err.Printf("Telegram: Failed to store users: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
msg := ""
|
if change {
|
||||||
if !req.Telegram {
|
msg := ""
|
||||||
msg = " not"
|
if !req.Telegram {
|
||||||
|
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)
|
|
||||||
}
|
}
|
||||||
if dcUser, ok := app.storage.discord[req.ID]; ok {
|
if dcUser, ok := app.storage.discord[req.ID]; ok {
|
||||||
|
change := dcUser.Contact != req.Discord
|
||||||
dcUser.Contact = req.Discord
|
dcUser.Contact = req.Discord
|
||||||
app.storage.discord[req.ID] = dcUser
|
app.storage.discord[req.ID] = dcUser
|
||||||
if err := app.storage.storeDiscordUsers(); err != nil {
|
if err := app.storage.storeDiscordUsers(); err != nil {
|
||||||
@ -2110,13 +2163,33 @@ func (app *appContext) SetContactMethods(gc *gin.Context) {
|
|||||||
app.err.Printf("Discord: Failed to store users: %v", err)
|
app.err.Printf("Discord: Failed to store users: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
msg := ""
|
if change {
|
||||||
if !req.Discord {
|
msg := ""
|
||||||
msg = " not"
|
if !req.Discord {
|
||||||
|
msg = " not"
|
||||||
|
}
|
||||||
|
app.debug.Printf("Discord: User \"%s\" will%s be notified through Discord.", dcUser.Username, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mxUser, ok := app.storage.matrix[req.ID]; ok {
|
||||||
|
change := mxUser.Contact != req.Matrix
|
||||||
|
mxUser.Contact = req.Matrix
|
||||||
|
app.storage.matrix[req.ID] = mxUser
|
||||||
|
if err := app.storage.storeMatrixUsers(); err != nil {
|
||||||
|
respondBool(500, false, gc)
|
||||||
|
app.err.Printf("Matrix: Failed to store users: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if change {
|
||||||
|
msg := ""
|
||||||
|
if !req.Matrix {
|
||||||
|
msg = " not"
|
||||||
|
}
|
||||||
|
app.debug.Printf("Matrix: User \"%s\" will%s be notified through Matrix.", mxUser.UserID, msg)
|
||||||
}
|
}
|
||||||
app.debug.Printf("Discord: User \"%s\" will%s be notified through Discord.", dcUser.Username, msg)
|
|
||||||
}
|
}
|
||||||
if email, ok := app.storage.emails[req.ID]; ok {
|
if email, ok := app.storage.emails[req.ID]; ok {
|
||||||
|
change := email.Contact != req.Email
|
||||||
email.Contact = req.Email
|
email.Contact = req.Email
|
||||||
app.storage.emails[req.ID] = email
|
app.storage.emails[req.ID] = email
|
||||||
if err := app.storage.storeEmails(); err != nil {
|
if err := app.storage.storeEmails(); err != nil {
|
||||||
@ -2124,11 +2197,13 @@ func (app *appContext) SetContactMethods(gc *gin.Context) {
|
|||||||
app.err.Printf("Failed to store emails: %v", err)
|
app.err.Printf("Failed to store emails: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
msg := ""
|
if change {
|
||||||
if !req.Email {
|
msg := ""
|
||||||
msg = " not"
|
if !req.Email {
|
||||||
|
msg = " not"
|
||||||
|
}
|
||||||
|
app.debug.Printf("\"%s\" will%s be notified via Email.", email.Addr, msg)
|
||||||
}
|
}
|
||||||
app.debug.Printf("\"%s\" will%s be notified via Email.", email.Addr, msg)
|
|
||||||
}
|
}
|
||||||
respondBool(200, true, gc)
|
respondBool(200, true, gc)
|
||||||
}
|
}
|
||||||
@ -2233,6 +2308,140 @@ func (app *appContext) DiscordServerInvite(gc *gin.Context) {
|
|||||||
gc.JSON(200, DiscordInviteDTO{invURL, iconURL})
|
gc.JSON(200, DiscordInviteDTO{invURL, iconURL})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Summary Generate and send a new PIN to a specified Matrix user.
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} boolResponse
|
||||||
|
// @Failure 400 {object} boolResponse
|
||||||
|
// @Failure 401 {object} boolResponse
|
||||||
|
// @Failure 500 {object} boolResponse
|
||||||
|
// @Param invCode path string true "invite Code"
|
||||||
|
// @Param MatrixSendPINDTO body MatrixSendPINDTO true "User's Matrix ID."
|
||||||
|
// @Router /invite/{invCode}/matrix/user [post]
|
||||||
|
// @tags Other
|
||||||
|
func (app *appContext) MatrixSendPIN(gc *gin.Context) {
|
||||||
|
code := gc.Param("invCode")
|
||||||
|
if _, ok := app.storage.invites[code]; !ok {
|
||||||
|
respondBool(401, false, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req MatrixSendPINDTO
|
||||||
|
gc.BindJSON(&req)
|
||||||
|
if req.UserID == "" {
|
||||||
|
respondBool(400, false, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ok := app.matrix.SendStart(req.UserID)
|
||||||
|
if !ok {
|
||||||
|
respondBool(500, false, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respondBool(200, true, gc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary Check whether a matrix PIN is valid, and mark the token as verified if so. Requires invite code.
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} boolResponse
|
||||||
|
// @Failure 401 {object} boolResponse
|
||||||
|
// @Param pin path string true "PIN code to check"
|
||||||
|
// @Param invCode path string true "invite Code"
|
||||||
|
// @Param userID path string true "Matrix User ID"
|
||||||
|
// @Router /invite/{invCode}/matrix/verified/{userID}/{pin} [get]
|
||||||
|
// @tags Other
|
||||||
|
func (app *appContext) MatrixCheckPIN(gc *gin.Context) {
|
||||||
|
code := gc.Param("invCode")
|
||||||
|
if _, ok := app.storage.invites[code]; !ok {
|
||||||
|
app.debug.Println("Matrix: Invite code was invalid")
|
||||||
|
respondBool(401, false, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userID := gc.Param("userID")
|
||||||
|
pin := gc.Param("pin")
|
||||||
|
user, ok := app.matrix.tokens[pin]
|
||||||
|
if !ok {
|
||||||
|
app.debug.Println("Matrix: PIN not found")
|
||||||
|
respondBool(200, false, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if user.User.UserID != userID {
|
||||||
|
app.debug.Println("Matrix: User ID of PIN didn't match")
|
||||||
|
respondBool(200, false, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user.Verified = true
|
||||||
|
app.matrix.tokens[pin] = user
|
||||||
|
respondBool(200, true, gc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary Generates a Matrix access token from a username and password.
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} boolResponse
|
||||||
|
// @Failure 400 {object} stringResponse
|
||||||
|
// @Failure 401 {object} boolResponse
|
||||||
|
// @Failure 500 {object} boolResponse
|
||||||
|
// @Param MatrixLoginDTO body MatrixLoginDTO true "Username & password."
|
||||||
|
// @Router /matrix/login [post]
|
||||||
|
// @tags Other
|
||||||
|
func (app *appContext) MatrixLogin(gc *gin.Context) {
|
||||||
|
var req MatrixLoginDTO
|
||||||
|
gc.BindJSON(&req)
|
||||||
|
if req.Username == "" || req.Password == "" {
|
||||||
|
respond(400, "errorLoginBlank", gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
token, err := app.matrix.generateAccessToken(req.Homeserver, req.Username, req.Password)
|
||||||
|
if err != nil {
|
||||||
|
app.err.Printf("Matrix: Failed to generate token: %v", err)
|
||||||
|
respond(401, "Unauthorized", gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tempConfig, _ := ini.Load(app.configPath)
|
||||||
|
matrix := tempConfig.Section("matrix")
|
||||||
|
matrix.Key("enabled").SetValue("true")
|
||||||
|
matrix.Key("homeserver").SetValue(req.Homeserver)
|
||||||
|
matrix.Key("token").SetValue(token)
|
||||||
|
matrix.Key("user_id").SetValue(req.Username)
|
||||||
|
if err := tempConfig.SaveTo(app.configPath); err != nil {
|
||||||
|
app.err.Printf("Failed to save config to \"%s\": %v", app.configPath, err)
|
||||||
|
respondBool(500, false, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respondBool(200, true, gc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary Links a Matrix user to a Jellyfin account via user IDs. Notifications are turned on by default.
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} boolResponse
|
||||||
|
// @Failure 400 {object} boolResponse
|
||||||
|
// @Failure 500 {object} boolResponse
|
||||||
|
// @Param MatrixConnectUserDTO body MatrixConnectUserDTO true "User's Jellyfin ID & Matrix user ID."
|
||||||
|
// @Router /users/matrix [post]
|
||||||
|
// @tags Other
|
||||||
|
func (app *appContext) MatrixConnect(gc *gin.Context) {
|
||||||
|
var req MatrixConnectUserDTO
|
||||||
|
gc.BindJSON(&req)
|
||||||
|
if app.storage.matrix == nil {
|
||||||
|
app.storage.matrix = map[string]MatrixUser{}
|
||||||
|
}
|
||||||
|
roomID, err := app.matrix.CreateRoom(req.UserID)
|
||||||
|
if err != nil {
|
||||||
|
app.err.Printf("Matrix: Failed to create room: %v", err)
|
||||||
|
respondBool(500, false, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
app.storage.matrix[req.JellyfinID] = MatrixUser{
|
||||||
|
UserID: req.UserID,
|
||||||
|
RoomID: roomID,
|
||||||
|
Lang: "en-us",
|
||||||
|
Contact: true,
|
||||||
|
}
|
||||||
|
if err := app.storage.storeMatrixUsers(); err != nil {
|
||||||
|
app.err.Printf("Failed to store Matrix users: %v", err)
|
||||||
|
respondBool(500, false, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respondBool(200, true, gc)
|
||||||
|
}
|
||||||
|
|
||||||
// @Summary Returns a list of matching users from a Discord guild, given a username (discriminator optional).
|
// @Summary Returns a list of matching users from a Discord guild, given a username (discriminator optional).
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} DiscordUsersDTO
|
// @Success 200 {object} DiscordUsersDTO
|
||||||
|
@ -15,6 +15,7 @@ var emailEnabled = false
|
|||||||
var messagesEnabled = false
|
var messagesEnabled = false
|
||||||
var telegramEnabled = false
|
var telegramEnabled = false
|
||||||
var discordEnabled = false
|
var discordEnabled = false
|
||||||
|
var matrixEnabled = false
|
||||||
|
|
||||||
func (app *appContext) GetPath(sect, key string) (fs.FS, string) {
|
func (app *appContext) GetPath(sect, key string) (fs.FS, string) {
|
||||||
val := app.config.Section(sect).Key(key).MustString("")
|
val := app.config.Section(sect).Key(key).MustString("")
|
||||||
@ -43,7 +44,7 @@ func (app *appContext) loadConfig() error {
|
|||||||
key.SetValue(key.MustString(filepath.Join(app.dataPath, (key.Name() + ".json"))))
|
key.SetValue(key.MustString(filepath.Join(app.dataPath, (key.Name() + ".json"))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, key := range []string{"user_configuration", "user_displayprefs", "user_profiles", "ombi_template", "invites", "emails", "user_template", "custom_emails", "users", "telegram_users", "discord_users"} {
|
for _, key := range []string{"user_configuration", "user_displayprefs", "user_profiles", "ombi_template", "invites", "emails", "user_template", "custom_emails", "users", "telegram_users", "discord_users", "matrix_users"} {
|
||||||
app.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.dataPath, (key + ".json"))))
|
app.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.dataPath, (key + ".json"))))
|
||||||
}
|
}
|
||||||
app.URLBase = strings.TrimSuffix(app.config.Section("ui").Key("url_base").MustString(""), "/")
|
app.URLBase = strings.TrimSuffix(app.config.Section("ui").Key("url_base").MustString(""), "/")
|
||||||
@ -83,22 +84,26 @@ func (app *appContext) loadConfig() error {
|
|||||||
app.MustSetValue("user_expiry", "email_html", "jfa-go:"+"user-expired.html")
|
app.MustSetValue("user_expiry", "email_html", "jfa-go:"+"user-expired.html")
|
||||||
app.MustSetValue("user_expiry", "email_text", "jfa-go:"+"user-expired.txt")
|
app.MustSetValue("user_expiry", "email_text", "jfa-go:"+"user-expired.txt")
|
||||||
|
|
||||||
|
app.MustSetValue("matrix", "topic", "Jellyfin notifications")
|
||||||
|
|
||||||
app.config.Section("jellyfin").Key("version").SetValue(version)
|
app.config.Section("jellyfin").Key("version").SetValue(version)
|
||||||
app.config.Section("jellyfin").Key("device").SetValue("jfa-go")
|
app.config.Section("jellyfin").Key("device").SetValue("jfa-go")
|
||||||
app.config.Section("jellyfin").Key("device_id").SetValue(fmt.Sprintf("jfa-go-%s-%s", version, commit))
|
app.config.Section("jellyfin").Key("device_id").SetValue(fmt.Sprintf("jfa-go-%s-%s", version, commit))
|
||||||
messagesEnabled = app.config.Section("messages").Key("enabled").MustBool(false)
|
messagesEnabled = app.config.Section("messages").Key("enabled").MustBool(false)
|
||||||
telegramEnabled = app.config.Section("telegram").Key("enabled").MustBool(false)
|
telegramEnabled = app.config.Section("telegram").Key("enabled").MustBool(false)
|
||||||
discordEnabled = app.config.Section("discord").Key("enabled").MustBool(false)
|
discordEnabled = app.config.Section("discord").Key("enabled").MustBool(false)
|
||||||
|
matrixEnabled = app.config.Section("matrix").Key("enabled").MustBool(false)
|
||||||
if !messagesEnabled {
|
if !messagesEnabled {
|
||||||
emailEnabled = false
|
emailEnabled = false
|
||||||
telegramEnabled = false
|
telegramEnabled = false
|
||||||
discordEnabled = false
|
discordEnabled = false
|
||||||
|
matrixEnabled = false
|
||||||
} else if app.config.Section("email").Key("method").MustString("") == "" {
|
} else if app.config.Section("email").Key("method").MustString("") == "" {
|
||||||
emailEnabled = false
|
emailEnabled = false
|
||||||
} else {
|
} else {
|
||||||
emailEnabled = true
|
emailEnabled = true
|
||||||
}
|
}
|
||||||
if !emailEnabled && !telegramEnabled && !discordEnabled {
|
if !emailEnabled && !telegramEnabled && !discordEnabled && !matrixEnabled {
|
||||||
messagesEnabled = false
|
messagesEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -568,7 +568,7 @@
|
|||||||
"depends_true": "enabled",
|
"depends_true": "enabled",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"value": false,
|
"value": false,
|
||||||
"description": "Require Discord connection on sign-up."
|
"description": "Require Discord connection on sign-up. See the jfa-go wiki for info on setting this up."
|
||||||
},
|
},
|
||||||
"token": {
|
"token": {
|
||||||
"name": "API Token",
|
"name": "API Token",
|
||||||
@ -633,7 +633,7 @@
|
|||||||
"order": [],
|
"order": [],
|
||||||
"meta": {
|
"meta": {
|
||||||
"name": "Telegram",
|
"name": "Telegram",
|
||||||
"description": "Settings for Telegram signup/notifications"
|
"description": "Settings for Telegram signup/notifications. See the jfa-go wiki for info on setting this up."
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"enabled": {
|
"enabled": {
|
||||||
@ -676,6 +676,80 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"matrix": {
|
||||||
|
"order": [],
|
||||||
|
"meta": {
|
||||||
|
"name": "Matrix",
|
||||||
|
"description": "Settings for Matrix invites/signup/notifications. See the jfa-go wiki for info on setting this up."
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"enabled": {
|
||||||
|
"name": "Enabled",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "bool",
|
||||||
|
"value": false,
|
||||||
|
"description": "Enable signup verification through Matrix and the sending of notifications through it.\nSee the jfa-go wiki for setting up a bot."
|
||||||
|
},
|
||||||
|
"required": {
|
||||||
|
"name": "Require on sign-up",
|
||||||
|
"required": false,
|
||||||
|
"required_restart": true,
|
||||||
|
"depends_true": "enabled",
|
||||||
|
"type": "bool",
|
||||||
|
"value": false,
|
||||||
|
"description": "Require Matrix connection on sign-up."
|
||||||
|
},
|
||||||
|
"homeserver": {
|
||||||
|
"name": "Home Server URL",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": true,
|
||||||
|
"depends_true": "enabled",
|
||||||
|
"type": "text",
|
||||||
|
"value": "",
|
||||||
|
"description": "Matrix Home server URL."
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"name": "Access Token",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": true,
|
||||||
|
"depends_true": "enabled",
|
||||||
|
"type": "text",
|
||||||
|
"value": "",
|
||||||
|
"description": "Matrix Bot API Token."
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "Bot User ID",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": true,
|
||||||
|
"depends_true": "enabled",
|
||||||
|
"type": "text",
|
||||||
|
"value": "",
|
||||||
|
"description": "User ID of bot account (Example: @jfa-bot:riot.im)"
|
||||||
|
},
|
||||||
|
"topic": {
|
||||||
|
"name": "Chat topic",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": true,
|
||||||
|
"depends_true": "enabled",
|
||||||
|
"type": "text",
|
||||||
|
"value": "Jellyfin notifications",
|
||||||
|
"description": "Topic of Matrix private chats."
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"name": "Language",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"depends_true": "enabled",
|
||||||
|
"type": "select",
|
||||||
|
"options": [
|
||||||
|
["en-us", "English (US)"]
|
||||||
|
],
|
||||||
|
"value": "en-us",
|
||||||
|
"description": "Default Matrix message language. Visit weblate if you'd like to translate."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"password_resets": {
|
"password_resets": {
|
||||||
"order": [],
|
"order": [],
|
||||||
"meta": {
|
"meta": {
|
||||||
@ -1225,6 +1299,14 @@
|
|||||||
"value": "",
|
"value": "",
|
||||||
"description": "Stores telegram user IDs and language preferences."
|
"description": "Stores telegram user IDs and language preferences."
|
||||||
},
|
},
|
||||||
|
"matrix_users": {
|
||||||
|
"name": "Matrix users",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": false,
|
||||||
|
"type": "text",
|
||||||
|
"value": "",
|
||||||
|
"description": "Stores matrix user IDs and language preferences."
|
||||||
|
},
|
||||||
"discord_users": {
|
"discord_users": {
|
||||||
"name": "Discord users",
|
"name": "Discord users",
|
||||||
"required": false,
|
"required": false,
|
||||||
|
9
email.go
9
email.go
@ -25,6 +25,8 @@ import (
|
|||||||
"github.com/mailgun/mailgun-go/v4"
|
"github.com/mailgun/mailgun-go/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var renderer = html.NewRenderer(html.RendererOptions{Flags: html.Smartypants})
|
||||||
|
|
||||||
// implements email sending, right now via smtp or mailgun.
|
// implements email sending, right now via smtp or mailgun.
|
||||||
type EmailClient interface {
|
type EmailClient interface {
|
||||||
Send(fromName, fromAddr string, message *Message, address ...string) error
|
Send(fromName, fromAddr string, message *Message, address ...string) error
|
||||||
@ -337,7 +339,6 @@ func (emailer *Emailer) constructConfirmation(code, username, key string, app *a
|
|||||||
|
|
||||||
func (emailer *Emailer) constructTemplate(subject, md string, app *appContext) (*Message, error) {
|
func (emailer *Emailer) constructTemplate(subject, md string, app *appContext) (*Message, error) {
|
||||||
email := &Message{Subject: subject}
|
email := &Message{Subject: subject}
|
||||||
renderer := html.NewRenderer(html.RendererOptions{Flags: html.Smartypants})
|
|
||||||
html := markdown.ToHTML([]byte(md), nil, renderer)
|
html := markdown.ToHTML([]byte(md), nil, renderer)
|
||||||
text := stripMarkdown(md)
|
text := stripMarkdown(md)
|
||||||
message := app.config.Section("messages").Key("message").String()
|
message := app.config.Section("messages").Key("message").String()
|
||||||
@ -817,6 +818,12 @@ func (app *appContext) sendByID(email *Message, ID ...string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if mxChat, ok := app.storage.matrix[id]; ok && mxChat.Contact && matrixEnabled {
|
||||||
|
err = app.matrix.Send(email, mxChat.RoomID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
if address, ok := app.storage.emails[id]; ok && address.Contact && emailEnabled {
|
if address, ok := app.storage.emails[id]; ok && address.Contact && emailEnabled {
|
||||||
err = app.email.send(email, address.Addr)
|
err = app.email.send(email, address.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
1
go.mod
1
go.mod
@ -38,6 +38,7 @@ require (
|
|||||||
github.com/lithammer/shortuuid/v3 v3.0.4
|
github.com/lithammer/shortuuid/v3 v3.0.4
|
||||||
github.com/mailgun/mailgun-go/v4 v4.5.1
|
github.com/mailgun/mailgun-go/v4 v4.5.1
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
|
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
|
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
|
||||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||||
|
2
go.sum
2
go.sum
@ -189,6 +189,8 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
|
|||||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
|
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 h1:ZtO5uywdd5dLDCud4r0r55eP4j9FuUNpl60Gmntcop4=
|
||||||
|
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s=
|
||||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
window.emailEnabled = {{ .email_enabled }};
|
window.emailEnabled = {{ .email_enabled }};
|
||||||
window.telegramEnabled = {{ .telegram_enabled }};
|
window.telegramEnabled = {{ .telegram_enabled }};
|
||||||
window.discordEnabled = {{ .discord_enabled }};
|
window.discordEnabled = {{ .discord_enabled }};
|
||||||
|
window.matrixEnabled = {{ .matrix_enabled }};
|
||||||
window.ombiEnabled = {{ .ombiEnabled }};
|
window.ombiEnabled = {{ .ombiEnabled }};
|
||||||
window.usernameEnabled = {{ .username }};
|
window.usernameEnabled = {{ .username }};
|
||||||
window.langFile = JSON.parse({{ .language }});
|
window.langFile = JSON.parse({{ .language }});
|
||||||
@ -340,6 +341,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
<div id="modal-matrix" class="modal">
|
||||||
|
<form class="modal-content card" id="form-matrix" href="">
|
||||||
|
<span class="heading">{{ .strings.linkMatrix }}</span>
|
||||||
|
<p class="content">{{ .strings.linkMatrixDescription }}</p>
|
||||||
|
<input type="text" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.matrixHomeServer }}" id="matrix-homeserver">
|
||||||
|
<input type="text" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.username }}" id="matrix-user">
|
||||||
|
<input type="password" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.password }}" id="matrix-password">
|
||||||
|
<label>
|
||||||
|
<input type="submit" class="unfocused">
|
||||||
|
<span class="button ~urge !normal full-width center supra submit">{{ .strings.submit }}</span>
|
||||||
|
</label>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
<div id="notification-box"></div>
|
<div id="notification-box"></div>
|
||||||
<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">
|
||||||
@ -545,6 +559,9 @@
|
|||||||
{{ if .telegram_enabled }}
|
{{ if .telegram_enabled }}
|
||||||
<th>Telegram</th>
|
<th>Telegram</th>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{ if .matrix_enabled }}
|
||||||
|
<th>Matrix</th>
|
||||||
|
{{ end }}
|
||||||
{{ if .discord_enabled }}
|
{{ if .discord_enabled }}
|
||||||
<th>Discord</th>
|
<th>Discord</th>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -22,6 +22,9 @@
|
|||||||
window.discordPIN = "{{ .discordPIN }}";
|
window.discordPIN = "{{ .discordPIN }}";
|
||||||
window.discordInviteLink = {{ .discordInviteLink }};
|
window.discordInviteLink = {{ .discordInviteLink }};
|
||||||
window.discordServerName = "{{ .discordServerName }}";
|
window.discordServerName = "{{ .discordServerName }}";
|
||||||
|
window.matrixEnabled = {{ .matrixEnabled }};
|
||||||
|
window.matrixRequired = {{ .matrixRequired }};
|
||||||
|
window.matrixUserID = "{{ .matrixUser }}";
|
||||||
</script>
|
</script>
|
||||||
<script src="js/form.js" type="module"></script>
|
<script src="js/form.js" type="module"></script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -48,6 +48,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{ if .matrixEnabled }}
|
||||||
|
<div id="modal-matrix" class="modal">
|
||||||
|
<div class="modal-content card">
|
||||||
|
<span class="heading mb-1">{{ .strings.linkMatrix }}</span>
|
||||||
|
<p class="content mb-1"> {{ .strings.matrixEnterUser }}</p>
|
||||||
|
<input type="text" class="input ~neutral !high" placeholder="@user:riot.im" id="matrix-userid">
|
||||||
|
<div class="subheading link-center mt-1">
|
||||||
|
<span class="shield ~info mr-1">
|
||||||
|
<span class="icon">
|
||||||
|
<i class="ri-chat-3-line"></i>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
{{ .matrixUser }}
|
||||||
|
</div>
|
||||||
|
<span class="button ~info !normal full-width center mt-1" id="matrix-send">{{ .strings.submit }}</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>
|
||||||
@ -84,7 +102,10 @@
|
|||||||
{{ if .discordEnabled }}
|
{{ if .discordEnabled }}
|
||||||
<span class="button ~info !normal full-width center mb-1" id="link-discord">{{ .strings.linkDiscord }}</span>
|
<span class="button ~info !normal full-width center mb-1" id="link-discord">{{ .strings.linkDiscord }}</span>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ if or (.telegramEnabled) (.discordEnabled) }}
|
{{ if .matrixEnabled }}
|
||||||
|
<span class="button ~info !normal full-width center mb-1" id="link-matrix">{{ .strings.linkMatrix }}</span>
|
||||||
|
{{ end }}
|
||||||
|
{{ if or (.telegramEnabled) (or .discordEnabled .matrixEnabled) }}
|
||||||
<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>
|
||||||
@ -99,6 +120,11 @@
|
|||||||
<input type="radio" name="contact-via" value="discord" id="contact-via-discord"><span>Contact through Discord</span>
|
<input type="radio" name="contact-via" value="discord" id="contact-via-discord"><span>Contact through Discord</span>
|
||||||
</label>
|
</label>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{ if .matrixEnabled }}
|
||||||
|
<label class="row switch pb-1">
|
||||||
|
<input type="radio" name="contact-via" value="matrix" id="contact-via-matrix"><span>Contact through Matrix</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>
|
||||||
|
BIN
images/matrix/1.png
Normal file
BIN
images/matrix/1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 179 KiB |
BIN
images/matrix/2.png
Normal file
BIN
images/matrix/2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 190 KiB |
BIN
images/matrix/3.png
Normal file
BIN
images/matrix/3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
BIN
images/matrix/4.png
Normal file
BIN
images/matrix/4.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
@ -98,7 +98,9 @@
|
|||||||
"notifyUserCreation": "On user creation",
|
"notifyUserCreation": "On user creation",
|
||||||
"sendPIN": "Ask the user to send the PIN below to the bot.",
|
"sendPIN": "Ask the user to send the PIN below to the bot.",
|
||||||
"searchDiscordUser": "Start typing the Discord username to find the user.",
|
"searchDiscordUser": "Start typing the Discord username to find the user.",
|
||||||
"findDiscordUser": "Find Discord user"
|
"findDiscordUser": "Find Discord user",
|
||||||
|
"linkMatrixDescription": "Enter the username and password of the user to use as a bot. Once submitted, the app will restart.",
|
||||||
|
"matrixHomeServer": "Home server address"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"changedEmailAddress": "Changed email address of {n}.",
|
"changedEmailAddress": "Changed email address of {n}.",
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
"emailAddress": "Email Address",
|
"emailAddress": "Email Address",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"submit": "Submit",
|
"submit": "Submit",
|
||||||
|
"send": "Send",
|
||||||
"success": "Success",
|
"success": "Success",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"copy": "Copy",
|
"copy": "Copy",
|
||||||
@ -18,6 +19,7 @@
|
|||||||
"contactEmail": "Contact through Email",
|
"contactEmail": "Contact through Email",
|
||||||
"contactTelegram": "Contact through Telegram",
|
"contactTelegram": "Contact through Telegram",
|
||||||
"linkDiscord": "Link Discord",
|
"linkDiscord": "Link Discord",
|
||||||
|
"linkMatrix": "Link Matrix",
|
||||||
"contactDiscord": "Contact through Discord",
|
"contactDiscord": "Contact through Discord",
|
||||||
"theme": "Theme"
|
"theme": "Theme"
|
||||||
}
|
}
|
||||||
|
@ -19,14 +19,17 @@
|
|||||||
"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."
|
"sendPINDiscord": "Type {command} in {server_channel} on Discord, then send the PIN below via DM to the bot.",
|
||||||
|
"matrixEnterUser": "Enter your User ID, press submit, and a PIN will be sent to you. Enter it here to continue."
|
||||||
},
|
},
|
||||||
"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.",
|
||||||
"errorDiscordVerification": "Discord verification required.",
|
"errorDiscordVerification": "Discord verification required.",
|
||||||
|
"errorMatrixVerification": "Matrix verification required.",
|
||||||
"errorInvalidPIN": "PIN is invalid.",
|
"errorInvalidPIN": "PIN is invalid.",
|
||||||
|
"errorUnknown": "Unknown error.",
|
||||||
"verified": "Account verified."
|
"verified": "Account verified."
|
||||||
},
|
},
|
||||||
"validationStrings": {
|
"validationStrings": {
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
},
|
},
|
||||||
"strings": {
|
"strings": {
|
||||||
"startMessage": "Hi!\nEnter your Jellyfin PIN code here to verify your account.",
|
"startMessage": "Hi!\nEnter your Jellyfin PIN code here to verify your account.",
|
||||||
|
"matrixStartMessage": "Hi\nEnter the below PIN in the Jellyfin sign-up page to verify your account.",
|
||||||
"invalidPIN": "That PIN was invalid, try again.",
|
"invalidPIN": "That PIN was invalid, try again.",
|
||||||
"pinSuccess": "Success! You can now return to the sign-up page.",
|
"pinSuccess": "Success! You can now return to the sign-up page.",
|
||||||
"languageMessage": "Note: See available languages with {command}, and set language with {command} <language code>."
|
"languageMessage": "Note: See available languages with {command}, and set language with {command} <language code>."
|
||||||
|
15
main.go
15
main.go
@ -100,6 +100,7 @@ type appContext struct {
|
|||||||
email *Emailer
|
email *Emailer
|
||||||
telegram *TelegramDaemon
|
telegram *TelegramDaemon
|
||||||
discord *DiscordDaemon
|
discord *DiscordDaemon
|
||||||
|
matrix *MatrixDaemon
|
||||||
info, debug, err logger.Logger
|
info, debug, err logger.Logger
|
||||||
host string
|
host string
|
||||||
port int
|
port int
|
||||||
@ -353,6 +354,10 @@ func start(asDaemon, firstCall bool) {
|
|||||||
if err := app.storage.loadDiscordUsers(); err != nil {
|
if err := app.storage.loadDiscordUsers(); err != nil {
|
||||||
app.err.Printf("Failed to load Discord users: %v", err)
|
app.err.Printf("Failed to load Discord users: %v", err)
|
||||||
}
|
}
|
||||||
|
app.storage.matrix_path = app.config.Section("files").Key("matrix_users").String()
|
||||||
|
if err := app.storage.loadMatrixUsers(); err != nil {
|
||||||
|
app.err.Printf("Failed to load Matrix users: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
app.storage.profiles_path = app.config.Section("files").Key("user_profiles").String()
|
app.storage.profiles_path = app.config.Section("files").Key("user_profiles").String()
|
||||||
app.storage.loadProfiles()
|
app.storage.loadProfiles()
|
||||||
@ -590,6 +595,16 @@ func start(asDaemon, firstCall bool) {
|
|||||||
defer app.discord.Shutdown()
|
defer app.discord.Shutdown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if matrixEnabled {
|
||||||
|
app.matrix, err = newMatrixDaemon(app)
|
||||||
|
if err != nil {
|
||||||
|
app.err.Printf("Failed to initialize Matrix daemon: %v", err)
|
||||||
|
matrixEnabled = false
|
||||||
|
} else {
|
||||||
|
go app.matrix.run()
|
||||||
|
defer app.matrix.Shutdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
debugMode = false
|
debugMode = false
|
||||||
address = "0.0.0.0:8056"
|
address = "0.0.0.0:8056"
|
||||||
|
238
matrix.go
Normal file
238
matrix.go
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gomarkdown/markdown"
|
||||||
|
"github.com/matrix-org/gomatrix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MatrixDaemon struct {
|
||||||
|
Stopped bool
|
||||||
|
ShutdownChannel chan string
|
||||||
|
bot *gomatrix.Client
|
||||||
|
userID string
|
||||||
|
tokens map[string]UnverifiedUser // Map of tokens to users
|
||||||
|
languages map[string]string // Map of roomIDs to language codes
|
||||||
|
app *appContext
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnverifiedUser struct {
|
||||||
|
Verified bool
|
||||||
|
User *MatrixUser
|
||||||
|
}
|
||||||
|
|
||||||
|
type MatrixUser struct {
|
||||||
|
RoomID string
|
||||||
|
UserID string
|
||||||
|
Lang string
|
||||||
|
Contact bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type MatrixIdentifier struct {
|
||||||
|
User string `json:"user"`
|
||||||
|
IdentType string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m MatrixIdentifier) Type() string { return m.IdentType }
|
||||||
|
|
||||||
|
var matrixFilter = gomatrix.Filter{
|
||||||
|
Room: gomatrix.RoomFilter{
|
||||||
|
Timeline: gomatrix.FilterPart{
|
||||||
|
Types: []string{
|
||||||
|
"m.room.message",
|
||||||
|
"m.room.member",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
EventFields: []string{
|
||||||
|
"type",
|
||||||
|
"event_id",
|
||||||
|
"room_id",
|
||||||
|
"state_key",
|
||||||
|
"sender",
|
||||||
|
"content.body",
|
||||||
|
"content.membership",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMatrixDaemon(app *appContext) (d *MatrixDaemon, err error) {
|
||||||
|
matrix := app.config.Section("matrix")
|
||||||
|
homeserver := matrix.Key("homeserver").String()
|
||||||
|
token := matrix.Key("token").String()
|
||||||
|
d = &MatrixDaemon{
|
||||||
|
ShutdownChannel: make(chan string),
|
||||||
|
userID: matrix.Key("user_id").String(),
|
||||||
|
tokens: map[string]UnverifiedUser{},
|
||||||
|
languages: map[string]string{},
|
||||||
|
app: app,
|
||||||
|
}
|
||||||
|
d.bot, err = gomatrix.NewClient(homeserver, d.userID, token)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filter, err := json.Marshal(matrixFilter)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := d.bot.CreateFilter(filter)
|
||||||
|
d.bot.Store.SaveFilterID(d.userID, resp.FilterID)
|
||||||
|
for _, user := range app.storage.matrix {
|
||||||
|
if user.Lang != "" {
|
||||||
|
d.languages[user.RoomID] = user.Lang
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *MatrixDaemon) generateAccessToken(homeserver, username, password string) (string, error) {
|
||||||
|
req := &gomatrix.ReqLogin{
|
||||||
|
Type: "m.login.password",
|
||||||
|
Identifier: MatrixIdentifier{
|
||||||
|
User: username,
|
||||||
|
IdentType: "m.id.user",
|
||||||
|
},
|
||||||
|
Password: password,
|
||||||
|
DeviceID: "jfa-go-" + commit,
|
||||||
|
}
|
||||||
|
bot, err := gomatrix.NewClient(homeserver, username, "")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
resp, err := bot.Login(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return resp.AccessToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *MatrixDaemon) run() {
|
||||||
|
d.app.info.Println("Starting Matrix bot daemon")
|
||||||
|
syncer := d.bot.Syncer.(*gomatrix.DefaultSyncer)
|
||||||
|
syncer.OnEventType("m.room.message", d.handleMessage)
|
||||||
|
// syncer.OnEventType("m.room.member", d.handleMembership)
|
||||||
|
if err := d.bot.Sync(); err != nil {
|
||||||
|
d.app.err.Printf("Matrix sync failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *MatrixDaemon) Shutdown() {
|
||||||
|
d.bot.StopSync()
|
||||||
|
d.Stopped = true
|
||||||
|
close(d.ShutdownChannel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *MatrixDaemon) handleMessage(event *gomatrix.Event) {
|
||||||
|
if event.Sender == d.userID {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lang := "en-us"
|
||||||
|
if l, ok := d.languages[event.RoomID]; ok {
|
||||||
|
if _, ok := d.app.storage.lang.Telegram[l]; ok {
|
||||||
|
lang = l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sects := strings.Split(event.Content["body"].(string), " ")
|
||||||
|
switch sects[0] {
|
||||||
|
case "!lang":
|
||||||
|
if len(sects) == 2 {
|
||||||
|
d.commandLang(event, sects[1], lang)
|
||||||
|
} else {
|
||||||
|
d.commandLang(event, "", lang)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *MatrixDaemon) commandLang(event *gomatrix.Event, code, lang string) {
|
||||||
|
if code == "" {
|
||||||
|
list := "!lang <lang>\n"
|
||||||
|
for c := range d.app.storage.lang.Telegram {
|
||||||
|
list += fmt.Sprintf("%s: %s\n", c, d.app.storage.lang.Telegram[c].Meta.Name)
|
||||||
|
}
|
||||||
|
_, err := d.bot.SendText(
|
||||||
|
event.RoomID,
|
||||||
|
list,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
d.app.err.Printf("Matrix: Failed to send message to \"%s\": %v", event.Sender, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := d.app.storage.lang.Telegram[code]; !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.languages[event.RoomID] = code
|
||||||
|
if u, ok := d.app.storage.matrix[event.RoomID]; ok {
|
||||||
|
u.Lang = code
|
||||||
|
d.app.storage.matrix[event.RoomID] = u
|
||||||
|
if err := d.app.storage.storeMatrixUsers(); err != nil {
|
||||||
|
d.app.err.Printf("Matrix: Failed to store Matrix users: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *MatrixDaemon) CreateRoom(userID string) (string, error) {
|
||||||
|
room, err := d.bot.CreateRoom(&gomatrix.ReqCreateRoom{
|
||||||
|
Visibility: "private",
|
||||||
|
Invite: []string{userID},
|
||||||
|
Topic: d.app.config.Section("matrix").Key("topic").String(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return room.RoomID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *MatrixDaemon) SendStart(userID string) (ok bool) {
|
||||||
|
roomID, err := d.CreateRoom(userID)
|
||||||
|
if err != nil {
|
||||||
|
d.app.err.Printf("Failed to create room for user \"%s\": %v", userID, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lang := "en-us"
|
||||||
|
pin := genAuthToken()
|
||||||
|
d.tokens[pin] = UnverifiedUser{
|
||||||
|
false,
|
||||||
|
&MatrixUser{
|
||||||
|
RoomID: roomID,
|
||||||
|
UserID: userID,
|
||||||
|
Lang: lang,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = d.bot.SendText(
|
||||||
|
roomID,
|
||||||
|
d.app.storage.lang.Telegram[lang].Strings.get("matrixStartMessage")+"\n\n"+pin+"\n\n"+
|
||||||
|
d.app.storage.lang.Telegram[lang].Strings.template("languageMessage", tmpl{"command": "!lang"}),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
d.app.err.Printf("Matrix: Failed to send welcome message to \"%s\": %v", userID, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ok = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *MatrixDaemon) Send(message *Message, roomID ...string) (err error) {
|
||||||
|
md := ""
|
||||||
|
if message.Markdown != "" {
|
||||||
|
// Convert images to links
|
||||||
|
md = string(markdown.ToHTML([]byte(strings.ReplaceAll(message.Markdown, "![", "[")), nil, renderer))
|
||||||
|
}
|
||||||
|
for _, id := range roomID {
|
||||||
|
if md != "" {
|
||||||
|
_, err = d.bot.SendFormattedText(id, message.Text, md)
|
||||||
|
} else {
|
||||||
|
_, err = d.bot.SendText(id, message.Text)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// User enters ID on sign-up, a PIN is sent to them. They enter it on sign-up.
|
||||||
|
|
||||||
|
// Message the user first, to avoid E2EE by default
|
24
models.go
24
models.go
@ -19,6 +19,8 @@ type newUserDTO struct {
|
|||||||
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)
|
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
|
DiscordContact bool `json:"discord_contact"` // Whether or not to use discord for notifications/pwrs
|
||||||
|
MatrixPIN string `json:"matrix_pin" example:"A1-B2-3C"` // Matrix verification PIN (if used)
|
||||||
|
MatrixContact bool `json:"matrix_contact"` // Whether or not to use matrix for notifications/pwrs
|
||||||
}
|
}
|
||||||
|
|
||||||
type newUserResponse struct {
|
type newUserResponse struct {
|
||||||
@ -137,6 +139,8 @@ type respUser struct {
|
|||||||
Discord string `json:"discord"` // Discord username (if known)
|
Discord string `json:"discord"` // Discord username (if known)
|
||||||
DiscordID string `json:"discord_id"` // Discord user ID for creating links.
|
DiscordID string `json:"discord_id"` // Discord user ID for creating links.
|
||||||
NotifyThroughDiscord bool `json:"notify_discord"`
|
NotifyThroughDiscord bool `json:"notify_discord"`
|
||||||
|
Matrix string `json:"matrix"` // Matrix ID (if known)
|
||||||
|
NotifyThroughMatrix bool `json:"notify_matrix"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type getUsersDTO struct {
|
type getUsersDTO struct {
|
||||||
@ -260,6 +264,7 @@ type SetContactMethodsDTO struct {
|
|||||||
Email bool `json:"email"`
|
Email bool `json:"email"`
|
||||||
Discord bool `json:"discord"`
|
Discord bool `json:"discord"`
|
||||||
Telegram bool `json:"telegram"`
|
Telegram bool `json:"telegram"`
|
||||||
|
Matrix bool `json:"matrix"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DiscordUserDTO struct {
|
type DiscordUserDTO struct {
|
||||||
@ -281,3 +286,22 @@ type DiscordInviteDTO struct {
|
|||||||
InviteURL string `json:"invite"`
|
InviteURL string `json:"invite"`
|
||||||
IconURL string `json:"icon"`
|
IconURL string `json:"icon"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MatrixSendPINDTO struct {
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MatrixCheckPINDTO struct {
|
||||||
|
PIN string `json:"pin"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MatrixConnectUserDTO struct {
|
||||||
|
JellyfinID string `json:"jf_id"`
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MatrixLoginDTO struct {
|
||||||
|
Homeserver string `json:"homeserver"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
@ -127,6 +127,11 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
|||||||
router.GET(p+"/invite/:invCode/discord/invite", app.DiscordServerInvite)
|
router.GET(p+"/invite/:invCode/discord/invite", app.DiscordServerInvite)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if matrixEnabled {
|
||||||
|
router.GET(p+"/invite/:invCode/matrix/verified/:userID/:pin", app.MatrixCheckPIN)
|
||||||
|
router.POST(p+"/invite/:invCode/matrix/user", app.MatrixSendPIN)
|
||||||
|
router.POST(p+"/users/matrix", app.MatrixConnect)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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"))
|
||||||
@ -164,7 +169,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
|||||||
api.GET(p+"/config", app.GetConfig)
|
api.GET(p+"/config", app.GetConfig)
|
||||||
api.POST(p+"/config", app.ModifyConfig)
|
api.POST(p+"/config", app.ModifyConfig)
|
||||||
api.POST(p+"/restart", app.restart)
|
api.POST(p+"/restart", app.restart)
|
||||||
if telegramEnabled || discordEnabled {
|
if telegramEnabled || discordEnabled || matrixEnabled {
|
||||||
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)
|
||||||
@ -178,6 +183,8 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
|||||||
api.GET(p+"/ombi/users", app.OmbiUsers)
|
api.GET(p+"/ombi/users", app.OmbiUsers)
|
||||||
api.POST(p+"/ombi/defaults", app.SetOmbiDefaults)
|
api.POST(p+"/ombi/defaults", app.SetOmbiDefaults)
|
||||||
}
|
}
|
||||||
|
api.POST(p+"/matrix/login", app.MatrixLogin)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
39
storage.go
39
storage.go
@ -15,21 +15,22 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Storage struct {
|
type Storage struct {
|
||||||
timePattern string
|
timePattern string
|
||||||
invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path, customEmails_path, users_path, telegram_path, discord_path string
|
invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path, customEmails_path, users_path, telegram_path, discord_path, matrix_path string
|
||||||
users map[string]time.Time
|
users map[string]time.Time
|
||||||
invites Invites
|
invites Invites
|
||||||
profiles map[string]Profile
|
profiles map[string]Profile
|
||||||
defaultProfile string
|
defaultProfile string
|
||||||
displayprefs, ombi_template map[string]interface{}
|
displayprefs, ombi_template map[string]interface{}
|
||||||
emails map[string]EmailAddress
|
emails map[string]EmailAddress
|
||||||
telegram map[string]TelegramUser // Map of Jellyfin User IDs to telegram users.
|
telegram map[string]TelegramUser // Map of Jellyfin User IDs to telegram users.
|
||||||
discord map[string]DiscordUser // Map of Jellyfin user IDs to discord users.
|
discord map[string]DiscordUser // Map of Jellyfin user IDs to discord users.
|
||||||
customEmails customEmails
|
matrix map[string]MatrixUser // Map of Jellyfin user IDs to Matrix users.
|
||||||
policy mediabrowser.Policy
|
customEmails customEmails
|
||||||
configuration mediabrowser.Configuration
|
policy mediabrowser.Policy
|
||||||
lang Lang
|
configuration mediabrowser.Configuration
|
||||||
invitesLock, usersLock sync.Mutex
|
lang Lang
|
||||||
|
invitesLock, usersLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type TelegramUser struct {
|
type TelegramUser struct {
|
||||||
@ -790,6 +791,14 @@ func (st *Storage) storeDiscordUsers() error {
|
|||||||
return storeJSON(st.discord_path, st.discord)
|
return storeJSON(st.discord_path, st.discord)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (st *Storage) loadMatrixUsers() error {
|
||||||
|
return loadJSON(st.matrix_path, &st.matrix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *Storage) storeMatrixUsers() error {
|
||||||
|
return storeJSON(st.matrix_path, st.matrix)
|
||||||
|
}
|
||||||
|
|
||||||
func (st *Storage) loadCustomEmails() error {
|
func (st *Storage) loadCustomEmails() error {
|
||||||
return loadJSON(st.customEmails_path, &st.customEmails)
|
return loadJSON(st.customEmails_path, &st.customEmails)
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ func genAuthToken() string {
|
|||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
pin := make([]rune, 8)
|
pin := make([]rune, 8)
|
||||||
for i := range pin {
|
for i := range pin {
|
||||||
if i == 2 || i == 5 {
|
if (i+1)%3 == 0 {
|
||||||
pin[i] = '-'
|
pin[i] = '-'
|
||||||
} else {
|
} else {
|
||||||
pin[i] = runes[rand.Intn(len(runes))]
|
pin[i] = runes[rand.Intn(len(runes))]
|
||||||
|
@ -63,6 +63,8 @@ window.availableProfiles = window.availableProfiles || [];
|
|||||||
|
|
||||||
window.modals.updateInfo = new Modal(document.getElementById("modal-update"));
|
window.modals.updateInfo = new Modal(document.getElementById("modal-update"));
|
||||||
|
|
||||||
|
window.modals.matrix = new Modal(document.getElementById("modal-matrix"));
|
||||||
|
|
||||||
if (window.telegramEnabled) {
|
if (window.telegramEnabled) {
|
||||||
window.modals.telegram = new Modal(document.getElementById("modal-telegram"));
|
window.modals.telegram = new Modal(document.getElementById("modal-telegram"));
|
||||||
}
|
}
|
||||||
|
77
ts/form.ts
77
ts/form.ts
@ -1,6 +1,6 @@
|
|||||||
import { Modal } from "./modules/modal.js";
|
import { Modal } from "./modules/modal.js";
|
||||||
import { notificationBox, whichAnimationEvent } from "./modules/common.js";
|
import { notificationBox, whichAnimationEvent } from "./modules/common.js";
|
||||||
import { _get, _post, toggleLoader, toDateString } from "./modules/common.js";
|
import { _get, _post, toggleLoader, addLoader, removeLoader, toDateString } from "./modules/common.js";
|
||||||
import { loadLangSelector } from "./modules/lang.js";
|
import { loadLangSelector } from "./modules/lang.js";
|
||||||
|
|
||||||
interface formWindow extends Window {
|
interface formWindow extends Window {
|
||||||
@ -9,6 +9,7 @@ interface formWindow extends Window {
|
|||||||
successModal: Modal;
|
successModal: Modal;
|
||||||
telegramModal: Modal;
|
telegramModal: Modal;
|
||||||
discordModal: Modal;
|
discordModal: Modal;
|
||||||
|
matrixModal: Modal;
|
||||||
confirmationModal: Modal
|
confirmationModal: Modal
|
||||||
code: string;
|
code: string;
|
||||||
messages: { [key: string]: string };
|
messages: { [key: string]: string };
|
||||||
@ -20,6 +21,8 @@ interface formWindow extends Window {
|
|||||||
discordStartCommand: string;
|
discordStartCommand: string;
|
||||||
discordInviteLink: boolean;
|
discordInviteLink: boolean;
|
||||||
discordServerName: string;
|
discordServerName: string;
|
||||||
|
matrixRequired: boolean;
|
||||||
|
matrixUserID: string;
|
||||||
userExpiryEnabled: boolean;
|
userExpiryEnabled: boolean;
|
||||||
userExpiryMonths: number;
|
userExpiryMonths: number;
|
||||||
userExpiryDays: number;
|
userExpiryDays: number;
|
||||||
@ -150,6 +153,69 @@ if (window.discordEnabled) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var matrixVerified = false;
|
||||||
|
var matrixPIN = "";
|
||||||
|
if (window.matrixEnabled) {
|
||||||
|
window.matrixModal = new Modal(document.getElementById("modal-matrix"), window.matrixRequired);
|
||||||
|
const matrixButton = document.getElementById("link-matrix") as HTMLSpanElement;
|
||||||
|
matrixButton.onclick = window.matrixModal.show;
|
||||||
|
const submitButton = document.getElementById("matrix-send") as HTMLSpanElement;
|
||||||
|
const input = document.getElementById("matrix-userid") as HTMLInputElement;
|
||||||
|
let userID = "";
|
||||||
|
submitButton.onclick = () => {
|
||||||
|
addLoader(submitButton);
|
||||||
|
if (userID == "") {
|
||||||
|
const send = {
|
||||||
|
user_id: input.value
|
||||||
|
};
|
||||||
|
_post("/invite/" + window.code + "/matrix/user", send, (req: XMLHttpRequest) => {
|
||||||
|
if (req.readyState == 4) {
|
||||||
|
removeLoader(submitButton);
|
||||||
|
userID = input.value;
|
||||||
|
if (req.status != 200) {
|
||||||
|
window.notifications.customError("errorUnknown", window.messages["errorUnknown"]);
|
||||||
|
window.matrixModal.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
submitButton.classList.add("~positive");
|
||||||
|
submitButton.classList.remove("~info");
|
||||||
|
setTimeout(() => {
|
||||||
|
submitButton.classList.add("~info");
|
||||||
|
submitButton.classList.remove("~positive");
|
||||||
|
}, 2000);
|
||||||
|
input.placeholder = "PIN";
|
||||||
|
input.value = "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_get("/invite/" + window.code + "/matrix/verified/" + userID + "/" + input.value, null, (req: XMLHttpRequest) => {
|
||||||
|
if (req.readyState == 4) {
|
||||||
|
removeLoader(submitButton)
|
||||||
|
const valid = req.response["success"] as boolean;
|
||||||
|
if (valid) {
|
||||||
|
window.matrixModal.close();
|
||||||
|
window.notifications.customPositive("successVerified", "", window.messages["verified"]);
|
||||||
|
matrixVerified = true;
|
||||||
|
matrixPIN = input.value;
|
||||||
|
matrixButton.classList.add("unfocused");
|
||||||
|
document.getElementById("contact-via").classList.remove("unfocused");
|
||||||
|
const radio = document.getElementById("contact-via-discord") as HTMLInputElement;
|
||||||
|
radio.checked = true;
|
||||||
|
} else {
|
||||||
|
window.notifications.customError("errorInvalidPIN", window.messages["errorInvalidPIN"]);
|
||||||
|
submitButton.classList.add("~critical");
|
||||||
|
submitButton.classList.remove("~info");
|
||||||
|
setTimeout(() => {
|
||||||
|
submitButton.classList.add("~info");
|
||||||
|
submitButton.classList.remove("~critical");
|
||||||
|
}, 800);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (window.confirmation) {
|
if (window.confirmation) {
|
||||||
window.confirmationModal = new Modal(document.getElementById("modal-confirmation"), true);
|
window.confirmationModal = new Modal(document.getElementById("modal-confirmation"), true);
|
||||||
}
|
}
|
||||||
@ -229,6 +295,8 @@ interface sendDTO {
|
|||||||
telegram_contact?: boolean;
|
telegram_contact?: boolean;
|
||||||
discord_pin?: string;
|
discord_pin?: string;
|
||||||
discord_contact?: boolean;
|
discord_contact?: boolean;
|
||||||
|
matrix_pin?: string;
|
||||||
|
matrix_contact?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const create = (event: SubmitEvent) => {
|
const create = (event: SubmitEvent) => {
|
||||||
@ -254,6 +322,13 @@ const create = (event: SubmitEvent) => {
|
|||||||
send.discord_contact = true;
|
send.discord_contact = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (matrixVerified) {
|
||||||
|
send.matrix_pin = matrixPIN;
|
||||||
|
const radio = document.getElementById("contact-via-matrix") as HTMLInputElement;
|
||||||
|
if (radio.checked) {
|
||||||
|
send.matrix_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;
|
||||||
|
@ -18,6 +18,8 @@ interface User {
|
|||||||
discord: string;
|
discord: string;
|
||||||
notify_discord: boolean;
|
notify_discord: boolean;
|
||||||
discord_id: string;
|
discord_id: string;
|
||||||
|
matrix: string;
|
||||||
|
notify_matrix: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface getPinResponse {
|
interface getPinResponse {
|
||||||
@ -44,13 +46,27 @@ class user implements User {
|
|||||||
private _discordUsername: string;
|
private _discordUsername: string;
|
||||||
private _discordID: string;
|
private _discordID: string;
|
||||||
private _notifyDiscord: boolean;
|
private _notifyDiscord: boolean;
|
||||||
|
private _matrix: HTMLTableDataCellElement;
|
||||||
|
private _matrixID: string;
|
||||||
|
private _notifyMatrix: boolean;
|
||||||
private _expiry: HTMLTableDataCellElement;
|
private _expiry: HTMLTableDataCellElement;
|
||||||
private _expiryUnix: number;
|
private _expiryUnix: number;
|
||||||
private _lastActive: HTMLTableDataCellElement;
|
private _lastActive: HTMLTableDataCellElement;
|
||||||
private _lastActiveUnix: number;
|
private _lastActiveUnix: number;
|
||||||
|
private _notifyDropdown: HTMLDivElement;
|
||||||
id = "";
|
id = "";
|
||||||
private _selected: boolean;
|
private _selected: boolean;
|
||||||
|
|
||||||
|
private _lastNotifyMethod = (): string => {
|
||||||
|
// Telegram, Matrix, Discord
|
||||||
|
const telegram = this._telegramUsername && this._telegramUsername != "";
|
||||||
|
const discord = this._discordUsername && this._discordUsername != "";
|
||||||
|
const matrix = this._matrixID && this._matrixID != "";
|
||||||
|
if (discord) return "discord";
|
||||||
|
if (matrix) return "matrix";
|
||||||
|
if (telegram) return "telegram";
|
||||||
|
}
|
||||||
|
|
||||||
get selected(): boolean { return this._selected; }
|
get selected(): boolean { return this._selected; }
|
||||||
set selected(state: boolean) {
|
set selected(state: boolean) {
|
||||||
this._selected = state;
|
this._selected = state;
|
||||||
@ -96,105 +112,179 @@ class user implements User {
|
|||||||
|
|
||||||
get notify_email(): boolean { return this._notifyEmail; }
|
get notify_email(): boolean { return this._notifyEmail; }
|
||||||
set notify_email(s: boolean) {
|
set notify_email(s: boolean) {
|
||||||
this._notifyEmail = s;
|
if (this._notifyDropdown) {
|
||||||
if (window.telegramEnabled && this._telegramUsername != "") {
|
(this._notifyDropdown.querySelector(".accounts-contact-email") as HTMLInputElement).checked = s;
|
||||||
const email = this._telegram.getElementsByClassName("accounts-contact-email")[0] as HTMLInputElement;
|
}
|
||||||
if (email) {
|
}
|
||||||
email.checked = s;
|
|
||||||
|
private _constructDropdown = (): HTMLDivElement => {
|
||||||
|
const el = document.createElement("div") as HTMLDivElement;
|
||||||
|
const telegram = this._telegramUsername != "";
|
||||||
|
const discord = this._discordUsername != "";
|
||||||
|
const matrix = this._matrixID != "";
|
||||||
|
if (!telegram && !discord && !matrix) return;
|
||||||
|
let innerHTML = `
|
||||||
|
<i class="icon ri-settings-2-line ml-half dropdown-button"></i>
|
||||||
|
<div class="dropdown manual">
|
||||||
|
<div class="dropdown-display lg">
|
||||||
|
<div class="card ~neutral !low">
|
||||||
|
<span class="supra sm">${window.lang.strings("contactThrough")}</span>
|
||||||
|
<label class="row switch pb-1 mt-half">
|
||||||
|
<input type="checkbox" name="accounts-contact-${this.id}" class="accounts-contact-email">
|
||||||
|
</span>Email</span>
|
||||||
|
</label>
|
||||||
|
<div class="accounts-area-telegram">
|
||||||
|
<label class="row switch pb-1">
|
||||||
|
<input type="checkbox" name="accounts-contact-${this.id}" class="accounts-contact-telegram">
|
||||||
|
<span>Telegram</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="accounts-area-discord">
|
||||||
|
<label class="row switch pb-1">
|
||||||
|
<input type="checkbox" name="accounts-contact-${this.id}" class="accounts-contact-discord">
|
||||||
|
<span>Discord</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="accounts-area-matrix">
|
||||||
|
<label class="row switch pb-1">
|
||||||
|
<input type="checkbox" name="accounts-contact-${this.id}" class="accounts-contact-matrix">
|
||||||
|
<span>Matrix</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
el.innerHTML = innerHTML;
|
||||||
|
const button = el.querySelector("i");
|
||||||
|
const dropdown = el.querySelector("div.dropdown") as HTMLDivElement;
|
||||||
|
const checks = el.querySelectorAll("input") as NodeListOf<HTMLInputElement>;
|
||||||
|
for (let i = 0; i < checks.length; i++) {
|
||||||
|
checks[i].onclick = () => this._setNotifyMethod();
|
||||||
|
}
|
||||||
|
|
||||||
|
button.onclick = () => {
|
||||||
|
dropdown.classList.add("selected");
|
||||||
|
document.addEventListener("click", outerClickListener);
|
||||||
|
};
|
||||||
|
const outerClickListener = (event: Event) => {
|
||||||
|
if (!(event.target instanceof HTMLElement && (el.contains(event.target) || button.contains(event.target)))) {
|
||||||
|
dropdown.classList.remove("selected");
|
||||||
|
document.removeEventListener("click", outerClickListener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
get matrix(): string { return this._matrixID; }
|
||||||
|
set matrix(u: string) {
|
||||||
|
if (!window.matrixEnabled) {
|
||||||
|
this._notifyDropdown.querySelector(".accounts-area-matrix").classList.add("unfocused");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const lastNotifyMethod = this._lastNotifyMethod() == "matrix";
|
||||||
|
this._matrixID = u;
|
||||||
|
if (!u) {
|
||||||
|
this._notifyDropdown.querySelector(".accounts-area-matrix").classList.add("unfocused");
|
||||||
|
this._matrix.innerHTML = `
|
||||||
|
<span class="chip btn !low">${window.lang.strings("add")}</span>
|
||||||
|
<input type="text" class="input ~neutral !normal stealth-input unfocused" placeholder="@user:riot.im">
|
||||||
|
`;
|
||||||
|
(this._matrix.querySelector("span") as HTMLSpanElement).onclick = this._addMatrix;
|
||||||
|
} else {
|
||||||
|
this._notifyDropdown.querySelector(".accounts-area-matrix").classList.remove("unfocused");
|
||||||
|
this._matrix.innerHTML = `
|
||||||
|
<div class="table-inline">
|
||||||
|
${u}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
if (lastNotifyMethod) {
|
||||||
|
(this._matrix.querySelector(".table-inline") as HTMLDivElement).appendChild(this._notifyDropdown);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (window.discordEnabled && this._discordUsername) {
|
}
|
||||||
const email = this._discord.getElementsByClassName("accounts-contact-email")[0] as HTMLInputElement;
|
|
||||||
email.checked = s;
|
private _addMatrix = () => {
|
||||||
|
const addButton = this._matrix.querySelector(".btn") as HTMLSpanElement;
|
||||||
|
const icon = this._matrix.querySelector("i");
|
||||||
|
const input = this._matrix.querySelector("input.stealth-input") as HTMLInputElement;
|
||||||
|
if (addButton.classList.contains("chip")) {
|
||||||
|
input.classList.remove("unfocused");
|
||||||
|
addButton.innerHTML = `<i class="ri-check-line"></i>`;
|
||||||
|
addButton.classList.remove("chip")
|
||||||
|
if (icon) {
|
||||||
|
icon.classList.add("unfocused");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (input.value.charAt(0) != "@" || !input.value.includes(":")) return;
|
||||||
|
const send = {
|
||||||
|
jf_id: this.id,
|
||||||
|
user_id: input.value
|
||||||
|
}
|
||||||
|
_post("/users/matrix", send, (req: XMLHttpRequest) => {
|
||||||
|
if (req.readyState == 4) {
|
||||||
|
document.dispatchEvent(new CustomEvent("accounts-reload"));
|
||||||
|
if (req.status != 200) {
|
||||||
|
window.notifications.customError("errorConnectMatrix", window.lang.notif("errorFailureCheckLogs"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.notifications.customSuccess("connectMatrix", window.lang.notif("accountConnected"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get notify_matrix(): boolean { return this._notifyMatrix; }
|
||||||
|
set notify_matrix(s: boolean) {
|
||||||
|
if (this._notifyDropdown) {
|
||||||
|
(this._notifyDropdown.querySelector(".accounts-contact-matrix") as HTMLInputElement).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) {
|
||||||
|
this._notifyDropdown.querySelector(".accounts-area-telegram").classList.add("unfocused");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const lastNotifyMethod = this._lastNotifyMethod() == "telegram";
|
||||||
this._telegramUsername = u;
|
this._telegramUsername = u;
|
||||||
if (u == "") {
|
if (!u) {
|
||||||
this._telegram.innerHTML = `<span class="chip btn !low">Add</span>`;
|
this._notifyDropdown.querySelector(".accounts-area-telegram").classList.add("unfocused");
|
||||||
|
this._telegram.innerHTML = `<span class="chip btn !low">${window.lang.strings("add")}</span>`;
|
||||||
(this._telegram.querySelector("span") as HTMLSpanElement).onclick = this._addTelegram;
|
(this._telegram.querySelector("span") as HTMLSpanElement).onclick = this._addTelegram;
|
||||||
} else {
|
} else {
|
||||||
let innerHTML = `
|
this._notifyDropdown.querySelector(".accounts-area-telegram").classList.remove("unfocused");
|
||||||
|
this._telegram.innerHTML = `
|
||||||
<div class="table-inline">
|
<div class="table-inline">
|
||||||
<a href="https://t.me/${u}" target="_blank">@${u}</a>
|
<a href="https://t.me/${u}" target="_blank">@${u}</a>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
if (!window.discordEnabled || !this._discordUsername) {
|
if (lastNotifyMethod) {
|
||||||
innerHTML += `
|
(this._telegram.querySelector(".table-inline") as HTMLDivElement).appendChild(this._notifyDropdown);
|
||||||
<i class="icon ri-settings-2-line ml-half dropdown-button"></i>
|
|
||||||
<div class="dropdown manual">
|
|
||||||
<div class="dropdown-display lg">
|
|
||||||
<div class="card ~neutral !low">
|
|
||||||
<span class="supra sm">${window.lang.strings("contactThrough")}</span>
|
|
||||||
<label class="row switch pb-1 mt-half">
|
|
||||||
<input type="checkbox" name="accounts-contact-${this.id}" class="accounts-contact-email">
|
|
||||||
<span>Email</span>
|
|
||||||
</label>
|
|
||||||
<label class="row switch pb-1">
|
|
||||||
<input type="checkbox" name="accounts-contact-${this.id}" class="accounts-contact-telegram">
|
|
||||||
<span>Telegram</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
innerHTML += "</div>";
|
|
||||||
this._telegram.innerHTML = innerHTML;
|
|
||||||
if (!window.discordEnabled || !this._discordUsername) {
|
|
||||||
// Javascript is necessary as including the button inside the dropdown would make it too wide to display next to the username.
|
|
||||||
const button = this._telegram.querySelector("i");
|
|
||||||
const dropdown = this._telegram.querySelector("div.dropdown") as HTMLDivElement;
|
|
||||||
const checks = this._telegram.querySelectorAll("input") as NodeListOf<HTMLInputElement>;
|
|
||||||
for (let i = 0; i < checks.length; i++) {
|
|
||||||
checks[i].onclick = () => this._setNotifyMethod("telegram");
|
|
||||||
}
|
|
||||||
|
|
||||||
button.onclick = () => {
|
|
||||||
dropdown.classList.add("selected");
|
|
||||||
document.addEventListener("click", outerClickListener);
|
|
||||||
};
|
|
||||||
const outerClickListener = (event: Event) => {
|
|
||||||
if (!(event.target instanceof HTMLElement && (this._telegram.contains(event.target) || button.contains(event.target)))) {
|
|
||||||
dropdown.classList.remove("selected");
|
|
||||||
document.removeEventListener("click", outerClickListener);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get notify_telegram(): boolean { return this._notifyTelegram; }
|
get notify_telegram(): boolean { return this._notifyTelegram; }
|
||||||
set notify_telegram(s: boolean) {
|
set notify_telegram(s: boolean) {
|
||||||
if (!window.telegramEnabled || !this._telegramUsername) return;
|
if (this._notifyDropdown) {
|
||||||
this._notifyTelegram = s;
|
(this._notifyDropdown.querySelector(".accounts-contact-telegram") as HTMLInputElement).checked = s;
|
||||||
const telegram = this._telegram.getElementsByClassName("accounts-contact-telegram")[0] as HTMLInputElement;
|
|
||||||
if (telegram) {
|
|
||||||
telegram.checked = s;
|
|
||||||
}
|
|
||||||
if (window.discordEnabled && this._discordUsername) {
|
|
||||||
const telegram = this._discord.getElementsByClassName("accounts-contact-telegram")[0] as HTMLInputElement;
|
|
||||||
telegram.checked = s;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setNotifyMethod = (mode: string = "telegram") => {
|
private _setNotifyMethod = () => {
|
||||||
let el: HTMLElement;
|
const email = this._notifyDropdown.getElementsByClassName("accounts-contact-email")[0] as HTMLInputElement;
|
||||||
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,
|
||||||
email: email.checked
|
email: email.checked
|
||||||
}
|
}
|
||||||
if (window.telegramEnabled && this._telegramUsername) {
|
if (window.telegramEnabled && this._telegramUsername) {
|
||||||
const telegram = el.getElementsByClassName("accounts-contact-telegram")[0] as HTMLInputElement;
|
const telegram = this._notifyDropdown.getElementsByClassName("accounts-contact-telegram")[0] as HTMLInputElement;
|
||||||
send["telegram"] = telegram.checked;
|
send["telegram"] = telegram.checked;
|
||||||
}
|
}
|
||||||
if (window.discordEnabled && this._discordUsername) {
|
if (window.discordEnabled && this._discordUsername) {
|
||||||
const discord = el.getElementsByClassName("accounts-contact-discord")[0] as HTMLInputElement;
|
const discord = this._notifyDropdown.getElementsByClassName("accounts-contact-discord")[0] as HTMLInputElement;
|
||||||
send["discord"] = discord.checked;
|
send["discord"] = discord.checked;
|
||||||
}
|
}
|
||||||
_post("/users/contact", send, (req: XMLHttpRequest) => {
|
_post("/users/contact", send, (req: XMLHttpRequest) => {
|
||||||
@ -219,62 +309,26 @@ class user implements User {
|
|||||||
|
|
||||||
get discord(): string { return this._discordUsername; }
|
get discord(): string { return this._discordUsername; }
|
||||||
set discord(u: string) {
|
set discord(u: string) {
|
||||||
if (!window.discordEnabled) return;
|
if (!window.discordEnabled) {
|
||||||
|
this._notifyDropdown.querySelector(".accounts-area-discord").classList.add("unfocused");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const lastNotifyMethod = this._lastNotifyMethod() == "discord";
|
||||||
this._discordUsername = u;
|
this._discordUsername = u;
|
||||||
if (u == "") {
|
if (!u) {
|
||||||
this._discord.innerHTML = `<span class="chip btn !low">Add</span>`;
|
this._discord.innerHTML = `<span class="chip btn !low">Add</span>`;
|
||||||
(this._discord.querySelector("span") as HTMLSpanElement).onclick = () => addDiscord(this.id);
|
(this._discord.querySelector("span") as HTMLSpanElement).onclick = () => addDiscord(this.id);
|
||||||
|
this._notifyDropdown.querySelector(".accounts-area-discord").classList.add("unfocused");
|
||||||
} else {
|
} else {
|
||||||
let innerHTML = `
|
this._notifyDropdown.querySelector(".accounts-area-discord").classList.remove("unfocused");
|
||||||
|
this._discord.innerHTML = `
|
||||||
<div class="table-inline">
|
<div class="table-inline">
|
||||||
<a href="https://discord.com/users/${this._discordID}" class="discord-link" target="_blank">${u}</a>
|
<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 lg">
|
|
||||||
<div class="card ~neutral !low">
|
|
||||||
<span class="supra sm">${window.lang.strings("contactThrough")}</span>
|
|
||||||
<label class="row switch pb-1 mt-half">
|
|
||||||
<input type="checkbox" name="accounts-contact-${this.id}" class="accounts-contact-email">
|
|
||||||
<span>Email</span>
|
|
||||||
</label>
|
|
||||||
<label class="row switch pb-1">
|
|
||||||
<input type="checkbox" name="accounts-contact-${this.id}" class="accounts-contact-discord">
|
|
||||||
<span>Discord</span>
|
|
||||||
</label>
|
|
||||||
`;
|
|
||||||
if (window.telegramEnabled && this._telegramUsername != "") {
|
|
||||||
innerHTML += `
|
|
||||||
<label class="row switch pb-1">
|
|
||||||
<input type="checkbox" name="accounts-contact-${this.id}" class="accounts-contact-telegram">
|
|
||||||
<span>Telegram</span>
|
|
||||||
</label>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
innerHTML += `
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
this._discord.innerHTML = innerHTML;
|
if (lastNotifyMethod) {
|
||||||
// Javascript is necessary as including the button inside the dropdown would make it too wide to display next to the username.
|
(this._discord.querySelector(".table-inline") as HTMLDivElement).appendChild(this._notifyDropdown);
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,13 +342,8 @@ class user implements User {
|
|||||||
|
|
||||||
get notify_discord(): boolean { return this._notifyDiscord; }
|
get notify_discord(): boolean { return this._notifyDiscord; }
|
||||||
set notify_discord(s: boolean) {
|
set notify_discord(s: boolean) {
|
||||||
if (!window.discordEnabled || !this._discordUsername) return;
|
if (this._notifyDropdown) {
|
||||||
this._notifyDiscord = s;
|
(this._notifyDropdown.querySelector(".accounts-contact-discord") as HTMLInputElement).checked = 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,6 +382,11 @@ class user implements User {
|
|||||||
<td class="accounts-telegram"></td>
|
<td class="accounts-telegram"></td>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
if (window.matrixEnabled) {
|
||||||
|
innerHTML += `
|
||||||
|
<td class="accounts-matrix"></td>
|
||||||
|
`;
|
||||||
|
}
|
||||||
if (window.discordEnabled) {
|
if (window.discordEnabled) {
|
||||||
innerHTML += `
|
innerHTML += `
|
||||||
<td class="accounts-discord"></td>
|
<td class="accounts-discord"></td>
|
||||||
@ -352,10 +406,13 @@ class user implements User {
|
|||||||
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._discord = this._row.querySelector(".accounts-discord") as HTMLTableDataCellElement;
|
||||||
|
this._matrix = this._row.querySelector(".accounts-matrix") 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; }
|
||||||
|
|
||||||
|
this._notifyDropdown = this._constructDropdown();
|
||||||
|
|
||||||
const toggleStealthInput = () => {
|
const toggleStealthInput = () => {
|
||||||
if (this._emailEditButton.classList.contains("ri-edit-line")) {
|
if (this._emailEditButton.classList.contains("ri-edit-line")) {
|
||||||
this._email.innerHTML = emailEditor;
|
this._email.innerHTML = emailEditor;
|
||||||
@ -458,14 +515,20 @@ class user implements User {
|
|||||||
this.id = user.id;
|
this.id = user.id;
|
||||||
this.name = user.name;
|
this.name = user.name;
|
||||||
this.email = user.email || "";
|
this.email = user.email || "";
|
||||||
|
// Little hack to get settings cogs to appear on first load
|
||||||
|
this._discordUsername = user.discord;
|
||||||
|
this._telegramUsername = user.telegram;
|
||||||
|
this._matrixID = user.matrix;
|
||||||
this.discord = user.discord;
|
this.discord = user.discord;
|
||||||
this.telegram = user.telegram;
|
this.telegram = user.telegram;
|
||||||
|
this.matrix = user.matrix;
|
||||||
this.last_active = user.last_active;
|
this.last_active = user.last_active;
|
||||||
this.admin = user.admin;
|
this.admin = user.admin;
|
||||||
this.disabled = user.disabled;
|
this.disabled = user.disabled;
|
||||||
this.expiry = user.expiry;
|
this.expiry = user.expiry;
|
||||||
this.notify_discord = user.notify_discord;
|
this.notify_discord = user.notify_discord;
|
||||||
this.notify_telegram = user.notify_telegram;
|
this.notify_telegram = user.notify_telegram;
|
||||||
|
this.notify_matrix = user.notify_matrix;
|
||||||
this.notify_email = user.notify_email;
|
this.notify_email = user.notify_email;
|
||||||
this.discord_id = user.discord_id;
|
this.discord_id = user.discord_id;
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,11 @@ export class notificationBox implements NotificationBox {
|
|||||||
private _error = (message: string): HTMLElement => {
|
private _error = (message: string): HTMLElement => {
|
||||||
const noti = document.createElement('aside');
|
const noti = document.createElement('aside');
|
||||||
noti.classList.add("aside", "~critical", "!normal", "mt-half", "notification-error");
|
noti.classList.add("aside", "~critical", "!normal", "mt-half", "notification-error");
|
||||||
noti.innerHTML = `<strong>${window.lang.strings("error")}:</strong> ${message}`;
|
let error = "";
|
||||||
|
if (window.lang) {
|
||||||
|
error = window.lang.strings("error") + ":"
|
||||||
|
}
|
||||||
|
noti.innerHTML = `<strong>${error}</strong> ${message}`;
|
||||||
const closeButton = document.createElement('span') as HTMLSpanElement;
|
const closeButton = document.createElement('span') as HTMLSpanElement;
|
||||||
closeButton.classList.add("button", "~critical", "!low", "ml-1");
|
closeButton.classList.add("button", "~critical", "!low", "ml-1");
|
||||||
closeButton.innerHTML = `<i class="icon ri-close-line"></i>`;
|
closeButton.innerHTML = `<i class="icon ri-close-line"></i>`;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { _get, _post, toggleLoader } from "../modules/common.js";
|
import { _get, _post, toggleLoader, addLoader, removeLoader } from "../modules/common.js";
|
||||||
import { Marked } from "@ts-stack/markdown";
|
import { Marked } from "@ts-stack/markdown";
|
||||||
import { stripMarkdown } from "../modules/stripmd.js";
|
import { stripMarkdown } from "../modules/stripmd.js";
|
||||||
|
|
||||||
@ -666,6 +666,40 @@ export class settingsList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _addMatrix = () => {
|
||||||
|
// Modify the login modal, why not
|
||||||
|
const modal = document.getElementById("form-matrix") as HTMLFormElement;
|
||||||
|
modal.onsubmit = (event: Event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const button = modal.querySelector("span.submit") as HTMLSpanElement;
|
||||||
|
addLoader(button);
|
||||||
|
let send = {
|
||||||
|
homeserver: (document.getElementById("matrix-homeserver") as HTMLInputElement).value,
|
||||||
|
username: (document.getElementById("matrix-user") as HTMLInputElement).value,
|
||||||
|
password: (document.getElementById("matrix-password") as HTMLInputElement).value
|
||||||
|
}
|
||||||
|
_post("/matrix/login", send, (req: XMLHttpRequest) => {
|
||||||
|
if (req.readyState == 4) {
|
||||||
|
removeLoader(button);
|
||||||
|
if (req.status == 400) {
|
||||||
|
window.notifications.customError("errorUnknown", window.lang.notif(req.response["error"] as string));
|
||||||
|
return;
|
||||||
|
} else if (req.status == 401) {
|
||||||
|
window.notifications.customError("errorUnauthorized", req.response["error"] as string);
|
||||||
|
return;
|
||||||
|
} else if (req.status == 500) {
|
||||||
|
window.notifications.customError("errorAddMatrix", window.lang.notif("errorFailureCheckLogs"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.modals.matrix.close();
|
||||||
|
_post("/restart", null, () => {});
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
};
|
||||||
|
window.modals.matrix.show();
|
||||||
|
}
|
||||||
|
|
||||||
reload = () => _get("/config", null, (req: XMLHttpRequest) => {
|
reload = () => _get("/config", null, (req: XMLHttpRequest) => {
|
||||||
if (req.readyState == 4) {
|
if (req.readyState == 4) {
|
||||||
if (req.status != 200) {
|
if (req.status != 200) {
|
||||||
@ -698,6 +732,17 @@ export class settingsList {
|
|||||||
icon.onclick = () => window.updater.checkForUpdates(window.modals.updateInfo.show);
|
icon.onclick = () => window.updater.checkForUpdates(window.modals.updateInfo.show);
|
||||||
}
|
}
|
||||||
this.addSection(name, settings.sections[name], icon);
|
this.addSection(name, settings.sections[name], icon);
|
||||||
|
} else if (name == "matrix" && !window.matrixEnabled) {
|
||||||
|
const addButton = document.createElement("div");
|
||||||
|
addButton.classList.add("tooltip", "left");
|
||||||
|
addButton.innerHTML = `
|
||||||
|
<span class="button ~neutral !normal">+</span>
|
||||||
|
<span class="content sm">
|
||||||
|
${window.lang.strings("linkMatrix")}
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
(addButton.querySelector("span.button") as HTMLSpanElement).onclick = this._addMatrix;
|
||||||
|
this.addSection(name, settings.sections[name], addButton);
|
||||||
} else {
|
} else {
|
||||||
this.addSection(name, settings.sections[name]);
|
this.addSection(name, settings.sections[name]);
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ declare interface Window {
|
|||||||
emailEnabled: boolean;
|
emailEnabled: boolean;
|
||||||
telegramEnabled: boolean;
|
telegramEnabled: boolean;
|
||||||
discordEnabled: boolean;
|
discordEnabled: boolean;
|
||||||
|
matrixEnabled: boolean;
|
||||||
ombiEnabled: boolean;
|
ombiEnabled: boolean;
|
||||||
usernameEnabled: boolean;
|
usernameEnabled: boolean;
|
||||||
token: string;
|
token: string;
|
||||||
@ -103,6 +104,7 @@ declare interface Modals {
|
|||||||
updateInfo: Modal;
|
updateInfo: Modal;
|
||||||
telegram: Modal;
|
telegram: Modal;
|
||||||
discord: Modal;
|
discord: Modal;
|
||||||
|
matrix: Modal;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Invite {
|
interface Invite {
|
||||||
|
6
views.go
6
views.go
@ -123,6 +123,7 @@ func (app *appContext) AdminPage(gc *gin.Context) {
|
|||||||
"email_enabled": emailEnabled,
|
"email_enabled": emailEnabled,
|
||||||
"telegram_enabled": telegramEnabled,
|
"telegram_enabled": telegramEnabled,
|
||||||
"discord_enabled": discordEnabled,
|
"discord_enabled": discordEnabled,
|
||||||
|
"matrix_enabled": matrixEnabled,
|
||||||
"notifications": notificationsEnabled,
|
"notifications": notificationsEnabled,
|
||||||
"version": version,
|
"version": version,
|
||||||
"commit": commit,
|
"commit": commit,
|
||||||
@ -295,6 +296,7 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
|||||||
"langName": lang,
|
"langName": lang,
|
||||||
"telegramEnabled": telegramEnabled,
|
"telegramEnabled": telegramEnabled,
|
||||||
"discordEnabled": discordEnabled,
|
"discordEnabled": discordEnabled,
|
||||||
|
"matrixEnabled": matrixEnabled,
|
||||||
}
|
}
|
||||||
if telegramEnabled {
|
if telegramEnabled {
|
||||||
data["telegramPIN"] = app.telegram.NewAuthToken()
|
data["telegramPIN"] = app.telegram.NewAuthToken()
|
||||||
@ -302,6 +304,10 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
|||||||
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 matrixEnabled {
|
||||||
|
data["matrixRequired"] = app.config.Section("matrix").Key("required").MustBool(false)
|
||||||
|
data["matrixUser"] = app.matrix.userID
|
||||||
|
}
|
||||||
if discordEnabled {
|
if discordEnabled {
|
||||||
data["discordPIN"] = app.discord.NewAuthToken()
|
data["discordPIN"] = app.discord.NewAuthToken()
|
||||||
data["discordUsername"] = app.discord.username
|
data["discordUsername"] = app.discord.username
|
||||||
|
Loading…
Reference in New Issue
Block a user