mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-28 20:10:11 +00:00
Harvey Tindall
4ea2dfdfb7
since the "Announcement" template doesn't actually exist, finding it in the DB would fail, which is now ignored.
738 lines
24 KiB
Go
738 lines
24 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gopkg.in/ini.v1"
|
|
)
|
|
|
|
// @Summary Get a list of email names and IDs.
|
|
// @Produce json
|
|
// @Param lang query string false "Language for email titles."
|
|
// @Success 200 {object} emailListDTO
|
|
// @Router /config/emails [get]
|
|
// @Security Bearer
|
|
// @tags Configuration
|
|
func (app *appContext) GetCustomContent(gc *gin.Context) {
|
|
lang := gc.Query("lang")
|
|
if _, ok := app.storage.lang.Email[lang]; !ok {
|
|
lang = app.storage.lang.chosenEmailLang
|
|
}
|
|
adminLang := lang
|
|
if _, ok := app.storage.lang.Admin[lang]; !ok {
|
|
adminLang = app.storage.lang.chosenAdminLang
|
|
}
|
|
list := emailListDTO{
|
|
"UserCreated": {Name: app.storage.lang.Email[lang].UserCreated["name"], Enabled: app.storage.MustGetCustomContentKey("UserCreated").Enabled},
|
|
"InviteExpiry": {Name: app.storage.lang.Email[lang].InviteExpiry["name"], Enabled: app.storage.MustGetCustomContentKey("InviteExpiry").Enabled},
|
|
"PasswordReset": {Name: app.storage.lang.Email[lang].PasswordReset["name"], Enabled: app.storage.MustGetCustomContentKey("PasswordReset").Enabled},
|
|
"UserDeleted": {Name: app.storage.lang.Email[lang].UserDeleted["name"], Enabled: app.storage.MustGetCustomContentKey("UserDeleted").Enabled},
|
|
"UserDisabled": {Name: app.storage.lang.Email[lang].UserDisabled["name"], Enabled: app.storage.MustGetCustomContentKey("UserDisabled").Enabled},
|
|
"UserEnabled": {Name: app.storage.lang.Email[lang].UserEnabled["name"], Enabled: app.storage.MustGetCustomContentKey("UserEnabled").Enabled},
|
|
"InviteEmail": {Name: app.storage.lang.Email[lang].InviteEmail["name"], Enabled: app.storage.MustGetCustomContentKey("InviteEmail").Enabled},
|
|
"WelcomeEmail": {Name: app.storage.lang.Email[lang].WelcomeEmail["name"], Enabled: app.storage.MustGetCustomContentKey("WelcomeEmail").Enabled},
|
|
"EmailConfirmation": {Name: app.storage.lang.Email[lang].EmailConfirmation["name"], Enabled: app.storage.MustGetCustomContentKey("EmailConfirmation").Enabled},
|
|
"UserExpired": {Name: app.storage.lang.Email[lang].UserExpired["name"], Enabled: app.storage.MustGetCustomContentKey("UserExpired").Enabled},
|
|
"UserLogin": {Name: app.storage.lang.Admin[adminLang].Strings["userPageLogin"], Enabled: app.storage.MustGetCustomContentKey("Login").Enabled},
|
|
"UserPage": {Name: app.storage.lang.Admin[adminLang].Strings["userPagePage"], Enabled: app.storage.MustGetCustomContentKey("Page").Enabled},
|
|
}
|
|
|
|
filter := gc.Query("filter")
|
|
if filter == "user" {
|
|
list = emailListDTO{"UserLogin": list["UserLogin"], "UserPage": list["UserPage"]}
|
|
} else {
|
|
delete(list, "UserLogin")
|
|
delete(list, "UserPage")
|
|
}
|
|
|
|
gc.JSON(200, list)
|
|
}
|
|
|
|
// No longer needed, these are stored by string keys in the database now.
|
|
/* func (app *appContext) getCustomMessage(id string) *CustomContent {
|
|
switch id {
|
|
case "Announcement":
|
|
return &CustomContent{}
|
|
case "UserCreated":
|
|
return &app.storage.customEmails.UserCreated
|
|
case "InviteExpiry":
|
|
return &app.storage.customEmails.InviteExpiry
|
|
case "PasswordReset":
|
|
return &app.storage.customEmails.PasswordReset
|
|
case "UserDeleted":
|
|
return &app.storage.customEmails.UserDeleted
|
|
case "UserDisabled":
|
|
return &app.storage.customEmails.UserDisabled
|
|
case "UserEnabled":
|
|
return &app.storage.customEmails.UserEnabled
|
|
case "InviteEmail":
|
|
return &app.storage.customEmails.InviteEmail
|
|
case "WelcomeEmail":
|
|
return &app.storage.customEmails.WelcomeEmail
|
|
case "EmailConfirmation":
|
|
return &app.storage.customEmails.EmailConfirmation
|
|
case "UserExpired":
|
|
return &app.storage.customEmails.UserExpired
|
|
case "UserLogin":
|
|
return &app.storage.userPage.Login
|
|
case "UserPage":
|
|
return &app.storage.userPage.Page
|
|
}
|
|
return nil
|
|
} */
|
|
|
|
// @Summary Sets the corresponding custom content.
|
|
// @Produce json
|
|
// @Param CustomContent body CustomContent true "Content = email (in markdown)."
|
|
// @Success 200 {object} boolResponse
|
|
// @Failure 400 {object} boolResponse
|
|
// @Failure 500 {object} boolResponse
|
|
// @Param id path string true "ID of content"
|
|
// @Router /config/emails/{id} [post]
|
|
// @Security Bearer
|
|
// @tags Configuration
|
|
func (app *appContext) SetCustomMessage(gc *gin.Context) {
|
|
var req CustomContent
|
|
gc.BindJSON(&req)
|
|
id := gc.Param("id")
|
|
if req.Content == "" {
|
|
respondBool(400, false, gc)
|
|
return
|
|
}
|
|
message, ok := app.storage.GetCustomContentKey(id)
|
|
if !ok {
|
|
respondBool(400, false, gc)
|
|
return
|
|
}
|
|
message.Content = req.Content
|
|
message.Enabled = true
|
|
app.storage.SetCustomContentKey(id, message)
|
|
respondBool(200, true, gc)
|
|
}
|
|
|
|
// @Summary Enable/Disable custom content.
|
|
// @Produce json
|
|
// @Success 200 {object} boolResponse
|
|
// @Failure 400 {object} boolResponse
|
|
// @Failure 500 {object} boolResponse
|
|
// @Param enable/disable path string true "enable/disable"
|
|
// @Param id path string true "ID of email"
|
|
// @Router /config/emails/{id}/state/{enable/disable} [post]
|
|
// @Security Bearer
|
|
// @tags Configuration
|
|
func (app *appContext) SetCustomMessageState(gc *gin.Context) {
|
|
id := gc.Param("id")
|
|
s := gc.Param("state")
|
|
enabled := false
|
|
if s == "enable" {
|
|
enabled = true
|
|
} else if s != "disable" {
|
|
respondBool(400, false, gc)
|
|
}
|
|
message, ok := app.storage.GetCustomContentKey(id)
|
|
if !ok {
|
|
respondBool(400, false, gc)
|
|
return
|
|
}
|
|
message.Enabled = enabled
|
|
app.storage.SetCustomContentKey(id, message)
|
|
respondBool(200, true, gc)
|
|
}
|
|
|
|
// @Summary Returns the custom content/message (generating it if not set) and list of used variables in it.
|
|
// @Produce json
|
|
// @Success 200 {object} customEmailDTO
|
|
// @Failure 400 {object} boolResponse
|
|
// @Failure 500 {object} boolResponse
|
|
// @Param id path string true "ID of email"
|
|
// @Router /config/emails/{id} [get]
|
|
// @Security Bearer
|
|
// @tags Configuration
|
|
func (app *appContext) GetCustomMessageTemplate(gc *gin.Context) {
|
|
lang := app.storage.lang.chosenEmailLang
|
|
id := gc.Param("id")
|
|
var content string
|
|
var err error
|
|
var msg *Message
|
|
var variables []string
|
|
var conditionals []string
|
|
var values map[string]interface{}
|
|
username := app.storage.lang.Email[lang].Strings.get("username")
|
|
emailAddress := app.storage.lang.Email[lang].Strings.get("emailAddress")
|
|
customMessage, ok := app.storage.GetCustomContentKey(id)
|
|
if !ok && id != "Announcement" {
|
|
app.err.Printf("Failed to get custom message with ID \"%s\"", id)
|
|
respondBool(400, false, gc)
|
|
return
|
|
}
|
|
if id == "WelcomeEmail" {
|
|
conditionals = []string{"{yourAccountWillExpire}"}
|
|
customMessage.Conditionals = conditionals
|
|
} else if id == "UserPage" {
|
|
variables = []string{"{username}"}
|
|
customMessage.Variables = variables
|
|
} else if id == "UserLogin" {
|
|
variables = []string{}
|
|
customMessage.Variables = variables
|
|
}
|
|
content = customMessage.Content
|
|
noContent := content == ""
|
|
if !noContent {
|
|
variables = customMessage.Variables
|
|
}
|
|
switch id {
|
|
case "Announcement":
|
|
// Just send the email html
|
|
content = ""
|
|
case "UserCreated":
|
|
if noContent {
|
|
msg, err = app.email.constructCreated("", "", "", Invite{}, app, true)
|
|
}
|
|
values = app.email.createdValues("xxxxxx", username, emailAddress, Invite{}, app, false)
|
|
case "InviteExpiry":
|
|
if noContent {
|
|
msg, err = app.email.constructExpiry("", Invite{}, app, true)
|
|
}
|
|
values = app.email.expiryValues("xxxxxx", Invite{}, app, false)
|
|
case "PasswordReset":
|
|
if noContent {
|
|
msg, err = app.email.constructReset(PasswordReset{}, app, true)
|
|
}
|
|
values = app.email.resetValues(PasswordReset{Pin: "12-34-56", Username: username}, app, false)
|
|
case "UserDeleted":
|
|
if noContent {
|
|
msg, err = app.email.constructDeleted("", app, true)
|
|
}
|
|
values = app.email.deletedValues(app.storage.lang.Email[lang].Strings.get("reason"), app, false)
|
|
case "UserDisabled":
|
|
if noContent {
|
|
msg, err = app.email.constructDisabled("", app, true)
|
|
}
|
|
values = app.email.deletedValues(app.storage.lang.Email[lang].Strings.get("reason"), app, false)
|
|
case "UserEnabled":
|
|
if noContent {
|
|
msg, err = app.email.constructEnabled("", app, true)
|
|
}
|
|
values = app.email.deletedValues(app.storage.lang.Email[lang].Strings.get("reason"), app, false)
|
|
case "InviteEmail":
|
|
if noContent {
|
|
msg, err = app.email.constructInvite("", Invite{}, app, true)
|
|
}
|
|
values = app.email.inviteValues("xxxxxx", Invite{}, app, false)
|
|
case "WelcomeEmail":
|
|
if noContent {
|
|
msg, err = app.email.constructWelcome("", time.Time{}, app, true)
|
|
}
|
|
values = app.email.welcomeValues(username, time.Now(), app, false, true)
|
|
case "EmailConfirmation":
|
|
if noContent {
|
|
msg, err = app.email.constructConfirmation("", "", "", app, true)
|
|
}
|
|
values = app.email.confirmationValues("xxxxxx", username, "xxxxxx", app, false)
|
|
case "UserExpired":
|
|
if noContent {
|
|
msg, err = app.email.constructUserExpired(app, true)
|
|
}
|
|
values = app.email.userExpiredValues(app, false)
|
|
case "UserLogin", "UserPage":
|
|
values = map[string]interface{}{}
|
|
}
|
|
if err != nil {
|
|
respondBool(500, false, gc)
|
|
return
|
|
}
|
|
if noContent && id != "Announcement" && id != "UserPage" && id != "UserLogin" {
|
|
content = msg.Text
|
|
variables = make([]string, strings.Count(content, "{"))
|
|
i := 0
|
|
found := false
|
|
buf := ""
|
|
for _, c := range content {
|
|
if !found && c != '{' && c != '}' {
|
|
continue
|
|
}
|
|
found = true
|
|
buf += string(c)
|
|
if c == '}' {
|
|
found = false
|
|
variables[i] = buf
|
|
buf = ""
|
|
i++
|
|
}
|
|
}
|
|
customMessage.Variables = variables
|
|
}
|
|
if variables == nil {
|
|
variables = []string{}
|
|
}
|
|
app.storage.SetCustomContentKey(id, customMessage)
|
|
var mail *Message
|
|
if id != "UserLogin" && id != "UserPage" {
|
|
mail, err = app.email.constructTemplate("", "<div class=\"preview-content\"></div>", app)
|
|
if err != nil {
|
|
respondBool(500, false, gc)
|
|
return
|
|
}
|
|
} else {
|
|
mail = &Message{
|
|
HTML: "<div class=\"card ~neutral dark:~d_neutral @low preview-content\"></div>",
|
|
Markdown: "<div class=\"card ~neutral dark:~d_neutral @low preview-content\"></div>",
|
|
}
|
|
}
|
|
gc.JSON(200, customEmailDTO{Content: content, Variables: variables, Conditionals: conditionals, Values: values, HTML: mail.HTML, Plaintext: mail.Text})
|
|
}
|
|
|
|
// @Summary Returns a new Telegram verification PIN, and the bot username.
|
|
// @Produce json
|
|
// @Success 200 {object} telegramPinDTO
|
|
// @Router /telegram/pin [get]
|
|
// @Security Bearer
|
|
// @tags Other
|
|
func (app *appContext) TelegramGetPin(gc *gin.Context) {
|
|
gc.JSON(200, telegramPinDTO{
|
|
Token: app.telegram.NewAuthToken(),
|
|
Username: app.telegram.username,
|
|
})
|
|
}
|
|
|
|
// @Summary Link a Jellyfin & Telegram user together via a verification PIN.
|
|
// @Produce json
|
|
// @Param telegramSetDTO body telegramSetDTO true "Token and user's Jellyfin ID."
|
|
// @Success 200 {object} boolResponse
|
|
// @Failure 500 {object} boolResponse
|
|
// @Failure 400 {object} boolResponse
|
|
// @Router /users/telegram [post]
|
|
// @Security Bearer
|
|
// @tags Other
|
|
func (app *appContext) TelegramAddUser(gc *gin.Context) {
|
|
var req telegramSetDTO
|
|
gc.BindJSON(&req)
|
|
if req.Token == "" || req.ID == "" {
|
|
respondBool(400, false, gc)
|
|
return
|
|
}
|
|
tgToken, ok := app.telegram.TokenVerified(req.Token)
|
|
app.telegram.DeleteVerifiedToken(req.Token)
|
|
if !ok {
|
|
respondBool(500, false, gc)
|
|
return
|
|
}
|
|
tgUser := TelegramUser{
|
|
ChatID: tgToken.ChatID,
|
|
Username: tgToken.Username,
|
|
Contact: true,
|
|
}
|
|
if lang, ok := app.telegram.languages[tgToken.ChatID]; ok {
|
|
tgUser.Lang = lang
|
|
}
|
|
app.storage.SetTelegramKey(req.ID, tgUser)
|
|
linkExistingOmbiDiscordTelegram(app)
|
|
respondBool(200, true, gc)
|
|
}
|
|
|
|
// @Summary Sets whether to notify a user through telegram/discord/matrix/email or not.
|
|
// @Produce json
|
|
// @Param SetContactMethodsDTO body SetContactMethodsDTO true "User's Jellyfin ID and whether or not to notify then through Telegram."
|
|
// @Success 200 {object} boolResponse
|
|
// @Success 400 {object} boolResponse
|
|
// @Success 500 {object} boolResponse
|
|
// @Router /users/contact [post]
|
|
// @Security Bearer
|
|
// @tags Other
|
|
func (app *appContext) SetContactMethods(gc *gin.Context) {
|
|
var req SetContactMethodsDTO
|
|
gc.BindJSON(&req)
|
|
if req.ID == "" {
|
|
respondBool(400, false, gc)
|
|
return
|
|
}
|
|
app.setContactMethods(req, gc)
|
|
}
|
|
|
|
func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Context) {
|
|
if tgUser, ok := app.storage.GetTelegramKey(req.ID); ok {
|
|
change := tgUser.Contact != req.Telegram
|
|
tgUser.Contact = req.Telegram
|
|
app.storage.SetTelegramKey(req.ID, tgUser)
|
|
if change {
|
|
msg := ""
|
|
if !req.Telegram {
|
|
msg = " not"
|
|
}
|
|
app.debug.Printf("Telegram: User \"%s\" will%s be notified through Telegram.", tgUser.Username, msg)
|
|
}
|
|
}
|
|
if dcUser, ok := app.storage.GetDiscordKey(req.ID); ok {
|
|
change := dcUser.Contact != req.Discord
|
|
dcUser.Contact = req.Discord
|
|
app.storage.SetDiscordKey(req.ID, dcUser)
|
|
if change {
|
|
msg := ""
|
|
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.GetMatrixKey(req.ID); ok {
|
|
change := mxUser.Contact != req.Matrix
|
|
mxUser.Contact = req.Matrix
|
|
app.storage.SetMatrixKey(req.ID, mxUser)
|
|
if change {
|
|
msg := ""
|
|
if !req.Matrix {
|
|
msg = " not"
|
|
}
|
|
app.debug.Printf("Matrix: User \"%s\" will%s be notified through Matrix.", mxUser.UserID, msg)
|
|
}
|
|
}
|
|
if email, ok := app.storage.GetEmailsKey(req.ID); ok {
|
|
change := email.Contact != req.Email
|
|
email.Contact = req.Email
|
|
app.storage.SetEmailsKey(req.ID, email)
|
|
if change {
|
|
msg := ""
|
|
if !req.Email {
|
|
msg = " not"
|
|
}
|
|
app.debug.Printf("\"%s\" will%s be notified via Email.", email.Addr, msg)
|
|
}
|
|
}
|
|
respondBool(200, true, gc)
|
|
}
|
|
|
|
// @Summary Returns true/false on whether or not a telegram PIN was verified. Requires bearer auth.
|
|
// @Produce json
|
|
// @Success 200 {object} boolResponse
|
|
// @Param pin path string true "PIN code to check"
|
|
// @Router /telegram/verified/{pin} [get]
|
|
// @Security Bearer
|
|
// @tags Other
|
|
func (app *appContext) TelegramVerified(gc *gin.Context) {
|
|
pin := gc.Param("pin")
|
|
_, ok := app.telegram.TokenVerified(pin)
|
|
respondBool(200, ok, gc)
|
|
}
|
|
|
|
// @Summary Returns true/false on whether or not a telegram PIN was verified. Requires invite code.
|
|
// @Produce json
|
|
// @Success 200 {object} boolResponse
|
|
// @Success 401 {object} boolResponse
|
|
// @Param pin path string true "PIN code to check"
|
|
// @Param invCode path string true "invite Code"
|
|
// @Router /invite/{invCode}/telegram/verified/{pin} [get]
|
|
// @tags Other
|
|
func (app *appContext) TelegramVerifiedInvite(gc *gin.Context) {
|
|
code := gc.Param("invCode")
|
|
if _, ok := app.storage.GetInvitesKey(code); !ok {
|
|
respondBool(401, false, gc)
|
|
return
|
|
}
|
|
pin := gc.Param("pin")
|
|
token, ok := app.telegram.TokenVerified(pin)
|
|
if ok && app.config.Section("telegram").Key("require_unique").MustBool(false) && app.telegram.UserExists(token.Username) {
|
|
app.discord.DeleteVerifiedUser(pin)
|
|
respondBool(400, false, gc)
|
|
return
|
|
}
|
|
respondBool(200, ok, gc)
|
|
}
|
|
|
|
// @Summary Returns true/false on whether or not a discord PIN was verified. 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"
|
|
// @Router /invite/{invCode}/discord/verified/{pin} [get]
|
|
// @tags Other
|
|
func (app *appContext) DiscordVerifiedInvite(gc *gin.Context) {
|
|
code := gc.Param("invCode")
|
|
if _, ok := app.storage.GetInvitesKey(code); !ok {
|
|
respondBool(401, false, gc)
|
|
return
|
|
}
|
|
pin := gc.Param("pin")
|
|
user, ok := app.discord.UserVerified(pin)
|
|
if ok && app.config.Section("discord").Key("require_unique").MustBool(false) && app.discord.UserExists(user.ID) {
|
|
delete(app.discord.verifiedTokens, pin)
|
|
respondBool(400, false, gc)
|
|
return
|
|
}
|
|
respondBool(200, ok, gc)
|
|
}
|
|
|
|
// @Summary Returns a 10-minute, one-use Discord server invite
|
|
// @Produce json
|
|
// @Success 200 {object} DiscordInviteDTO
|
|
// @Failure 400 {object} boolResponse
|
|
// @Failure 401 {object} boolResponse
|
|
// @Failure 500 {object} boolResponse
|
|
// @Param invCode path string true "invite Code"
|
|
// @Router /invite/{invCode}/discord/invite [get]
|
|
// @tags Other
|
|
func (app *appContext) DiscordServerInvite(gc *gin.Context) {
|
|
if app.discord.inviteChannelName == "" {
|
|
respondBool(400, false, gc)
|
|
return
|
|
}
|
|
code := gc.Param("invCode")
|
|
if _, ok := app.storage.GetInvitesKey(code); !ok {
|
|
respondBool(401, false, gc)
|
|
return
|
|
}
|
|
invURL, iconURL := app.discord.NewTempInvite(10*60, 1)
|
|
if invURL == "" {
|
|
respondBool(500, false, gc)
|
|
return
|
|
}
|
|
gc.JSON(200, DiscordInviteDTO{invURL, iconURL})
|
|
}
|
|
|
|
// @Summary Generate and send a new PIN to a specified Matrix user.
|
|
// @Produce json
|
|
// @Success 200 {object} boolResponse
|
|
// @Failure 400 {object} stringResponse
|
|
// @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.GetInvitesKey(code); !ok {
|
|
respondBool(401, false, gc)
|
|
return
|
|
}
|
|
var req MatrixSendPINDTO
|
|
gc.BindJSON(&req)
|
|
if req.UserID == "" {
|
|
respond(400, "errorNoUserID", gc)
|
|
return
|
|
}
|
|
if app.config.Section("matrix").Key("require_unique").MustBool(false) {
|
|
for _, u := range app.storage.GetMatrix() {
|
|
if req.UserID == u.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.GetInvitesKey(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.GetMatrix() == nil {
|
|
app.storage.deprecatedMatrix = matrixStore{}
|
|
}
|
|
roomID, encrypted, 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.SetMatrixKey(req.JellyfinID, MatrixUser{
|
|
UserID: req.UserID,
|
|
RoomID: string(roomID),
|
|
Lang: "en-us",
|
|
Contact: true,
|
|
Encrypted: encrypted,
|
|
})
|
|
app.matrix.isEncrypted[roomID] = encrypted
|
|
respondBool(200, true, gc)
|
|
}
|
|
|
|
// @Summary Returns a list of matching users from a Discord guild, given a username (discriminator optional).
|
|
// @Produce json
|
|
// @Success 200 {object} DiscordUsersDTO
|
|
// @Failure 400 {object} boolResponse
|
|
// @Failure 500 {object} boolResponse
|
|
// @Param username path string true "username to search."
|
|
// @Router /users/discord/{username} [get]
|
|
// @tags Other
|
|
func (app *appContext) DiscordGetUsers(gc *gin.Context) {
|
|
name := gc.Param("username")
|
|
if name == "" {
|
|
respondBool(400, false, gc)
|
|
return
|
|
}
|
|
users := app.discord.GetUsers(name)
|
|
resp := DiscordUsersDTO{Users: make([]DiscordUserDTO, len(users))}
|
|
for i, u := range users {
|
|
resp.Users[i] = DiscordUserDTO{
|
|
Name: RenderDiscordUsername(u.User),
|
|
ID: u.User.ID,
|
|
AvatarURL: u.User.AvatarURL("32"),
|
|
}
|
|
}
|
|
gc.JSON(200, resp)
|
|
}
|
|
|
|
// @Summary Links a Discord account 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 DiscordConnectUserDTO body DiscordConnectUserDTO true "User's Jellyfin ID & Discord ID."
|
|
// @Router /users/discord [post]
|
|
// @tags Other
|
|
func (app *appContext) DiscordConnect(gc *gin.Context) {
|
|
var req DiscordConnectUserDTO
|
|
gc.BindJSON(&req)
|
|
if req.JellyfinID == "" || req.DiscordID == "" {
|
|
respondBool(400, false, gc)
|
|
return
|
|
}
|
|
user, ok := app.discord.NewUser(req.DiscordID)
|
|
if !ok {
|
|
respondBool(500, false, gc)
|
|
return
|
|
}
|
|
app.storage.SetDiscordKey(req.JellyfinID, user)
|
|
linkExistingOmbiDiscordTelegram(app)
|
|
respondBool(200, true, gc)
|
|
}
|
|
|
|
// @Summary unlink a Discord account from a Jellyfin user. Always succeeds.
|
|
// @Produce json
|
|
// @Success 200 {object} boolResponse
|
|
// @Param forUserDTO body forUserDTO true "User's Jellyfin ID."
|
|
// @Router /users/discord [delete]
|
|
// @Tags Users
|
|
func (app *appContext) UnlinkDiscord(gc *gin.Context) {
|
|
var req forUserDTO
|
|
gc.BindJSON(&req)
|
|
/* user, status, err := app.jf.UserByID(req.ID, false)
|
|
if req.ID == "" || status != 200 || err != nil {
|
|
respond(400, "User not found", gc)
|
|
return
|
|
} */
|
|
app.storage.DeleteDiscordKey(req.ID)
|
|
respondBool(200, true, gc)
|
|
}
|
|
|
|
// @Summary unlink a Telegram account from a Jellyfin user. Always succeeds.
|
|
// @Produce json
|
|
// @Success 200 {object} boolResponse
|
|
// @Param forUserDTO body forUserDTO true "User's Jellyfin ID."
|
|
// @Router /users/telegram [delete]
|
|
// @Tags Users
|
|
func (app *appContext) UnlinkTelegram(gc *gin.Context) {
|
|
var req forUserDTO
|
|
gc.BindJSON(&req)
|
|
/* user, status, err := app.jf.UserByID(req.ID, false)
|
|
if req.ID == "" || status != 200 || err != nil {
|
|
respond(400, "User not found", gc)
|
|
return
|
|
} */
|
|
app.storage.DeleteTelegramKey(req.ID)
|
|
respondBool(200, true, gc)
|
|
}
|
|
|
|
// @Summary unlink a Matrix account from a Jellyfin user. Always succeeds.
|
|
// @Produce json
|
|
// @Success 200 {object} boolResponse
|
|
// @Param forUserDTO body forUserDTO true "User's Jellyfin ID."
|
|
// @Router /users/matrix [delete]
|
|
// @Tags Users
|
|
func (app *appContext) UnlinkMatrix(gc *gin.Context) {
|
|
var req forUserDTO
|
|
gc.BindJSON(&req)
|
|
/* user, status, err := app.jf.UserByID(req.ID, false)
|
|
if req.ID == "" || status != 200 || err != nil {
|
|
respond(400, "User not found", gc)
|
|
return
|
|
} */
|
|
app.storage.DeleteMatrixKey(req.ID)
|
|
respondBool(200, true, gc)
|
|
}
|